rippled
Loading...
Searching...
No Matches
LoanBroker_test.cpp
1#include <test/jtx.h>
2
3#include <xrpl/beast/unit_test/suite.h>
4#include <xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h>
5
6namespace xrpl {
7namespace test {
8
10{
11 // Ensure that all the features needed for Lending Protocol are included,
12 // even if they are set to unsupported.
14 jtx::testable_amendments() | featureMPTokensV1 | featureSingleAssetVault |
15 featureLendingProtocol};
16
17 void
19 {
20 testcase("Disabled");
21 // Lending Protocol depends on Single Asset Vault (SAV). Test
22 // combinations of the two amendments.
23 // Single Asset Vault depends on MPTokensV1, but don't test every combo
24 // of that.
25 using namespace jtx;
26 auto failAll = [this](FeatureBitset features, bool goodVault = false) {
27 Env env(*this, features);
28
29 Account const alice{"alice"};
30 env.fund(XRP(10000), alice);
31
32 // Try to create a vault
33 PrettyAsset const asset{xrpIssue(), 1'000'000};
34 Vault const vault{env};
35 auto const [tx, keylet] = vault.create({.owner = alice, .asset = asset});
36 env(tx, ter(goodVault ? ter(tesSUCCESS) : ter(temDISABLED)));
37 env.close();
38 BEAST_EXPECT(static_cast<bool>(env.le(keylet)) == goodVault);
39
40 using namespace loanBroker;
41 // Can't create a loan broker regardless of whether the vault exists
42 env(set(alice, keylet.key), ter(temDISABLED));
43 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
44 // Other LoanBroker transactions are disabled, too.
45 // 1. LoanBrokerCoverDeposit
46 env(coverDeposit(alice, brokerKeylet.key, asset(1000)), ter(temDISABLED));
47 // 2. LoanBrokerCoverWithdraw
48 env(coverWithdraw(alice, brokerKeylet.key, asset(1000)), ter(temDISABLED));
49 // 3. LoanBrokerCoverClawback
50 env(coverClawback(alice), ter(temDISABLED));
51 env(coverClawback(alice), loanBrokerID(brokerKeylet.key), ter(temDISABLED));
52 env(coverClawback(alice), amount(asset(0)), ter(temDISABLED));
53 env(coverClawback(alice),
54 loanBrokerID(brokerKeylet.key),
55 amount(asset(1000)),
57 // 4. LoanBrokerDelete
58 env(del(alice, brokerKeylet.key), ter(temDISABLED));
59 };
60 failAll(all - featureMPTokensV1);
61 failAll(all - featureSingleAssetVault - featureLendingProtocol);
62 failAll(all - featureSingleAssetVault);
63 failAll(all - featureLendingProtocol, true);
64 }
65
66 struct VaultInfo
67 {
71 VaultInfo(jtx::PrettyAsset const& asset_, uint256 const& vaultID_, AccountID const& pseudo)
72 : asset(asset_), vaultID(vaultID_), pseudoAccount("vault", pseudo)
73 {
74 }
75 };
76
77 void
79 char const* label,
80 jtx::Env& env,
81 jtx::Account const& issuer,
82 jtx::Account const& alice,
83 jtx::Account const& evan,
84 jtx::Account const& bystander,
85 VaultInfo const& vault,
86 VaultInfo const& badVault,
87 std::function<jtx::JTx(jtx::JTx const&)> modifyJTx,
88 std::function<void(SLE::const_ref)> checkBroker,
89 std::function<void(SLE::const_ref)> changeBroker,
90 std::function<void(SLE::const_ref)> checkChangedBroker)
91 {
92 {
93 auto const& asset = vault.asset.raw();
94 std::string_view assetLabel;
95 if (asset.native())
96 {
97 assetLabel = "XRP ";
98 }
99 else if (asset.holds<Issue>())
100 {
101 assetLabel = "IOU ";
102 }
103 else if (asset.holds<MPTIssue>())
104 {
105 assetLabel = "MPT ";
106 }
107 else
108 {
109 assetLabel = "Unknown ";
110 }
111 testcase << "Lifecycle: " << assetLabel << label;
112 }
113
114 using namespace jtx;
115 using namespace loanBroker;
116
117 // Bogus assets to use in test cases
118 static PrettyAsset const badMptAsset = [&]() {
119 MPTTester badMptt{env, evan, mptInitNoFund};
120 badMptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
121 env.close();
122 return badMptt["BAD"];
123 }();
124 static PrettyAsset const badIouAsset = evan["BAD"];
125 static Account const nonExistent{"NonExistent"};
126 static PrettyAsset const ghostIouAsset = nonExistent["GST"];
127 PrettyAsset const vaultPseudoIouAsset = vault.pseudoAccount["PSD"];
128
129 auto const badKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
130 env(set(alice, badVault.vaultID));
131 env.close();
132 auto const badBrokerPseudo = [&]() {
133 if (auto const le = env.le(badKeylet); BEAST_EXPECT(le))
134 {
135 return Account{"Bad Broker pseudo-account", le->at(sfAccount)};
136 }
137 // Just to make the build work
138 return vault.pseudoAccount;
139 }();
140 PrettyAsset const badBrokerPseudoIouAsset = badBrokerPseudo["WAT"];
141
142 auto const keylet = keylet::loanbroker(alice.id(), env.seq(alice));
143 {
144 // Start with default values
145 auto jtx = env.jt(set(alice, vault.vaultID));
146 // Modify as desired
147 if (modifyJTx)
148 jtx = modifyJTx(jtx);
149 // Successfully create a Loan Broker
150 env(jtx);
151 }
152
153 env.close();
154 if (auto broker = env.le(keylet); BEAST_EXPECT(broker))
155 {
156 // log << "Broker after create: " << to_string(broker->getJson())
157 // << std::endl;
158 BEAST_EXPECT(broker->at(sfVaultID) == vault.vaultID);
159 BEAST_EXPECT(broker->at(sfAccount) != alice.id());
160 BEAST_EXPECT(broker->at(sfOwner) == alice.id());
161 BEAST_EXPECT(broker->at(sfFlags) == 0);
162 BEAST_EXPECT(broker->at(sfSequence) == env.seq(alice) - 1);
163 BEAST_EXPECT(broker->at(sfOwnerCount) == 0);
164 BEAST_EXPECT(broker->at(sfLoanSequence) == 1);
165 BEAST_EXPECT(broker->at(sfDebtTotal) == 0);
166 BEAST_EXPECT(broker->at(sfCoverAvailable) == 0);
167 if (checkBroker)
168 checkBroker(broker);
169
170 // if (auto const vaultSLE = env.le(keylet::vault(vault.vaultID)))
171 //{
172 // log << "Vault: " << to_string(vaultSLE->getJson()) <<
173 // std::endl;
174 // }
175 // Load the pseudo-account
176 Account const pseudoAccount{"Broker pseudo-account", broker->at(sfAccount)};
177
178 auto const pseudoKeylet = keylet::account(pseudoAccount);
179 if (auto const pseudo = env.le(pseudoKeylet); BEAST_EXPECT(pseudo))
180 {
181 // log << "Pseudo-account after create: "
182 // << to_string(pseudo->getJson()) << std::endl
183 // << std::endl;
184 BEAST_EXPECT(
185 pseudo->at(sfFlags) == (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
186 BEAST_EXPECT(pseudo->at(sfSequence) == 0);
187 BEAST_EXPECT(pseudo->at(sfBalance) == beast::zero);
188 BEAST_EXPECT(pseudo->at(sfOwnerCount) == (vault.asset.raw().native() ? 0 : 1));
189 BEAST_EXPECT(!pseudo->isFieldPresent(sfAccountTxnID));
190 BEAST_EXPECT(!pseudo->isFieldPresent(sfRegularKey));
191 BEAST_EXPECT(!pseudo->isFieldPresent(sfEmailHash));
192 BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletLocator));
193 BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletSize));
194 BEAST_EXPECT(!pseudo->isFieldPresent(sfMessageKey));
195 BEAST_EXPECT(!pseudo->isFieldPresent(sfTransferRate));
196 BEAST_EXPECT(!pseudo->isFieldPresent(sfDomain));
197 BEAST_EXPECT(!pseudo->isFieldPresent(sfTickSize));
198 BEAST_EXPECT(!pseudo->isFieldPresent(sfTicketCount));
199 BEAST_EXPECT(!pseudo->isFieldPresent(sfNFTokenMinter));
200 BEAST_EXPECT(!pseudo->isFieldPresent(sfMintedNFTokens));
201 BEAST_EXPECT(!pseudo->isFieldPresent(sfBurnedNFTokens));
202 BEAST_EXPECT(!pseudo->isFieldPresent(sfFirstNFTokenSequence));
203 BEAST_EXPECT(!pseudo->isFieldPresent(sfAMMID));
204 BEAST_EXPECT(!pseudo->isFieldPresent(sfVaultID));
205 BEAST_EXPECT(pseudo->at(sfLoanBrokerID) == keylet.key);
206 }
207
208 {
209 // Get the AccountInfo RPC result for the broker pseudo-account
210 std::string const pseudoStr = to_string(pseudoAccount.id());
211 auto const accountInfo = env.rpc("account_info", pseudoStr);
212 if (BEAST_EXPECT(accountInfo.isObject()))
213 {
214 auto const& accountData = accountInfo[jss::result][jss::account_data];
215 if (BEAST_EXPECT(accountData.isObject()))
216 {
217 BEAST_EXPECT(accountData[jss::Account] == pseudoStr);
218 BEAST_EXPECT(accountData[sfLoanBrokerID] == to_string(keylet.key));
219 }
220 auto const& pseudoInfo = accountInfo[jss::result][jss::pseudo_account];
221 if (BEAST_EXPECT(pseudoInfo.isObject()))
222 {
223 BEAST_EXPECT(pseudoInfo[jss::type] == "LoanBroker");
224 }
225 }
226 }
227
228 auto verifyCoverAmount =
229 [&env, &vault, &pseudoAccount, &broker, &keylet, this](auto n) {
230 using namespace jtx;
231
232 if (BEAST_EXPECT(broker = env.le(keylet)))
233 {
234 auto const amount = vault.asset(n);
235 BEAST_EXPECT(broker->at(sfCoverAvailable) == amount.number());
236 env.require(balance(pseudoAccount, amount));
237 }
238 };
239
240 // Test Cover funding before allowing alterations
241 env(coverDeposit(alice, uint256(0), vault.asset(10)), ter(temINVALID));
242 env(coverDeposit(evan, keylet.key, vault.asset(10)), ter(tecNO_PERMISSION));
243 env(coverDeposit(evan, keylet.key, vault.asset(0)), ter(temBAD_AMOUNT));
244 env(coverDeposit(evan, keylet.key, vault.asset(-10)), ter(temBAD_AMOUNT));
245 env(coverDeposit(alice, vault.vaultID, vault.asset(10)), ter(tecNO_ENTRY));
246
247 verifyCoverAmount(0);
248
249 // Test cover clawback failure cases BEFORE depositing any cover
250 // Need one of brokerID or amount
251 env(coverClawback(alice), ter(temINVALID));
252 env(coverClawback(alice), loanBrokerID(uint256(0)), ter(temINVALID));
253 env(coverClawback(alice), amount(XRP(1000)), ter(temBAD_AMOUNT));
254 env(coverClawback(alice), amount(vault.asset(-10)), ter(temBAD_AMOUNT));
255 // Clawbacks with an MPT need to specify the broker ID
256 env(coverClawback(alice), amount(badMptAsset(1)), ter(temINVALID));
257 env(coverClawback(evan), loanBrokerID(vault.vaultID), ter(tecNO_ENTRY));
258 // Only the issuer can clawback
259 env(coverClawback(alice), loanBrokerID(keylet.key), ter(tecNO_PERMISSION));
260 if (vault.asset.raw().native())
261 {
262 // Can not clawback XRP under any circumstances
263 env(coverClawback(issuer), loanBrokerID(keylet.key), ter(tecNO_PERMISSION));
264 }
265 else
266 {
267 if (vault.asset.raw().holds<Issue>())
268 {
269 // Clawbacks without a loanBrokerID need to specify an IOU
270 // with the broker's pseudo-account as the issuer
271 env(coverClawback(alice), amount(ghostIouAsset(1)), ter(tecNO_ENTRY));
272 env(coverClawback(alice), amount(badIouAsset(1)), ter(tecOBJECT_NOT_FOUND));
273 // Pseudo-account is not for a broker
274 env(coverClawback(alice),
275 amount(vaultPseudoIouAsset(1)),
277 // If we specify a pseudo-account as the IOU amount, it
278 // needs to match the loan broker
279 env(coverClawback(issuer),
280 loanBrokerID(keylet.key),
281 amount(badBrokerPseudoIouAsset(10)),
283 PrettyAsset const brokerWrongCurrencyAsset = pseudoAccount["WAT"];
284 env(coverClawback(issuer),
285 loanBrokerID(keylet.key),
286 amount(brokerWrongCurrencyAsset(10)),
288 }
289 else
290 {
291 // Clawbacks with an MPT need to specify the broker ID, even
292 // if the asset is valid
293 BEAST_EXPECT(vault.asset.raw().holds<MPTIssue>());
294 env(coverClawback(alice), amount(vault.asset(10)), ter(temINVALID));
295 }
296 // Since no cover has been deposited, there's nothing to claw
297 // back
298 env(coverClawback(issuer),
299 loanBrokerID(keylet.key),
300 amount(vault.asset(10)),
302 }
303 env.close();
304
305 // Fund the cover deposit
306 env(coverDeposit(alice, keylet.key, vault.asset(10)));
307 env.close();
308 verifyCoverAmount(10);
309
310 // Test withdrawal failure cases
311 env(coverWithdraw(alice, uint256(0), vault.asset(10)), ter(temINVALID));
312 env(coverWithdraw(evan, keylet.key, vault.asset(10)), ter(tecNO_PERMISSION));
313 env(coverWithdraw(evan, keylet.key, vault.asset(0)), ter(temBAD_AMOUNT));
314 env(coverWithdraw(evan, keylet.key, vault.asset(-10)), ter(temBAD_AMOUNT));
315 env(coverWithdraw(alice, vault.vaultID, vault.asset(10)), ter(tecNO_ENTRY));
316 env(coverWithdraw(alice, keylet.key, vault.asset(900)), ter(tecINSUFFICIENT_FUNDS));
317
318 // Skip this test for XRP, because that can always be sent
319 if (!vault.asset.raw().native())
320 {
321 TER const expected = vault.asset.raw().holds<MPTIssue>() ? tecNO_AUTH : tecNO_LINE;
322 env(coverWithdraw(alice, keylet.key, vault.asset(1)),
323 destination(bystander),
324 ter(expected));
325 }
326
327 // Can not withdraw to the zero address
328 env(coverWithdraw(alice, keylet.key, vault.asset(1)),
329 destination(AccountID{}),
331
332 // Withdraw some of the cover amount
333 env(coverWithdraw(alice, keylet.key, vault.asset(7)));
334 env.close();
335 verifyCoverAmount(3);
336
337 // Add some more cover
338 env(coverDeposit(alice, keylet.key, vault.asset(5)));
339 env.close();
340 verifyCoverAmount(8);
341
342 // Withdraw some more. Send it to Evan. Very generous, considering
343 // how much trouble he's been.
344 env(coverWithdraw(alice, keylet.key, vault.asset(1)), destination(evan));
345 env.close();
346 verifyCoverAmount(7);
347
348 // Withdraw some more. Send it to Evan. Very generous, considering
349 // how much trouble he's been.
350 env(coverWithdraw(alice, keylet.key, vault.asset(1)), destination(evan), dtag(3));
351 env.close();
352 verifyCoverAmount(6);
353
354 if (!vault.asset.raw().native())
355 {
356 // Issuer claws back some of the cover
357 env(coverClawback(issuer), loanBrokerID(keylet.key), amount(vault.asset(2)));
358 env.close();
359 verifyCoverAmount(4);
360
361 // Deposit some back
362 env(coverDeposit(alice, keylet.key, vault.asset(5)));
363 env.close();
364 verifyCoverAmount(9);
365
366 // Issuer claws it all back in various different ways
367 for (auto const& tx : {
368 // defer autofills until submission time
369 env.json(
370 coverClawback(issuer),
371 loanBrokerID(keylet.key),
372 fee(none),
373 seq(none),
374 sig(none)),
375 env.json(
376 coverClawback(issuer),
377 loanBrokerID(keylet.key),
378 amount(vault.asset(0)),
379 fee(none),
380 seq(none),
381 sig(none)),
382 env.json(
383 coverClawback(issuer),
384 loanBrokerID(keylet.key),
385 amount(vault.asset(6)),
386 fee(none),
387 seq(none),
388 sig(none)),
389 // amount will be truncated to what's available
390 env.json(
391 coverClawback(issuer),
392 loanBrokerID(keylet.key),
393 amount(vault.asset(100)),
394 fee(none),
395 seq(none),
396 sig(none)),
397 })
398 {
399 // Issuer claws it all back
400 env(tx);
401 env.close();
402 verifyCoverAmount(0);
403
404 // Deposit some back
405 env(coverDeposit(alice, keylet.key, vault.asset(6)));
406 env.close();
407 verifyCoverAmount(6);
408 }
409 }
410
411 // no-op
412 env(set(alice, vault.vaultID), loanBrokerID(keylet.key));
413 env.close();
414
415 // Make modifications to the broker
416 if (changeBroker)
417 changeBroker(broker);
418
419 env.close();
420
421 // Check the results of modifications
422 if (BEAST_EXPECT(broker = env.le(keylet)) && checkChangedBroker)
423 checkChangedBroker(broker);
424
425 // Verify that fields get removed when set to default values
426 // Debt maximum: explicit 0
427 // Data: explicit empty
428 env(set(alice, vault.vaultID),
429 loanBrokerID(broker->key()),
430 debtMaximum(Number(0)),
431 data(""));
432 env.close();
433
434 // Check the updated fields
435 if (BEAST_EXPECT(broker = env.le(keylet)))
436 {
437 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
438 BEAST_EXPECT(!broker->isFieldPresent(sfData));
439 }
440
442 // try to delete the wrong broker object
443 env(del(alice, vault.vaultID), ter(tecNO_ENTRY));
444 // evan tries to delete the broker
445 env(del(evan, keylet.key), ter(tecNO_PERMISSION));
446
447 // Get the "bad" broker out of the way
448 env(del(alice, badKeylet.key));
449 env.close();
450
451 // Note alice's balance of the asset and the broker account's cover
452 // funds
453 auto const aliceBalance = env.balance(alice, vault.asset);
454 auto const coverFunds = env.balance(pseudoAccount, vault.asset);
455 BEAST_EXPECT(coverFunds.number() == broker->at(sfCoverAvailable));
456 BEAST_EXPECT(coverFunds != beast::zero);
457 verifyCoverAmount(6);
458
459 // delete the broker
460 // log << "Broker before delete: " << to_string(broker->getJson())
461 // << std::endl;
462 // if (auto const pseudo = env.le(pseudoKeylet);
463 // BEAST_EXPECT(pseudo))
464 //{
465 // log << "Pseudo-account before delete: "
466 // << to_string(pseudo->getJson()) << std::endl
467 // << std::endl;
468 //}
469
470 env(del(alice, keylet.key));
471 env.close();
472 {
473 broker = env.le(keylet);
474 BEAST_EXPECT(!broker);
475 auto pseudo = env.le(pseudoKeylet);
476 BEAST_EXPECT(!pseudo);
477 }
478 auto const expectedBalance = aliceBalance + coverFunds -
479 (aliceBalance.value().native() ? STAmount(env.current()->fees().base.value())
480 : vault.asset(0));
481 env.require(balance(alice, expectedBalance));
482 env.require(balance(pseudoAccount, vault.asset(none)));
483 }
484 }
485
486 void
488 {
489 testcase("Lifecycle");
490 using namespace jtx;
491
492 // Create 3 loan brokers: one for XRP, one for an IOU, and one for an
493 // MPT. That'll require three corresponding SAVs.
494 Env env(*this, all);
495
496 Account const issuer{"issuer"};
497 // For simplicity, alice will be the sole actor for the vault & brokers.
498 Account alice{"alice"};
499 // Evan will attempt to be naughty
500 Account evan{"evan"};
501 // Bystander doesn't have anything to do with the SAV or Broker, or any
502 // of the relevant tokens
503 Account const bystander{"bystander"};
504 Vault vault{env};
505
506 // Fund the accounts and trust lines with the same amount so that tests
507 // can use the same values regardless of the asset.
508 env.fund(XRP(100'000), issuer, noripple(alice, evan, bystander));
509 env.close();
510
511 env(fset(issuer, asfAllowTrustLineClawback));
512 env.close();
513
514 // Create assets
515 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
516 PrettyAsset const iouAsset = issuer["IOU"];
517 env(trust(alice, iouAsset(1'000'000)));
518 env(trust(evan, iouAsset(1'000'000)));
519 env.close();
520 env(pay(issuer, evan, iouAsset(100'000)));
521 env(pay(issuer, alice, iouAsset(100'000)));
522 env.close();
523
524 MPTTester mptt{env, issuer, mptInitNoFund};
525 mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
526 env.close();
527 PrettyAsset const mptAsset = mptt["MPT"];
528 mptt.authorize({.account = alice});
529 mptt.authorize({.account = evan});
530 env.close();
531 env(pay(issuer, alice, mptAsset(100'000)));
532 env(pay(issuer, evan, mptAsset(100'000)));
533 env.close();
534
535 std::array const assets{xrpAsset, iouAsset, mptAsset};
536
537 // Create vaults
539 for (auto const& asset : assets)
540 {
541 auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
542 env(tx);
543 env.close();
544 if (auto const le = env.le(keylet); BEAST_EXPECT(env.le(keylet)))
545 {
546 vaults.emplace_back(asset, keylet.key, le->at(sfAccount));
547 }
548
549 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(50)}));
550 env.close();
551 }
552 VaultInfo const badVault = [&]() -> VaultInfo {
553 auto [tx, keylet] = vault.create({.owner = alice, .asset = iouAsset});
554 env(tx);
555 env.close();
556 if (auto const le = env.le(keylet); BEAST_EXPECT(env.le(keylet)))
557 {
558 return {iouAsset, keylet.key, le->at(sfAccount)};
559 }
560 // This should never happen
561 return {iouAsset, keylet.key, evan.id()};
562 }();
563
564 auto const aliceOriginalCount = env.ownerCount(alice);
565
566 // Create and update Loan Brokers
567 for (auto const& vault : vaults)
568 {
569 {
570 // Get the AccountInfo RPC result for the vault pseudo-account
571 std::string const pseudoStr = to_string(vault.pseudoAccount.id());
572 auto const accountInfo = env.rpc("account_info", pseudoStr);
573 if (BEAST_EXPECT(accountInfo.isObject()))
574 {
575 auto const& accountData = accountInfo[jss::result][jss::account_data];
576 if (BEAST_EXPECT(accountData.isObject()))
577 {
578 BEAST_EXPECT(accountData[jss::Account] == pseudoStr);
579 BEAST_EXPECT(accountData[sfVaultID] == to_string(vault.vaultID));
580 }
581 auto const& pseudoInfo = accountInfo[jss::result][jss::pseudo_account];
582 if (BEAST_EXPECT(pseudoInfo.isObject()))
583 {
584 BEAST_EXPECT(pseudoInfo[jss::type] == "Vault");
585 }
586 }
587 }
588
589 using namespace loanBroker;
590 using namespace xrpl::Lending;
591
592 TenthBips32 const tenthBipsZero{0};
593
594 auto badKeylet = keylet::vault(alice.id(), env.seq(alice));
595 // Try some failure cases
596 // not the vault owner
597 env(set(evan, vault.vaultID), ter(tecNO_PERMISSION));
598 // not a vault
599 env(set(alice, badKeylet.key), ter(tecNO_ENTRY));
600 // flags are checked first
601 env(set(evan, vault.vaultID, ~tfUniversal), ter(temINVALID_FLAG));
602 // field length validation
603 // sfData: good length, bad account
604 env(set(evan, vault.vaultID),
607 // sfData: too long
608 env(set(evan, vault.vaultID),
610 ter(temINVALID));
611 // sfManagementFeeRate: good value, bad account
612 env(set(evan, vault.vaultID),
613 managementFeeRate(maxManagementFeeRate),
615 // sfManagementFeeRate: too big
616 env(set(evan, vault.vaultID),
617 managementFeeRate(maxManagementFeeRate + TenthBips16(10)),
618 ter(temINVALID));
619 // sfCoverRateMinimum and sfCoverRateLiquidation are linked
620 // Cover: good value, bad account
621 env(set(evan, vault.vaultID),
622 coverRateMinimum(maxCoverRate),
623 coverRateLiquidation(maxCoverRate),
625 // CoverMinimum: too big
626 env(set(evan, vault.vaultID),
627 coverRateMinimum(maxCoverRate + 1),
628 coverRateLiquidation(maxCoverRate + 1),
629 ter(temINVALID));
630 // CoverLiquidation: too big
631 env(set(evan, vault.vaultID),
632 coverRateMinimum(maxCoverRate / 2),
633 coverRateLiquidation(maxCoverRate + 1),
634 ter(temINVALID));
635 // Cover: zero min, non-zero liquidation - implicit and
636 // explicit zero values.
637 env(set(evan, vault.vaultID), coverRateLiquidation(maxCoverRate), ter(temINVALID));
638 env(set(evan, vault.vaultID),
639 coverRateMinimum(tenthBipsZero),
640 coverRateLiquidation(maxCoverRate),
641 ter(temINVALID));
642 // Cover: non-zero min, zero liquidation - implicit and
643 // explicit zero values.
644 env(set(evan, vault.vaultID), coverRateMinimum(maxCoverRate), ter(temINVALID));
645 env(set(evan, vault.vaultID),
646 coverRateMinimum(maxCoverRate),
647 coverRateLiquidation(tenthBipsZero),
648 ter(temINVALID));
649 // sfDebtMaximum: good value, bad account
650 env(set(evan, vault.vaultID), debtMaximum(Number(0)), ter(tecNO_PERMISSION));
651 // sfDebtMaximum: overflow
652 env(set(evan, vault.vaultID), debtMaximum(Number(1, 100)), ter(temINVALID));
653 // sfDebtMaximum: negative
654 env(set(evan, vault.vaultID), debtMaximum(Number(-1)), ter(temINVALID));
655
656 std::string testData;
657 lifecycle(
658 "default fields",
659 env,
660 issuer,
661 alice,
662 evan,
663 bystander,
664 vault,
665 badVault,
666 // No modifications
667 {},
668 [&](SLE::const_ref broker) {
669 // Extra checks
670 BEAST_EXPECT(!broker->isFieldPresent(sfManagementFeeRate));
671 BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateMinimum));
672 BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateLiquidation));
673 BEAST_EXPECT(!broker->isFieldPresent(sfData));
674 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
675 BEAST_EXPECT(broker->at(sfDebtMaximum) == 0);
676 BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 0);
677 BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 0);
678
679 BEAST_EXPECT(env.ownerCount(alice) == aliceOriginalCount + 4);
680 },
681 [&](SLE::const_ref broker) {
682 // Modifications
683
684 // Update the fields
685 auto const nextKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
686
687 // fields that can't be changed
688 // LoanBrokerID
689 env(set(alice, vault.vaultID), loanBrokerID(nextKeylet.key), ter(tecNO_ENTRY));
690 // VaultID
691 env(set(alice, nextKeylet.key), loanBrokerID(broker->key()), ter(tecNO_ENTRY));
692 // Owner
693 env(set(evan, vault.vaultID),
694 loanBrokerID(broker->key()),
696 // ManagementFeeRate
697 env(set(alice, vault.vaultID),
698 loanBrokerID(broker->key()),
699 managementFeeRate(maxManagementFeeRate),
700 ter(temINVALID));
701 // CoverRateMinimum
702 env(set(alice, vault.vaultID),
703 loanBrokerID(broker->key()),
704 coverRateMinimum(maxManagementFeeRate),
705 ter(temINVALID));
706 // CoverRateLiquidation
707 env(set(alice, vault.vaultID),
708 loanBrokerID(broker->key()),
709 coverRateLiquidation(maxManagementFeeRate),
710 ter(temINVALID));
711
712 // fields that can be changed
713 testData = "Test Data 1234";
714 // Bad data: too long
715 env(set(alice, vault.vaultID),
716 loanBrokerID(broker->key()),
718 ter(temINVALID));
719
720 // Bad debt maximum
721 env(set(alice, vault.vaultID),
722 loanBrokerID(broker->key()),
723 debtMaximum(Number(-175, -1)),
724 ter(temINVALID));
725 Number debtMax{175, -1};
726 if (vault.asset.integral())
727 {
728 env(set(alice, vault.vaultID),
729 loanBrokerID(broker->key()),
730 data(testData),
731 debtMaximum(debtMax),
733 roundToAsset(vault.asset, debtMax);
734 }
735 // Data & Debt maximum
736 env(set(alice, vault.vaultID),
737 loanBrokerID(broker->key()),
738 data(testData),
739 debtMaximum(debtMax));
740 },
741 [&](SLE::const_ref broker) {
742 // Check the updated fields
743 BEAST_EXPECT(checkVL(broker->at(sfData), testData));
744 Number const expected = STAmount{vault.asset, Number(175, -1)};
745 auto const actual = broker->at(sfDebtMaximum);
746 BEAST_EXPECTS(
747 actual == expected,
748 "Expected: " + to_string(expected) + ", Actual: " + to_string(actual));
749 });
750
751 lifecycle(
752 "non-default fields",
753 env,
754 issuer,
755 alice,
756 evan,
757 bystander,
758 vault,
759 badVault,
760 [&](jtx::JTx const& jv) {
761 testData = "spam spam spam spam";
762 // Finally, create another Loan Broker with none of the
763 // values at default
764 return env.jt(
765 jv,
766 data(testData),
767 managementFeeRate(TenthBips16(123)),
768 debtMaximum(Number(9)),
769 coverRateMinimum(TenthBips32(100)),
770 coverRateLiquidation(TenthBips32(200)));
771 },
772 [&](SLE::const_ref broker) {
773 // Extra checks
774 BEAST_EXPECT(broker->at(sfManagementFeeRate) == 123);
775 BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 100);
776 BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 200);
777 BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(9));
778 BEAST_EXPECT(checkVL(broker->at(sfData), testData));
779 },
780 [&](SLE::const_ref broker) {
781 // Reset Data & Debt maximum to default values
782 env(set(alice, vault.vaultID),
783 loanBrokerID(broker->key()),
784 data(""),
785 debtMaximum(Number(0)));
786 },
787 [&](SLE::const_ref broker) {
788 // Check the updated fields
789 BEAST_EXPECT(!broker->isFieldPresent(sfData));
790 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
791 });
792 }
793
794 BEAST_EXPECT(env.ownerCount(alice) == aliceOriginalCount);
795 }
796
798
799 void
802 getAsset,
803 LoanBrokerTest brokerTest)
804 {
805 using namespace jtx;
806 using namespace loanBroker;
807 Account const issuer{"issuer"};
808 Account const alice{"alice"};
809 Env env(*this);
810 Vault const vault{env};
811
812 env.fund(XRP(100'000), issuer, alice);
813 env.close();
814
815 PrettyAsset const asset = [&]() {
816 if (getAsset)
817 return getAsset(env, issuer, alice);
818 env(trust(alice, issuer["IOU"](1'000'000)));
819 env.close();
820 return PrettyAsset(issuer["IOU"]);
821 }();
822
823 env(pay(issuer, alice, asset(100'000)));
824 env.close();
825
826 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
827 env(tx);
828 env.close();
829 auto const le = env.le(vaultKeylet);
830 VaultInfo vaultInfo = [&]() {
831 if (BEAST_EXPECT(le))
832 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
833 return VaultInfo{asset, {}, {}};
834 }();
835 if (vaultInfo.vaultID == uint256{})
836 return;
837
838 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(50)}));
839 env.close();
840
841 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
842 env(set(alice, vaultInfo.vaultID));
843 env.close();
844
845 auto broker = env.le(brokerKeylet);
846 if (!BEAST_EXPECT(broker))
847 return;
848
849 auto testZeroBrokerID = [&](auto&& getTxJv) {
850 auto jv = getTxJv();
851 // empty broker ID
852 jv[sfLoanBrokerID] = "";
853 env(jv, ter(temINVALID));
854 // zero broker ID
855 jv[sfLoanBrokerID] = to_string(uint256{});
856 // needs a flag to distinguish the parsed STTx from the prior
857 // test
858 env(jv, txflags(tfFullyCanonicalSig), ter(temINVALID));
859 };
860 auto testZeroVaultID = [&](auto&& getTxJv) {
861 auto jv = getTxJv();
862 // empty broker ID
863 jv[sfVaultID] = "";
864 env(jv, ter(temINVALID));
865 // zero broker ID
866 jv[sfVaultID] = to_string(uint256{});
867 // needs a flag to distinguish the parsed STTx from the prior
868 // test
869 env(jv, txflags(tfFullyCanonicalSig), ter(temINVALID));
870 };
871
872 if (brokerTest == CoverDeposit)
873 {
874 // preflight: temINVALID (empty/zero broker id)
875 testZeroBrokerID([&]() { return coverDeposit(alice, brokerKeylet.key, asset(10)); });
876
877 // preclaim: tecWRONG_ASSET
878 env(coverDeposit(alice, brokerKeylet.key, issuer["BAD"](10)), ter(tecWRONG_ASSET));
879
880 // preclaim: tecINSUFFICIENT_FUNDS
881 env(pay(alice, issuer, asset(100'000 - 50)));
882 env.close();
883 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)),
885
886 // preclaim: tecFROZEN
887 env(fset(issuer, asfGlobalFreeze));
888 env.close();
889 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)), ter(tecFROZEN));
890 }
891 else
892 {
893 // Fund the cover deposit
894 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)));
895 }
896 env.close();
897
898 if (brokerTest == CoverWithdraw)
899 {
900 // preflight: temINVALID (empty/zero broker id)
901 testZeroBrokerID([&]() { return coverWithdraw(alice, brokerKeylet.key, asset(10)); });
902
903 // preclaim: tecWRONG_ASSET
904 env(coverWithdraw(alice, brokerKeylet.key, issuer["BAD"](10)), ter(tecWRONG_ASSET));
905
906 // preclaim: tecNO_DST
907 Account const bogus{"bogus"};
908 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
909 destination(bogus),
910 ter(tecNO_DST));
911
912 // preclaim: tecDST_TAG_NEEDED
913 Account const dest{"dest"};
914 env.fund(XRP(1'000), dest);
915 env(fset(dest, asfRequireDest));
916 env.close();
917 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
918 destination(dest),
919 ter(tecDST_TAG_NEEDED));
920
921 // preclaim: tecNO_PERMISSION
922 env(fclear(dest, asfRequireDest));
923 env(fset(dest, asfDepositAuth));
924 env.close();
925 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
926 destination(dest),
927 ter(tecNO_PERMISSION));
928
929 // preclaim: tecFROZEN
930 env(trust(dest, asset(1'000)));
931 env(fclear(dest, asfDepositAuth));
932 env(fset(issuer, asfGlobalFreeze));
933 env.close();
934 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
935 destination(dest),
936 ter(tecFROZEN));
937
938 // preclaim:: tecFROZEN (deep frozen)
939 env(fclear(issuer, asfGlobalFreeze));
940 env(trust(issuer, asset(1'000), dest, tfSetFreeze | tfSetDeepFreeze));
941 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
942 destination(dest),
943 ter(tecFROZEN));
944
945 // preclaim: tecPSEUDO_ACCOUNT
946 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
947 destination(vaultInfo.pseudoAccount),
948 ter(tecPSEUDO_ACCOUNT));
949 }
950
951 if (brokerTest == CoverClawback)
952 {
953 // preflight: temINVALID (empty/zero broker id)
954 testZeroBrokerID([&]() {
955 return env.json(
956 coverClawback(alice),
957 loanBrokerID(brokerKeylet.key),
958 amount(vaultInfo.asset(2)));
959 });
960
961 if (asset.holds<Issue>())
962 {
963 // preclaim: AllowTrustLineClawback is not set
964 env(coverClawback(issuer),
965 loanBrokerID(brokerKeylet.key),
966 amount(vaultInfo.asset(2)),
967 ter(tecNO_PERMISSION));
968
969 // preclaim: NoFreeze is set
970 env(fset(issuer, asfAllowTrustLineClawback | asfNoFreeze));
971 env.close();
972 env(coverClawback(issuer),
973 loanBrokerID(brokerKeylet.key),
974 amount(vaultInfo.asset(2)),
975 ter(tecNO_PERMISSION));
976 }
977 else
978 {
979 // preclaim: MPTCanClawback is not set or MPTCanLock is not set
980 env(coverClawback(issuer),
981 loanBrokerID(brokerKeylet.key),
982 amount(vaultInfo.asset(2)),
983 ter(tecNO_PERMISSION));
984 }
985 env.close();
986 }
987
988 if (brokerTest == Delete)
989 {
990 Account const borrower{"borrower"};
991 env.fund(XRP(1'000), borrower);
992 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
993 sig(sfCounterpartySignature, alice),
994 fee(env.current()->fees().base * 2));
995
996 // preflight: temINVALID (empty/zero broker id)
997 testZeroBrokerID([&]() { return del(alice, brokerKeylet.key); });
998
999 // preclaim: tecHAS_OBLIGATIONS
1000 env(del(alice, brokerKeylet.key), ter(tecHAS_OBLIGATIONS));
1001
1002 // Repay and delete the loan
1003 auto const loanKeylet = keylet::loan(brokerKeylet.key, 1);
1004 env(loan::pay(borrower, loanKeylet.key, asset(50).value()));
1005 env(loan::del(alice, loanKeylet.key));
1006
1007 env(trust(issuer, asset(0), alice, tfSetFreeze | tfSetDeepFreeze));
1008 // preclaim: tecFROZEN (deep frozen)
1009 env(del(alice, brokerKeylet.key), ter(tecFROZEN));
1010 env(trust(issuer, asset(0), alice, tfClearFreeze | tfClearDeepFreeze));
1011
1012 // successful delete the loan broker object
1013 env(del(alice, brokerKeylet.key), ter(tesSUCCESS));
1014 }
1015 else
1016 {
1017 env(del(alice, brokerKeylet.key));
1018 }
1019
1020 if (brokerTest == Set)
1021 {
1022 // preflight: temINVALID (empty/zero broker id)
1023 testZeroBrokerID([&]() {
1024 return env.json(set(alice, vaultInfo.vaultID), loanBrokerID(brokerKeylet.key));
1025 });
1026 // preflight: temINVALID (empty/zero vault id)
1027 testZeroVaultID([&]() {
1028 return env.json(set(alice, vaultInfo.vaultID), loanBrokerID(brokerKeylet.key));
1029 });
1030
1031 if (asset.holds<Issue>())
1032 {
1033 env(fclear(issuer, asfDefaultRipple));
1034 env.close();
1035 // preclaim: DefaultRipple is not set
1036 env(set(alice, vaultInfo.vaultID), ter(terNO_RIPPLE));
1037
1038 env(fset(issuer, asfDefaultRipple));
1039 env.close();
1040 }
1041
1042 auto const amt =
1043 env.balance(alice) - env.current()->fees().accountReserve(env.ownerCount(alice));
1044 env(pay(alice, issuer, amt));
1045
1046 // preclaim:: tecINSUFFICIENT_RESERVE
1047 env(set(alice, vaultInfo.vaultID), ter(tecINSUFFICIENT_RESERVE));
1048 }
1049 }
1050
1051 void
1053 {
1054 testcase("Invalid LoanBrokerCoverClawback");
1055 using namespace jtx;
1056 using namespace loanBroker;
1057
1058 // preflight
1059 {
1060 Account const alice{"alice"};
1061 Account const issuer{"issuer"};
1062 auto const USD = alice["USD"];
1063 Env env(*this);
1064 env.fund(XRP(100'000), alice);
1065 env.close();
1066
1067 auto jtx = env.jt(coverClawback(alice), amount(USD(100)));
1068
1069 // holder == account
1070 env(jtx, ter(temINVALID));
1071
1072 // holder == beast::zero
1073 STAmount const bad(Issue{USD.currency, beast::zero}, 100);
1074 jtx.jv[sfAmount] = bad.getJson();
1075 jtx.stx = env.ust(jtx);
1076 Serializer s;
1077 jtx.stx->add(s);
1078 auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
1079 // fails in doSubmit() on STTx construction
1080 BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
1081 BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
1082 }
1083
1084 // preclaim
1085
1086 // Issue:
1087 // AllowTrustLineClawback is not set or NoFreeze is set
1088 testLoanBroker({}, CoverClawback);
1089
1090 // MPTIssue:
1091 // MPTCanClawback is not set
1092 testLoanBroker(
1093 [&](Env& env, Account const& issuer, Account const& alice) -> MPT {
1094 MPTTester const mpt({.env = env, .issuer = issuer, .holders = {alice}});
1095 return mpt;
1096 },
1097 CoverClawback);
1098 }
1099
1100 void
1102 {
1103 testcase("Invalid LoanBrokerCoverDeposit");
1104 using namespace jtx;
1105
1106 // preclaim:
1107 // tecWRONG_ASSET, tecINSUFFICIENT_FUNDS, frozen asset
1108 testLoanBroker({}, CoverDeposit);
1109 }
1110
1111 void
1113 {
1114 testcase("Invalid LoanBrokerCoverWithdraw");
1115 using namespace jtx;
1116
1117 /*
1118 preflight: illegal net
1119 isLegalNet() check is probably redundant. STAmount parsing
1120 should throw an exception on deserialize
1121
1122 preclaim: tecWRONG_ASSET, tecNO_DST, tecDST_TAG_NEEDED,
1123 tecNO_PERMISSION, checkFrozen failure, checkDeepFrozenFailure,
1124 second+third tecINSUFFICIENT_FUNDS (can this happen)?
1125 doApply: tecPATH_DRY (can it happen, funds already checked?)
1126 */
1127 testLoanBroker({}, CoverWithdraw);
1128 }
1129
1130 void
1132 {
1133 using namespace jtx;
1134 testcase("Invalid LoanBrokerDelete");
1135 /*
1136 preclaim: tecHAS_OBLIGATIONS
1137 doApply:
1138 accountSend failure, removeEmptyHolding failure,
1139 all tecHAS_OBLIGATIONS (can any of these happen?)
1140 */
1141 testLoanBroker({}, Delete);
1142 }
1143
1144 void
1146 {
1147 using namespace jtx;
1148 testcase("Invalid LoanBrokerSet");
1149
1150 /*preclaim: canAddHolding failure (can it happen with MPT?
1151 can't create Vault if CanTransfer is not enabled.)
1152 doApply:
1153 first+second dirLink failure, createPseudoAccount failure,
1154 addEmptyHolding failure
1155 can any of these happen?
1156 */
1157 testLoanBroker({}, Set);
1158 }
1159
1160 void
1162 {
1163 // This test is lifted directly from
1164 // https://bugs.immunefi.com/dashboard/submission/57808
1165 using namespace jtx;
1166 Env env(*this);
1167
1168 Account const alice{"alice"};
1169 env.fund(XRP(10000), alice);
1170 env.close();
1171
1172 // Create a Vault owned by alice with an XRP asset
1173 PrettyAsset const asset{xrpIssue(), 1};
1174 Vault const vault{env};
1175 auto const [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
1176 env(createTx);
1177 env.close();
1178
1179 // Predict LoanBroker key using alice's current sequence BEFORE submit
1180 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
1181
1182 // Create LoanBroker pointing to the vault
1183 env(loanBroker::set(alice, vaultKeylet.key));
1184 env.close();
1185
1186 // Build the CoverDeposit STTx directly
1187 STTx tx{ttLOAN_BROKER_COVER_DEPOSIT, [](STObject&) {}};
1188 tx.setAccountID(sfAccount, alice.id());
1189 tx.setFieldH256(sfLoanBrokerID, brokerKeylet.key);
1190 tx.setFieldAmount(sfAmount, asset(1));
1191
1192 // Create a writable view cloned from the current ledger and remove the
1193 // vault SLE
1194 OpenView ov{*env.current()};
1196 beast::Journal const jlog{sink};
1197 ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, tapNONE, jlog};
1198
1199 if (auto sleBroker = ac.view().peek(keylet::loanbroker(brokerKeylet.key)))
1200 {
1201 auto const vaultID = (*sleBroker)[sfVaultID];
1202 if (auto sleVault = ac.view().peek(keylet::vault(vaultID)))
1203 {
1204 ac.view().erase(sleVault);
1205 }
1206 }
1207
1208 // Invoke preclaim against the mutated (ApplyView) view; triggers
1209 // nullptr deref
1210 PreclaimContext const pctx{env.app(), ac.view(), tesSUCCESS, tx, tapNONE, jlog};
1212 }
1213
1214 void
1216 {
1217 testcase("Require Auth - Implicit Pseudo-account authorization");
1218 using namespace jtx;
1219 using namespace loanBroker;
1220
1221 Account const issuer{"issuer"};
1222 Account const alice{"alice"};
1223 Env env(*this);
1224 Vault vault{env};
1225
1226 env.fund(XRP(100'000), issuer, alice);
1227 env.close();
1228
1229 auto asset = MPTTester({
1230 .env = env,
1231 .issuer = issuer,
1232 .holders = {alice},
1233 .flags = MPTDEXFlags | tfMPTRequireAuth | tfMPTCanClawback | tfMPTCanLock,
1234 .authHolder = true,
1235 });
1236
1237 env(pay(issuer, alice, asset(100'000)));
1238 env.close();
1239
1240 // Alice is not authorized, can still create the vault
1241 asset.authorize({.account = issuer, .holder = alice, .flags = tfMPTUnauthorize});
1242 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
1243 env(tx);
1244 env.close();
1245
1246 auto const le = env.le(vaultKeylet);
1247 VaultInfo vaultInfo = [&]() {
1248 if (BEAST_EXPECT(le))
1249 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
1250 return VaultInfo{asset, {}, {}};
1251 }();
1252 if (vaultInfo.vaultID == uint256{})
1253 return;
1254
1255 // Can't unauthorize Vault pseudo-account
1256 asset.authorize(
1257 {.account = issuer,
1258 .holder = vaultInfo.pseudoAccount,
1259 .flags = tfMPTUnauthorize,
1260 .err = tecNO_PERMISSION});
1261
1262 auto forUnauthAuth = [&](auto&& doTx) {
1263 for (auto const flag : {tfMPTUnauthorize, 0u})
1264 {
1265 asset.authorize({.account = issuer, .holder = alice, .flags = flag});
1266 env.close();
1267 doTx(flag == 0);
1268 env.close();
1269 }
1270 };
1271
1272 // Can't deposit into Vault if the vault owner is not authorized
1273 forUnauthAuth([&](bool authorized) {
1274 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1275 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(51)}),
1276 err);
1277 });
1278
1279 // Can't withdraw from Vault if the vault owner is not authorized
1280 forUnauthAuth([&](bool authorized) {
1281 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1282 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = asset(1)}),
1283 err);
1284 });
1285
1286 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
1287 // Can create LoanBroker if the vault owner is not authorized
1288 forUnauthAuth([&](auto) { env(set(alice, vaultInfo.vaultID)); });
1289
1290 auto const broker = env.le(brokerKeylet);
1291 if (!BEAST_EXPECT(broker))
1292 return;
1293 Account brokerPseudo("pseudo", broker->at(sfAccount));
1294
1295 // Can't unauthorize LoanBroker pseudo-account
1296 asset.authorize(
1297 {.account = issuer,
1298 .holder = brokerPseudo,
1299 .flags = tfMPTUnauthorize,
1300 .err = tecNO_PERMISSION});
1301
1302 // Can't cover deposit into Vault if the vault owner is not authorized
1303 forUnauthAuth([&](bool authorized) {
1304 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1305 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)), err);
1306 });
1307
1308 // Can't cover withdraw from Vault if the vault owner is not authorized
1309 forUnauthAuth([&](bool authorized) {
1310 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1311 env(coverWithdraw(alice, brokerKeylet.key, vaultInfo.asset(5)), err);
1312 });
1313
1314 // Issuer can always cover clawback. The holder authorization is n/a.
1315 forUnauthAuth([&](bool) {
1316 env(coverClawback(issuer), loanBrokerID(brokerKeylet.key), amount(vaultInfo.asset(1)));
1317 });
1318 }
1319
1320 void
1322 {
1323 testcase("testLoanBrokerSetDebtMaximum");
1324 using namespace jtx;
1325 using namespace loanBroker;
1326 Account const issuer{"issuer"};
1327 Account const alice{"alice"};
1328 Env env(*this);
1329 Vault const vault{env};
1330
1331 env.fund(XRP(100'000), issuer, alice);
1332 env.close();
1333
1334 PrettyAsset const asset = [&]() {
1335 MPTTester mptt{env, issuer, mptInitNoFund};
1336 mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
1337 env.close();
1338 PrettyAsset const mptAsset = mptt["MPT"];
1339 mptt.authorize({.account = alice});
1340 env.close();
1341 return mptAsset;
1342 }();
1343
1344 env(pay(issuer, alice, asset(100'000)));
1345 env.close();
1346
1347 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
1348 env(tx);
1349 env.close();
1350 auto const le = env.le(vaultKeylet);
1351 VaultInfo const vaultInfo = [&]() {
1352 if (BEAST_EXPECT(le))
1353 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
1354 return VaultInfo{asset, {}, {}};
1355 }();
1356 if (vaultInfo.vaultID == uint256{})
1357 return;
1358
1359 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(50)}));
1360 env.close();
1361
1362 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
1363 env(set(alice, vaultInfo.vaultID));
1364 env.close();
1365
1366 Account const borrower{"borrower"};
1367 env.fund(XRP(1'000), borrower);
1368 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
1369 sig(sfCounterpartySignature, alice),
1370 fee(env.current()->fees().base * 2));
1371 auto const broker = env.le(brokerKeylet);
1372 if (!BEAST_EXPECT(broker))
1373 return;
1374
1375 BEAST_EXPECT(broker->at(sfDebtTotal) == 50);
1376 auto debtTotal = broker->at(sfDebtTotal);
1377
1378 auto tx2 = set(alice, vaultInfo.vaultID);
1379 tx2[sfLoanBrokerID] = to_string(brokerKeylet.key);
1380 tx2[sfDebtMaximum] = debtTotal - 1;
1381 env(tx2, ter(tecLIMIT_EXCEEDED));
1382
1383 tx2[sfDebtMaximum] = debtTotal + 1;
1384 env(tx2, ter(tesSUCCESS));
1385
1386 tx2[sfDebtMaximum] = 0;
1387 env(tx2, ter(tesSUCCESS));
1388
1389 tx2[sfDebtMaximum] = Json::Value::maxInt;
1390 env(tx2, ter(tesSUCCESS));
1391
1392 {
1393 auto const dm = power(2, 64) - 1;
1394 BEAST_EXPECT(dm > maxMPTokenAmount);
1395 tx2[sfDebtMaximum] = dm;
1396 env(tx2, ter(temINVALID));
1397 }
1398
1399 {
1400 auto const dm = power(2, 63) - 1;
1401 BEAST_EXPECTS(dm > maxMPTokenAmount, to_string(dm));
1402 tx2[sfDebtMaximum] = dm;
1403 env(tx2, ter(temINVALID));
1404 }
1405
1406 {
1407 auto const dm = power(2, 63) - 3;
1408 BEAST_EXPECTS(dm == maxMPTokenAmount, to_string(dm));
1409 tx2[sfDebtMaximum] = dm;
1410 env(tx2, ter(tesSUCCESS));
1411 }
1412
1413 {
1414 auto const dm = 2 * (power(2, 62) - 1) + 1;
1415 BEAST_EXPECTS(dm == maxMPTokenAmount, to_string(dm));
1416 tx2[sfDebtMaximum] = dm;
1417 env(tx2, ter(tesSUCCESS));
1418 }
1419
1420 tx2[sfDebtMaximum] = Number{9223372036854775807, 0};
1421 env(tx2, ter(tesSUCCESS));
1422 }
1423
1424 void
1426 {
1427 testcase << "RIPD-4323";
1428 using namespace jtx;
1429 Account const issuer("issuer");
1430 Account const holder("holder");
1431 Account const& broker = issuer;
1432
1433 auto test = [&](auto&& getToken) {
1434 Env env(*this);
1435
1436 env.fund(XRP(1'000), issuer, holder);
1437 env.close();
1438
1439 auto const [token, deposit, err] = getToken(env);
1440
1441 Vault const vault(env);
1442 auto const [tx, keylet] = vault.create({.owner = broker, .asset = token.asset()});
1443 env(tx);
1444 env.close();
1445
1446 env(vault.deposit({.depositor = broker, .id = keylet.key, .amount = deposit}),
1447 ter(err));
1448 env.close();
1449
1450 auto const brokerKeylet = keylet::loanbroker(broker, env.seq(broker));
1451
1452 env(loanBroker::set(broker, keylet.key));
1453 env.close();
1454
1455 env(loanBroker::coverDeposit(broker, brokerKeylet.key, deposit), ter(err));
1456 env.close();
1457 };
1458
1459 test([&](Env&) {
1460 // issuer can issue any amount
1461 auto const token = issuer["IOU"];
1462 return std::make_tuple(token, token(1'000), tesSUCCESS);
1463 });
1465 std::uint64_t, // pay to holder
1466 std::optional<std::uint64_t>, // max amount
1467 std::uint64_t, // deposit amount
1468 TER>> // expected error
1469 const mptTests = {
1470 // issuer can issue up to 2'000 tokens
1471 {2'000, 4'000, 1'000, tesSUCCESS},
1472 // issuer can issue 500 tokens (250 VaultDeposit +
1473 // 250 LoanBrokerCoverDeposit)
1474 {2'000, 2'500, 250, tesSUCCESS},
1475 // issuer can issue 500 tokens (250 VaultDeposit +
1476 // 250 LoanBrokerCoverDeposit). MaximumAmount is default.
1478 // issuer can issue 500, and fails on depositing 1'000
1479 {2'000, 2'500, 1'000, tecINSUFFICIENT_FUNDS},
1480 // issuer has already issued MaximumAmount
1481 {2'000, 2'000, 1'000, tecINSUFFICIENT_FUNDS},
1482 // issuer has already issued MaximumAmount. MaximumAmount is
1483 // default.
1485 };
1486 for (auto const& [pay, max, deposit, err] : mptTests)
1487 {
1488 test([&](Env& env) -> std::tuple<MPT, PrettyAmount, TER> {
1489 MPT const token = MPTTester(
1490 {.env = env,
1491 .issuer = issuer,
1492 .holders = {holder},
1493 .pay = pay,
1494 .flags = MPTDEXFlags,
1495 .maxAmt = max});
1496 return std::make_tuple(token, token(deposit), err);
1497 });
1498 }
1499 }
1500
1501 void
1503 {
1504 testcase << "RIPD-4466 - LoanBrokerSet disallows frozen vaults";
1505 using namespace jtx;
1506 Env env(*this);
1507
1508 Account const issuer{"issuer"}, lender{"lender"}, borrower{"borrower"};
1509 env.fund(XRP(20'000), issuer, lender, borrower);
1510 auto const IOU = issuer["IOU"];
1511
1512 Vault const vault{env};
1513 auto [tx, vaultKeylet] = vault.create({.owner = lender, .asset = IOU.asset()});
1514 env(tx);
1515 env.close();
1516
1517 // Get vault pseudo-account and FREEZE it
1518 auto const vaultSle = env.le(vaultKeylet);
1519 auto const vaultPseudo = vaultSle->at(sfAccount);
1520 auto const vaultPseudoAcct = Account("VaultPseudo", vaultPseudo);
1521 env(trust(issuer, vaultPseudoAcct["IOU"](0), tfSetFreeze));
1522
1523 env(loanBroker::set(lender, vaultKeylet.key), ter(tecFROZEN));
1524 }
1525
1526 void
1528 {
1529 using namespace jtx;
1530 Account issuer("broker");
1531 Account broker("issuer");
1532 Account dest("destination");
1533 auto const token = issuer["IOU"];
1534
1535 enum TrustState {
1536 RequireAuth,
1537 ZeroLimit,
1538 ReachedLimit,
1539 NearLimit,
1540 NoTrustLine,
1541 };
1542
1543 auto test = [&](TrustState trustState) {
1544 Env env(*this);
1545
1546 testcase << "RIPD-4274 IOU with state: " << static_cast<int>(trustState);
1547
1548 auto setTrustLine = [&](Account const& acct, TrustState state) {
1549 switch (state)
1550 {
1551 case RequireAuth:
1552 env(trust(issuer, token(0), acct, tfSetfAuth));
1553 break;
1554 case ZeroLimit: {
1555 auto jv = trust(acct, token(0));
1556 // set QualityIn so that the trustline is not
1557 // auto-deleted
1558 jv[sfQualityIn] = 10'000'000;
1559 env(jv);
1560 }
1561 break;
1562 case ReachedLimit: {
1563 env(trust(acct, token(1'000)));
1564 env(pay(issuer, acct, token(1'000)));
1565 env.close();
1566 }
1567 break;
1568 case NearLimit: {
1569 env(trust(acct, token(1'000)));
1570 env(pay(issuer, acct, token(950)));
1571 env.close();
1572 }
1573 break;
1574 case NoTrustLine:
1575 // don't create a trustline
1576 break;
1577 default:
1578 BEAST_EXPECT(false);
1579 }
1580 env.close();
1581 };
1582
1583 env.fund(XRP(1'000), issuer, broker, dest);
1584 env.close();
1585
1586 if (trustState == RequireAuth)
1587 {
1588 env(fset(issuer, asfRequireAuth));
1589 env.close();
1590
1591 setTrustLine(broker, RequireAuth);
1592 }
1593
1594 setTrustLine(dest, trustState);
1595
1596 env(trust(broker, token(2'000), 0));
1597 env(pay(issuer, broker, token(2'000)));
1598 env.close();
1599
1600 Vault const vault(env);
1601 auto const [tx, keylet] = vault.create({.owner = broker, .asset = token.asset()});
1602 env(tx);
1603 env.close();
1604
1605 // Test Vault withdraw
1606 env(vault.deposit({.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1607 env.close();
1608
1609 env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}),
1610 loanBroker::destination(dest),
1611 ter(std::ignore));
1612 BEAST_EXPECT(env.ter() == tecNO_LINE);
1613 env.close();
1614
1615 env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1616
1617 // Test LoanBroker withdraw
1618 auto const brokerKeylet = keylet::loanbroker(broker, env.seq(broker));
1619
1620 env(loanBroker::set(broker, keylet.key));
1621 env.close();
1622
1623 env(loanBroker::coverDeposit(broker, brokerKeylet.key, token(1'000)));
1624 env.close();
1625
1626 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
1627 loanBroker::destination(dest),
1628 ter(std::ignore));
1629 BEAST_EXPECT(env.ter() == tecNO_LINE);
1630 env.close();
1631
1632 // Clearing RequireAuth shouldn't change the result
1633 if (trustState == RequireAuth)
1634 {
1635 env(fclear(issuer, asfRequireAuth));
1636 env.close();
1637
1638 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
1639 loanBroker::destination(dest),
1640 ter(std::ignore));
1641 BEAST_EXPECT(env.ter() == tecNO_LINE);
1642 env.close();
1643 }
1644 };
1645
1646 test(RequireAuth);
1647 test(ZeroLimit);
1648 test(ReachedLimit);
1649 test(NearLimit);
1650 test(NoTrustLine);
1651 }
1652
1653 void
1655 {
1656 using namespace jtx;
1657 Account issuer("broker");
1658 Account broker("issuer");
1659 Account dest("destination");
1660
1661 enum MPTState {
1662 RequireAuth,
1663 ReachedMAX,
1664 NoMPT,
1665 };
1666
1667 auto test = [&](MPTState MPTState) {
1668 Env env(*this);
1669
1670 testcase << "RIPD-4274 MPT with state: " << static_cast<int>(MPTState);
1671
1672 env.fund(XRP(1'000), issuer, broker, dest);
1673 env.close();
1674
1675 auto const maybeToken = [&]() -> std::optional<MPT> {
1676 switch (MPTState)
1677 {
1678 case RequireAuth: {
1679 auto tester = MPTTester(
1680 {.env = env,
1681 .issuer = issuer,
1682 .holders = {broker, dest},
1683 .pay = 2'000,
1684 .flags = MPTDEXFlags | tfMPTRequireAuth,
1685 .authHolder = true,
1686 .maxAmt = 5'000});
1687 // unauthorize dest
1688 tester.authorize(
1689 {.account = issuer, .holder = dest, .flags = tfMPTUnauthorize});
1690 return tester;
1691 }
1692 case ReachedMAX: {
1693 auto tester = MPTTester(
1694 {.env = env,
1695 .issuer = issuer,
1696 .holders = {broker, dest},
1697 .pay = 2'000,
1698 .flags = MPTDEXFlags,
1699 .maxAmt = 4'000});
1700 BEAST_EXPECT(env.balance(issuer, tester) == tester(-4'000));
1701 return tester;
1702 }
1703 case NoMPT: {
1704 return MPTTester(
1705 {.env = env,
1706 .issuer = issuer,
1707 .holders = {broker},
1708 .pay = 2'000,
1709 .flags = MPTDEXFlags,
1710 .maxAmt = 4'000});
1711 }
1712 default:
1713 return std::nullopt;
1714 }
1715 }();
1716 if (!BEAST_EXPECT(maybeToken))
1717 return;
1718
1719 auto const& token = *maybeToken;
1720
1721 Vault const vault(env);
1722 auto const [tx, keylet] = vault.create({.owner = broker, .asset = token.asset()});
1723 env(tx);
1724 env.close();
1725
1726 // Test Vault withdraw
1727 env(vault.deposit({.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1728 env.close();
1729
1730 env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}),
1731 loanBroker::destination(dest),
1732 ter(std::ignore));
1733
1734 // Shouldn't fail if at MaximumAmount since no new tokens are issued
1735 TER const err = MPTState == ReachedMAX ? TER(tesSUCCESS) : tecNO_AUTH;
1736 BEAST_EXPECT(env.ter() == err);
1737 env.close();
1738
1739 if (!isTesSuccess(err))
1740 {
1741 env(vault.withdraw(
1742 {.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1743 }
1744
1745 // Test LoanBroker withdraw
1746 auto const brokerKeylet = keylet::loanbroker(broker, env.seq(broker));
1747
1748 env(loanBroker::set(broker, keylet.key));
1749 env.close();
1750
1751 env(loanBroker::coverDeposit(broker, brokerKeylet.key, token(1'000)));
1752 env.close();
1753
1754 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
1755 loanBroker::destination(dest),
1756 ter(std::ignore));
1757 BEAST_EXPECT(env.ter() == err);
1758 env.close();
1759 };
1760
1761 test(RequireAuth);
1762 test(ReachedMAX);
1763 test(NoMPT);
1764 }
1765
1766 void
1768 {
1769 testRIPD4274IOU();
1770 testRIPD4274MPT();
1771 }
1772
1773public:
1774 void
1775 run() override
1776 {
1777 testLoanBrokerSetDebtMaximum();
1778 testLoanBrokerCoverDepositNullVault();
1779
1780 testDisabled();
1781 testLifecycle();
1782 testInvalidLoanBrokerCoverClawback();
1783 testInvalidLoanBrokerCoverDeposit();
1784 testInvalidLoanBrokerCoverWithdraw();
1785 testInvalidLoanBrokerDelete();
1786 testInvalidLoanBrokerSet();
1787 testRequireAuth();
1788
1789 testRIPD4323();
1790 testAMB06_VaultFreezeCheckMissing();
1791
1792 testRIPD4274();
1793
1794 // TODO: Write clawback failure tests with an issuer / MPT that doesn't
1795 // have the right flags set.
1796 }
1797};
1798
1799BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl);
1800
1801} // namespace test
1802} // namespace xrpl
static constexpr Int maxInt
Definition json_value.h:143
A generic endpoint for log messages.
Definition Journal.h:40
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
State information when applying a tx.
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
static TER preclaim(PreclaimContext const &ctx)
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:45
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:744
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:771
Slice slice() const noexcept
Definition Serializer.h:44
void lifecycle(char const *label, jtx::Env &env, jtx::Account const &issuer, jtx::Account const &alice, jtx::Account const &evan, jtx::Account const &bystander, VaultInfo const &vault, VaultInfo const &badVault, std::function< jtx::JTx(jtx::JTx const &)> modifyJTx, std::function< void(SLE::const_ref)> checkBroker, std::function< void(SLE::const_ref)> changeBroker, std::function< void(SLE::const_ref)> checkChangedBroker)
void run() override
Runs the suite.
void testLoanBroker(std::function< jtx::PrettyAsset(jtx::Env &, jtx::Account const &, jtx::Account const &)> getAsset, LoanBrokerTest brokerTest)
Immutable cryptographic account descriptor.
Definition Account.h:19
AccountID id() const
Returns the Account ID.
Definition Account.h:87
A transaction testing environment.
Definition Env.h:122
Application & app()
Definition Env.h:259
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
TER ter() const
Return the TER for the last JTx.
Definition Env.h:658
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition Env.cpp:240
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:258
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:249
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:549
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:168
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition Env.h:575
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:847
std::shared_ptr< STTx const > ust(JTx const &jt)
Create a STTx from a JTx without sanitizing Use to inject bogus values into test transactions by firs...
Definition Env.cpp:609
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:588
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:329
Converts to IOU Issue or STAmount.
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:250
Converts to MPT Issue or STAmount.
A balance matches.
Definition balance.h:19
Set the fee on a JTx.
Definition fee.h:17
Set the regular signature on a JTx.
Definition sig.h:15
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
T emplace_back(T... args)
T is_same_v
T make_tuple(T... args)
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:510
Keylet loan(uint256 const &loanBrokerID, std::uint32_t loanSeq) noexcept
Definition Indexes.cpp:516
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:504
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Json::Value del(jtx::Account const &account)
Definition dids.cpp:33
Json::Value coverDeposit(AccountID const &account, uint256 const &brokerID, STAmount const &amount, uint32_t flags)
Json::Value coverWithdraw(AccountID const &account, uint256 const &brokerID, STAmount const &amount, uint32_t flags)
Json::Value set(AccountID const &account, uint256 const &vaultId, uint32_t flags)
Json::Value coverClawback(AccountID const &account, std::uint32_t flags)
Json::Value pay(AccountID const &account, uint256 const &loanID, STAmount const &amount, std::uint32_t flags)
Json::Value set(AccountID const &account, uint256 const &loanBrokerID, Number principalRequested, std::uint32_t flags)
Json::Value del(AccountID const &account, uint256 const &loanID, std::uint32_t flags)
static MPTInit const mptInitNoFund
Definition mpt.h:106
auto const data
General field definitions, or fields used in multiple transaction namespaces.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:95
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:101
static none_t const none
Definition tags.h:14
FeatureBitset testable_amendments()
Definition Env.h:78
auto const MPTDEXFlags
Definition mpt.h:16
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
bool checkVL(Slice const &result, std::string const &expected)
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_RIPPLE
Definition TER.h:204
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:234
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:238
Number power(Number const &f, unsigned n)
Definition Number.cpp:935
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:437
constexpr FlagValue tfUniversal
Definition TxFlags.h:42
base_uint< 256 > uint256
Definition base_uint.h:531
void roundToAsset(A const &asset, Number &value)
Round an arbitrary precision Number IN PLACE to the precision of a given Asset.
Definition STAmount.h:695
@ tapNONE
Definition ApplyView.h:11
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:436
@ temINVALID
Definition TER.h:90
@ temINVALID_FLAG
Definition TER.h:91
@ temMALFORMED
Definition TER.h:67
@ temDISABLED
Definition TER.h:94
@ temBAD_AMOUNT
Definition TER.h:69
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
@ tecWRONG_ASSET
Definition TER.h:341
@ tecPSEUDO_ACCOUNT
Definition TER.h:343
@ tecNO_ENTRY
Definition TER.h:287
@ tecOBJECT_NOT_FOUND
Definition TER.h:307
@ tecNO_AUTH
Definition TER.h:281
@ tecFROZEN
Definition TER.h:284
@ tecINSUFFICIENT_FUNDS
Definition TER.h:306
@ tecNO_LINE
Definition TER.h:282
@ tecPRECISION_LOSS
Definition TER.h:344
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecLIMIT_EXCEEDED
Definition TER.h:342
@ tecNO_PERMISSION
Definition TER.h:286
@ tecDST_TAG_NEEDED
Definition TER.h:290
@ tecHAS_OBLIGATIONS
Definition TER.h:298
@ tecNO_DST
Definition TER.h:271
@ tesSUCCESS
Definition TER.h:225
constexpr FlagValue tfFullyCanonicalSig
Definition TxFlags.h:40
uint256 key
Definition Keylet.h:20
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:57
VaultInfo(jtx::PrettyAsset const &asset_, uint256 const &vaultID_, AccountID const &pseudo)
Execution context for applying a JSON transaction.
Definition JTx.h:25
std::optional< MPTCreate > create
Definition mpt.h:104
Set the destination tag on a JTx.
Definition tag.h:12
Set the sequence number on a JTx.
Definition seq.h:14