82 int principalOutstanding = 0;
83 int totalValueOutstanding = 0;
84 int managementFeeOutstanding = 0;
119 auto const mptIssuanceID = (*sleVault)[sfShareMPTID];
125 if (args.lossUnrealized)
126 (*sleVault)[sfLossUnrealized] = *args.lossUnrealized;
127 if (args.assetsMaximum)
128 (*sleVault)[sfAssetsMaximum] = *args.assetsMaximum;
131 if (args.assetsTotal)
132 (*sleVault)[sfAssetsTotal] = *(*sleVault)[sfAssetsTotal] + *args.assetsTotal;
133 if (args.assetsAvailable)
135 (*sleVault)[sfAssetsAvailable] =
136 *(*sleVault)[sfAssetsAvailable] + *args.assetsAvailable;
140 if (args.sharesTotal)
142 (*sleShares)[sfOutstandingAmount] =
143 addSigned(*(*sleShares)[sfOutstandingAmount], *args.sharesTotal);
147 auto const assets = *(*sleVault)[sfAsset];
148 auto const pseudoId = *(*sleVault)[sfAccount];
149 if (args.vaultAssets)
154 if (!slePseudoAccount)
156 (*slePseudoAccount)[sfBalance] =
157 *(*slePseudoAccount)[sfBalance] + *args.vaultAssets;
158 ac.
update(slePseudoAccount);
162 auto const mptId = assets.get<
MPTIssue>().getMptID();
166 (*sleMPToken)[sfMPTAmount] =
167 addSigned(*(*sleMPToken)[sfMPTAmount], *args.vaultAssets);
176 if (args.accountAssets)
178 auto const& pair = *args.accountAssets;
184 (*sleAccount)[sfBalance] = *(*sleAccount)[sfBalance] + pair.amount;
189 auto const mptID = assets.get<
MPTIssue>().getMptID();
193 (*sleMPToken)[sfMPTAmount] =
194 addSigned(*(*sleMPToken)[sfMPTAmount], pair.amount);
203 if (args.accountShares)
205 auto const& pair = *args.accountShares;
209 (*sleMPToken)[sfMPTAmount] = addSigned(*(*sleMPToken)[sfMPTAmount], pair.amount);
215 auto const& lp = *args.createLoan;
216 bool const anyOutstanding = lp.principalOutstanding != 0 ||
217 lp.totalValueOutstanding != 0 || lp.managementFeeOutstanding != 0;
221 for (
std::uint32_t seq = 1; seq <= static_cast<std::uint32_t>(args.loanCount);
224 auto sleLoan =
makeLoanSle(brokerKey, seq, lp.borrower);
225 sleLoan->at(sfPrincipalOutstanding) =
Number(lp.principalOutstanding);
226 sleLoan->at(sfTotalValueOutstanding) =
Number(lp.totalValueOutstanding);
227 sleLoan->at(sfManagementFeeOutstanding) =
Number(lp.managementFeeOutstanding);
228 sleLoan->setFieldU32(sfPaymentRemaining, anyOutstanding ? 1 : 0);
235 static constexpr auto kArgs = [](
AccountID id,
int adjustment,
auto fn) -> Adjustments {
236 Adjustments sample = {
237 .assetsTotal = adjustment,
238 .assetsAvailable = adjustment,
240 .sharesTotal = adjustment,
241 .vaultAssets = adjustment,
243 AccountAmount{.account = id, .amount = -adjustment},
245 AccountAmount{.account = id, .amount = adjustment}};
252 auto const precloseXrp = [&](
Account const& a1,
256 env.fund(
XRP(1000), a3, a4);
257 Vault const vault{env};
258 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
260 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
261 env(vault.deposit({.depositor = a2, .id = keylet.key, .amount = XRP(10)}));
262 env(vault.deposit({.depositor = a3, .id = keylet.key, .amount = XRP(10)}));
266 auto const createClosedXrpBroker =
270 auto const sleBroker = env.le(brokerKeylet);
271 if (!BEAST_EXPECT(sleBroker))
273 auto const vaultKeylet =
keylet::vault(sleBroker->at(sfVaultID));
275 return std::pair{vaultKeylet, brokerKeylet};
280 {
"vault deletion succeeded without deleting a vault"},
286 ac.view().
update(sleVault);
293 Vault const vault{env};
294 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
300 {
"vault updated by a wrong transaction type",
301 "deleted Vault without deleting its pseudo-account"},
307 ac.view().
erase(sleVault);
314 Vault const vault{env};
315 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
321 {
"vault updated by a wrong transaction type"},
327 ac.view().
update(sleVault);
334 Vault const vault{env};
335 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
341 {
"vault updated by a wrong transaction type"},
343 auto const sequence = ac.view().
seq();
346 auto const vaultPage = ac.view().
dirInsert(
348 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
349 sleVault->setAccountID(sfAccount, a1.
id());
350 ac.view().
insert(sleVault);
358 {
"vault deleted by a wrong transaction type",
359 "deleted Vault without deleting its pseudo-account"},
365 ac.view().
erase(sleVault);
372 Vault const vault{env};
373 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
379 {
"vault operation updated more than single vault",
380 "deleted Vault without deleting its pseudo-account"},
388 ac.view().
erase(sleVault);
396 ac.view().
erase(sleVault);
404 Vault const vault{env};
406 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
410 auto [tx, _] = vault.create({.owner = a2, .asset =
xrpIssue()});
417 {
"vault operation updated more than single vault"},
419 auto const sequence = ac.view().
seq();
420 auto const insertVault = [&](
Account const a) {
423 auto const vaultPage = ac.view().
dirInsert(
425 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
426 sleVault->setAccountID(sfAccount, a.id());
427 ac.view().
insert(sleVault);
438 {
"deleted vault must also delete shares",
439 "deleted Vault without deleting its pseudo-account"},
445 ac.view().
erase(sleVault);
452 Vault const vault{env};
453 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
459 {
"deleted vault must have no shares outstanding",
460 "deleted vault must have no assets outstanding",
461 "deleted vault must have no assets available"},
470 ac.view().
erase(sleVault);
471 ac.view().
erase(sleShares);
478 Vault const vault{env};
479 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
481 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
486 {
"vault operation succeeded without modifying a vault"},
497 sleShares->setFieldH256(sfDomainID,
UInt256(13));
498 ac.view().
update(sleShares);
508 {
"vault operation succeeded without modifying a vault"},
514 Vault const vault{env};
515 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
521 {
"vault operation succeeded without modifying a vault"},
527 Vault const vault{env};
528 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
534 {
"vault operation succeeded without modifying a vault"},
540 Vault const vault{env};
541 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
547 {
"vault operation succeeded without modifying a vault"},
553 Vault const vault{env};
554 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
560 {
"vault operation succeeded without modifying a vault"},
566 Vault const vault{env};
567 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
573 {
"updated vault must have shares"},
579 (*sleVault)[sfAssetsMaximum] = 200;
580 ac.view().
update(sleVault);
585 ac.view().
erase(sleShares);
592 Vault const vault{env};
593 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
599 {
"vault operation succeeded without updating shares",
600 "assets available must not be greater than assets outstanding"},
606 (*sleVault)[sfAssetsTotal] = 9;
607 ac.view().
update(sleVault);
614 Vault const vault{env};
615 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
617 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
622 {
"set must not change assets outstanding",
623 "set must not change assets available",
624 "set must not change shares outstanding",
625 "set must not change vault balance",
626 "assets available must not be negative",
627 "assets available must not be greater than assets outstanding",
628 "assets outstanding must not be negative"},
635 if (!slePseudoAccount)
637 (*slePseudoAccount)[sfBalance] = *(*slePseudoAccount)[sfBalance] - 10;
638 ac.view().
update(slePseudoAccount);
644 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
647 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
648 sample.assetsAvailable = (kDropsPerXrp * -100).value();
649 sample.assetsTotal = (kDropsPerXrp * -200).value();
650 sample.sharesTotal = -1;
662 {
"changed an unchangeable field"},
669 ac.view().
update(sleVault);
678 {
"changed an unchangeable field"},
684 sleVault->setAccountID(sfAccount, a2.id());
685 ac.view().
update(sleVault);
694 {
"changed an unchangeable field"},
700 (*sleVault)[sfShareMPTID] =
MPTID(42);
701 ac.view().
update(sleVault);
710 {
"changed an unchangeable field"},
717 ac.view().
update(sleVault);
731 auto const preLendingV11Amendments =
all_ - featureLendingProtocolV1_1;
733 makeEnv(preLendingV11Amendments),
734 {
"violation of vault immutable data"},
741 ac.view().
update(sleVault);
750 makeEnv(preLendingV11Amendments),
751 {
"violation of vault immutable data"},
757 sleVault->setAccountID(sfAccount, a2.id());
758 ac.view().
update(sleVault);
767 makeEnv(preLendingV11Amendments),
768 {
"violation of vault immutable data"},
774 (*sleVault)[sfShareMPTID] =
MPTID(42);
775 ac.view().
update(sleVault);
784 {
"vault transaction must not change loss unrealized",
785 "set must not change assets outstanding"},
788 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
789 sample.lossUnrealized = 13;
790 sample.assetsTotal = 20;
800 {
"loss unrealized must not exceed the difference "
801 "between assets outstanding and available",
802 "vault transaction must not change loss unrealized"},
805 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 100, [&](Adjustments& sample) {
806 sample.lossUnrealized = 13;
811 ttVAULT_DEPOSIT, [](
STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
821 {
"loss unrealized must not be negative"},
824 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
825 sample.lossUnrealized = -1;
840 makeEnv(
all_ - fixCleanup3_4_0 - featureLendingProtocolV1_1),
844 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
845 sample.lossUnrealized = -1;
855 {
"set assets outstanding must not exceed assets maximum"},
858 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
859 sample.assetsMaximum = 1;
878 Number const vaultCap =
XRP(30).number();
880 {
"set must not change assets outstanding",
881 "set assets outstanding must not exceed assets maximum"},
884 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
885 sample.assetsTotal = XRP(1).value().xrp().drops();
889 STTx{ttVAULT_SET, [&](
STObject& tx) { tx[sfAssetsMaximum] = vaultCap; }},
892 env.fund(
XRP(1000), a3, a4);
893 Vault const vault{env};
894 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
895 tx[sfAssetsMaximum] = vaultCap;
897 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
898 env(vault.deposit({.depositor = a2, .id = keylet.key, .amount = XRP(10)}));
899 env(vault.deposit({.depositor = a3, .id = keylet.key, .amount = XRP(10)}));
905 {
"assets maximum must not be negative"},
908 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
909 sample.assetsMaximum = -1;
919 {
"set must not change shares outstanding",
920 "updated zero sized vault must have no assets outstanding",
921 "updated zero sized vault must have no assets available"},
927 ac.view().
update(sleVault);
931 (*sleShares)[sfOutstandingAmount] = 0;
932 ac.view().
update(sleShares);
942 {
"updated shares must not exceed maximum"},
951 (*sleShares)[sfMaximumAmount] = 10;
952 ac.view().
update(sleShares);
954 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
963 {
"updated shares must not exceed maximum"},
966 kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
975 ac.view().
update(sleShares);
997 .assetsAvailable = -200,
999 .accountAssets = AccountAmount{.account = a2.id(), .amount = 200},
1000 .createLoan = LoanParams{
1001 .principalOutstanding = 300,
1002 .totalValueOutstanding = 300,
1003 .borrower = a1.id(),
1014 {
"Loan created by a transaction other than LoanSet"},
1021 .createLoan = LoanParams{
1022 .principalOutstanding = 100,
1023 .totalValueOutstanding = 100,
1024 .borrower = a1.id(),
1028 STTx{ttLOAN_MANAGE, [](STObject& tx) { tx.setFieldU32(sfFlags, tfLoanImpair); }},
1034 {
"loss unrealized must not be negative"},
1037 return kAdjust(ac.view(), keylet, Adjustments{.lossUnrealized = -1});
1040 STTx{ttLOAN_MANAGE, [](STObject&) {}},
1049 std::uint32_t before;
1050 std::uint32_t
after;
1051 std::string expected;
1053 auto const cases = std::to_array<Case>({
1055 .after = lsfLoanImpaired,
1056 .expected =
"lsfLoanImpaired changed outside LoanManage or LoanPay"},
1057 {.before = lsfLoanImpaired,
1059 .expected =
"lsfLoanImpaired changed outside LoanManage or LoanPay"},
1061 .after = lsfLoanDefault,
1062 .expected =
"lsfLoanDefault changed outside LoanManage"},
1065 for (
auto const& c : cases)
1067 Env env{*
this, all_};
1070 env.fund(
XRP(1000), a1, a2);
1071 auto const keys = createClosedXrpBroker(a1, env);
1074 auto const& brokerKeylet = keys->second;
1076 OpenView ov{*env.current()};
1079 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a1.id());
1080 sleLoan->at(sfPrincipalOutstanding) = Number(100);
1081 sleLoan->at(sfTotalValueOutstanding) = Number(150);
1082 sleLoan->setFieldU32(sfPaymentRemaining, 1);
1083 sleLoan->setFieldU32(sfFlags, c.before);
1084 ov.rawInsert(sleLoan);
1087 STTx
const tx{ttACCOUNT_SET, [](STObject&) {}};
1089 beast::Journal
const jlog{sink};
1092 CurrentTransactionRulesGuard
const rulesGuard(ov.rules());
1094 auto sleLoan = ac.view().peek(loanKeylet);
1095 if (!BEAST_EXPECT(sleLoan))
1097 sleLoan->setFieldU32(sfFlags, c.after);
1098 ac.view().update(sleLoan);
1101 if (!BEAST_EXPECT(transactor))
1103 TER const result = transactor->checkInvariants(
1106 BEAST_EXPECT(sink.messages().str().contains(c.expected));
1117 Env env{*
this, all_};
1120 env.fund(
XRP(1000), a1, a2);
1121 BEAST_EXPECT(precloseXrp(a1, a2, env));
1124 OpenView ov{*env.current()};
1133 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1134 sleLoan->setFieldU32(sfNextPaymentDueDate, 123);
1135 ov.rawInsert(sleLoan);
1139 ttLOAN_MANAGE, [](STObject& t) { t.setFieldU32(sfFlags, tfLoanDefault); }};
1141 beast::Journal
const jlog{sink};
1144 CurrentTransactionRulesGuard
const rulesGuard(ov.rules());
1146 auto sleLoan = ac.view().peek(loanKeylet);
1147 if (!BEAST_EXPECT(sleLoan))
1149 sleLoan->setFieldU32(sfFlags, lsfLoanDefault);
1150 ac.view().update(sleLoan);
1153 if (!BEAST_EXPECT(transactor))
1155 TER const result = transactor->checkInvariants(
1158 BEAST_EXPECT(sink.messages().str().contains(
1159 "Loan with zero payments must have zero next payment due date"));
1167 makeEnv(all_ - featureLendingProtocolV1_1),
1171 return kAdjust(ac.view(), keylet, Adjustments{});
1174 STTx{ttLOAN_PAY, [](STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
1182 {
"Loan created by a transaction other than LoanSet"},
1190 .assetsAvailable = 50,
1192 .accountAssets = AccountAmount{.account = a2.id(), .amount = -50},
1193 .createLoan = LoanParams{
1194 .principalOutstanding = 100,
1195 .totalValueOutstanding = 100,
1196 .borrower = a1.id(),
1200 STTx{ttLOAN_PAY, [](STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(50)); }},
1207 {
"loss unrealized must not be negative"},
1209 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1215 .assetsAvailable = 100,
1216 .lossUnrealized = -1,
1218 .accountAssets = AccountAmount{.account = a2.id(), .amount = -100}});
1221 STTx{ttLOAN_PAY, [](STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
1238 std::uint32_t remaining;
1239 std::uint32_t dueDate;
1240 std::string expected;
1242 auto const cases = std::to_array<Case>({
1243 {.principal = Number(100),
1244 .totalValue = Number(150),
1247 .expected =
"loan pay must decrease PrincipalOutstanding or "
1248 "TotalValueOutstanding"},
1249 {.principal = Number(110),
1250 .totalValue = Number(150),
1253 .expected =
"loan pay must not increase PrincipalOutstanding"},
1254 {.principal = Number(50),
1255 .totalValue = Number(160),
1258 .expected =
"loan pay must not increase TotalValueOutstanding"},
1259 {.principal = Number(50),
1260 .totalValue = Number(150),
1263 .expected =
"loan pay must decrease PaymentRemaining"},
1264 {.principal = Number(50),
1265 .totalValue = Number(150),
1268 .expected =
"loan pay must advance NextPaymentDueDate"},
1270 {.principal = Number(50),
1271 .totalValue = Number(150),
1274 .expected =
"loan pay must advance NextPaymentDueDate"},
1277 for (
auto const& c : cases)
1279 Env env{*
this, all_};
1282 env.fund(
XRP(1000), a1, a2);
1283 auto const keys = createClosedXrpBroker(a1, env);
1286 auto const& brokerKeylet = keys->second;
1288 OpenView ov{*env.current()};
1289 auto const loanKeylet = keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
1291 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1292 sleLoan->at(sfPrincipalOutstanding) = Number(100);
1293 sleLoan->at(sfTotalValueOutstanding) = Number(150);
1294 sleLoan->at(sfPaymentInterval) = 10u;
1295 sleLoan->setFieldU32(sfPaymentRemaining, 2);
1296 sleLoan->setFieldU32(sfNextPaymentDueDate, 100);
1297 ov.rawInsert(sleLoan);
1301 ttLOAN_PAY, [](STObject& t) { t.setFieldAmount(sfAmount,
XRPAmount(50)); }};
1306 CurrentTransactionRulesGuard
const rulesGuard(ov.rules());
1308 auto sleLoan = ac.view().peek(loanKeylet);
1309 if (!BEAST_EXPECT(sleLoan))
1311 sleLoan->at(sfPrincipalOutstanding) = c.principal;
1312 sleLoan->at(sfTotalValueOutstanding) = c.totalValue;
1313 sleLoan->setFieldU32(sfPaymentRemaining, c.remaining);
1314 sleLoan->setFieldU32(sfNextPaymentDueDate, c.dueDate);
1315 ac.view().update(sleLoan);
1318 if (!BEAST_EXPECT(transactor))
1320 TER const result = transactor->checkInvariants(
1321 tesSUCCESS, XRPAmount{}, Transactor::InvariantScope::Full);
1322 BEAST_EXPECT(result == tecINVARIANT_FAILED);
1323 BEAST_EXPECT(sink.messages().str().contains(c.expected));
1330 Env env{*
this, all_};
1333 env.fund(
XRP(1000), a1, a2);
1334 auto const keys = createClosedXrpBroker(a1, env);
1341 auto const& brokerKeylet = keys->second;
1342 OpenView ov{*env.current()};
1343 auto const loanKeylet =
1344 keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
1346 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1347 sleLoan->at(sfPrincipalOutstanding) = Number(100);
1348 sleLoan->at(sfTotalValueOutstanding) = Number(150);
1349 sleLoan->at(sfPaymentInterval) = 10u;
1350 sleLoan->setFieldU32(sfPaymentRemaining, 2);
1351 sleLoan->setFieldU32(sfNextPaymentDueDate, 100);
1352 ov.rawInsert(sleLoan);
1356 ttLOAN_PAY, [](STObject& t) { t.setFieldAmount(sfAmount,
XRPAmount(50)); }};
1361 CurrentTransactionRulesGuard
const rulesGuard(ov.rules());
1363 auto sleLoan = ac.view().peek(loanKeylet);
1364 if (BEAST_EXPECT(sleLoan))
1366 sleLoan->at(sfPrincipalOutstanding) = Number(100);
1367 sleLoan->at(sfTotalValueOutstanding) = Number(140);
1368 sleLoan->setFieldU32(sfPaymentRemaining, 1);
1369 sleLoan->setFieldU32(sfNextPaymentDueDate, 110);
1370 ac.view().update(sleLoan);
1373 if (BEAST_EXPECT(transactor))
1375 std::ignore = transactor->checkInvariants(
1376 tesSUCCESS, XRPAmount{}, Transactor::InvariantScope::Full);
1377 auto const logs = sink.messages().str();
1378 BEAST_EXPECT(!logs.contains(
"Invariant failed: Loan"));
1379 BEAST_EXPECT(!logs.contains(
"loan pay"));
1392 Env env{*
this, all_ | featureLendingProtocolV1_1};
1393 Account const issuer{
"issuer"};
1395 Account const borrower{
"borrower"};
1396 env.fund(
XRP(1000), issuer, owner, borrower);
1399 PrettyAsset
const iouAsset{issuer[
"IOU"]};
1400 auto const brokerKeylet = createLoanBroker(owner, env, iouAsset);
1401 auto const sleBrokerBase = env.le(brokerKeylet);
1402 if (!BEAST_EXPECT(sleBrokerBase))
1404 auto const vaultKeylet = keylet::vault(sleBrokerBase->at(sfVaultID));
1407 Number
const assetsTotalBefore{1, 1};
1408 Number
const loanOwed{9'999'999'999'999'999LL, -15};
1409 Number
const assetsTotalAfter{1, -14};
1410 auto const beforeScale =
scale(assetsTotalBefore, iouAsset);
1411 auto const afterScale =
scale(assetsTotalAfter, iouAsset);
1412 Number
const residual = (assetsTotalAfter - assetsTotalBefore) - (-loanOwed);
1413 Number
const beforeTolerance{1, beforeScale};
1414 Number
const afterTolerance{1, afterScale};
1416 BEAST_EXPECT(afterScale < beforeScale);
1417 BEAST_EXPECT(residual >
beast::kZero && residual < beforeTolerance);
1418 BEAST_EXPECT(residual > afterTolerance);
1420 OpenView ov{*env.current()};
1422 auto const sleVaultRead = ov.read(vaultKeylet);
1423 if (!BEAST_EXPECT(sleVaultRead))
1426 sleVault->at(sfAssetsTotal) = assetsTotalBefore;
1427 sleVault->at(sfAssetsAvailable) = Number(0);
1428 ov.rawReplace(sleVault);
1430 auto const sharesKeylet = keylet::mptokenIssuance(sleVaultRead->at(sfShareMPTID));
1431 auto const sleSharesRead = ov.read(sharesKeylet);
1432 if (!BEAST_EXPECT(sleSharesRead))
1435 sleShares->at(sfOutstandingAmount) = 1;
1436 ov.rawReplace(sleShares);
1439 auto const sleBrokerRead = ov.read(brokerKeylet);
1440 if (!BEAST_EXPECT(sleBrokerRead))
1443 sleBroker->at(sfDebtTotal) = loanOwed;
1444 ov.rawReplace(sleBroker);
1446 auto const loanKeylet = keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
1448 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, borrower.id());
1449 sleLoan->at(sfPrincipalOutstanding) = loanOwed;
1450 sleLoan->at(sfTotalValueOutstanding) = loanOwed;
1451 sleLoan->setFieldU32(sfPaymentRemaining, 1);
1452 ov.rawInsert(sleLoan);
1456 ttLOAN_MANAGE, [](STObject& t) { t.setFieldU32(sfFlags, tfLoanDefault); }};
1461 CurrentTransactionRulesGuard
const rulesGuard(ov.rules());
1464 auto sleVault = ac.view().peek(vaultKeylet);
1465 if (!BEAST_EXPECT(sleVault))
1467 sleVault->at(sfAssetsTotal) = assetsTotalAfter;
1468 ac.view().update(sleVault);
1471 auto sleBroker = ac.view().peek(brokerKeylet);
1472 if (!BEAST_EXPECT(sleBroker))
1474 sleBroker->at(sfDebtTotal) = Number(0);
1475 ac.view().update(sleBroker);
1478 auto sleLoan = ac.view().peek(loanKeylet);
1479 if (!BEAST_EXPECT(sleLoan))
1481 sleLoan->at(sfPrincipalOutstanding) = Number(0);
1482 sleLoan->at(sfTotalValueOutstanding) = Number(0);
1483 sleLoan->setFieldU32(sfPaymentRemaining, 0);
1484 sleLoan->setFieldU32(sfFlags, lsfLoanDefault);
1485 ac.view().update(sleLoan);
1489 if (!BEAST_EXPECT(transactor))
1491 TER const result = transactor->checkInvariants(
1492 tesSUCCESS, XRPAmount{}, Transactor::InvariantScope::Full);
1493 BEAST_EXPECT(result == tesSUCCESS);
1501 Keylet loanKeylet = keylet::amendments();
1502 auto const precloseLoan = [&loanKeylet,
this](
1504 PrettyAsset
const xrpAsset{
xrpIssue(), 1'000'000};
1505 auto const brokerKeylet = createLoanBroker(a1, env, xrpAsset);
1506 auto const brokerSle = env.le(brokerKeylet);
1507 if (!BEAST_EXPECT(brokerSle))
1509 auto const vaultKeylet = keylet::vault(brokerSle->at(sfVaultID));
1512 {.depositor = a1, .id = vaultKeylet.key, .amount = xrpAsset(100)}));
1515 loanKeylet = keylet::loan(
1516 brokerKeylet.key, SeqProxy::rawSequence(brokerSle->at(sfLoanSequence)));
1517 env(loan::set(a2, brokerKeylet.key, xrpAsset(50).value()),
1518 loan::kCounterparty(a1),
1519 Sig(sfCounterpartySignature, a1),
1520 loan::kPaymentInterval(60),
1521 loan::kPaymentTotal(1),
1522 Fee(env.current()->fees().base * 2));
1524 return BEAST_EXPECT(env.le(loanKeylet));
1527 auto const eraseLoan = [&loanKeylet](
Account const&,
Account const&, ApplyContext& ac) {
1528 auto sle = ac.view().peek(loanKeylet);
1531 ac.view().erase(sle);
1540 {
"Loan deleted by a transaction other than LoanDelete"},
1543 STTx{ttACCOUNT_SET, [](STObject&) {}},
1548 STTx
const loanSetTx{
1549 ttLOAN_SET, [](STObject& tx) { tx.at(sfPrincipalRequested) = Number(0); }};
1558 Keylet brokerKeylet = keylet::amendments();
1559 auto const precloseBroker = [&brokerKeylet,
this](
1561 PrettyAsset
const xrpAsset{
xrpIssue(), 1'000'000};
1562 brokerKeylet = this->createLoanBroker(a1, env, xrpAsset);
1564 return BEAST_EXPECT(env.le(brokerKeylet));
1568 {
"Loan interest due is negative"},
1570 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1571 sleLoan->at(sfPrincipalOutstanding) = Number(100);
1572 sleLoan->at(sfTotalValueOutstanding) = Number(90);
1573 sleLoan->setFieldU32(sfPaymentRemaining, 1);
1574 ac.view().insert(sleLoan);
1587 for (
auto const field : {
1591 &sfPrincipalOutstanding,
1592 &sfTotalValueOutstanding,
1593 &sfManagementFeeOutstanding,
1599 bool const isOutstanding = *field == sfPrincipalOutstanding ||
1600 *field == sfTotalValueOutstanding || *field == sfManagementFeeOutstanding;
1602 {field->getName() +
" is negative"},
1603 [&, field](
Account const& a1,
Account const& a2, ApplyContext& ac) {
1604 auto const brokerKeylet = keylet::loanBroker(a1.id(), SeqProxy::rawSequence(1));
1605 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1606 sleLoan->at(*field) = Number(-10);
1607 sleLoan->setFieldU32(sfPaymentRemaining, isOutstanding ? 1 : 0);
1608 ac.view().insert(sleLoan);
1618 for (Number
const& badValue : {Number(0), Number(-1)})
1621 {
std::string{sfPeriodicPayment.getName()} +
" is zero or negative"},
1622 [&, badValue](
Account const& a1,
Account const& a2, ApplyContext& ac) {
1623 auto const brokerKeylet = keylet::loanBroker(a1.id(), SeqProxy::rawSequence(1));
1624 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1625 sleLoan->at(sfPeriodicPayment) = badValue;
1626 ac.view().insert(sleLoan);
1639 {
"Loan with zero payments remaining has not been paid off"},
1641 auto const brokerKeylet = keylet::loanBroker(a1.id(), SeqProxy::rawSequence(1));
1642 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1643 sleLoan->at(sfPrincipalOutstanding) = Number(100);
1644 sleLoan->at(sfTotalValueOutstanding) = Number(100);
1645 sleLoan->at(sfPeriodicPayment) = Number(1);
1646 sleLoan->setFieldU32(sfPaymentRemaining, 0);
1647 ac.view().insert(sleLoan);
1657 {
"Fully paid off Loan still has payments remaining"},
1659 auto const brokerKeylet = keylet::loanBroker(a1.id(), SeqProxy::rawSequence(1));
1660 auto sleLoan = makeLoanSle(brokerKeylet.key, 1, a2.id());
1661 sleLoan->setFieldU32(sfPaymentRemaining, 1);
1662 ac.view().insert(sleLoan);
1673 {
"Loan broker does not exist"},
1675 auto sleLoan = makeLoanSle(UInt256{}, 1, a2.id());
1676 ac.view().insert(sleLoan);
1688 Keylet brokerKeylet = keylet::amendments();
1689 auto const precloseBroker = [&brokerKeylet,
this](
1691 PrettyAsset
const xrpAsset{
xrpIssue(), 1'000'000};
1692 brokerKeylet = this->createLoanBroker(a1, env, xrpAsset);
1694 return BEAST_EXPECT(env.le(brokerKeylet));
1698 {
"Loan broker vault does not exist"},
1699 [&brokerKeylet](
Account const&,
Account const&, ApplyContext& ac) {
1700 auto sleBroker = ac.view().peek(brokerKeylet);
1703 auto sleVault = ac.view().peek(keylet::vault(sleBroker->at(sfVaultID)));
1706 ac.view().erase(sleVault);
1708 auto const loanKeylet =
1709 keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
1711 sleLoan->at(sfLoanBrokerID) = brokerKeylet.key;
1712 sleLoan->at(sfPrincipalOutstanding) = Number(0);
1713 sleLoan->at(sfTotalValueOutstanding) = Number(0);
1714 sleLoan->at(sfManagementFeeOutstanding) = Number(0);
1715 sleLoan->at(sfPeriodicPayment) = Number(1);
1716 sleLoan->setFieldU32(sfPaymentRemaining, 0);
1717 ac.view().insert(sleLoan);
1729 {
"changed an unchangeable field"},
1731 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1732 auto sleVault = ac.view().peek(keylet);
1735 sleVault->setAccountID(sfOwner, a2.id());
1736 ac.view().update(sleVault);
1740 STTx{ttVAULT_SET, [](STObject& tx) {}},
1746 {
"changed an unchangeable field"},
1748 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1749 auto sleVault = ac.view().peek(keylet);
1752 sleVault->setFieldU8(
1754 static_cast<std::uint8_t>(sleVault->getFieldU8(sfWithdrawalPolicy) + 1));
1755 ac.view().update(sleVault);
1759 STTx{ttVAULT_SET, [](STObject& tx) {}},
1765 {
"changed an unchangeable field"},
1767 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1768 auto sleVault = ac.view().peek(keylet);
1771 sleVault->setFieldU8(
1772 sfScale,
static_cast<std::uint8_t>(sleVault->getFieldU8(sfScale) + 1));
1773 ac.view().update(sleVault);
1777 STTx{ttVAULT_SET, [](STObject& tx) {}},
1785 {
"changed an unchangeable field"},
1787 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1788 auto sleVault = ac.view().peek(keylet);
1791 sleVault->setFieldU8(
1793 static_cast<std::uint8_t>(sleVault->getFieldU8(sfWithdrawalPolicy) + 1));
1794 ac.view().update(sleVault);
1798 STTx{ttVAULT_SET, [](STObject& tx) {}},
1802 testcase <<
"Vault create";
1805 "created vault must be empty",
1806 "updated zero sized vault must have no assets outstanding",
1807 "create operation must not have updated a vault",
1810 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1811 auto sleVault = ac.view().peek(keylet);
1814 (*sleVault)[sfAssetsTotal] = 9;
1815 ac.view().update(sleVault);
1819 STTx{ttVAULT_CREATE, [](STObject&) {}},
1830 "created vault must be empty",
1831 "updated zero sized vault must have no assets available",
1832 "assets available must not be greater than assets outstanding",
1833 "create operation must not have updated a vault",
1836 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1837 auto sleVault = ac.view().peek(keylet);
1840 (*sleVault)[sfAssetsAvailable] = 9;
1841 ac.view().update(sleVault);
1845 STTx{ttVAULT_CREATE, [](STObject&) {}},
1856 "created vault must be empty",
1857 "loss unrealized must not exceed the difference between assets "
1858 "outstanding and available",
1859 "vault transaction must not change loss unrealized",
1860 "create operation must not have updated a vault",
1863 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1864 auto sleVault = ac.view().peek(keylet);
1867 (*sleVault)[sfLossUnrealized] = 1;
1868 ac.view().update(sleVault);
1872 STTx{ttVAULT_CREATE, [](STObject&) {}},
1883 "created vault must be empty",
1884 "create operation must not have updated a vault",
1885 "invalid OutstandingAmount balance 0 9 0",
1888 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1889 auto sleVault = ac.view().peek(keylet);
1892 auto sleShares = ac.view().peek(keylet::mptokenIssuance((*sleVault)[sfShareMPTID]));
1895 ac.view().update(sleVault);
1896 (*sleShares)[sfOutstandingAmount] = 9;
1897 ac.view().update(sleShares);
1901 STTx{ttVAULT_CREATE, [](STObject&) {}},
1912 "assets maximum must not be negative",
1913 "create operation must not have updated a vault",
1916 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1917 auto sleVault = ac.view().peek(keylet);
1920 (*sleVault)[sfAssetsMaximum] = Number(-1);
1921 ac.view().update(sleVault);
1925 STTx{ttVAULT_CREATE, [](STObject&) {}},
1935 {
"create operation must not have updated a vault",
1936 "shares issuer and vault pseudo-account must be the same",
1937 "shares issuer must be a pseudo-account",
1938 "shares issuer pseudo-account must point back to the vault"},
1940 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
1941 auto sleVault = ac.view().peek(keylet);
1944 auto sleShares = ac.view().peek(keylet::mptokenIssuance((*sleVault)[sfShareMPTID]));
1947 ac.view().update(sleVault);
1948 (*sleShares)[sfIssuer] = a1.id();
1949 ac.view().update(sleShares);
1953 STTx{ttVAULT_CREATE, [](STObject&) {}},
1963 {
"vault created by a wrong transaction type",
"account root created illegally"},
1968 auto const sequence = ac.view().seq();
1969 auto const vaultKeylet = keylet::vault(a1.id(), SeqProxy::rawSequence(sequence));
1971 auto const vaultPage = ac.view().dirInsert(
1973 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
1978 sleAccount->setAccountID(sfAccount, pseudoId);
1979 sleAccount->setFieldAmount(sfBalance, STAmount{});
1981 ac.view().rules().enabled(featureSingleAssetVault)
1984 sleAccount->setFieldU32(sfSequence, seqno);
1985 sleAccount->setFieldU32(
1986 sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
1987 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
1988 ac.view().insert(sleAccount);
1990 auto const sharesMptId =
makeMptID(sequence, pseudoId);
1991 auto const sharesKeylet = keylet::mptokenIssuance(sharesMptId);
1993 auto const sharesPage = ac.view().dirInsert(
1995 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
1997 sleShares->at(sfFlags) = 0;
1998 sleShares->at(sfIssuer) = pseudoId;
1999 sleShares->at(sfOutstandingAmount) = 0;
2000 sleShares->at(sfSequence) = sequence;
2002 sleVault->at(sfAccount) = pseudoId;
2003 sleVault->at(sfFlags) = 0;
2004 sleVault->at(sfSequence) = sequence;
2005 sleVault->at(sfOwner) = a1.id();
2006 sleVault->at(sfAssetsTotal) = Number(0);
2007 sleVault->at(sfAssetsAvailable) = Number(0);
2008 sleVault->at(sfLossUnrealized) = Number(0);
2009 sleVault->at(sfShareMPTID) = sharesMptId;
2012 ac.view().insert(sleVault);
2013 ac.view().insert(sleShares);
2017 STTx{ttVAULT_SET, [](STObject&) {}},
2021 {
"shares issuer and vault pseudo-account must be the same",
2022 "shares issuer pseudo-account must point back to the vault"},
2024 auto const sequence = ac.view().seq();
2025 auto const vaultKeylet = keylet::vault(a1.id(), SeqProxy::rawSequence(sequence));
2027 auto const vaultPage = ac.view().dirInsert(
2029 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2034 sleAccount->setAccountID(sfAccount, pseudoId);
2035 sleAccount->setFieldAmount(sfBalance, STAmount{});
2037 ac.view().rules().enabled(featureSingleAssetVault)
2040 sleAccount->setFieldU32(sfSequence, seqno);
2041 sleAccount->setFieldU32(
2042 sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
2045 sleAccount->setFieldH256(sfVaultID,
UInt256(42));
2046 ac.view().insert(sleAccount);
2048 auto const sharesMptId =
makeMptID(sequence, pseudoId);
2049 auto const sharesKeylet = keylet::mptokenIssuance(sharesMptId);
2051 auto const sharesPage = ac.view().dirInsert(
2053 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2055 sleShares->at(sfFlags) = 0;
2056 sleShares->at(sfIssuer) = pseudoId;
2057 sleShares->at(sfOutstandingAmount) = 0;
2058 sleShares->at(sfSequence) = sequence;
2062 sleVault->at(sfAccount) = a2.id();
2063 sleVault->at(sfFlags) = 0;
2064 sleVault->at(sfSequence) = sequence;
2065 sleVault->at(sfOwner) = a1.id();
2066 sleVault->at(sfAssetsTotal) = Number(0);
2067 sleVault->at(sfAssetsAvailable) = Number(0);
2068 sleVault->at(sfLossUnrealized) = Number(0);
2069 sleVault->at(sfShareMPTID) = sharesMptId;
2072 ac.view().insert(sleVault);
2073 ac.view().insert(sleShares);
2077 STTx{ttVAULT_CREATE, [](STObject&) {}},
2081 {
"shares issuer and vault pseudo-account must be the same",
"shares issuer must exist"},
2083 auto const sequence = ac.view().seq();
2084 auto const vaultKeylet = keylet::vault(a1.id(), SeqProxy::rawSequence(sequence));
2086 auto const vaultPage = ac.view().dirInsert(
2088 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2090 auto const sharesMptId =
makeMptID(sequence, a2.id());
2091 auto const sharesKeylet = keylet::mptokenIssuance(sharesMptId);
2093 auto const sharesPage = ac.view().dirInsert(
2095 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2097 sleShares->at(sfFlags) = 0;
2099 sleShares->at(sfIssuer) =
AccountID(42);
2100 sleShares->at(sfOutstandingAmount) = 0;
2101 sleShares->at(sfSequence) = sequence;
2103 sleVault->at(sfAccount) = a2.id();
2104 sleVault->at(sfFlags) = 0;
2105 sleVault->at(sfSequence) = sequence;
2106 sleVault->at(sfOwner) = a1.id();
2107 sleVault->at(sfAssetsTotal) = Number(0);
2108 sleVault->at(sfAssetsAvailable) = Number(0);
2109 sleVault->at(sfLossUnrealized) = Number(0);
2110 sleVault->at(sfShareMPTID) = sharesMptId;
2113 ac.view().insert(sleVault);
2114 ac.view().insert(sleShares);
2118 STTx{ttVAULT_CREATE, [](STObject&) {}},
2121 testcase <<
"Vault deposit";
2123 {
"deposit must change vault balance"},
2125 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2126 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 0, [](Adjustments& sample) {
2127 sample.vaultAssets.reset();
2131 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2136 {
"deposit assets outstanding must not exceed assets maximum"},
2138 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2139 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 200, [&](Adjustments& sample) {
2140 sample.assetsMaximum = 1;
2145 ttVAULT_DEPOSIT, [](STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
2155 {
"deposit must increase vault balance",
"deposit must change depositor balance"},
2157 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2160 auto sleA4 = ac.view().peek(keylet::account(a4.id()));
2163 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2164 ac.view().update(sleA4);
2166 return kAdjust(ac.view(), keylet, kArgs(a3.id(), -10, [&](Adjustments& sample) {
2167 sample.accountAssets->amount = -100;
2175 tx[sfAccount] = a3.id();
2181 {
"deposit must increase vault balance",
2182 "deposit must decrease depositor balance",
2183 "deposit must change vault and depositor balance by equal amount",
2184 "deposit and assets outstanding must add up",
2185 "deposit and assets available must add up"},
2187 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2190 auto sleA3 = ac.view().peek(keylet::account(a3.id()));
2193 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
2194 ac.view().update(sleA3);
2196 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
2197 sample.vaultAssets = -20;
2198 sample.accountAssets->amount = 10;
2202 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] =
XRPAmount(10); }},
2208 {
"deposit must change depositor balance"},
2210 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2213 auto sleA3 = ac.view().peek(keylet::account(a3.id()));
2216 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 10;
2217 ac.view().update(sleA3);
2219 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
2220 sample.accountAssets->amount = 0;
2224 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] =
XRPAmount(10); }},
2230 {
"deposit must change depositor shares"},
2232 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2233 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
2234 sample.accountShares.reset();
2238 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] =
XRPAmount(10); }},
2244 {
"deposit must change vault shares"},
2246 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2248 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 10, [](Adjustments& sample) {
2249 sample.sharesTotal = 0;
2253 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] =
XRPAmount(10); }},
2259 {
"deposit must increase depositor shares",
2260 "deposit must change depositor and vault shares by equal amount",
2261 "deposit must not change vault balance by more than deposited "
2264 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2265 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
2266 sample.accountShares->amount = -5;
2267 sample.sharesTotal = -10;
2271 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] =
XRPAmount(5); }},
2277 {
"deposit and assets outstanding must add up"},
2279 auto sleA3 = ac.view().peek(keylet::account(a3.id()));
2280 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
2281 ac.view().update(sleA3);
2283 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2284 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
2285 sample.assetsTotal = 11;
2293 tx[sfDelegate] = a3.id();
2301 {
"deposit and assets outstanding must add up",
2302 "deposit and assets available must add up"},
2304 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2305 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
2306 sample.assetsTotal = 7;
2307 sample.assetsAvailable = 7;
2311 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] =
XRPAmount(10); }},
2316 testcase <<
"Vault withdrawal";
2318 {
"withdrawal must change vault balance"},
2320 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2321 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 0, [](Adjustments& sample) {
2322 sample.vaultAssets.reset();
2326 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2335 {
"withdrawal must change one destination balance"},
2337 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2340 auto sleA4 = ac.view().peek(keylet::account(a4.id()));
2343 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2344 ac.view().update(sleA4);
2346 return kAdjust(ac.view(), keylet, kArgs(a3.id(), -10, [&](Adjustments& sample) {
2347 sample.accountAssets->amount = -100;
2355 tx[sfAccount] = a3.id();
2364 "withdrawal must change vault and destination balance by equal amount",
2365 "withdrawal must decrease vault balance",
2366 "withdrawal must increase destination balance",
2367 "withdrawal and assets outstanding must add up",
2368 "withdrawal and assets available must add up",
2371 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2374 auto sleA3 = ac.view().peek(keylet::account(a3.id()));
2377 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
2378 ac.view().update(sleA3);
2380 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
2381 sample.vaultAssets = 10;
2382 sample.accountAssets->amount = -20;
2386 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2392 {
"withdrawal must change one destination balance"},
2394 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2395 if (!kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
2396 *sample.vaultAssets -= 5;
2399 auto sleA3 = ac.view().peek(keylet::account(a3.id()));
2402 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 5;
2403 ac.view().update(sleA3);
2407 STTx{ttVAULT_WITHDRAW, [&](STObject& tx) { tx.setAccountID(sfDestination, a3.id()); }},
2413 {
"withdrawal must change depositor shares"},
2415 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2416 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
2417 sample.accountShares.reset();
2421 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2427 {
"withdrawal must change vault shares"},
2429 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2430 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [](Adjustments& sample) {
2431 sample.sharesTotal = 0;
2435 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2441 {
"withdrawal must decrease depositor shares",
2442 "withdrawal must change depositor and vault shares by equal "
2445 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2446 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
2447 sample.accountShares->amount = 5;
2448 sample.sharesTotal = 10;
2452 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2458 {
"withdrawal and assets outstanding must add up",
2459 "withdrawal and assets available must add up"},
2461 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2462 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
2463 sample.assetsTotal = -15;
2464 sample.assetsAvailable = -15;
2468 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2474 {
"withdrawal and assets outstanding must add up"},
2476 auto sleA3 = ac.view().peek(keylet::account(a3.id()));
2477 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
2478 ac.view().update(sleA3);
2480 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2481 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
2482 sample.assetsTotal = -7;
2490 tx[sfDelegate] = a3.id();
2497 auto const precloseMpt = [&](
Account const& a1,
Account const& a2, Env& env) ->
bool {
2498 env.fund(
XRP(1000), a3, a4);
2503 jv[sfAccount] = a3.human();
2504 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
2505 jv[sfFlags] = tfMPTCanTransfer;
2510 auto const mptID =
makeMptID(env.seq(a3) - 1, a3);
2511 Asset
const asset = MPTIssue(mptID);
2515 jv[sfAccount] = a1.human();
2516 jv[sfTransactionType] = jss::MPTokenAuthorize;
2517 jv[sfMPTokenIssuanceID] =
to_string(mptID);
2519 jv[sfAccount] = a2.human();
2521 jv[sfAccount] = a4.human();
2528 env(
pay(a3, a1, asset(1000)));
2529 env(
pay(a3, a2, asset(1000)));
2530 env(
pay(a3, a4, asset(1000)));
2535 auto [tx,
keylet] =
vault.create({.owner = a1, .asset = asset});
2537 env(
vault.deposit({.depositor = a1, .id = keylet.key, .amount = asset(10)}));
2538 env(
vault.deposit({.depositor = a2, .id = keylet.key, .amount = asset(10)}));
2539 env(
vault.deposit({.depositor = a4, .id = keylet.key, .amount = asset(10)}));
2544 {
"withdrawal must decrease depositor shares",
2545 "withdrawal must change depositor and vault shares by equal "
2549 keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq() - 2));
2550 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
2551 sample.accountShares->amount = 5;
2555 STTx{ttVAULT_WITHDRAW, [&](STObject& tx) { tx[sfAccount] = a3.id(); }},
2560 testcase <<
"Vault clawback";
2562 {
"clawback must change vault balance"},
2565 keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq() - 2));
2566 return kAdjust(ac.view(), keylet, kArgs(a2.id(), -1, [&](Adjustments& sample) {
2567 sample.vaultAssets.reset();
2571 STTx{ttVAULT_CLAWBACK, [&](STObject& tx) { tx[sfAccount] = a3.id(); }},
2577 {
"clawback may only be performed by the asset issuer"},
2579 auto const keylet = keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq()));
2580 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {}));
2583 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
2589 {
"clawback may only be performed by the asset issuer"},
2592 keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq() - 2));
2593 return kAdjust(ac.view(), keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {}));
2596 STTx{ttVAULT_CLAWBACK, [&](STObject& tx) { tx[sfAccount] = a4.id(); }},
2601 {
"clawback must decrease vault balance",
2602 "clawback must decrease holder shares",
2603 "clawback must change vault shares"},
2606 keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq() - 2));
2607 return kAdjust(ac.view(), keylet, kArgs(a4.id(), 10, [&](Adjustments& sample) {
2608 sample.sharesTotal = 0;
2615 tx[sfAccount] = a3.id();
2616 tx[sfHolder] = a4.id();
2622 {
"clawback must change holder shares"},
2625 keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq() - 2));
2626 return kAdjust(ac.view(), keylet, kArgs(a4.id(), -10, [&](Adjustments& sample) {
2627 sample.accountShares.reset();
2634 tx[sfAccount] = a3.id();
2635 tx[sfHolder] = a4.id();
2641 {
"clawback must change holder and vault shares by equal amount",
2642 "clawback and assets outstanding must add up",
2643 "clawback and assets available must add up"},
2646 keylet::vault(a1.id(), SeqProxy::rawSequence(ac.view().seq() - 2));
2647 return kAdjust(ac.view(), keylet, kArgs(a4.id(), -10, [&](Adjustments& sample) {
2648 sample.accountShares->amount = -8;
2649 sample.assetsTotal = -7;
2650 sample.assetsAvailable = -7;
2657 tx[sfAccount] = a3.id();
2658 tx[sfHolder] = a4.id();
2668 using D = NetClock::duration;
2669 using Tp = NetClock::time_point;
2671 auto const closedEnded = std::to_underlying(VaultKind::ClosedEnded);
2675 Keylet closedEndedKeylet = keylet::amendments();
2681 auto const precloseClosedEnded = [&](
std::int32_t advanceBySub,
bool doDeposit) {
2682 return [&, advanceBySub, doDeposit](
2684 env.fund(
XRP(1000), a3, a4);
2685 auto const sub = env.now().time_since_epoch().count() + 60;
2691 .vaultKind = closedEnded,
2692 .subscriptionDate = sub,
2693 .redemptionDate = red});
2695 closedEndedKeylet =
keylet;
2698 env(
vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
2699 env(
vault.deposit({.depositor = a2, .id = keylet.key, .amount = XRP(10)}));
2700 env(
vault.deposit({.depositor = a3, .id = keylet.key, .amount = XRP(10)}));
2702 if (advanceBySub >= 0)
2703 env.close(Tp{D{sub + advanceBySub}});
2711 auto const insertBareClosedEndedVault =
2717 auto const sequence = ac.view().seq();
2718 auto const vaultKeylet = keylet::vault(owner.id(), SeqProxy::rawSequence(sequence));
2720 auto const vaultPage = ac.view().dirInsert(
2721 keylet::ownerDir(owner.id()), sleVault->key(),
describeOwnerDir(owner.id()));
2724 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2728 sleAccount->setAccountID(sfAccount, pseudoId);
2729 sleAccount->setFieldAmount(sfBalance, STAmount{});
2730 sleAccount->setFieldU32(sfSequence, 0);
2731 sleAccount->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
2732 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
2733 ac.view().insert(sleAccount);
2735 auto const sharesMptId =
makeMptID(sequence, pseudoId);
2736 auto const sharesKeylet = keylet::mptokenIssuance(sharesMptId);
2738 auto const sharesPage = ac.view().dirInsert(
2742 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2743 sleShares->at(sfFlags) = 0;
2744 sleShares->at(sfIssuer) = pseudoId;
2745 sleShares->at(sfOutstandingAmount) = 0;
2746 sleShares->at(sfSequence) = sequence;
2748 sleVault->at(sfAccount) = pseudoId;
2749 sleVault->at(sfFlags) = 0;
2750 sleVault->at(sfSequence) = sequence;
2751 sleVault->at(sfOwner) = owner.id();
2752 sleVault->setFieldIssue(sfAsset, STIssue{sfAsset, Asset{
xrpIssue()}});
2753 sleVault->at(sfAssetsTotal) = Number(0);
2754 sleVault->at(sfAssetsAvailable) = Number(0);
2755 sleVault->at(sfLossUnrealized) = Number(0);
2756 sleVault->at(sfShareMPTID) = sharesMptId;
2758 sleVault->at(sfVaultKind) = closedEnded;
2759 if (subscriptionDate)
2760 sleVault->at(sfSubscriptionDate) = *subscriptionDate;
2762 sleVault->at(sfRedemptionDate) = *redemptionDate;
2764 ac.view().insert(sleVault);
2765 ac.view().insert(sleShares);
2769 testcase <<
"Vault create closed-ended";
2773 {
"closed-ended vault must have SubscriptionDate and RedemptionDate"},
2775 return insertBareClosedEndedVault(ac, a1, std::nullopt, std::nullopt);
2778 STTx{ttVAULT_CREATE, [](STObject&) {}},
2784 {
"closed-ended vault RedemptionDate - SubscriptionDate must be "
2785 "within [MIN_INVESTMENT_PERIOD, MAX_INVESTMENT_PERIOD)"},
2789 return insertBareClosedEndedVault(ac, a1, sub, red);
2792 STTx{ttVAULT_CREATE, [](STObject&) {}},
2798 {
"closed-ended vault RedemptionDate - SubscriptionDate must be "
2799 "within [MIN_INVESTMENT_PERIOD, MAX_INVESTMENT_PERIOD)"},
2803 return insertBareClosedEndedVault(ac, a1, sub, red);
2806 STTx{ttVAULT_CREATE, [](STObject&) {}},
2811 {
"closed-ended vault RedemptionDate - SubscriptionDate must be "
2812 "within [MIN_INVESTMENT_PERIOD, MAX_INVESTMENT_PERIOD)"},
2816 return insertBareClosedEndedVault(ac, a1, sub, red);
2819 STTx{ttVAULT_CREATE, [](STObject&) {}},
2822 testcase <<
"Vault deposit closed-ended";
2827 {
"deposit only allowed in Subscription or NoPhase"},
2830 ac.view(), closedEndedKeylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
2833 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] =
XRPAmount(10); }},
2835 precloseClosedEnded(1,
true),
2838 testcase <<
"Vault withdrawal closed-ended";
2842 {
"withdrawal not allowed during Investment phase"},
2845 ac.view(), closedEndedKeylet, kArgs(a2.id(), -10, [](Adjustments&) {}));
2848 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2850 precloseClosedEnded(1,
true),
2853 testcase <<
"Vault loan set";
2858 {
"loan origination only allowed in Investment phase"},
2860 auto sleVault = ac.view().peek(closedEndedKeylet);
2863 ac.view().update(sleVault);
2867 STTx{ttLOAN_SET, [](STObject&) {}},
2869 precloseClosedEnded(-1,
false));
2871 testcase <<
"Vault loan set - closed-ended final payment past "
2879 Keylet closedEndedBrokerKeylet = keylet::amendments();
2882 {
"closed-ended loan final payment must precede RedemptionDate by at least "
2883 "kLoanRedemptionBuffer"},
2888 auto sleVault = ac.view().peek(closedEndedKeylet);
2891 ac.view().update(sleVault);
2895 auto sleBroker = ac.view().peek(closedEndedBrokerKeylet);
2898 std::uint32_t const loanSeq = sleBroker->at(sfLoanSequence);
2902 auto sleLoan = makeLoanSle(closedEndedBrokerKeylet.key, loanSeq, a1.id());
2903 sleLoan->at(sfLoanBrokerID) = closedEndedBrokerKeylet.key;
2904 sleLoan->at(sfLoanSequence) = loanSeq;
2905 sleLoan->at(sfBorrower) = a1.id();
2907 sleLoan->at(sfPaymentInterval) = 1;
2908 sleLoan->at(sfPaymentRemaining) = 1;
2909 sleLoan->at(sfTotalValueOutstanding) = Number(100);
2910 sleLoan->at(sfPeriodicPayment) = Number(1);
2911 ac.view().insert(sleLoan);
2915 STTx{ttLOAN_SET, [](STObject&) {}},
2918 auto const sub = env.now().time_since_epoch().count() + 60;
2920 closedEndedRed = red;
2926 .vaultKind = closedEnded,
2927 .subscriptionDate = sub,
2928 .redemptionDate = red});
2930 closedEndedKeylet =
keylet;
2933 closedEndedBrokerKeylet =
2934 keylet::loanBroker(a1.id(), SeqProxy::rawSequence(env.seq(a1)));
2939 env.close(Tp{D{sub + 1}});