51 testcase(
"closed-ended VaultCreate");
54 auto const withEnv = [
this](
FeatureBitset features,
auto&& body) {
55 Env env{*
this, features};
57 env.fund(XRP(1000), owner);
60 body(env, owner, vault);
70 testableAmendments() - featureLendingProtocolV1_1,
72 auto const sub = env.now().time_since_epoch().count() + 60;
73 auto [tx,
keylet] = vault.create(
76 .vaultKind = closedEnded,
77 .subscriptionDate = sub,
78 .redemptionDate = sub + minPeriod});
86 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
87 auto const sub = env.now().time_since_epoch().count() + 60;
88 auto const red = sub + 86400;
89 auto [tx,
keylet] = vault.create(
92 .vaultKind = closedEnded,
93 .subscriptionDate = sub,
94 .redemptionDate = red});
97 auto const sle = env.le(
keylet);
98 if (BEAST_EXPECT(sle))
100 BEAST_EXPECT(sle->at(sfVaultKind) == closedEnded);
101 BEAST_EXPECT(sle->at(sfSubscriptionDate) == sub);
102 BEAST_EXPECT(sle->at(sfRedemptionDate) == red);
107 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
108 auto const sub = env.now().time_since_epoch().count() + 60;
109 auto [tx,
keylet] = vault.create(
112 .vaultKind = closedEnded,
113 .redemptionDate = sub + minPeriod});
116 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
117 auto const sub = env.now().time_since_epoch().count() + 60;
118 auto [tx,
keylet] = vault.create(
121 .vaultKind = closedEnded,
122 .subscriptionDate = sub});
136 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
137 auto const nowSec = env.now().time_since_epoch().count();
138 auto [tx,
keylet] = vault.create(
141 .vaultKind = closedEnded,
142 .subscriptionDate = nowSec,
143 .redemptionDate = nowSec + minPeriod});
153 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
154 auto const sub = env.now().time_since_epoch().count() + 60;
155 auto [tx,
keylet] = vault.create(
158 .vaultKind = closedEnded,
159 .subscriptionDate = sub,
160 .redemptionDate = sub + minPeriod - 1});
163 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
164 auto const sub = env.now().time_since_epoch().count() + 60;
165 auto [tx,
keylet] = vault.create(
168 .vaultKind = closedEnded,
169 .subscriptionDate = sub,
170 .redemptionDate = sub});
173 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
174 auto const sub = env.now().time_since_epoch().count() + 60;
175 auto [tx,
keylet] = vault.create(
178 .vaultKind = closedEnded,
179 .subscriptionDate = sub,
180 .redemptionDate = sub - 1});
185 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
186 auto const sub = env.now().time_since_epoch().count() + 60;
187 auto [tx,
keylet] = vault.create(
190 .vaultKind = closedEnded,
191 .subscriptionDate = sub,
192 .redemptionDate = sub + maxPeriod});
198 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
199 auto const sub = env.now().time_since_epoch().count() + 60;
200 auto [tx,
keylet] = vault.create(
203 .vaultKind = closedEnded,
204 .subscriptionDate = sub,
205 .redemptionDate = sub + maxPeriod + 1});
212 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
213 auto const sub = env.now().time_since_epoch().count() + 60;
214 auto const red = sub + minPeriod;
215 auto [tx,
keylet] = vault.create(
218 .vaultKind = closedEnded,
219 .subscriptionDate = sub,
220 .redemptionDate = red});
223 auto const sle = env.le(
keylet);
224 if (BEAST_EXPECT(sle))
226 BEAST_EXPECT(sle->at(sfRedemptionDate) == red);
232 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
233 auto const sub = env.now().time_since_epoch().count() + 60;
234 auto const red = sub + maxPeriod - 1;
235 auto [tx,
keylet] = vault.create(
238 .vaultKind = closedEnded,
239 .subscriptionDate = sub,
240 .redemptionDate = red});
243 auto const sle = env.le(
keylet);
244 if (BEAST_EXPECT(sle))
246 BEAST_EXPECT(sle->at(sfRedemptionDate) == red);
252 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
253 auto const sub = env.now().time_since_epoch().count() + 60;
255 vault.create({.owner = owner, .asset = asset, .subscriptionDate = sub});
258 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
259 auto const sub = env.now().time_since_epoch().count() + 60;
261 vault.create({.owner = owner, .asset = asset, .redemptionDate = sub + minPeriod});
266 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
267 auto [tx,
keylet] = vault.create(
270 .vaultKind =
static_cast<std::uint8_t>(closedEnded + 1)});
275 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
276 auto [tx,
keylet] = vault.create({.owner = owner, .asset = asset});
279 auto const sle = env.le(
keylet);
280 if (BEAST_EXPECT(sle))
282 BEAST_EXPECT(!sle->isFieldPresent(sfVaultKind));
283 BEAST_EXPECT(!sle->isFieldPresent(sfSubscriptionDate));
284 BEAST_EXPECT(!sle->isFieldPresent(sfRedemptionDate));
290 withEnv(testableAmendments(), [&](Env& env,
Account const& owner,
Vault& vault) {
291 auto [tx,
keylet] = vault.create(
297 auto const sle = env.le(
keylet);
298 if (BEAST_EXPECT(sle))
302 BEAST_EXPECT(!sle->isFieldPresent(sfVaultKind));
303 BEAST_EXPECT(!sle->isFieldPresent(sfSubscriptionDate));
304 BEAST_EXPECT(!sle->isFieldPresent(sfRedemptionDate));
615 testcase(
"closed-ended vault lifecycle (subscribe → invest → redeem)");
617 using namespace loan_broker;
618 using namespace loan;
620 Env env{*
this, testableAmendments()};
624 Account const borrower{
"borrower"};
625 env.fund(XRP(10'000), owner, alice, bob, borrower);
632 auto const [vault,
keylet, sub, red] =
635 auto const sleCreate = env.le(
keylet);
636 BEAST_EXPECT(sleCreate);
637 MPTIssue const shares{sleCreate->at(sfShareMPTID)};
639 auto const balancesEq = [&](
STAmount const& available,
STAmount const& total) {
640 auto const sle = env.le(
keylet);
641 BEAST_EXPECT(sle->at(sfAssetsAvailable) == available);
642 BEAST_EXPECT(sle->at(sfAssetsTotal) == total);
644 auto const availableEq = [&](
STAmount const& expected) { balancesEq(expected, expected); };
651 std::uint64_t const actual = sle ? sle->getFieldU64(sfMPTAmount) : 0u;
652 BEAST_EXPECT(actual == expected);
658 auto tx = vault.set({.owner = owner, .id =
keylet.key});
665 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(100).value()}));
667 sharesEq(alice, 100'000'000);
668 availableEq(XRP(100).value());
671 env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = XRP(200).value()}));
673 sharesEq(bob, 200'000'000);
674 availableEq(XRP(300).value());
677 env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(25).value()}));
679 sharesEq(alice, 75'000'000);
680 availableEq(XRP(275).value());
684 auto const brokerKeylet =
686 env(loan_broker::set(owner,
keylet.key));
690 env.close(
Tp{
D{sub + 1}});
693 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(10).value()}),
697 env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(10).value()}),
705 env(loan::set(borrower, brokerKeylet.key, XRP(60).value()),
708 kPaymentInterval(60),
710 Sig(sfCounterpartySignature, owner),
711 Fee(env.current()->fees().base * 2));
714 BEAST_EXPECT(sleBroker);
716 BEAST_EXPECT(env.le(loanKeylet));
717 balancesEq(XRP(215).value(), XRP(275).value());
721 auto tx = vault.set({.owner = owner, .id =
keylet.key});
728 sharesEq(alice, 75'000'000);
729 sharesEq(bob, 200'000'000);
732 env.close(
Tp{
D{red}});
736 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(10).value()}),
741 env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(75).value()}));
744 balancesEq(XRP(140).value(), XRP(200).value());
751 env(vault.withdraw({.depositor = bob, .id = keylet.key, .amount = XRP(200).value()}),
752 Ter{tecINSUFFICIENT_FUNDS});
754 env(vault.withdraw({.depositor = bob, .id = keylet.key, .amount = XRP(140).value()}));
756 sharesEq(bob, 60'000'000);
757 balancesEq(XRP(0).value(), XRP(60).value());
761 auto const sleFinal = env.le(
keylet);
762 if (BEAST_EXPECT(sleFinal))
764 BEAST_EXPECT(sleFinal->at(sfVaultKind) == closedEnded);
765 BEAST_EXPECT(sleFinal->at(sfSubscriptionDate) == sub);
766 BEAST_EXPECT(sleFinal->at(sfRedemptionDate) == red);
776 testcase(
"closed-ended vault: late loan payment during Redemption succeeds");
778 using namespace loan_broker;
779 using namespace loan;
781 Env env{*
this, testableAmendments()};
784 Account const borrower{
"borrower"};
785 env.fund(XRP(10'000), owner, alice, borrower);
789 auto const [vault,
keylet, sub, red] =
792 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(100).value()}));
795 auto const brokerKeylet =
797 env(loan_broker::set(owner,
keylet.key));
803 env.close(
Tp{
D{sub + 1}});
804 env(loan::set(borrower, brokerKeylet.key, XRP(60).value()),
807 kPaymentInterval(300),
809 Sig(sfCounterpartySignature, owner),
810 Fee(env.current()->fees().base * 2));
813 BEAST_EXPECT(env.le(loanKeylet));
819 env(loan::pay(borrower, loanKeylet.key, XRP(60).value(), tfLoanLatePayment));
823 auto const sleAfter = env.le(
keylet);
824 if (BEAST_EXPECT(sleAfter))
826 BEAST_EXPECT(sleAfter->at(sfAssetsAvailable) == sleAfter->at(sfAssetsTotal));
827 BEAST_EXPECT(sleAfter->at(sfAssetsAvailable) == XRP(100).value());
830 env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(100).value()}));
841 testcase(
"closed-ended vault: multiple concurrent loans in Investment");
843 using namespace loan_broker;
844 using namespace loan;
846 Env env{*
this, testableAmendments()};
850 Account const borrower1{
"borrower1"};
851 Account const borrower2{
"borrower2"};
852 env.fund(XRP(10'000), owner, alice, bob, borrower1, borrower2);
856 auto const [vault,
keylet, sub, red] =
859 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(100).value()}));
861 env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = XRP(100).value()}));
864 auto const brokerKeylet =
866 env(loan_broker::set(owner,
keylet.key));
869 env.close(
Tp{
D{sub + 1}});
871 auto const originate = [&](
Account const& b,
STAmount const& principal) {
872 env(loan::set(b, brokerKeylet.key, principal),
875 kPaymentInterval(300),
877 Sig(sfCounterpartySignature, owner),
878 Fee(env.current()->fees().base * 2));
881 originate(borrower1, XRP(50).value());
882 originate(borrower2, XRP(70).value());
886 BEAST_EXPECT(env.le(loan1));
887 BEAST_EXPECT(env.le(loan2));
892 auto const sle = env.le(
keylet);
893 if (BEAST_EXPECT(sle))
895 BEAST_EXPECT(sle->at(sfAssetsTotal) == XRP(200).value());
896 BEAST_EXPECT(sle->at(sfAssetsAvailable) == XRP(80).value());
901 env(loan::pay(borrower1, loan1.key, XRP(50).value()));
904 auto const sle = env.le(
keylet);
905 if (BEAST_EXPECT(sle))
907 BEAST_EXPECT(sle->at(sfAssetsTotal) == XRP(200).value());
908 BEAST_EXPECT(sle->at(sfAssetsAvailable) == XRP(130).value());
913 env(loan::pay(borrower2, loan2.key, XRP(70).value()));
916 auto const sle = env.le(
keylet);
917 if (BEAST_EXPECT(sle))
919 BEAST_EXPECT(sle->at(sfAssetsAvailable) == sle->at(sfAssetsTotal));
920 BEAST_EXPECT(sle->at(sfAssetsAvailable) == XRP(200).value());
925 env.close(
Tp{
D{red}});
926 env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(100).value()}));
928 env(vault.withdraw({.depositor = bob, .id = keylet.key, .amount = XRP(100).value()}));
939 testcase(
"closed-ended vault: VaultClawback succeeds in each phase");
942 Env env{*
this, testableAmendments()};
943 Account const issuer{
"issuer"};
946 env.fund(XRP(10'000), issuer, owner, alice);
949 env(fset(issuer, asfAllowTrustLineClawback));
953 env.trust(iou(10'000), alice);
954 env(pay(issuer, alice, iou(1'000)));
957 auto const [vault,
keylet, sub, red] =
960 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = iou(300).value()}));
963 auto const totalsEq = [&](
STAmount const& expected) {
964 auto const sle = env.le(
keylet);
965 if (BEAST_EXPECT(sle))
966 BEAST_EXPECT(sle->at(sfAssetsTotal) == expected);
971 {.issuer = issuer, .id = keylet.key, .holder = alice, .amount = iou(10).value()}));
973 totalsEq(iou(290).value());
976 env.close(
Tp{
D{sub + 1}});
978 {.issuer = issuer, .id = keylet.key, .holder = alice, .amount = iou(10).value()}));
980 totalsEq(iou(280).value());
983 env.close(
Tp{
D{red}});
985 {.issuer = issuer, .id = keylet.key, .holder = alice, .amount = iou(10).value()}));
987 totalsEq(iou(270).value());