50 using namespace loan_broker;
54 auto const vaultAssetBalance = [&](
Keylet const& vaultKeylet) {
55 auto const sleVault = env.le(vaultKeylet);
56 BEAST_EXPECT(sleVault !=
nullptr);
58 return std::make_pair(sleVault->at(sfAssetsAvailable), sleVault->at(sfAssetsTotal));
61 auto const vaultShareBalance = [&](
Keylet const& vaultKeylet) {
62 auto const sleVault = env.le(vaultKeylet);
63 BEAST_EXPECT(sleVault !=
nullptr);
66 BEAST_EXPECT(sleIssuance !=
nullptr);
68 return sleIssuance->at(sfOutstandingAmount);
78 auto const setupVault = [&](
PrettyAsset const& asset,
81 Vault const vault{env};
83 auto const& [tx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
88 auto const& vaultSle = env.le(vaultKeylet);
89 BEAST_EXPECT(vaultSle !=
nullptr);
91 Asset const share = vaultSle->at(sfShareMPTID);
94 {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}),
100 vault.closePastSubscription(subscriptionDate);
102 auto const& [availablePreDefault, totalPreDefault] = vaultAssetBalance(vaultKeylet);
103 BEAST_EXPECT(availablePreDefault == totalPreDefault);
104 BEAST_EXPECT(availablePreDefault == asset(100).value());
109 .id = vaultKeylet.key,
111 .amount = share(0).value()}),
115 auto const& sharesAvailable = vaultShareBalance(vaultKeylet);
116 auto const& brokerKeylet =
119 env(
set(owner, vaultKeylet.key));
125 env(
set(depositor, brokerKeylet.key, asset(100).value()),
128 kPaymentInterval(120),
130 Sig(sfCounterpartySignature, owner),
131 Fee(env.current()->fees().base * 2),
139 .id = vaultKeylet.key,
141 .amount = share(0).value()}),
147 env(manage(owner, loanKeylet.key, tfLoanDefault), Ter(
tesSUCCESS));
149 auto const& [availablePostDefault, totalPostDefault] = vaultAssetBalance(vaultKeylet);
151 BEAST_EXPECT(availablePostDefault == totalPostDefault);
152 BEAST_EXPECT(availablePostDefault == asset(0).value());
153 BEAST_EXPECT(vaultShareBalance(vaultKeylet) == sharesAvailable);
158 auto const testCase = [&](
PrettyAsset const& asset,
163 testcase(
"VaultClawback (share) - " + prefix +
" owner asset clawback fails");
164 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
168 auto const expectedTer = [&]() {
177 .id = vaultKeylet.key,
179 .amount = asset(100).value(),
187 "VaultClawback (share) - " + prefix +
" owner incomplete share clawback fails");
188 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
189 auto const& vaultSle = env.le(vaultKeylet);
190 if (!BEAST_EXPECT(vaultSle))
192 Asset const share = vaultSle->at(sfShareMPTID);
195 .id = vaultKeylet.key,
197 .amount = share(1).value(),
205 "VaultClawback (share) - " + prefix +
206 " owner implicit complete share clawback");
207 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
210 .id = vaultKeylet.key,
221 "VaultClawback (share) - " + prefix +
222 " owner explicit complete share clawback succeeds");
223 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor);
224 auto const& vaultSle = env.le(vaultKeylet);
225 if (!BEAST_EXPECT(vaultSle))
227 Asset const share = vaultSle->at(sfShareMPTID);
230 .id = vaultKeylet.key,
232 .amount = share(vaultShareBalance(vaultKeylet)).value(),
238 testcase(
"VaultClawback (share) - " + prefix +
" owner can clawback own shares");
239 auto [vault, vaultKeylet] = setupVault(asset, owner, owner);
240 auto const& vaultSle = env.le(vaultKeylet);
241 if (!BEAST_EXPECT(vaultSle))
243 Asset const share = vaultSle->at(sfShareMPTID);
246 .id = vaultKeylet.key,
248 .amount = share(vaultShareBalance(vaultKeylet)).value(),
255 testcase(
"VaultClawback (share) - " + prefix +
" empty vault share clawback fails");
256 auto [vault, vaultKeylet] = setupVault(asset, owner, owner);
257 auto const& vaultSle = env.le(vaultKeylet);
258 if (!BEAST_EXPECT(vaultSle))
260 Asset const share = vaultSle->at(sfShareMPTID);
263 .id = vaultKeylet.key,
265 .amount = share(vaultShareBalance(vaultKeylet)).value(),
272 .id = vaultKeylet.key,
274 .amount = share(vaultShareBalance(vaultKeylet)).value(),
282 Account const depositor{
"bob"};
283 Account const issuer{
"issuer"};
285 env.fund(XRP(10000), issuer, owner, depositor);
290 testCase(xrp,
"XRP", owner, depositor);
291 testCase(xrp,
"XRP (depositor is owner)", owner, owner);
295 env(fset(issuer, asfAllowTrustLineClawback));
298 env.trust(iou(1000), owner);
299 env.trust(iou(1000), depositor);
300 env(pay(issuer, owner, iou(100)));
301 env(pay(issuer, depositor, iou(100)));
303 testCase(iou,
"IOU", owner, depositor);
304 testCase(iou,
"IOU (owner is issuer)", issuer, depositor);
307 MPTTester mptt{env, issuer, kMptInitNoFund};
308 mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
310 mptt.authorize({.account = owner});
311 mptt.authorize({.account = depositor});
312 env(pay(issuer, owner, mpt(1000)));
313 env(pay(issuer, depositor, mpt(1000)));
315 testCase(mpt,
"MPT", owner, depositor);
316 testCase(mpt,
"MPT (owner is issuer)", issuer, depositor);
323 using namespace loan_broker;
324 using namespace loan;
326 env.enableFeature(fixCleanup3_1_3);
335 auto const setupVault = [&](
PrettyAsset const& asset,
339 Vault const vault{env};
341 auto const& [tx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
346 auto const& vaultSle = env.le(vaultKeylet);
347 BEAST_EXPECT(vaultSle !=
nullptr);
348 env.memoize(
Account(
"vault", vaultSle->at(sfAccount)));
350 {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}),
354 vault.closePastSubscription(subscriptionDate);
359 auto const testCase = [&](
PrettyAsset const& asset,
366 testcase(
"VaultClawback (asset) - " + prefix +
" issuer XRP clawback fails");
367 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
372 .id = vaultKeylet.key,
374 .amount = asset(1).value(),
380 .id = vaultKeylet.key,
389 "VaultClawback (asset) - " + prefix +
" clawback for different asset fails");
390 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
392 Account const issuer2{
"issuer2"};
396 .id = vaultKeylet.key,
398 .amount = asset2(1).value(),
405 "VaultClawback (asset) - " + prefix +
406 " ambiguous owner/issuer asset clawback fails");
407 auto [vault, vaultKeylet] = setupVault(asset, issuer, depositor, issuer);
410 .id = vaultKeylet.key,
417 testcase(
"VaultClawback (asset) - " + prefix +
" non-issuer asset clawback fails");
418 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
422 .id = vaultKeylet.key,
429 .id = vaultKeylet.key,
431 .amount = asset(1).value(),
437 testcase(
"VaultClawback (asset) - " + prefix +
" issuer clawback from self fails");
438 auto [vault, vaultKeylet] = setupVault(asset, owner, issuer, issuer);
441 .id = vaultKeylet.key,
448 testcase(
"VaultClawback (asset) - " + prefix +
" issuer share clawback fails");
449 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
450 auto const& vaultSle = env.le(vaultKeylet);
451 if (!BEAST_EXPECT(vaultSle))
453 Asset const share = vaultSle->at(sfShareMPTID);
457 .id = vaultKeylet.key,
459 .amount = share(1).value(),
466 "VaultClawback (asset) - " + prefix +
467 " partial issuer asset clawback succeeds");
468 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
472 .id = vaultKeylet.key,
474 .amount = asset(1).value(),
481 "VaultClawback (asset) - " + prefix +
" full issuer asset clawback succeeds");
482 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
486 .id = vaultKeylet.key,
488 .amount = asset(100).value(),
495 "VaultClawback (asset) - " + prefix +
496 " implicit full issuer asset clawback succeeds");
497 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
501 .id = vaultKeylet.key,
509 "VaultClawback (asset) - " + prefix +
510 " zero-amount clawback clamped with outstanding loan");
511 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
513 auto const vaultSle = env.le(vaultKeylet);
514 if (!BEAST_EXPECT(vaultSle))
520 auto const brokerKeylet =
522 env(
set(owner, vaultKeylet.key));
527 env(
set(depositor, brokerKeylet.key, asset(40).value()),
530 kPaymentInterval(120),
532 Sig(sfCounterpartySignature, owner),
533 Fee(env.current()->fees().base * 2),
538 auto const sle = env.le(vaultKeylet);
539 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value());
540 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value());
548 .id = vaultKeylet.key,
556 auto const sle = env.le(vaultKeylet);
557 BEAST_EXPECT(sle !=
nullptr);
558 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value());
559 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value());
562 auto const sharesAfter = env.balance(depositor, shares);
563 BEAST_EXPECT(sharesAfter == shares(
Number{4, sle->at(sfScale) + 1}));
569 "VaultClawback (asset) - " + prefix +
570 " non-zero clawback clamped with outstanding loan");
571 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
573 auto const vaultSle = env.le(vaultKeylet);
574 if (!BEAST_EXPECT(vaultSle))
579 auto const brokerKeylet =
581 env(
set(owner, vaultKeylet.key));
585 env(
set(depositor, brokerKeylet.key, asset(40).value()),
588 kPaymentInterval(120),
590 Sig(sfCounterpartySignature, owner),
591 Fee(env.current()->fees().base * 2),
596 auto const sle = env.le(vaultKeylet);
597 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value());
598 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value());
604 .id = vaultKeylet.key,
606 .amount = asset(100).value(),
612 auto const sle = env.le(vaultKeylet);
613 BEAST_EXPECT(sle !=
nullptr);
614 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value());
615 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value());
618 auto const sharesAfter = env.balance(depositor, shares);
619 BEAST_EXPECT(sharesAfter == shares(
Number{4, sle->at(sfScale) + 1}));
625 "VaultClawback (asset) - " + prefix +
626 " partial clawback below available with outstanding loan");
627 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
629 auto const vaultSle = env.le(vaultKeylet);
630 if (!BEAST_EXPECT(vaultSle))
635 auto const brokerKeylet =
637 env(
set(owner, vaultKeylet.key));
641 env(
set(depositor, brokerKeylet.key, asset(40).value()),
644 kPaymentInterval(120),
646 Sig(sfCounterpartySignature, owner),
647 Fee(env.current()->fees().base * 2),
652 auto const sle = env.le(vaultKeylet);
653 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value());
654 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value());
660 .id = vaultKeylet.key,
662 .amount = asset(30).value(),
668 auto const sle = env.le(vaultKeylet);
669 BEAST_EXPECT(sle !=
nullptr);
670 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(30).value());
671 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(70).value());
674 auto const sharesAfter = env.balance(depositor, shares);
675 BEAST_EXPECT(sharesAfter == shares(
Number{7, sle->at(sfScale) + 1}));
681 "VaultClawback (asset) - " + prefix +
682 " clawback exactly equal to available with outstanding loan");
683 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
685 auto const vaultSle = env.le(vaultKeylet);
686 if (!BEAST_EXPECT(vaultSle))
690 auto const brokerKeylet =
692 env(
set(owner, vaultKeylet.key));
696 env(
set(depositor, brokerKeylet.key, asset(40).value()),
699 kPaymentInterval(120),
701 Sig(sfCounterpartySignature, owner),
702 Fee(env.current()->fees().base * 2),
709 .id = vaultKeylet.key,
711 .amount = asset(60).value(),
717 auto const sle = env.le(vaultKeylet);
718 BEAST_EXPECT(sle !=
nullptr);
719 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value());
720 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value());
723 auto const sharesAfter = env.balance(depositor, shares);
724 BEAST_EXPECT(sharesAfter == shares(
Number{4, sle->at(sfScale) + 1}));
730 "VaultClawback (asset) - " + prefix +
731 " clawback with zero available (fully borrowed)");
732 auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer);
734 auto const vaultSle = env.le(vaultKeylet);
735 if (!BEAST_EXPECT(vaultSle))
739 auto const brokerKeylet =
741 env(
set(owner, vaultKeylet.key));
745 env(
set(depositor, brokerKeylet.key, asset(100).value()),
748 kPaymentInterval(120),
750 Sig(sfCounterpartySignature, owner),
751 Fee(env.current()->fees().base * 2),
756 auto const sle = env.le(vaultKeylet);
757 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value());
758 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value());
761 auto const sharesBefore = env.balance(depositor, shares);
767 .id = vaultKeylet.key,
776 .id = vaultKeylet.key,
778 .amount = asset(50).value(),
785 auto const sle = env.le(vaultKeylet);
786 BEAST_EXPECT(sle !=
nullptr);
787 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value());
788 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value());
789 auto const sharesAfter = env.balance(depositor, shares);
790 BEAST_EXPECT(sharesAfter == sharesBefore);
796 Account const depositor{
"bob"};
797 Account const issuer{
"issuer"};
799 env.fund(XRP(10000), issuer, owner, depositor);
804 testCase(xrp,
"XRP", owner, depositor, issuer);
808 env(fset(issuer, asfAllowTrustLineClawback));
810 env.trust(iou(2000), owner);
811 env.trust(iou(2000), depositor);
812 env(pay(issuer, owner, iou(2000)));
813 env(pay(issuer, depositor, iou(2000)));
815 testCase(iou,
"IOU", owner, depositor, issuer);
818 MPTTester mptt{env, issuer, kMptInitNoFund};
819 mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
822 mptt.authorize({.account = owner});
823 mptt.authorize({.account = depositor});
824 env(pay(issuer, depositor, mpt(2000)));
826 testCase(mpt,
"MPT", owner, depositor, issuer);
832 "VaultClawback (asset) - IOU pre-fixCleanup3_1_3"
833 " zero-amount clawback unclamped with outstanding loan");
835 env.disableFeature(fixCleanup3_1_3);
837 auto [vault, vaultKeylet] = setupVault(iou, owner, depositor, issuer);
839 auto const vaultSle = env.le(vaultKeylet);
840 BEAST_EXPECT(vaultSle !=
nullptr);
847 auto const brokerKeylet =
849 env(
set(owner, vaultKeylet.key));
854 env(
set(depositor, brokerKeylet.key, iou(40).value()),
857 kPaymentInterval(120),
859 Sig(sfCounterpartySignature, owner),
860 Fee(env.current()->fees().base * 2),
865 auto const sle = env.le(vaultKeylet);
866 BEAST_EXPECT(sle->at(sfAssetsAvailable) == iou(60).value());
867 BEAST_EXPECT(sle->at(sfAssetsTotal) == iou(100).value());
870 auto const sharesBefore = env.balance(depositor, shares);
878 .id = vaultKeylet.key,
886 auto const sle = env.le(vaultKeylet);
887 BEAST_EXPECT(sle !=
nullptr);
888 BEAST_EXPECT(sle->at(sfAssetsAvailable) == iou(60).value());
889 BEAST_EXPECT(sle->at(sfAssetsTotal) == iou(100).value());
890 auto const sharesAfter = env.balance(depositor, shares);
891 BEAST_EXPECT(sharesAfter == sharesBefore);
894 env.enableFeature(fixCleanup3_1_3);
911 testcase(
"Vault deposit fails when MPT asset is escrowed");
913 Env env{*
this, testableAmendments()};
914 auto const baseFee = env.current()->fees().base;
916 Account const depositor{
"depositor"};
917 Account const issuer{
"issuer"};
920 env.fund(XRP(10000), issuer, owner, depositor, bob);
923 MPTTester mptt{env, issuer, kMptInitNoFund};
925 {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock | tfMPTCanEscrow});
926 mptt.authorize({.account = owner});
927 mptt.authorize({.account = depositor});
928 mptt.authorize({.account = bob});
930 env(pay(issuer, depositor, asset(100)));
934 auto const escrowSeq = env.seq(depositor);
935 env(escrow::create(depositor, bob, asset(60)),
936 escrow::kCondition(escrow::kCb1),
937 escrow::kFinishTime(env.now() + 1s),
942 Vault const vault{env};
943 auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
949 {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}),
954 env(vault.deposit({.depositor = depositor, .id = vaultKeylet.key, .amount = asset(40)}),
959 auto const sle = env.le(vaultKeylet);
960 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value());
964 env(escrow::finish(bob, depositor, escrowSeq),
965 escrow::kCondition(escrow::kCb1),
966 escrow::kFulfillment(escrow::kFb1),
973 testcase(
"Vault withdraw respects escrowed shares");
975 Env env{*
this, testableAmendments()};
976 auto const baseFee = env.current()->fees().base;
978 Account const depositor{
"depositor"};
979 Account const issuer{
"issuer"};
982 env.fund(XRP(10000), issuer, owner, depositor, bob);
985 MPTTester mptt{env, issuer, kMptInitNoFund};
987 {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock | tfMPTCanEscrow});
988 mptt.authorize({.account = owner});
989 mptt.authorize({.account = depositor});
991 env(pay(issuer, depositor, asset(100)));
994 Vault const vault{env};
995 auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
1001 {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}),
1005 auto const vaultSle = env.le(vaultKeylet);
1006 if (!BEAST_EXPECT(vaultSle))
1008 env.memoize(
Account(
"vault", vaultSle->at(sfAccount)));
1012 auto const shareMPTID = vaultSle->at(sfShareMPTID);
1015 jv[jss::Account] = bob.human();
1016 jv[sfMPTokenIssuanceID] =
to_string(shareMPTID);
1017 jv[jss::TransactionType] = jss::MPTokenAuthorize;
1023 auto const escrowAmount = shares(
Number{6, vaultSle->at(sfScale) + 1});
1024 env(escrow::create(depositor, bob, escrowAmount),
1025 escrow::kCondition(escrow::kCb1),
1026 escrow::kFinishTime(env.now() + 1s),
1033 {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}),
1039 {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(40)}),
1044 auto const sle = env.le(vaultKeylet);
1045 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(60).value());
1050 testcase(
"Vault clawback only recovers unlocked shares");
1052 Env env{*
this, testableAmendments() | fixCleanup3_1_3};
1053 auto const baseFee = env.current()->fees().base;
1055 Account const depositor{
"depositor"};
1056 Account const issuer{
"issuer"};
1059 env.fund(XRP(10000), issuer, owner, depositor, bob);
1062 MPTTester mptt{env, issuer, kMptInitNoFund};
1064 {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock | tfMPTCanEscrow});
1065 mptt.authorize({.account = owner});
1066 mptt.authorize({.account = depositor});
1068 env(pay(issuer, depositor, asset(100)));
1071 Vault const vault{env};
1072 auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
1078 {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}),
1082 auto const vaultSle = env.le(vaultKeylet);
1083 if (!BEAST_EXPECT(vaultSle))
1085 env.memoize(
Account(
"vault", vaultSle->at(sfAccount)));
1089 auto const shareMPTID = vaultSle->at(sfShareMPTID);
1092 jv[jss::Account] = bob.human();
1093 jv[sfMPTokenIssuanceID] =
to_string(shareMPTID);
1094 jv[jss::TransactionType] = jss::MPTokenAuthorize;
1100 auto const escrowAmount = shares(
Number{6, vaultSle->at(sfScale) + 1});
1101 env(escrow::create(depositor, bob, escrowAmount),
1102 escrow::kCondition(escrow::kCb1),
1103 escrow::kFinishTime(env.now() + 1s),
1110 env(vault.clawback({
1112 .id = vaultKeylet.key,
1113 .holder = depositor,
1119 auto const sle = env.le(vaultKeylet);
1120 BEAST_EXPECT(sle !=
nullptr);
1122 BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(60).value());
1123 BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value());
1126 auto const sharesAfter = env.balance(depositor, shares);
1127 BEAST_EXPECT(sharesAfter == shares(0));