469 using namespace loan_broker;
470 using namespace loan;
478 enum class Line { Holding, InDebt };
480 auto runScenario = [
this](
FeatureBitset features, Line line,
TER expected) {
484 Account const issuer{
"issuer"};
489 env.fund(XRP(100'000), issuer, alice, carol, bob);
491 env(fset(issuer, asfDefaultRipple));
503 env(trust(alice, trustLimit));
504 env(trust(carol, trustLimit));
505 env(trust(bob, trustLimit));
508 env(pay(issuer, alice, usd(1'000)));
509 env(pay(issuer, carol, usd(1'000)));
510 if (line == Line::Holding)
512 env(pay(issuer, bob, bobEdge));
518 env(trust(issuer, oppositeLimit));
520 env(pay(bob, issuer, bobDebt));
524 Vault const vault{env};
525 auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
526 vaultTx[sfScale] = 0;
531 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1'000)}));
536 auto const brokerKeylet =
538 env(
set(alice, vaultKeylet.key));
542 env(
set(carol, brokerKeylet.key, usd(1'000).value()),
545 kPaymentInterval(365 * 24 * 60 * 60),
547 Sig(sfCounterpartySignature, alice),
548 Fee(env.current()->fees().base * 2),
558 env(pay(carol, loanKeylet.key, usd(2'000).value()), Ter(
tesSUCCESS));
563 auto const sleVault = env.le(vaultKeylet);
564 BEAST_EXPECT(sleVault && sleVault->at(sfAssetsTotal) ==
Number{1'240});
566 BEAST_EXPECT(sleIssuance && sleIssuance->at(sfOutstandingAmount) == 1'000);
573 env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = usd(6)}),
584 auto const legacy = testableAmendments() - featureLendingProtocolV1_1;
587 "bug: VaultDeposit share truncation lets depositor debit "
588 "round away to zero (pre-fixCleanup3_4_0)");
593 "bug: VaultDeposit share truncation lets depositor debit "
594 "round away to zero (pre-fixCleanup3_2_0 and pre-fixCleanup3_4_0)");
600 "bug: VaultDeposit share truncation rejected with "
601 "tecPRECISION_LOSS (post-fixCleanup3_4_0)");
606 "bug: VaultDeposit share truncation rejected with "
607 "tecPRECISION_LOSS (post-fixCleanup3_4_0, pre-fixCleanup3_2_0)");
612 "bug: VaultDeposit share truncation against a debt balance "
613 "round away to zero (pre-fixCleanup3_4_0)");
618 "bug: VaultDeposit share truncation against a debt balance rejected with "
619 "tecPRECISION_LOSS (post-fixCleanup3_4_0)");
642 enum class DestKind :
bool { ThirdParty =
false, Self =
true };
644 auto runScenario = [
this](
FeatureBitset features, DestKind destKind,
TER expected) {
648 Account const issuer{
"issuer"};
652 env.fund(XRP(100'000), issuer, alice, bob);
654 env(fset(issuer, asfDefaultRipple));
662 env(trust(alice, aliceLimit));
663 if (destKind == DestKind::ThirdParty)
664 env(trust(bob, bobLimit));
667 env(pay(issuer, alice, usd(1'000)));
668 if (destKind == DestKind::ThirdParty)
669 env(pay(issuer, bob, atEdge));
672 Vault const vault{env};
673 auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
674 vaultTx[sfScale] = 0;
678 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(1'000)}));
683 if (destKind == DestKind::Self)
685 env(pay(issuer, alice, atEdge));
689 auto tx = vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(2)});
690 if (destKind == DestKind::ThirdParty)
691 tx[sfDestination] = bob.human();
692 env(tx, Ter(expected));
698 "bug: VaultWithdraw to third-party at IOU edge fires invariant "
699 "(pre-fixCleanup3_2_0)");
705 "bug: VaultWithdraw to third-party at IOU edge succeeds "
706 "(post-fixCleanup3_2_0)");
707 runScenario(testableAmendments(), DestKind::ThirdParty,
tesSUCCESS);
711 "bug: VaultWithdraw to self at IOU edge fires invariant "
712 "(pre-fixCleanup3_2_0)");
718 "bug: VaultWithdraw to self at IOU edge succeeds "
719 "(post-fixCleanup3_2_0)");
720 runScenario(testableAmendments(), DestKind::Self,
tesSUCCESS);
1027 testcase(
"Bug6 - limit bypass with share-denominated withdrawal");
1029 auto const allAmendments = testableAmendments() | featureSingleAssetVault;
1033 bool const withFix = features[fixCleanup3_1_3];
1035 Env env{*
this, features};
1037 Account const issuer{
"issuer"};
1038 Account const depositor{
"depositor"};
1039 Account const charlie{
"charlie"};
1040 Vault const vault{env};
1042 env.fund(XRP(1000), issuer, owner, depositor, charlie);
1043 env(fset(issuer, asfAllowTrustLineClawback));
1047 env.trust(asset(1000), owner);
1048 env.trust(asset(1000), depositor);
1049 env(pay(issuer, owner, asset(200)));
1050 env(pay(issuer, depositor, asset(200)));
1054 env.trust(asset(5), charlie);
1057 auto const [tx,
keylet] = vault.create({.owner = owner, .asset = asset});
1061 auto const depositTx =
1062 vault.deposit({.depositor = depositor, .id =
keylet.key, .amount = asset(100)});
1067 auto const vaultSle = env.le(
keylet);
1068 if (!BEAST_EXPECT(vaultSle))
1070 auto const mptIssuanceID = vaultSle->at(sfShareMPTID);
1071 MPTIssue const shares(mptIssuanceID);
1079 vault.withdraw({.depositor = depositor, .id =
keylet.key, .amount = asset(10)});
1080 withdrawTx[sfDestination] = charlie.human();
1084 auto const charlieBalanceBefore = env.balance(charlie, asset.
raw().
get<
Issue>());
1092 auto withdrawTx = vault.withdraw(
1093 {.depositor = depositor,
1095 .amount =
STAmount(share, 10'000'000)});
1096 withdrawTx[sfDestination] = charlie.human();
1100 auto const charlieBalanceAfter = env.balance(charlie, asset.
raw().
get<
Issue>());
1105 BEAST_EXPECT(charlieBalanceAfter == charlieBalanceBefore);
1111 BEAST_EXPECT(charlieBalanceAfter > charlieBalanceBefore);
1145 using namespace loan_broker;
1146 using namespace loan;
1148 Account const issuer{
"issuer"};
1150 Account const holder{
"holder"};
1151 Account const borrower{
"borrower"};
1153 env.
fund(XRP(10'000), issuer, owner, holder, borrower);
1156 env(fset(issuer, asfAllowTrustLineClawback));
1160 env.
trust(usd(1'000), owner);
1161 env.
trust(usd(1'000), holder);
1162 env.
trust(usd(1'000), borrower);
1165 env(pay(issuer, holder, usd(100)));
1166 env(pay(issuer, borrower, usd(100)));
1169 Vault const vault{env};
1170 auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset = usd});
1171 vaultTx[sfScale] = 0;
1176 env(vault.deposit({.depositor = holder, .id = vaultKeylet.key, .amount = usd(5)}));
1181 auto const brokerKeylet =
1183 env(
set(owner, vaultKeylet.key));
1187 env(
set(borrower, brokerKeylet.key, usd(5).value()),
1190 kPaymentInterval(365 * 24 * 60 * 60),
1192 Sig(sfCounterpartySignature, owner),
1193 Fee(env.
current()->fees().base * 2),
1201 env(pay(borrower, loanKeylet.key, usd(10).value()), Ter(
tesSUCCESS));
1204 auto const vaultSle = env.
le(vaultKeylet);
1205 if (!BEAST_EXPECT(vaultSle))
1206 return std::nullopt;
1207 auto const mptIssuanceID = vaultSle->at(sfShareMPTID);
1209 Number const initialAssetsTotal = vaultSle->at(sfAssetsTotal);
1210 Number const initialAssetsAvailable = vaultSle->at(sfAssetsAvailable);
1211 BEAST_EXPECT(initialAssetsTotal == usd(7).number());
1212 BEAST_EXPECT(initialAssetsAvailable == usd(7).number());
1215 if (!BEAST_EXPECT(sleIssuance))
1216 return std::nullopt;
1217 BEAST_EXPECT(sleIssuance->getFieldU64(sfOutstandingAmount) == 5);
1225 .vaultKeylet = vaultKeylet,
1226 .initialAssetsTotal = initialAssetsTotal,
1227 .initialAssetsAvailable = initialAssetsAvailable};
1376 using namespace loan_broker;
1377 using namespace loan;
1379 Account const issuer{
"issuer"};
1381 Account const holder{
"holder"};
1383 Account const borrower{
"borrower"};
1385 env.
fund(XRP(100'000), issuer, owner, holder, other, borrower);
1388 env(fset(issuer, asfAllowTrustLineClawback));
1389 env(fset(issuer, asfDefaultRipple));
1393 env.
trust(usd(100'000), owner);
1394 env.
trust(usd(100'000), holder);
1395 env.
trust(usd(100'000), other);
1396 env.
trust(usd(100'000), borrower);
1399 int const holderDeposit = 10'000 - otherDeposit;
1400 env(pay(issuer, holder, usd(holderDeposit)));
1401 if (otherDeposit != 0)
1403 env(pay(issuer, other, usd(otherDeposit)));
1407 Vault const vault{env};
1408 auto const [createTx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
1413 auto const vaultSle = env.
le(vaultKeylet);
1414 if (!BEAST_EXPECT(vaultSle))
1415 return std::nullopt;
1416 MPTID const shareId = vaultSle->at(sfShareMPTID);
1419 {.depositor = holder, .id = vaultKeylet.key, .amount = usd(holderDeposit)}));
1420 if (otherDeposit != 0)
1423 {.depositor = other, .id = vaultKeylet.key, .amount = usd(otherDeposit)}));
1427 vault.closePastSubscription(subscriptionDate);
1429 auto const brokerKeylet =
1431 env(
set(owner, vaultKeylet.key));
1434 auto const sleBroker = env.
le(brokerKeylet);
1435 if (!BEAST_EXPECT(sleBroker))
1436 return std::nullopt;
1437 auto const loanKeylet =
1440 env(
set(borrower, brokerKeylet.key, usd(1'000).value()),
1443 kPaymentInterval(120),
1445 Sig(sfCounterpartySignature, owner),
1446 Fee(env.
current()->fees().base * 2),
1453 if (env.
current()->rules().enabled(fixCleanup3_4_0))
1455 auto const loanBefore = env.
le(loanKeylet);
1456 if (!BEAST_EXPECT(loanBefore))
1457 return std::nullopt;
1458 std::uint32_t const dueDate = loanBefore->at(sfNextPaymentDueDate);
1462 env(manage(owner, loanKeylet.key, tfLoanImpair), Ter(
tesSUCCESS));
1465 auto const vaultAfter = env.
le(vaultKeylet);
1466 if (!BEAST_EXPECT(vaultAfter))
1467 return std::nullopt;
1468 BEAST_EXPECT(vaultAfter->at(sfAssetsAvailable) == usd(9'000).value());
1469 BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == usd(1'000).value());
1476 .vaultKeylet = vaultKeylet,
1477 .shareId = shareId};
1602 using namespace loan_broker;
1603 using namespace loan;
1604 using namespace std::chrono_literals;
1606 auto runScenario = [
this](
1608 bool removeAssetToken,
1609 bool withdrawAllAliceShares,
1612 std::string{
"bug: MPT vault zero-value withdraw "} +
1613 (removeAssetToken ?
"without asset MPToken" :
"with asset MPToken") +
1614 (withdrawAllAliceShares ?
", Alice's last share" :
", Alice has leftover shares") +
1615 (features[fixCleanup3_4_0] ?
" (post-fixCleanup3_4_0)" :
" (pre-fixCleanup3_4_0)"));
1617 Env env(*
this, features);
1619 Account const issuer{
"issuer"};
1623 Account const borrower{
"borrower"};
1625 env.fund(XRP(100'000), issuer, owner, alice, bob, borrower);
1628 MPTTester mptt{env, issuer, kMptInitNoFund};
1629 mptt.create({.flags = tfMPTCanTransfer});
1631 mptt.authorize({.account = owner});
1632 mptt.authorize({.account = alice});
1633 mptt.authorize({.account = bob});
1634 mptt.authorize({.account = borrower});
1637 env(pay(issuer, alice, asset(2)));
1638 env(pay(issuer, bob, asset(8)));
1641 Vault const vault{env};
1642 auto const [createTx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
1643 {.owner = owner, .asset = asset, .subscriptionOffset = 60s});
1647 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(2)}));
1648 env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = asset(8)}));
1651 vault.closePastSubscription(subscriptionDate);
1653 auto const brokerKeylet =
1655 env(
set(owner, vaultKeylet.key));
1658 auto const sleBroker = env.le(brokerKeylet);
1659 if (!BEAST_EXPECT(sleBroker))
1664 env(
set(borrower, brokerKeylet.key, asset(10).value()),
1667 kPaymentInterval(120),
1669 Sig(sfCounterpartySignature, owner),
1670 Fee(env.current()->fees().base * 2),
1674 auto const loanBefore = env.le(loanKeylet);
1675 if (!BEAST_EXPECT(loanBefore))
1677 std::uint32_t const dueDate = loanBefore->at(sfNextPaymentDueDate);
1680 env(manage(owner, loanKeylet.key, tfLoanImpair), Ter(
tesSUCCESS));
1683 auto const vaultImpaired = env.le(vaultKeylet);
1684 if (!BEAST_EXPECT(vaultImpaired))
1686 BEAST_EXPECT(vaultImpaired->at(sfAssetsAvailable) == asset(0).value());
1687 BEAST_EXPECT(vaultImpaired->at(sfAssetsTotal) == vaultImpaired->at(sfLossUnrealized));
1688 Number const totalBefore = vaultImpaired->at(sfAssetsTotal);
1689 Number const lossBefore = vaultImpaired->at(sfLossUnrealized);
1691 MPTID const shareId = vaultImpaired->at(sfShareMPTID);
1693 if (!BEAST_EXPECT(issuanceBefore))
1696 issuanceBefore->getFieldU64(sfOutstandingAmount);
1699 if (!BEAST_EXPECT(tokenAlice))
1701 std::uint64_t const sharesBefore = tokenAlice->getFieldU64(sfMPTAmount);
1702 BEAST_EXPECT(sharesBefore == 2);
1703 std::uint64_t const sharesToRedeem = withdrawAllAliceShares ? sharesBefore : 1;
1706 auto const assetTokenKeylet =
keylet::mptoken(mptt.issuanceID(), alice.id());
1707 if (removeAssetToken)
1709 mptt.authorize({.account = alice, .flags = tfMPTUnauthorize});
1711 BEAST_EXPECT(!env.le(assetTokenKeylet));
1715 auto const existing = env.le(assetTokenKeylet);
1716 if (!BEAST_EXPECT(existing))
1718 BEAST_EXPECT(existing->getFieldU64(sfMPTAmount) == 0);
1721 std::uint32_t const redemptionDate = vaultImpaired->at(sfRedemptionDate);
1724 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = redeemShares}),
1730 if (removeAssetToken)
1732 BEAST_EXPECT(!env.le(assetTokenKeylet));
1736 auto const assetAfter = env.le(assetTokenKeylet);
1737 if (!BEAST_EXPECT(assetAfter))
1739 BEAST_EXPECT(assetAfter->getFieldU64(sfMPTAmount) == 0);
1743 if (withdrawAllAliceShares)
1745 BEAST_EXPECT(!shareAfter);
1747 else if (BEAST_EXPECT(shareAfter))
1749 BEAST_EXPECT(shareAfter->getFieldU64(sfMPTAmount) == sharesBefore - sharesToRedeem);
1752 auto const vaultAfter = env.le(vaultKeylet);
1753 if (!BEAST_EXPECT(vaultAfter))
1755 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore);
1756 BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore);
1757 BEAST_EXPECT(vaultAfter->at(sfAssetsAvailable) == asset(0).value());
1760 if (!BEAST_EXPECT(issuanceAfter))
1763 issuanceAfter->getFieldU64(sfOutstandingAmount) ==
1764 outstandingBefore - sharesToRedeem);
1776 all_ - fixCleanup3_4_0,
1781 all_ - fixCleanup3_4_0,
1786 all_ - fixCleanup3_4_0,
1791 all_ - fixCleanup3_4_0,
1807 using namespace loan_broker;
1808 using namespace loan;
1809 using namespace std::chrono_literals;
1811 Env env(*
this,
all_);
1813 Account const issuer{
"issuer"};
1817 Account const borrower{
"borrower"};
1819 env.fund(XRP(100'000), issuer, owner, alice, bob, borrower);
1821 env(fset(issuer, asfDefaultRipple));
1825 env.trust(asset(100), owner);
1826 env.trust(asset(100), alice);
1827 env.trust(asset(100), bob);
1828 env.trust(asset(100), borrower);
1831 env(pay(issuer, alice, asset(2)));
1832 env(pay(issuer, bob, asset(8)));
1835 Vault const vault{env};
1836 auto const [createTx, vaultKeylet, subscriptionDate] =
1837 vault.createClosedEnded({.owner = owner, .asset = asset, .subscriptionOffset = 60s});
1841 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(2)}));
1842 env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = asset(8)}));
1846 if (!BEAST_EXPECT(env.le(assetLine)))
1848 env.trust(asset(0), alice);
1850 BEAST_EXPECT(!env.le(assetLine));
1852 vault.closePastSubscription(subscriptionDate);
1854 auto const brokerKeylet =
1856 env(
set(owner, vaultKeylet.key));
1859 auto const sleBroker = env.le(brokerKeylet);
1860 if (!BEAST_EXPECT(sleBroker))
1862 auto const loanKeylet =
1865 env(
set(borrower, brokerKeylet.key, asset(10).value()),
1868 kPaymentInterval(120),
1870 Sig(sfCounterpartySignature, owner),
1871 Fee(env.current()->fees().base * 2),
1875 auto const loanBefore = env.le(loanKeylet);
1876 if (!BEAST_EXPECT(loanBefore))
1878 std::uint32_t const dueDate = loanBefore->at(sfNextPaymentDueDate);
1881 env(manage(owner, loanKeylet.key, tfLoanImpair), Ter(
tesSUCCESS));
1884 auto const vaultImpaired = env.le(vaultKeylet);
1885 if (!BEAST_EXPECT(vaultImpaired))
1887 BEAST_EXPECT(vaultImpaired->at(sfAssetsAvailable) == asset(0).value());
1888 BEAST_EXPECT(vaultImpaired->at(sfAssetsTotal) == vaultImpaired->at(sfLossUnrealized));
1889 Number const totalBefore = vaultImpaired->at(sfAssetsTotal);
1890 Number const lossBefore = vaultImpaired->at(sfLossUnrealized);
1892 MPTID const shareId = vaultImpaired->at(sfShareMPTID);
1894 if (!BEAST_EXPECT(tokenAlice))
1896 std::uint64_t const sharesBefore = tokenAlice->getFieldU64(sfMPTAmount);
1899 BEAST_EXPECT(sharesBefore > 1);
1902 std::uint32_t const redemptionDate = vaultImpaired->at(sfRedemptionDate);
1905 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = redeemShares}),
1911 BEAST_EXPECT(!env.le(assetLine));
1914 if (!BEAST_EXPECT(shareAfter))
1916 BEAST_EXPECT(shareAfter->getFieldU64(sfMPTAmount) == sharesBefore - 1);
1918 auto const vaultAfter = env.le(vaultKeylet);
1919 if (!BEAST_EXPECT(vaultAfter))
1921 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore);
1922 BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore);
1923 BEAST_EXPECT(vaultAfter->at(sfAssetsAvailable) == asset(0).value());
1939 using namespace loan_broker;
1940 using namespace loan;
1941 using namespace std::chrono_literals;
1945 std::string{
"bug: XRP vault zero-value withdraw with sponsored fee"} +
1946 (features[fixCleanup3_4_0] ?
" (post-fixCleanup3_4_0)" :
" (pre-fixCleanup3_4_0)"));
1948 Env env(*
this, features);
1953 Account const borrower{
"borrower"};
1954 Account const sponsor{
"sponsor"};
1956 env.fund(XRP(100'000), owner, alice, bob, borrower, sponsor);
1960 Vault const vault{env};
1961 auto const [createTx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
1962 {.owner = owner, .asset = asset, .subscriptionOffset = 60s});
1966 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(2)}));
1967 env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = asset(8)}));
1970 vault.closePastSubscription(subscriptionDate);
1972 auto const brokerKeylet =
1974 env(
set(owner, vaultKeylet.key));
1977 auto const sleBroker = env.le(brokerKeylet);
1978 if (!BEAST_EXPECT(sleBroker))
1983 env(
set(borrower, brokerKeylet.key, asset(10).value()),
1986 kPaymentInterval(120),
1988 Sig(sfCounterpartySignature, owner),
1989 Fee(env.current()->fees().base * 2),
1993 auto const loanBefore = env.le(loanKeylet);
1994 if (!BEAST_EXPECT(loanBefore))
1996 std::uint32_t const dueDate = loanBefore->at(sfNextPaymentDueDate);
1999 env(manage(owner, loanKeylet.key, tfLoanImpair), Ter(
tesSUCCESS));
2002 auto const vaultImpaired = env.le(vaultKeylet);
2003 if (!BEAST_EXPECT(vaultImpaired))
2005 BEAST_EXPECT(vaultImpaired->at(sfAssetsAvailable) == asset(0).value());
2006 BEAST_EXPECT(vaultImpaired->at(sfAssetsTotal) == vaultImpaired->at(sfLossUnrealized));
2007 Number const totalBefore = vaultImpaired->at(sfAssetsTotal);
2008 Number const lossBefore = vaultImpaired->at(sfLossUnrealized);
2010 MPTID const shareId = vaultImpaired->at(sfShareMPTID);
2012 if (!BEAST_EXPECT(tokenAlice))
2014 std::uint64_t const sharesBefore = tokenAlice->getFieldU64(sfMPTAmount);
2015 BEAST_EXPECT(sharesBefore == 2);
2018 std::uint32_t const redemptionDate = vaultImpaired->at(sfRedemptionDate);
2021 auto const aliceBalanceBefore = env.balance(alice);
2022 auto const sponsorBalanceBefore = env.balance(sponsor);
2023 auto const fee = env.current()->fees().base;
2025 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = redeemShares}),
2028 Sig(sfSponsorSignature, sponsor),
2032 BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore - fee);
2033 BEAST_EXPECT(env.balance(alice) == aliceBalanceBefore);
2039 if (!BEAST_EXPECT(shareAfter))
2041 BEAST_EXPECT(shareAfter->getFieldU64(sfMPTAmount) == sharesBefore - 1);
2043 auto const vaultAfter = env.le(vaultKeylet);
2044 if (!BEAST_EXPECT(vaultAfter))
2046 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore);
2047 BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore);
2048 BEAST_EXPECT(vaultAfter->at(sfAssetsAvailable) == asset(0).value());
2068 auto runExistingLine = [
this](
2071 bool issuerGlobalFreeze =
false) {
2072 Env env(*
this, features);
2073 Account const issuer{
"issuer"};
2077 env.fund(XRP(10'000), issuer, alice, bob);
2079 env(fset(issuer, asfDefaultRipple));
2084 env(trust(alice, usd(10'000)));
2085 env(trust(bob, usd(10'000)));
2087 env(pay(issuer, alice, usd(1'000)));
2090 Vault const vault{env};
2091 auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
2095 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(500)}));
2098 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(50)}));
2101 env(fclear(issuer, asfDefaultRipple));
2103 if (issuerGlobalFreeze)
2105 env(fset(issuer, asfGlobalFreeze));
2113 if (!issuerGlobalFreeze)
2115 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(10)}));
2119 Number const destBefore = env.balance(alice, usd.
raw()).number();
2120 Number const vaultBefore = env.le(vaultKeylet)->at(sfAssetsTotal);
2121 Number const withdrawAmt{50};
2123 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(50)}),
2127 Number const destAfter = env.balance(alice, usd.
raw()).number();
2128 Number const vaultAfter = env.le(vaultKeylet)->at(sfAssetsTotal);
2131 BEAST_EXPECT(destAfter == destBefore + withdrawAmt);
2132 BEAST_EXPECT(vaultAfter == vaultBefore - withdrawAmt);
2136 BEAST_EXPECT(destAfter == destBefore);
2137 BEAST_EXPECT(vaultAfter == vaultBefore);
2140 if (!issuerGlobalFreeze)
2143 vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(50)});
2144 destTx[sfDestination] = bob.human();
2151 Env env(*
this, features);
2152 Account const issuer{
"issuer"};
2155 env.fund(XRP(10'000), issuer, alice);
2157 env(fset(issuer, asfDefaultRipple));
2162 env(trust(alice, usd(10'000)));
2164 env(pay(issuer, alice, usd(500)));
2167 Vault const vault{env};
2168 auto [vaultTx, vaultKeylet] = vault.create({.owner = alice, .asset = usd});
2172 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(500)}));
2175 env(trust(alice, usd(0)));
2178 env(fclear(issuer, asfDefaultRipple));
2181 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(50)}),
2187 using namespace loan_broker;
2189 Env env(*
this, features);
2190 Account const issuer{
"issuer"};
2193 env.fund(XRP(10'000), issuer, alice);
2195 env(fset(issuer, asfDefaultRipple));
2200 env(trust(alice, usd(10'000)));
2202 env(pay(issuer, alice, usd(1'000)));
2205 Vault const vault{env};
2206 auto const [createTx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
2208 (void)subscriptionDate;
2212 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(500)}));
2215 auto const brokerKeylet =
2217 env(
set(alice, vaultKeylet.key));
2219 env(coverDeposit(alice, brokerKeylet.key, usd(100).value()));
2222 env(fclear(issuer, asfDefaultRipple));
2226 Number const destBefore = env.balance(alice, usd.
raw()).number();
2227 Number const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable);
2228 Number const withdrawAmt{50};
2230 env(coverWithdraw(alice, brokerKeylet.key, usd(50).value()), Ter(selfExpected));
2233 Number const destAfter = env.balance(alice, usd.
raw()).number();
2234 Number const coverAfter = env.le(brokerKeylet)->at(sfCoverAvailable);
2237 BEAST_EXPECT(destAfter == destBefore + withdrawAmt);
2238 BEAST_EXPECT(coverAfter == coverBefore - withdrawAmt);
2242 BEAST_EXPECT(destAfter == destBefore);
2243 BEAST_EXPECT(coverAfter == coverBefore);
2247 auto runDeletedCoverWithdraw = [
this](
FeatureBitset features,
TER selfExpected) {
2248 using namespace loan_broker;
2250 Env env(*
this, features);
2251 Account const issuer{
"issuer"};
2254 env.fund(XRP(10'000), issuer, alice);
2256 env(fset(issuer, asfDefaultRipple));
2261 env(trust(alice, usd(10'000)));
2263 env(pay(issuer, alice, usd(600)));
2266 Vault const vault{env};
2267 auto const [createTx, vaultKeylet, subscriptionDate] = vault.createClosedEnded(
2269 (void)subscriptionDate;
2273 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(500)}));
2276 auto const brokerKeylet =
2278 env(
set(alice, vaultKeylet.key));
2280 env(coverDeposit(alice, brokerKeylet.key, usd(100).value()));
2283 env(trust(alice, usd(0)));
2286 env(fclear(issuer, asfDefaultRipple));
2289 env(coverWithdraw(alice, brokerKeylet.key, usd(50).value()), Ter(selfExpected));
2294 Env env(*
this, features);
2295 Account const issuer{
"issuer"};
2297 Account const pdOwner{
"pdOwner"};
2298 Account const credIssuer{
"credIssuer"};
2301 env.fund(XRP(10'000), issuer, alice, pdOwner, credIssuer);
2303 env(fset(issuer, asfDefaultRipple));
2307 env(trust(alice, usd(10'000)));
2309 env(pay(issuer, alice, usd(1'000)));
2312 Vault const vault{env};
2313 auto [vaultTx, vaultKeylet] =
2314 vault.create({.owner = alice, .asset = usd, .flags = tfVaultPrivate});
2318 pdomain::Credentials
const credentials{{.issuer = credIssuer, .credType = credType}};
2320 auto const domainId = pdomain::getNewDomain(env.meta());
2322 auto domainTx = vault.set({.owner = alice, .id = vaultKeylet.key});
2323 domainTx[sfDomainID] =
to_string(domainId);
2328 env(credentials::create(alice, credIssuer, credType));
2329 env(credentials::accept(alice, credIssuer, credType));
2332 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = usd(500)}));
2335 env(fclear(issuer, asfDefaultRipple));
2338 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = usd(50)}),
2344 "bug: VaultWithdraw to self fails with tecINTERNAL after issuer "
2345 "clears asfDefaultRipple even though the trust line exists "
2346 "(pre-fixCleanup3_4_0)");
2350 "bug: VaultWithdraw to self succeeds after issuer clears "
2351 "asfDefaultRipple when the trust line exists (post-fixCleanup3_4_0)");
2355 "bug: VaultWithdraw to self with an existing line still gets "
2356 "tecFROZEN under asfGlobalFreeze (post-fixCleanup3_4_0)");
2360 "bug: VaultWithdraw to self fails with tecINTERNAL after issuer "
2361 "clears asfDefaultRipple and the trust line was deleted "
2362 "(pre-fixCleanup3_4_0)");
2366 "bug: VaultWithdraw to self fails with terNO_RIPPLE after issuer "
2367 "clears asfDefaultRipple and the trust line was deleted "
2368 "(post-fixCleanup3_4_0)");
2372 "bug: LoanBrokerCoverWithdraw to self fails with tecINTERNAL after "
2373 "issuer clears asfDefaultRipple even though the trust line exists "
2374 "(pre-fixCleanup3_4_0)");
2378 "bug: LoanBrokerCoverWithdraw to self succeeds after issuer clears "
2379 "asfDefaultRipple when the trust line exists (post-fixCleanup3_4_0)");
2383 "bug: LoanBrokerCoverWithdraw to self fails with tecINTERNAL after "
2384 "issuer clears asfDefaultRipple and the trust line was deleted "
2385 "(pre-fixCleanup3_4_0)");
2389 "bug: LoanBrokerCoverWithdraw to self fails with terNO_RIPPLE after "
2390 "issuer clears asfDefaultRipple and the trust line was deleted "
2391 "(post-fixCleanup3_4_0)");
2395 "bug: private VaultWithdraw to self fails with tecINTERNAL after "
2396 "issuer clears asfDefaultRipple even though the trust line exists "
2397 "(pre-fixCleanup3_4_0)");
2401 "bug: private VaultWithdraw to self succeeds after issuer clears "
2402 "asfDefaultRipple when the trust line exists (post-fixCleanup3_4_0)");
2424 Env env{*
this, features};
2426 Account const holder{
"holder"};
2427 Account const destination{
"destination"};
2428 Account const sponsor{
"sponsor"};
2429 env.fund(XRP(10'000), owner, holder, destination, sponsor);
2432 constexpr std::uint32_t investmentPeriod = 14u * 24u * 60u * 60u;
2433 auto const [vault, vaultKeylet, subscriptionDate, redemptionDate] =
2435 BEAST_EXPECT(redemptionDate - subscriptionDate == investmentPeriod);
2438 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()}));
2445 auto const vaultBefore = env.le(vaultKeylet);
2446 if (!BEAST_EXPECT(vaultBefore))
2448 auto const assetsTotalBefore = vaultBefore->at(sfAssetsTotal);
2449 auto const holderBalanceBefore = env.balance(holder);
2450 auto const destinationBalanceBefore = env.balance(destination);
2451 auto const sponsorBalanceBefore = env.balance(sponsor);
2452 auto const fee = env.current()->fees().base;
2454 auto withdraw = vault.withdraw(
2455 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()});
2456 withdraw[sfDestination] = destination.human();
2460 Sig(sfSponsorSignature, sponsor),
2464 auto const vaultAfter = env.le(vaultKeylet);
2465 if (!BEAST_EXPECT(vaultAfter))
2467 BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore - fee);
2471 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == assetsTotalBefore - XRP(100).value());
2472 BEAST_EXPECT(env.balance(holder) == holderBalanceBefore);
2473 BEAST_EXPECT(env.balance(destination) == destinationBalanceBefore + XRP(100));
2479 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == assetsTotalBefore);
2480 BEAST_EXPECT(env.balance(holder) == holderBalanceBefore);
2481 BEAST_EXPECT(env.balance(destination) == destinationBalanceBefore);
2485 auto retry = vault.withdraw(
2486 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()});
2487 retry[sfDestination] = destination.human();
2492 "bug: sponsored XRP withdrawal to a distinct destination misreads a "
2493 "touched-but-zero sender delta as a second recipient "
2494 "(pre-fixCleanup3_4_0)");
2498 "bug: sponsored XRP withdrawal to a distinct destination succeeds "
2499 "(post-fixCleanup3_4_0)");
2518 Env env{*
this, features};
2520 Account const holder{
"holder"};
2521 Account const sponsor{
"sponsor"};
2522 env.fund(XRP(10'000), owner, holder, sponsor);
2525 Vault const vault{env};
2526 auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset =
xrpIssue()});
2531 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()}));
2534 auto const vaultBefore = env.le(vaultKeylet);
2535 if (!BEAST_EXPECT(vaultBefore))
2537 auto const assetsTotalBefore = vaultBefore->at(sfAssetsTotal);
2538 auto const sponsorBalanceBefore = env.balance(sponsor);
2539 auto const fee = env.current()->fees().base;
2543 auto withdraw = vault.withdraw(
2544 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()});
2545 withdraw[sfDestination] = sponsor.human();
2549 Sig(sfSponsorSignature, sponsor),
2553 auto const vaultAfter = env.le(vaultKeylet);
2554 if (!BEAST_EXPECT(vaultAfter))
2559 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == assetsTotalBefore - XRP(100).value());
2562 BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore + XRP(100) - fee);
2566 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == assetsTotalBefore);
2567 BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore - fee);
2571 "bug: co-signed sponsor named as withdrawal destination has its "
2572 "own fee debit misread as breaking the payout equality "
2573 "(pre-fixCleanup3_4_0)");
2577 "bug: co-signed sponsor named as withdrawal destination succeeds "
2578 "(post-fixCleanup3_4_0)");
2598 auto runScenario = [
this](
2601 bool const sponsorIsDestination) {
2602 Env env{*
this, features};
2604 Account const holder{
"holder"};
2605 Account const destination{
"destination"};
2606 Account const sponsor{
"sponsor"};
2607 env.fund(XRP(10'000), owner, holder, destination, sponsor);
2610 Vault const vault{env};
2611 auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset =
xrpIssue()});
2616 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()}));
2619 auto const fee = env.current()->fees().base;
2620 env(sponsor::set_fee(sponsor, 0, fee), sponsor::SponseeAcc(holder));
2623 auto const vaultBefore = env.le(vaultKeylet);
2624 if (!BEAST_EXPECT(vaultBefore))
2626 auto const assetsTotalBefore = vaultBefore->at(sfAssetsTotal);
2627 auto const holderBalanceBefore = env.balance(holder);
2628 auto const destinationBalanceBefore = env.balance(destination);
2629 auto const sponsorBalanceBefore = env.balance(sponsor);
2631 Account const& recipient = sponsorIsDestination ? sponsor : destination;
2632 auto withdraw = vault.withdraw(
2633 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()});
2634 withdraw[sfDestination] = recipient.human();
2635 env(withdraw, Fee(fee), sponsor::As(sponsor,
spfSponsorFee), Ter(expected));
2638 auto const vaultAfter = env.le(vaultKeylet);
2639 if (!BEAST_EXPECT(vaultAfter))
2643 BEAST_EXPECT(env.balance(holder) == holderBalanceBefore);
2647 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == assetsTotalBefore - XRP(100).value());
2648 if (sponsorIsDestination)
2653 BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore + XRP(100));
2657 BEAST_EXPECT(env.balance(destination) == destinationBalanceBefore + XRP(100));
2658 BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore);
2661 if (!BEAST_EXPECT(sponsorship))
2663 BEAST_EXPECT(!sponsorship->isFieldPresent(sfFeeAmount));
2667 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == assetsTotalBefore);
2668 BEAST_EXPECT(env.balance(sponsor) == sponsorBalanceBefore);
2669 if (!sponsorIsDestination)
2670 BEAST_EXPECT(env.balance(destination) == destinationBalanceBefore);
2674 "pre-funded fee XRP withdrawal to a distinct destination succeeds "
2675 "(post-fixCleanup3_4_0)");
2679 "bug: pre-funded sponsor named as withdrawal destination misreads "
2680 "the sender's touched-but-zero delta as a second recipient "
2681 "(pre-fixCleanup3_4_0)");
2685 "bug: pre-funded sponsor named as withdrawal destination receives "
2686 "the full payout (post-fixCleanup3_4_0)");
2699 "unsponsored XRP withdrawal to a distinct destination succeeds "
2700 "(pre-fixCleanup3_4_0)");
2702 Env env{*
this,
all_ - fixCleanup3_4_0};
2704 Account const holder{
"holder"};
2705 Account const destination{
"destination"};
2706 env.fund(XRP(10'000), owner, holder, destination);
2709 Vault const vault{env};
2710 auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset =
xrpIssue()});
2715 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()}));
2718 auto const vaultBefore = env.le(vaultKeylet);
2719 if (!BEAST_EXPECT(vaultBefore))
2721 auto const assetsTotalBefore = vaultBefore->at(sfAssetsTotal);
2722 auto const holderBalanceBefore = env.balance(holder);
2723 auto const destinationBalanceBefore = env.balance(destination);
2724 auto const fee = env.current()->fees().base;
2726 auto withdraw = vault.withdraw(
2727 {.depositor = holder, .id = vaultKeylet.key, .amount = XRP(100).value()});
2728 withdraw[sfDestination] = destination.human();
2732 auto const vaultAfter = env.le(vaultKeylet);
2733 if (!BEAST_EXPECT(vaultAfter))
2735 BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == assetsTotalBefore - XRP(100).value());
2736 BEAST_EXPECT(env.balance(holder) == holderBalanceBefore - fee);
2737 BEAST_EXPECT(env.balance(destination) == destinationBalanceBefore + XRP(100));