rippled
Loading...
Searching...
No Matches
LoanBroker_test.cpp
1#include <test/jtx.h>
2
3#include <xrpld/app/tx/detail/LoanBrokerCoverDeposit.h>
4
5#include <xrpl/beast/unit_test/suite.h>
6
7namespace xrpl {
8namespace test {
9
11{
12 // Ensure that all the features needed for Lending Protocol are included,
13 // even if they are set to unsupported.
15 jtx::testable_amendments() | featureMPTokensV1 |
16 featureSingleAssetVault | featureLendingProtocol};
17
18 void
20 {
21 testcase("Disabled");
22 // Lending Protocol depends on Single Asset Vault (SAV). Test
23 // combinations of the two amendments.
24 // Single Asset Vault depends on MPTokensV1, but don't test every combo
25 // of that.
26 using namespace jtx;
27 auto failAll = [this](FeatureBitset features, bool goodVault = false) {
28 Env env(*this, features);
29
30 Account const alice{"alice"};
31 env.fund(XRP(10000), alice);
32
33 // Try to create a vault
34 PrettyAsset const asset{xrpIssue(), 1'000'000};
35 Vault vault{env};
36 auto const [tx, keylet] =
37 vault.create({.owner = alice, .asset = asset});
38 env(tx, ter(goodVault ? ter(tesSUCCESS) : ter(temDISABLED)));
39 env.close();
40 BEAST_EXPECT(static_cast<bool>(env.le(keylet)) == goodVault);
41
42 using namespace loanBroker;
43 // Can't create a loan broker regardless of whether the vault exists
44 env(set(alice, keylet.key), ter(temDISABLED));
45 auto const brokerKeylet =
46 keylet::loanbroker(alice.id(), env.seq(alice));
47 // Other LoanBroker transactions are disabled, too.
48 // 1. LoanBrokerCoverDeposit
49 env(coverDeposit(alice, brokerKeylet.key, asset(1000)),
51 // 2. LoanBrokerCoverWithdraw
52 env(coverWithdraw(alice, brokerKeylet.key, asset(1000)),
54 // 3. LoanBrokerCoverClawback
55 env(coverClawback(alice), ter(temDISABLED));
56 env(coverClawback(alice),
57 loanBrokerID(brokerKeylet.key),
59 env(coverClawback(alice), amount(asset(0)), ter(temDISABLED));
60 env(coverClawback(alice),
61 loanBrokerID(brokerKeylet.key),
62 amount(asset(1000)),
64 // 4. LoanBrokerDelete
65 env(del(alice, brokerKeylet.key), ter(temDISABLED));
66 };
67 failAll(all - featureMPTokensV1);
68 failAll(all - featureSingleAssetVault - featureLendingProtocol);
69 failAll(all - featureSingleAssetVault);
70 failAll(all - featureLendingProtocol, true);
71 }
72
73 struct VaultInfo
74 {
79 jtx::PrettyAsset const& asset_,
80 uint256 const& vaultID_,
81 AccountID const& pseudo)
82 : asset(asset_), vaultID(vaultID_), pseudoAccount("vault", pseudo)
83 {
84 }
85 };
86
87 void
89 char const* label,
90 jtx::Env& env,
91 jtx::Account const& issuer,
92 jtx::Account const& alice,
93 jtx::Account const& evan,
94 jtx::Account const& bystander,
95 VaultInfo const& vault,
96 VaultInfo const& badVault,
97 std::function<jtx::JTx(jtx::JTx const&)> modifyJTx,
98 std::function<void(SLE::const_ref)> checkBroker,
99 std::function<void(SLE::const_ref)> changeBroker,
100 std::function<void(SLE::const_ref)> checkChangedBroker)
101 {
102 {
103 auto const& asset = vault.asset.raw();
104 testcase << "Lifecycle: "
105 << (asset.native() ? "XRP "
106 : asset.holds<Issue>() ? "IOU "
107 : asset.holds<MPTIssue>() ? "MPT "
108 : "Unknown ")
109 << label;
110 }
111
112 using namespace jtx;
113 using namespace loanBroker;
114
115 // Bogus assets to use in test cases
116 static PrettyAsset const badMptAsset = [&]() {
117 MPTTester badMptt{env, evan, mptInitNoFund};
118 badMptt.create(
120 env.close();
121 return badMptt["BAD"];
122 }();
123 static PrettyAsset const badIouAsset = evan["BAD"];
124 static Account const nonExistent{"NonExistent"};
125 static PrettyAsset const ghostIouAsset = nonExistent["GST"];
126 PrettyAsset const vaultPseudoIouAsset = vault.pseudoAccount["PSD"];
127
128 auto const badKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
129 env(set(alice, badVault.vaultID));
130 env.close();
131 auto const badBrokerPseudo = [&]() {
132 if (auto const le = env.le(badKeylet); BEAST_EXPECT(le))
133 {
134 return Account{"Bad Broker pseudo-account", le->at(sfAccount)};
135 }
136 // Just to make the build work
137 return vault.pseudoAccount;
138 }();
139 PrettyAsset const badBrokerPseudoIouAsset = badBrokerPseudo["WAT"];
140
141 auto const keylet = keylet::loanbroker(alice.id(), env.seq(alice));
142 {
143 // Start with default values
144 auto jtx = env.jt(set(alice, vault.vaultID));
145 // Modify as desired
146 if (modifyJTx)
147 jtx = modifyJTx(jtx);
148 // Successfully create a Loan Broker
149 env(jtx);
150 }
151
152 env.close();
153 if (auto broker = env.le(keylet); BEAST_EXPECT(broker))
154 {
155 // log << "Broker after create: " << to_string(broker->getJson())
156 // << std::endl;
157 BEAST_EXPECT(broker->at(sfVaultID) == vault.vaultID);
158 BEAST_EXPECT(broker->at(sfAccount) != alice.id());
159 BEAST_EXPECT(broker->at(sfOwner) == alice.id());
160 BEAST_EXPECT(broker->at(sfFlags) == 0);
161 BEAST_EXPECT(broker->at(sfSequence) == env.seq(alice) - 1);
162 BEAST_EXPECT(broker->at(sfOwnerCount) == 0);
163 BEAST_EXPECT(broker->at(sfLoanSequence) == 1);
164 BEAST_EXPECT(broker->at(sfDebtTotal) == 0);
165 BEAST_EXPECT(broker->at(sfCoverAvailable) == 0);
166 if (checkBroker)
167 checkBroker(broker);
168
169 // if (auto const vaultSLE = env.le(keylet::vault(vault.vaultID)))
170 //{
171 // log << "Vault: " << to_string(vaultSLE->getJson()) <<
172 // std::endl;
173 // }
174 // Load the pseudo-account
175 Account const pseudoAccount{
176 "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) ==
187 BEAST_EXPECT(pseudo->at(sfSequence) == 0);
188 BEAST_EXPECT(pseudo->at(sfBalance) == beast::zero);
189 BEAST_EXPECT(
190 pseudo->at(sfOwnerCount) ==
191 (vault.asset.raw().native() ? 0 : 1));
192 BEAST_EXPECT(!pseudo->isFieldPresent(sfAccountTxnID));
193 BEAST_EXPECT(!pseudo->isFieldPresent(sfRegularKey));
194 BEAST_EXPECT(!pseudo->isFieldPresent(sfEmailHash));
195 BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletLocator));
196 BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletSize));
197 BEAST_EXPECT(!pseudo->isFieldPresent(sfMessageKey));
198 BEAST_EXPECT(!pseudo->isFieldPresent(sfTransferRate));
199 BEAST_EXPECT(!pseudo->isFieldPresent(sfDomain));
200 BEAST_EXPECT(!pseudo->isFieldPresent(sfTickSize));
201 BEAST_EXPECT(!pseudo->isFieldPresent(sfTicketCount));
202 BEAST_EXPECT(!pseudo->isFieldPresent(sfNFTokenMinter));
203 BEAST_EXPECT(!pseudo->isFieldPresent(sfMintedNFTokens));
204 BEAST_EXPECT(!pseudo->isFieldPresent(sfBurnedNFTokens));
205 BEAST_EXPECT(!pseudo->isFieldPresent(sfFirstNFTokenSequence));
206 BEAST_EXPECT(!pseudo->isFieldPresent(sfAMMID));
207 BEAST_EXPECT(!pseudo->isFieldPresent(sfVaultID));
208 BEAST_EXPECT(pseudo->at(sfLoanBrokerID) == keylet.key);
209 }
210
211 {
212 // Get the AccountInfo RPC result for the broker pseudo-account
213 std::string const pseudoStr = to_string(pseudoAccount.id());
214 auto const accountInfo = env.rpc("account_info", pseudoStr);
215 if (BEAST_EXPECT(accountInfo.isObject()))
216 {
217 auto const& accountData =
218 accountInfo[jss::result][jss::account_data];
219 if (BEAST_EXPECT(accountData.isObject()))
220 {
221 BEAST_EXPECT(accountData[jss::Account] == pseudoStr);
222 BEAST_EXPECT(
223 accountData[sfLoanBrokerID] ==
224 to_string(keylet.key));
225 }
226 auto const& pseudoInfo =
227 accountInfo[jss::result][jss::pseudo_account];
228 if (BEAST_EXPECT(pseudoInfo.isObject()))
229 {
230 BEAST_EXPECT(pseudoInfo[jss::type] == "LoanBroker");
231 }
232 }
233 }
234
235 auto verifyCoverAmount =
236 [&env, &vault, &pseudoAccount, &broker, &keylet, this](auto n) {
237 using namespace jtx;
238
239 if (BEAST_EXPECT(broker = env.le(keylet)))
240 {
241 auto const amount = vault.asset(n);
242 BEAST_EXPECT(
243 broker->at(sfCoverAvailable) == amount.number());
244 env.require(balance(pseudoAccount, amount));
245 }
246 };
247
248 // Test Cover funding before allowing alterations
249 env(coverDeposit(alice, uint256(0), vault.asset(10)),
250 ter(temINVALID));
251 env(coverDeposit(evan, keylet.key, vault.asset(10)),
253 env(coverDeposit(evan, keylet.key, vault.asset(0)),
255 env(coverDeposit(evan, keylet.key, vault.asset(-10)),
257 env(coverDeposit(alice, vault.vaultID, vault.asset(10)),
259
260 verifyCoverAmount(0);
261
262 // Test cover clawback failure cases BEFORE depositing any cover
263 // Need one of brokerID or amount
264 env(coverClawback(alice), ter(temINVALID));
265 env(coverClawback(alice),
266 loanBrokerID(uint256(0)),
267 ter(temINVALID));
268 env(coverClawback(alice), amount(XRP(1000)), ter(temBAD_AMOUNT));
269 env(coverClawback(alice),
270 amount(vault.asset(-10)),
272 // Clawbacks with an MPT need to specify the broker ID
273 env(coverClawback(alice), amount(badMptAsset(1)), ter(temINVALID));
274 env(coverClawback(evan),
275 loanBrokerID(vault.vaultID),
277 // Only the issuer can clawback
278 env(coverClawback(alice),
279 loanBrokerID(keylet.key),
281 if (vault.asset.raw().native())
282 {
283 // Can not clawback XRP under any circumstances
284 env(coverClawback(issuer),
285 loanBrokerID(keylet.key),
287 }
288 else
289 {
290 if (vault.asset.raw().holds<Issue>())
291 {
292 // Clawbacks without a loanBrokerID need to specify an IOU
293 // with the broker's pseudo-account as the issuer
294 env(coverClawback(alice),
295 amount(ghostIouAsset(1)),
297 env(coverClawback(alice),
298 amount(badIouAsset(1)),
300 // Pseudo-account is not for a broker
301 env(coverClawback(alice),
302 amount(vaultPseudoIouAsset(1)),
304 // If we specify a pseudo-account as the IOU amount, it
305 // needs to match the loan broker
306 env(coverClawback(issuer),
307 loanBrokerID(keylet.key),
308 amount(badBrokerPseudoIouAsset(10)),
310 PrettyAsset const brokerWrongCurrencyAsset =
311 pseudoAccount["WAT"];
312 env(coverClawback(issuer),
313 loanBrokerID(keylet.key),
314 amount(brokerWrongCurrencyAsset(10)),
316 }
317 else
318 {
319 // Clawbacks with an MPT need to specify the broker ID, even
320 // if the asset is valid
321 BEAST_EXPECT(vault.asset.raw().holds<MPTIssue>());
322 env(coverClawback(alice),
323 amount(vault.asset(10)),
324 ter(temINVALID));
325 }
326 // Since no cover has been deposited, there's nothing to claw
327 // back
328 env(coverClawback(issuer),
329 loanBrokerID(keylet.key),
330 amount(vault.asset(10)),
332 }
333 env.close();
334
335 // Fund the cover deposit
336 env(coverDeposit(alice, keylet.key, vault.asset(10)));
337 env.close();
338 verifyCoverAmount(10);
339
340 // Test withdrawal failure cases
341 env(coverWithdraw(alice, uint256(0), vault.asset(10)),
342 ter(temINVALID));
343 env(coverWithdraw(evan, keylet.key, vault.asset(10)),
345 env(coverWithdraw(evan, keylet.key, vault.asset(0)),
347 env(coverWithdraw(evan, keylet.key, vault.asset(-10)),
349 env(coverWithdraw(alice, vault.vaultID, vault.asset(10)),
351 env(coverWithdraw(alice, keylet.key, vault.asset(900)),
353
354 // Skip this test for XRP, because that can always be sent
355 if (!vault.asset.raw().native())
356 {
357 TER const expected = vault.asset.raw().holds<MPTIssue>()
358 ? tecNO_AUTH
359 : tecNO_LINE;
360 env(coverWithdraw(alice, keylet.key, vault.asset(1)),
361 destination(bystander),
362 ter(expected));
363 }
364
365 // Can not withdraw to the zero address
366 env(coverWithdraw(alice, keylet.key, vault.asset(1)),
367 destination(AccountID{}),
369
370 // Withdraw some of the cover amount
371 env(coverWithdraw(alice, keylet.key, vault.asset(7)));
372 env.close();
373 verifyCoverAmount(3);
374
375 // Add some more cover
376 env(coverDeposit(alice, keylet.key, vault.asset(5)));
377 env.close();
378 verifyCoverAmount(8);
379
380 // Withdraw some more. Send it to Evan. Very generous, considering
381 // how much trouble he's been.
382 env(coverWithdraw(alice, keylet.key, vault.asset(1)),
383 destination(evan));
384 env.close();
385 verifyCoverAmount(7);
386
387 // Withdraw some more. Send it to Evan. Very generous, considering
388 // how much trouble he's been.
389 env(coverWithdraw(alice, keylet.key, vault.asset(1)),
390 destination(evan),
391 dtag(3));
392 env.close();
393 verifyCoverAmount(6);
394
395 if (!vault.asset.raw().native())
396 {
397 // Issuer claws back some of the cover
398 env(coverClawback(issuer),
399 loanBrokerID(keylet.key),
400 amount(vault.asset(2)));
401 env.close();
402 verifyCoverAmount(4);
403
404 // Deposit some back
405 env(coverDeposit(alice, keylet.key, vault.asset(5)));
406 env.close();
407 verifyCoverAmount(9);
408
409 // Issuer claws it all back in various different ways
410 for (auto const& tx : {
411 // defer autofills until submission time
412 env.json(
413 coverClawback(issuer),
414 loanBrokerID(keylet.key),
415 fee(none),
416 seq(none),
417 sig(none)),
418 env.json(
419 coverClawback(issuer),
420 loanBrokerID(keylet.key),
421 amount(vault.asset(0)),
422 fee(none),
423 seq(none),
424 sig(none)),
425 env.json(
426 coverClawback(issuer),
427 loanBrokerID(keylet.key),
428 amount(vault.asset(6)),
429 fee(none),
430 seq(none),
431 sig(none)),
432 // amount will be truncated to what's available
433 env.json(
434 coverClawback(issuer),
435 loanBrokerID(keylet.key),
436 amount(vault.asset(100)),
437 fee(none),
438 seq(none),
439 sig(none)),
440 })
441 {
442 // Issuer claws it all back
443 env(tx);
444 env.close();
445 verifyCoverAmount(0);
446
447 // Deposit some back
448 env(coverDeposit(alice, keylet.key, vault.asset(6)));
449 env.close();
450 verifyCoverAmount(6);
451 }
452 }
453
454 // no-op
455 env(set(alice, vault.vaultID), loanBrokerID(keylet.key));
456 env.close();
457
458 // Make modifications to the broker
459 if (changeBroker)
460 changeBroker(broker);
461
462 env.close();
463
464 // Check the results of modifications
465 if (BEAST_EXPECT(broker = env.le(keylet)) && checkChangedBroker)
466 checkChangedBroker(broker);
467
468 // Verify that fields get removed when set to default values
469 // Debt maximum: explicit 0
470 // Data: explicit empty
471 env(set(alice, vault.vaultID),
472 loanBrokerID(broker->key()),
473 debtMaximum(Number(0)),
474 data(""));
475 env.close();
476
477 // Check the updated fields
478 if (BEAST_EXPECT(broker = env.le(keylet)))
479 {
480 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
481 BEAST_EXPECT(!broker->isFieldPresent(sfData));
482 }
483
485 // try to delete the wrong broker object
486 env(del(alice, vault.vaultID), ter(tecNO_ENTRY));
487 // evan tries to delete the broker
488 env(del(evan, keylet.key), ter(tecNO_PERMISSION));
489
490 // Get the "bad" broker out of the way
491 env(del(alice, badKeylet.key));
492 env.close();
493
494 // Note alice's balance of the asset and the broker account's cover
495 // funds
496 auto const aliceBalance = env.balance(alice, vault.asset);
497 auto const coverFunds = env.balance(pseudoAccount, vault.asset);
498 BEAST_EXPECT(coverFunds.number() == broker->at(sfCoverAvailable));
499 BEAST_EXPECT(coverFunds != beast::zero);
500 verifyCoverAmount(6);
501
502 // delete the broker
503 // log << "Broker before delete: " << to_string(broker->getJson())
504 // << std::endl;
505 // if (auto const pseudo = env.le(pseudoKeylet);
506 // BEAST_EXPECT(pseudo))
507 //{
508 // log << "Pseudo-account before delete: "
509 // << to_string(pseudo->getJson()) << std::endl
510 // << std::endl;
511 //}
512
513 env(del(alice, keylet.key));
514 env.close();
515 {
516 broker = env.le(keylet);
517 BEAST_EXPECT(!broker);
518 auto pseudo = env.le(pseudoKeylet);
519 BEAST_EXPECT(!pseudo);
520 }
521 auto const expectedBalance = aliceBalance + coverFunds -
522 (aliceBalance.value().native()
523 ? STAmount(env.current()->fees().base.value())
524 : vault.asset(0));
525 env.require(balance(alice, expectedBalance));
526 env.require(balance(pseudoAccount, vault.asset(none)));
527 }
528 }
529
530 void
532 {
533 testcase("Lifecycle");
534 using namespace jtx;
535
536 // Create 3 loan brokers: one for XRP, one for an IOU, and one for an
537 // MPT. That'll require three corresponding SAVs.
538 Env env(*this, all);
539
540 Account issuer{"issuer"};
541 // For simplicity, alice will be the sole actor for the vault & brokers.
542 Account alice{"alice"};
543 // Evan will attempt to be naughty
544 Account evan{"evan"};
545 // Bystander doesn't have anything to do with the SAV or Broker, or any
546 // of the relevant tokens
547 Account bystander{"bystander"};
548 Vault vault{env};
549
550 // Fund the accounts and trust lines with the same amount so that tests
551 // can use the same values regardless of the asset.
552 env.fund(XRP(100'000), issuer, noripple(alice, evan, bystander));
553 env.close();
554
555 env(fset(issuer, asfAllowTrustLineClawback));
556 env.close();
557
558 // Create assets
559 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
560 PrettyAsset const iouAsset = issuer["IOU"];
561 env(trust(alice, iouAsset(1'000'000)));
562 env(trust(evan, iouAsset(1'000'000)));
563 env.close();
564 env(pay(issuer, evan, iouAsset(100'000)));
565 env(pay(issuer, alice, iouAsset(100'000)));
566 env.close();
567
568 MPTTester mptt{env, issuer, mptInitNoFund};
569 mptt.create(
571 env.close();
572 PrettyAsset const mptAsset = mptt["MPT"];
573 mptt.authorize({.account = alice});
574 mptt.authorize({.account = evan});
575 env.close();
576 env(pay(issuer, alice, mptAsset(100'000)));
577 env(pay(issuer, evan, mptAsset(100'000)));
578 env.close();
579
580 std::array const assets{xrpAsset, iouAsset, mptAsset};
581
582 // Create vaults
584 for (auto const& asset : assets)
585 {
586 auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
587 env(tx);
588 env.close();
589 if (auto const le = env.le(keylet); BEAST_EXPECT(env.le(keylet)))
590 {
591 vaults.emplace_back(asset, keylet.key, le->at(sfAccount));
592 }
593
594 env(vault.deposit(
595 {.depositor = alice, .id = keylet.key, .amount = asset(50)}));
596 env.close();
597 }
598 VaultInfo const badVault = [&]() -> VaultInfo {
599 auto [tx, keylet] =
600 vault.create({.owner = alice, .asset = iouAsset});
601 env(tx);
602 env.close();
603 if (auto const le = env.le(keylet); BEAST_EXPECT(env.le(keylet)))
604 {
605 return {iouAsset, keylet.key, le->at(sfAccount)};
606 }
607 // This should never happen
608 return {iouAsset, keylet.key, evan.id()};
609 }();
610
611 auto const aliceOriginalCount = env.ownerCount(alice);
612
613 // Create and update Loan Brokers
614 for (auto const& vault : vaults)
615 {
616 {
617 // Get the AccountInfo RPC result for the vault pseudo-account
618 std::string const pseudoStr =
619 to_string(vault.pseudoAccount.id());
620 auto const accountInfo = env.rpc("account_info", pseudoStr);
621 if (BEAST_EXPECT(accountInfo.isObject()))
622 {
623 auto const& accountData =
624 accountInfo[jss::result][jss::account_data];
625 if (BEAST_EXPECT(accountData.isObject()))
626 {
627 BEAST_EXPECT(accountData[jss::Account] == pseudoStr);
628 BEAST_EXPECT(
629 accountData[sfVaultID] == to_string(vault.vaultID));
630 }
631 auto const& pseudoInfo =
632 accountInfo[jss::result][jss::pseudo_account];
633 if (BEAST_EXPECT(pseudoInfo.isObject()))
634 {
635 BEAST_EXPECT(pseudoInfo[jss::type] == "Vault");
636 }
637 }
638 }
639
640 using namespace loanBroker;
641 using namespace xrpl::Lending;
642
643 TenthBips32 const tenthBipsZero{0};
644
645 auto badKeylet = keylet::vault(alice.id(), env.seq(alice));
646 // Try some failure cases
647 // not the vault owner
648 env(set(evan, vault.vaultID), ter(tecNO_PERMISSION));
649 // not a vault
650 env(set(alice, badKeylet.key), ter(tecNO_ENTRY));
651 // flags are checked first
652 env(set(evan, vault.vaultID, ~tfUniversal), ter(temINVALID_FLAG));
653 // field length validation
654 // sfData: good length, bad account
655 env(set(evan, vault.vaultID),
658 // sfData: too long
659 env(set(evan, vault.vaultID),
661 ter(temINVALID));
662 // sfManagementFeeRate: good value, bad account
663 env(set(evan, vault.vaultID),
664 managementFeeRate(maxManagementFeeRate),
666 // sfManagementFeeRate: too big
667 env(set(evan, vault.vaultID),
668 managementFeeRate(maxManagementFeeRate + TenthBips16(10)),
669 ter(temINVALID));
670 // sfCoverRateMinimum and sfCoverRateLiquidation are linked
671 // Cover: good value, bad account
672 env(set(evan, vault.vaultID),
673 coverRateMinimum(maxCoverRate),
674 coverRateLiquidation(maxCoverRate),
676 // CoverMinimum: too big
677 env(set(evan, vault.vaultID),
678 coverRateMinimum(maxCoverRate + 1),
679 coverRateLiquidation(maxCoverRate + 1),
680 ter(temINVALID));
681 // CoverLiquidation: too big
682 env(set(evan, vault.vaultID),
683 coverRateMinimum(maxCoverRate / 2),
684 coverRateLiquidation(maxCoverRate + 1),
685 ter(temINVALID));
686 // Cover: zero min, non-zero liquidation - implicit and
687 // explicit zero values.
688 env(set(evan, vault.vaultID),
689 coverRateLiquidation(maxCoverRate),
690 ter(temINVALID));
691 env(set(evan, vault.vaultID),
692 coverRateMinimum(tenthBipsZero),
693 coverRateLiquidation(maxCoverRate),
694 ter(temINVALID));
695 // Cover: non-zero min, zero liquidation - implicit and
696 // explicit zero values.
697 env(set(evan, vault.vaultID),
698 coverRateMinimum(maxCoverRate),
699 ter(temINVALID));
700 env(set(evan, vault.vaultID),
701 coverRateMinimum(maxCoverRate),
702 coverRateLiquidation(tenthBipsZero),
703 ter(temINVALID));
704 // sfDebtMaximum: good value, bad account
705 env(set(evan, vault.vaultID),
706 debtMaximum(Number(0)),
708 // sfDebtMaximum: overflow
709 env(set(evan, vault.vaultID),
710 debtMaximum(Number(1, 100)),
711 ter(temINVALID));
712 // sfDebtMaximum: negative
713 env(set(evan, vault.vaultID),
714 debtMaximum(Number(-1)),
715 ter(temINVALID));
716
717 std::string testData;
718 lifecycle(
719 "default fields",
720 env,
721 issuer,
722 alice,
723 evan,
724 bystander,
725 vault,
726 badVault,
727 // No modifications
728 {},
729 [&](SLE::const_ref broker) {
730 // Extra checks
731 BEAST_EXPECT(!broker->isFieldPresent(sfManagementFeeRate));
732 BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateMinimum));
733 BEAST_EXPECT(
734 !broker->isFieldPresent(sfCoverRateLiquidation));
735 BEAST_EXPECT(!broker->isFieldPresent(sfData));
736 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
737 BEAST_EXPECT(broker->at(sfDebtMaximum) == 0);
738 BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 0);
739 BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 0);
740
741 BEAST_EXPECT(
742 env.ownerCount(alice) == aliceOriginalCount + 4);
743 },
744 [&](SLE::const_ref broker) {
745 // Modifications
746
747 // Update the fields
748 auto const nextKeylet =
749 keylet::loanbroker(alice.id(), env.seq(alice));
750
751 // fields that can't be changed
752 // LoanBrokerID
753 env(set(alice, vault.vaultID),
754 loanBrokerID(nextKeylet.key),
756 // VaultID
757 env(set(alice, nextKeylet.key),
758 loanBrokerID(broker->key()),
760 // Owner
761 env(set(evan, vault.vaultID),
762 loanBrokerID(broker->key()),
764 // ManagementFeeRate
765 env(set(alice, vault.vaultID),
766 loanBrokerID(broker->key()),
767 managementFeeRate(maxManagementFeeRate),
768 ter(temINVALID));
769 // CoverRateMinimum
770 env(set(alice, vault.vaultID),
771 loanBrokerID(broker->key()),
772 coverRateMinimum(maxManagementFeeRate),
773 ter(temINVALID));
774 // CoverRateLiquidation
775 env(set(alice, vault.vaultID),
776 loanBrokerID(broker->key()),
777 coverRateLiquidation(maxManagementFeeRate),
778 ter(temINVALID));
779
780 // fields that can be changed
781 testData = "Test Data 1234";
782 // Bad data: too long
783 env(set(alice, vault.vaultID),
784 loanBrokerID(broker->key()),
786 ter(temINVALID));
787
788 // Bad debt maximum
789 env(set(alice, vault.vaultID),
790 loanBrokerID(broker->key()),
791 debtMaximum(Number(-175, -1)),
792 ter(temINVALID));
793 // Data & Debt maximum
794 env(set(alice, vault.vaultID),
795 loanBrokerID(broker->key()),
796 data(testData),
797 debtMaximum(Number(175, -1)));
798 },
799 [&](SLE::const_ref broker) {
800 // Check the updated fields
801 BEAST_EXPECT(checkVL(broker->at(sfData), testData));
802 BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(175, -1));
803 });
804
805 lifecycle(
806 "non-default fields",
807 env,
808 issuer,
809 alice,
810 evan,
811 bystander,
812 vault,
813 badVault,
814 [&](jtx::JTx const& jv) {
815 testData = "spam spam spam spam";
816 // Finally, create another Loan Broker with none of the
817 // values at default
818 return env.jt(
819 jv,
820 data(testData),
821 managementFeeRate(TenthBips16(123)),
822 debtMaximum(Number(9)),
823 coverRateMinimum(TenthBips32(100)),
824 coverRateLiquidation(TenthBips32(200)));
825 },
826 [&](SLE::const_ref broker) {
827 // Extra checks
828 BEAST_EXPECT(broker->at(sfManagementFeeRate) == 123);
829 BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 100);
830 BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 200);
831 BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(9));
832 BEAST_EXPECT(checkVL(broker->at(sfData), testData));
833 },
834 [&](SLE::const_ref broker) {
835 // Reset Data & Debt maximum to default values
836 env(set(alice, vault.vaultID),
837 loanBrokerID(broker->key()),
838 data(""),
839 debtMaximum(Number(0)));
840 },
841 [&](SLE::const_ref broker) {
842 // Check the updated fields
843 BEAST_EXPECT(!broker->isFieldPresent(sfData));
844 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
845 });
846 }
847
848 BEAST_EXPECT(env.ownerCount(alice) == aliceOriginalCount);
849 }
850
858
859 void
862 jtx::Env&,
863 jtx::Account const&,
864 jtx::Account const&)> getAsset,
865 LoanBrokerTest brokerTest)
866 {
867 using namespace jtx;
868 using namespace loanBroker;
869 Account const issuer{"issuer"};
870 Account const alice{"alice"};
871 Env env(*this);
872 Vault vault{env};
873
874 env.fund(XRP(100'000), issuer, alice);
875 env.close();
876
877 PrettyAsset const asset = [&]() {
878 if (getAsset)
879 return getAsset(env, issuer, alice);
880 env(trust(alice, issuer["IOU"](1'000'000)), THISLINE);
881 env.close();
882 return PrettyAsset(issuer["IOU"]);
883 }();
884
885 env(pay(issuer, alice, asset(100'000)), THISLINE);
886 env.close();
887
888 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
889 env(tx, THISLINE);
890 env.close();
891 auto const le = env.le(vaultKeylet);
892 VaultInfo vaultInfo = [&]() {
893 if (BEAST_EXPECT(le))
894 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
895 return VaultInfo{asset, {}, {}};
896 }();
897 if (vaultInfo.vaultID == uint256{})
898 return;
899
900 env(vault.deposit(
901 {.depositor = alice,
902 .id = vaultKeylet.key,
903 .amount = asset(50)}),
904 THISLINE);
905 env.close();
906
907 auto const brokerKeylet =
908 keylet::loanbroker(alice.id(), env.seq(alice));
909 env(set(alice, vaultInfo.vaultID), THISLINE);
910 env.close();
911
912 auto broker = env.le(brokerKeylet);
913 if (!BEAST_EXPECT(broker))
914 return;
915
916 auto testZeroBrokerID = [&](auto&& getTxJv) {
917 auto jv = getTxJv();
918 // empty broker ID
919 jv[sfLoanBrokerID] = "";
920 env(jv, ter(temINVALID), THISLINE);
921 // zero broker ID
922 jv[sfLoanBrokerID] = to_string(uint256{});
923 // needs a flag to distinguish the parsed STTx from the prior
924 // test
925 env(jv, txflags(tfFullyCanonicalSig), ter(temINVALID), THISLINE);
926 };
927 auto testZeroVaultID = [&](auto&& getTxJv) {
928 auto jv = getTxJv();
929 // empty broker ID
930 jv[sfVaultID] = "";
931 env(jv, ter(temINVALID), THISLINE);
932 // zero broker ID
933 jv[sfVaultID] = to_string(uint256{});
934 // needs a flag to distinguish the parsed STTx from the prior
935 // test
936 env(jv, txflags(tfFullyCanonicalSig), ter(temINVALID), THISLINE);
937 };
938
939 if (brokerTest == CoverDeposit)
940 {
941 // preflight: temINVALID (empty/zero broker id)
942 testZeroBrokerID([&]() {
943 return coverDeposit(alice, brokerKeylet.key, asset(10));
944 });
945
946 // preclaim: tecWRONG_ASSET
947 env(coverDeposit(alice, brokerKeylet.key, issuer["BAD"](10)),
948 ter(tecWRONG_ASSET),
949 THISLINE);
950
951 // preclaim: tecINSUFFICIENT_FUNDS
952 env(pay(alice, issuer, asset(100'000 - 50)), THISLINE);
953 env.close();
954 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)),
956
957 // preclaim: tecFROZEN
958 env(fset(issuer, asfGlobalFreeze), THISLINE);
959 env.close();
960 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)),
961 ter(tecFROZEN),
962 THISLINE);
963 }
964 else
965 // Fund the cover deposit
966 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)),
967 THISLINE);
968 env.close();
969
970 if (brokerTest == CoverWithdraw)
971 {
972 // preflight: temINVALID (empty/zero broker id)
973 testZeroBrokerID([&]() {
974 return coverWithdraw(alice, brokerKeylet.key, asset(10));
975 });
976
977 // preclaim: tecWRONG_ASSET
978 env(coverWithdraw(alice, brokerKeylet.key, issuer["BAD"](10)),
979 ter(tecWRONG_ASSET),
980 THISLINE);
981
982 // preclaim: tecNO_DST
983 Account const bogus{"bogus"};
984 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
985 destination(bogus),
986 ter(tecNO_DST),
987 THISLINE);
988
989 // preclaim: tecDST_TAG_NEEDED
990 Account const dest{"dest"};
991 env.fund(XRP(1'000), dest);
992 env(fset(dest, asfRequireDest), THISLINE);
993 env.close();
994 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
995 destination(dest),
997 THISLINE);
998
999 // preclaim: tecNO_PERMISSION
1000 env(fclear(dest, asfRequireDest), THISLINE);
1001 env(fset(dest, asfDepositAuth), THISLINE);
1002 env.close();
1003 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
1004 destination(dest),
1005 ter(tecNO_PERMISSION),
1006 THISLINE);
1007
1008 // preclaim: tecFROZEN
1009 env(trust(dest, asset(1'000)), THISLINE);
1010 env(fclear(dest, asfDepositAuth), THISLINE);
1011 env(fset(issuer, asfGlobalFreeze), THISLINE);
1012 env.close();
1013 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
1014 destination(dest),
1015 ter(tecFROZEN),
1016 THISLINE);
1017
1018 // preclaim:: tecFROZEN (deep frozen)
1019 env(fclear(issuer, asfGlobalFreeze), THISLINE);
1020 env(trust(
1021 issuer, asset(1'000), dest, tfSetFreeze | tfSetDeepFreeze),
1022 THISLINE);
1023 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
1024 destination(dest),
1025 ter(tecFROZEN),
1026 THISLINE);
1027 }
1028
1029 if (brokerTest == CoverClawback)
1030 {
1031 // preflight: temINVALID (empty/zero broker id)
1032 testZeroBrokerID([&]() {
1033 return env.json(
1034 coverClawback(alice),
1035 loanBrokerID(brokerKeylet.key),
1036 amount(vaultInfo.asset(2)));
1037 });
1038
1039 if (asset.holds<Issue>())
1040 {
1041 // preclaim: AllowTrustLineClaback is not set
1042 env(coverClawback(issuer),
1043 loanBrokerID(brokerKeylet.key),
1044 amount(vaultInfo.asset(2)),
1045 ter(tecNO_PERMISSION),
1046 THISLINE);
1047
1048 // preclaim: NoFreeze is set
1050 THISLINE);
1051 env.close();
1052 env(coverClawback(issuer),
1053 loanBrokerID(brokerKeylet.key),
1054 amount(vaultInfo.asset(2)),
1055 ter(tecNO_PERMISSION),
1056 THISLINE);
1057 }
1058 else
1059 {
1060 // preclaim: MPTCanClawback is not set or MPTCanLock is not set
1061 env(coverClawback(issuer),
1062 loanBrokerID(brokerKeylet.key),
1063 amount(vaultInfo.asset(2)),
1064 ter(tecNO_PERMISSION),
1065 THISLINE);
1066 }
1067 env.close();
1068 }
1069
1070 if (brokerTest == Delete)
1071 {
1072 Account const borrower{"borrower"};
1073 env.fund(XRP(1'000), borrower);
1074 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
1075 sig(sfCounterpartySignature, alice),
1076 fee(env.current()->fees().base * 2),
1077 THISLINE);
1078
1079 // preflight: temINVALID (empty/zero broker id)
1080 testZeroBrokerID([&]() { return del(alice, brokerKeylet.key); });
1081
1082 // preclaim: tecHAS_OBLIGATIONS
1083 env(del(alice, brokerKeylet.key),
1084 ter(tecHAS_OBLIGATIONS),
1085 THISLINE);
1086
1087 // Repay and delete the loan
1088 auto const loanKeylet = keylet::loan(brokerKeylet.key, 1);
1089 env(loan::pay(borrower, loanKeylet.key, asset(50).value()),
1090 THISLINE);
1091 env(loan::del(alice, loanKeylet.key), THISLINE);
1092
1093 env(trust(issuer, asset(0), alice, tfSetFreeze | tfSetDeepFreeze),
1094 THISLINE);
1095 // preclaim: tecFROZEN (deep frozen)
1096 env(del(alice, brokerKeylet.key), ter(tecFROZEN), THISLINE);
1097 env(trust(
1098 issuer, asset(0), alice, tfClearFreeze | tfClearDeepFreeze),
1099 THISLINE);
1100
1101 // successful delete the loan broker object
1102 env(del(alice, brokerKeylet.key), ter(tesSUCCESS), THISLINE);
1103 }
1104 else
1105 env(del(alice, brokerKeylet.key), THISLINE);
1106
1107 if (brokerTest == Set)
1108 {
1109 // preflight: temINVALID (empty/zero broker id)
1110 testZeroBrokerID([&]() {
1111 return env.json(
1112 set(alice, vaultInfo.vaultID),
1113 loanBrokerID(brokerKeylet.key));
1114 });
1115 // preflight: temINVALID (empty/zero vault id)
1116 testZeroVaultID([&]() {
1117 return env.json(
1118 set(alice, vaultInfo.vaultID),
1119 loanBrokerID(brokerKeylet.key));
1120 });
1121
1122 if (asset.holds<Issue>())
1123 {
1124 env(fclear(issuer, asfDefaultRipple), THISLINE);
1125 env.close();
1126 // preclaim: DefaultRipple is not set
1127 env(set(alice, vaultInfo.vaultID), ter(terNO_RIPPLE), THISLINE);
1128
1129 env(fset(issuer, asfDefaultRipple), THISLINE);
1130 env.close();
1131 }
1132
1133 auto const amt = env.balance(alice) -
1134 env.current()->fees().accountReserve(env.ownerCount(alice));
1135 env(pay(alice, issuer, amt), THISLINE);
1136
1137 // preclaim:: tecINSUFFICIENT_RESERVE
1138 env(set(alice, vaultInfo.vaultID),
1140 THISLINE);
1141 }
1142 }
1143
1144 void
1146 {
1147 testcase("Invalid LoanBrokerCoverClawback");
1148 using namespace jtx;
1149 using namespace loanBroker;
1150
1151 // preflight
1152 {
1153 Account const alice{"alice"};
1154 Account const issuer{"issuer"};
1155 auto const USD = alice["USD"];
1156 Env env(*this);
1157 env.fund(XRP(100'000), alice);
1158 env.close();
1159
1160 auto jtx = env.jt(coverClawback(alice), amount(USD(100)));
1161
1162 // holder == account
1163 env(jtx, ter(temINVALID), THISLINE);
1164
1165 // holder == beast::zero
1166 STAmount bad(Issue{USD.currency, beast::zero}, 100);
1167 jtx.jv[sfAmount] = bad.getJson();
1168 jtx.stx = env.ust(jtx);
1169 Serializer s;
1170 jtx.stx->add(s);
1171 auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
1172 // fails in doSubmit() on STTx construction
1173 BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
1174 BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
1175 }
1176
1177 // preclaim
1178
1179 // Issue:
1180 // AllowTrustLineClawback is not set or NoFreeze is set
1181 testLoanBroker({}, CoverClawback);
1182
1183 // MPTIssue:
1184 // MPTCanClawback is not set
1185 testLoanBroker(
1186 [&](Env& env, Account const& issuer, Account const& alice) -> MPT {
1187 MPTTester mpt(
1188 {.env = env, .issuer = issuer, .holders = {alice}});
1189 return mpt;
1190 },
1191 CoverClawback);
1192 }
1193
1194 void
1196 {
1197 testcase("Invalid LoanBrokerCoverDeposit");
1198 using namespace jtx;
1199
1200 // preclaim:
1201 // tecWRONG_ASSET, tecINSUFFICIENT_FUNDS, frozen asset
1202 testLoanBroker({}, CoverDeposit);
1203 }
1204
1205 void
1207 {
1208 testcase("Invalid LoanBrokerCoverWithdraw");
1209 using namespace jtx;
1210
1211 /*
1212 preflight: illegal net
1213 isLegalNet() check is probably redundant. STAmount parsing
1214 should throw an exception on deserialize
1215
1216 preclaim: tecWRONG_ASSET, tecNO_DST, tecDST_TAG_NEEDED,
1217 tecNO_PERMISSION, checkFrozen failure, checkDeepFrozenFailure,
1218 second+third tecINSUFFICIENT_FUNDS (can this happen)?
1219 doApply: tecPATH_DRY (can it happen, funds already checked?)
1220 */
1221 testLoanBroker({}, CoverWithdraw);
1222 }
1223
1224 void
1226 {
1227 using namespace jtx;
1228 testcase("Invalid LoanBrokerDelete");
1229 /*
1230 preclaim: tecHAS_OBLIGATIONS
1231 doApply:
1232 accountSend failure, removeEmptyHolding failure,
1233 all tecHAS_OBLIGATIONS (can any of these happen?)
1234 */
1235 testLoanBroker({}, Delete);
1236 }
1237
1238 void
1240 {
1241 using namespace jtx;
1242 testcase("Invalid LoanBrokerSet");
1243
1244 /*preclaim: canAddHolding failure (can it happen with MPT?
1245 can't create Vault if CanTransfer is not enabled.)
1246 doApply:
1247 first+second dirLink failure, createPseudoAccount failure,
1248 addEmptyHolding failure
1249 can any of these happen?
1250 */
1251 testLoanBroker({}, Set);
1252 }
1253
1254 void
1256 {
1257 // This test is lifted directly from
1258 // https://bugs.immunefi.com/dashboard/submission/57808
1259 using namespace jtx;
1260 Env env(*this);
1261
1262 Account const alice{"alice"};
1263 env.fund(XRP(10000), alice);
1264 env.close();
1265
1266 // Create a Vault owned by alice with an XRP asset
1267 PrettyAsset const asset{xrpIssue(), 1};
1268 Vault vault{env};
1269 auto const [createTx, vaultKeylet] =
1270 vault.create({.owner = alice, .asset = asset});
1271 env(createTx);
1272 env.close();
1273
1274 // Predict LoanBroker key using alice's current sequence BEFORE submit
1275 auto const brokerKeylet =
1276 keylet::loanbroker(alice.id(), env.seq(alice));
1277
1278 // Create LoanBroker pointing to the vault
1279 env(loanBroker::set(alice, vaultKeylet.key));
1280 env.close();
1281
1282 // Build the CoverDeposit STTx directly
1283 STTx tx{ttLOAN_BROKER_COVER_DEPOSIT, [](STObject&) {}};
1284 tx.setAccountID(sfAccount, alice.id());
1285 tx.setFieldH256(sfLoanBrokerID, brokerKeylet.key);
1286 tx.setFieldAmount(sfAmount, asset(1));
1287
1288 // Create a writable view cloned from the current ledger and remove the
1289 // vault SLE
1290 OpenView ov{*env.current()};
1292 beast::Journal jlog{sink};
1293 ApplyContext ac{
1294 env.app(),
1295 ov,
1296 tx,
1297 tesSUCCESS,
1298 env.current()->fees().base,
1299 tapNONE,
1300 jlog};
1301
1302 if (auto sleBroker =
1303 ac.view().peek(keylet::loanbroker(brokerKeylet.key)))
1304 {
1305 auto const vaultID = (*sleBroker)[sfVaultID];
1306 if (auto sleVault = ac.view().peek(keylet::vault(vaultID)))
1307 {
1308 ac.view().erase(sleVault);
1309 }
1310 }
1311
1312 // Invoke preclaim against the mutated (ApplyView) view; triggers
1313 // nullptr deref
1314 PreclaimContext pctx{
1315 env.app(), ac.view(), tesSUCCESS, tx, tapNONE, jlog};
1317 }
1318
1319 void
1321 {
1322 testcase("Require Auth - Implicit Pseudo-account authorization");
1323 using namespace jtx;
1324 using namespace loanBroker;
1325
1326 Account const issuer{"issuer"};
1327 Account const alice{"alice"};
1328 Env env(*this);
1329 Vault vault{env};
1330
1331 env.fund(XRP(100'000), issuer, alice);
1332 env.close();
1333
1334 auto asset = MPTTester({
1335 .env = env,
1336 .issuer = issuer,
1337 .holders = {alice},
1340 .authHolder = true,
1341 });
1342
1343 env(pay(issuer, alice, asset(100'000)));
1344 env.close();
1345
1346 // Alice is not authorized, can still create the vault
1347 asset.authorize(
1348 {.account = issuer, .holder = alice, .flags = tfMPTUnauthorize});
1349 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
1350 env(tx);
1351 env.close();
1352
1353 auto const le = env.le(vaultKeylet);
1354 VaultInfo vaultInfo = [&]() {
1355 if (BEAST_EXPECT(le))
1356 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
1357 return VaultInfo{asset, {}, {}};
1358 }();
1359 if (vaultInfo.vaultID == uint256{})
1360 return;
1361
1362 // Can't unauthorize Vault pseudo-account
1363 asset.authorize(
1364 {.account = issuer,
1365 .holder = vaultInfo.pseudoAccount,
1366 .flags = tfMPTUnauthorize,
1367 .err = tecNO_PERMISSION});
1368
1369 auto forUnauthAuth = [&](auto&& doTx) {
1370 for (auto const flag : {tfMPTUnauthorize, 0u})
1371 {
1372 asset.authorize(
1373 {.account = issuer, .holder = alice, .flags = flag});
1374 env.close();
1375 doTx(flag == 0);
1376 env.close();
1377 }
1378 };
1379
1380 // Can't deposit into Vault if the vault owner is not authorized
1381 forUnauthAuth([&](bool authorized) {
1382 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1383 env(vault.deposit(
1384 {.depositor = alice,
1385 .id = vaultKeylet.key,
1386 .amount = asset(51)}),
1387 err);
1388 });
1389
1390 // Can't withdraw from Vault if the vault owner is not authorized
1391 forUnauthAuth([&](bool authorized) {
1392 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1393 env(vault.withdraw(
1394 {.depositor = alice,
1395 .id = vaultKeylet.key,
1396 .amount = asset(1)}),
1397 err);
1398 });
1399
1400 auto const brokerKeylet =
1401 keylet::loanbroker(alice.id(), env.seq(alice));
1402 // Can create LoanBroker if the vault owner is not authorized
1403 forUnauthAuth([&](auto) { env(set(alice, vaultInfo.vaultID)); });
1404
1405 auto const broker = env.le(brokerKeylet);
1406 if (!BEAST_EXPECT(broker))
1407 return;
1408 Account brokerPseudo("pseudo", broker->at(sfAccount));
1409
1410 // Can't unauthorize LoanBroker pseudo-account
1411 asset.authorize(
1412 {.account = issuer,
1413 .holder = brokerPseudo,
1414 .flags = tfMPTUnauthorize,
1415 .err = tecNO_PERMISSION});
1416
1417 // Can't cover deposit into Vault if the vault owner is not authorized
1418 forUnauthAuth([&](bool authorized) {
1419 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1420 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)),
1421 err);
1422 });
1423
1424 // Can't cover withdraw from Vault if the vault owner is not authorized
1425 forUnauthAuth([&](bool authorized) {
1426 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1427 env(coverWithdraw(alice, brokerKeylet.key, vaultInfo.asset(5)),
1428 err);
1429 });
1430
1431 // Issuer can always cover clawback. The holder authorization is n/a.
1432 forUnauthAuth([&](bool) {
1433 env(coverClawback(issuer),
1434 loanBrokerID(brokerKeylet.key),
1435 amount(vaultInfo.asset(1)));
1436 });
1437 }
1438
1439public:
1440 void
1441 run() override
1442 {
1443 testLoanBrokerCoverDepositNullVault();
1444
1445 testDisabled();
1446 testLifecycle();
1447 testInvalidLoanBrokerCoverClawback();
1448 testInvalidLoanBrokerCoverDeposit();
1449 testInvalidLoanBrokerCoverWithdraw();
1450 testInvalidLoanBrokerDelete();
1451 testInvalidLoanBrokerSet();
1452 testRequireAuth();
1453
1454 // TODO: Write clawback failure tests with an issuer / MPT that doesn't
1455 // have the right flags set.
1456 }
1457};
1458
1459BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl);
1460
1461} // namespace test
1462} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
State information when applying a tx.
A currency issued by an account.
Definition Issue.h:14
Currency currency
Definition Issue.h:16
static TER preclaim(PreclaimContext const &ctx)
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:46
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:751
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:774
Slice slice() const noexcept
Definition Serializer.h:47
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:20
AccountID id() const
Returns the Account ID.
Definition Account.h:92
A transaction testing environment.
Definition Env.h:102
Application & app()
Definition Env.h:244
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:104
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition Env.cpp:242
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:260
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:272
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:251
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:491
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:166
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition Env.h:517
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:774
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:610
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:530
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:314
Converts to MPT Issue or STAmount.
A balance matches.
Definition balance.h:20
Set the fee on a JTx.
Definition fee.h:18
Set the regular signature on a JTx.
Definition sig.h:16
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:16
T emplace_back(T... args)
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
Keylet loan(uint256 const &loanBrokerID, std::uint32_t loanSeq) noexcept
Definition Indexes.cpp:558
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
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:108
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:92
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:102
static none_t const none
Definition tags.h:15
FeatureBitset testable_amendments()
Definition Env.h:55
auto const amount
auto const MPTDEXFlags
Definition mpt.h:17
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:6
constexpr std::uint32_t asfAllowTrustLineClawback
Definition TxFlags.h:75
@ terNO_RIPPLE
Definition TER.h:205
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,...
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:64
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:130
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
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:443
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:129
base_uint< 256 > uint256
Definition base_uint.h:539
constexpr std::uint32_t asfDefaultRipple
Definition TxFlags.h:65
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:134
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
Definition TxFlags.h:41
constexpr std::uint32_t tfClearDeepFreeze
Definition TxFlags.h:102
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:101
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
constexpr std::uint32_t tfUniversal
Definition TxFlags.h:43
@ tapNONE
Definition ApplyView.h:12
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:442
constexpr std::uint32_t asfNoFreeze
Definition TxFlags.h:63
@ temINVALID
Definition TER.h:91
@ temINVALID_FLAG
Definition TER.h:92
@ temMALFORMED
Definition TER.h:68
@ temDISABLED
Definition TER.h:95
@ temBAD_AMOUNT
Definition TER.h:70
@ tecWRONG_ASSET
Definition TER.h:342
@ tecNO_ENTRY
Definition TER.h:288
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecNO_AUTH
Definition TER.h:282
@ tecFROZEN
Definition TER.h:285
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecNO_LINE
Definition TER.h:283
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecNO_DST
Definition TER.h:272
@ lsfDepositAuth
@ lsfDefaultRipple
@ lsfDisableMaster
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
@ tesSUCCESS
Definition TER.h:226
uint256 key
Definition Keylet.h:21
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
VaultInfo(jtx::PrettyAsset const &asset_, uint256 const &vaultID_, AccountID const &pseudo)
Execution context for applying a JSON transaction.
Definition JTx.h:26
std::optional< MPTCreate > create
Definition mpt.h:106
Set the destination tag on a JTx.
Definition tag.h:13
Set the sequence number on a JTx.
Definition seq.h:15