19 #include <ripple/app/misc/AMMHelpers.h>
20 #include <ripple/app/paths/AMMContext.h>
21 #include <ripple/app/paths/AMMOffer.h>
22 #include <ripple/protocol/AMMCore.h>
23 #include <ripple/protocol/STParsedJSON.h>
24 #include <ripple/resource/Fees.h>
25 #include <ripple/rpc/RPCHandler.h>
26 #include <ripple/rpc/impl/RPCHelpers.h>
28 #include <test/jtx/AMM.h>
29 #include <test/jtx/AMMTest.h>
30 #include <test/jtx/TestHelpers.h>
31 #include <test/jtx/amount.h>
32 #include <test/jtx/sendmax.h>
47 testcase(
"Instance Create");
61 USD(20
'000), BTC(0.5), IOUAmount{100, 0}));
63 {{USD(20'000),
BTC(0.5)}});
68 fund(env,
gw, {
alice}, {
USD(20
'000), BTC(0.5)}, Fund::All);
71 // no transfer fee on create
72 AMM ammAlice(env, alice, USD(20'000),
BTC(0.5));
73 BEAST_EXPECT(ammAlice.expectBalances(
74 USD(20
'000), BTC(0.5), IOUAmount{100, 0}));
75 BEAST_EXPECT(expectLine(env, alice, USD(0)));
76 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
79 // Require authorization is set, account is authorized
82 env.fund(XRP(30'000),
gw,
alice);
86 env.trust(
USD(30
'000), alice);
88 env(trust(gw, alice["USD"](30'000)), txflags(
tfSetfAuth));
92 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000));
95 // Cleared global freeze
98 env.fund(XRP(30'000),
gw,
alice);
100 env.trust(
USD(30
'000), alice);
102 env(pay(gw, alice, USD(10'000)));
110 AMM ammAlice(env,
alice, XRP(10
'000), USD(10'000));
117 testcase(
"Invalid Instance");
126 env, alice, XRP(10'000),
XRP(10
'000), ter(temBAD_AMM_TOKENS));
127 BEAST_EXPECT(!ammAlice.ammExists());
130 // Can't have both tokens the
same IOU
135 env, alice, USD(10'000),
USD(10
'000), ter(temBAD_AMM_TOKENS));
136 BEAST_EXPECT(!ammAlice.ammExists());
139 // Can't have zero or negative amounts
144 BEAST_EXPECT(!ammAlice.ammExists());
145 AMM ammAlice1(env,
alice,
XRP(10
'000), USD(0), ter(temBAD_AMOUNT));
146 BEAST_EXPECT(!ammAlice1.ammExists());
148 env, alice, XRP(10'000),
USD(-10
'000), ter(temBAD_AMOUNT));
149 BEAST_EXPECT(!ammAlice2.ammExists());
151 env, alice, XRP(-10'000),
USD(10
'000), ter(temBAD_AMOUNT));
152 BEAST_EXPECT(!ammAlice3.ammExists());
158 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
161 BEAST_EXPECT(!ammAlice.ammExists());
169 env, alice, XRP(10'000),
USD(40
'000), ter(tecUNFUNDED_AMM));
170 BEAST_EXPECT(!ammAlice.ammExists());
173 // Insufficient XRP balance
176 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
179 BEAST_EXPECT(!ammAlice.ammExists());
198 BEAST_EXPECT(!ammAlice.ammExists());
222 ter(temINVALID_FLAG));
223 BEAST_EXPECT(!ammAlice.ammExists());
243 BEAST_EXPECT(!ammAlice.ammExists());
246 // Require authorization is set
249 env.fund(XRP(30'000),
gw,
alice);
253 env(trust(
gw,
alice[
"USD"](30
'000)));
255 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000), ter(tecNO_AUTH));
256 BEAST_EXPECT(!ammAlice.ammExists());
262 env.fund(XRP(30'000),
gw,
alice);
266 env(trust(
gw,
alice[
"USD"](30
'000)));
268 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000), ter(tecFROZEN));
269 BEAST_EXPECT(!ammAlice.ammExists());
272 // Individually frozen
275 env.fund(XRP(30'000),
gw,
alice);
277 env(trust(
gw,
alice[
"USD"](30
'000)));
279 env(trust(gw, alice["USD"](0), tfSetFreeze));
281 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000), ter(tecFROZEN));
282 BEAST_EXPECT(!ammAlice.ammExists());
285 // Insufficient reserve, XRP/IOU
288 auto const starting_xrp =
290 env.
fund(starting_xrp,
gw);
294 env(pay(gw, alice, USD(2'000)));
305 auto const starting_xrp =
307 env.
fund(starting_xrp,
gw);
310 env.trust(EUR(2'000),
alice);
313 env(pay(gw, alice, EUR(2'000)));
332 ammCrtFee(env).drops() - 1,
336 ter(telINSUF_FEE_P));
341 // AMM with one LPToken from another AMM.
342 testAMM([&](AMM& ammAlice, Env& env) {
343 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
348 STAmount{ammAlice.lptIssue(), 1'000
'000},
349 ter(tecAMM_INVALID_TOKENS));
353 STAmount{ammAlice.lptIssue(), 1'000
'000},
361 AMM ammAlice1(env, alice, XRP(10'000),
EUR(10
'000));
362 auto const token1 = ammAlice.lptIssue();
363 auto const token2 = ammAlice1.lptIssue();
367 STAmount{token1, 1'000
'000},
368 STAmount{token2, 1'000
'000},
369 ter(tecAMM_INVALID_TOKENS));
372 // Issuer has DefaultRipple disabled
375 env.fund(XRP(30'000),
gw);
378 env.fund(
XRP(30
'000), alice);
379 env.trust(USD(30'000),
alice);
382 env, alice, XRP(10'000),
USD(10
'000), ter(terNO_RIPPLE));
383 Account const gw1("gw1");
384 env.fund(XRP(30'000), gw1);
386 env.trust(
USD(30
'000), gw1);
387 env(pay(gw, gw1, USD(30'000)));
388 auto const USD1 = gw1[
"USD"];
390 env.trust(USD1(30
'000), alice);
391 env(pay(gw1, alice, USD1(30'000)));
400 testcase(
"Invalid Deposit");
422 {
tfLPToken, 1
'000, std::nullopt, USD(100), std::nullopt},
423 {tfLPToken, 1'000,
XRP(100), std::nullopt, std::nullopt},
428 STAmount{USD, 1, -1}},
433 STAmount{USD, 1, -1}},
453 STAmount{USD, 1, -1}},
488 STAmount{USD, 1, -1}},
508 STAmount{USD, 1, -1}}};
509 for (auto const& it : invalidOptions)
525 alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
531 ter(temBAD_AMM_TOKENS));
533 // Invalid tokens - bogus currency
535 auto const iss1 = Issue{Currency(0xabc), gw.id()};
536 auto const iss2 = Issue{Currency(0xdef), gw.id()};
606 ter(temBAD_CURRENCY));
647 // Single deposit: 100000 tokens worth of XRP
648 // Amount to deposit exceeds Max
687 STAmount{USD, 1, -12},
691 ter(tecAMM_INVALID_TOKENS));
695 testAMM([&](AMM& ammAlice, Env& env) {
696 ammAlice.withdrawAll(alice);
698 alice, 10'000, std::nullopt, std::nullopt,
ter(
terNO_AMM));
756 // Adds LPT trustline
757 ammAlice.deposit(bob, XRP(10));
793 ter(tecUNFUNDED_AMM));
796 // Insufficient XRP balance by tokens
797 testAMM([&](AMM& ammAlice, Env& env) {
798 env.fund(XRP(1'000),
bob);
801 env(pay(gw, bob, USD(90'000)));
818 auto const starting_xrp =
821 env.fund(XRP(10'000),
alice);
824 env.trust(USD(2'000),
carol);
827 env(pay(gw, carol, USD(2'000)));
844 auto const starting_xrp =
847 env.fund(XRP(10'000),
alice);
850 env.trust(EUR(2'000),
alice);
852 env.trust(EUR(2'000),
carol);
855 env(pay(gw, alice, EUR(2'000)));
857 env(pay(gw, carol, EUR(2'000)));
919 ter(temBAD_CURRENCY));
920 // min amount bad token pair
940 ter(temBAD_AMM_TOKENS));
944 testAMM([&](AMM& ammAlice, Env& env) {
945 // Equal deposit by tokens
966 // Equal deposit by asset
1005 // 30,000 less deposited 1,000 and 10 drops tx fee
1007 expectLedgerEntryRoot(env, carol, XRPAmount{28'999
'999'990}));
1023 testAMM([&](AMM& ammAlice, Env&) {
1024 ammAlice.deposit(
carol,
USD(200), XRP(100));
1025 BEAST_EXPECT(ammAlice.expectBalances(
1026 XRP(10
'100), USD(10'100), IOUAmount{10
'100'000, 0}));
1029 testAMM([&](AMM& ammAlice, Env&) {
1031 BEAST_EXPECT(ammAlice.expectBalances(
1032 XRP(10
'100), USD(10'100), IOUAmount{10
'100'000, 0}));
1036 testAMM([&](AMM& ammAlice, Env&) {
1037 ammAlice.deposit(
carol,
USD(1
'000));
1038 BEAST_EXPECT(ammAlice.expectBalances(
1040 STAmount{
USD, UINT64_C(10
'999'99999999999), -11},
1041 IOUAmount{10
'488'088
'48170151, -8}));
1044 // Single deposit: 1000 XRP
1045 testAMM([&](AMM& ammAlice, Env&) {
1046 ammAlice.deposit(carol, XRP(1'000));
1047 BEAST_EXPECT(ammAlice.expectBalances(
1048 XRP(11
'000), USD(10'000), IOUAmount{10
'488'088
'48170151, -8}));
1051 // Single deposit: 100000 tokens worth of USD
1052 testAMM([&](AMM& ammAlice, Env&) {
1053 ammAlice.deposit(carol, 100000, USD(205));
1054 BEAST_EXPECT(ammAlice.expectBalances(
1055 XRP(10'000),
USD(10
'201), IOUAmount{10'100
'000, 0}));
1058 // Single deposit: 100000 tokens worth of XRP
1059 testAMM([&](AMM& ammAlice, Env&) {
1060 ammAlice.deposit(carol, 100'000,
XRP(205));
1061 BEAST_EXPECT(ammAlice.expectBalances(
1062 XRP(10
'201), USD(10'000), IOUAmount{10
'100'000, 0}));
1067 testAMM([&](AMM& ammAlice, Env&) {
1069 carol,
USD(1
'000), std::nullopt, STAmount{USD, 1, -1});
1070 BEAST_EXPECT(ammAlice.expectBalances(
1072 STAmount{
USD, UINT64_C(10
'999'99999999999), -11},
1073 IOUAmount{10
'488'088
'48170151, -8}));
1076 // Single deposit with EP not exceeding specified:
1077 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
1078 testAMM([&](AMM& ammAlice, Env&) {
1080 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
1081 BEAST_EXPECT(ammAlice.expectBalances(
1083 STAmount{
USD, 10
'080'16, -2},
1084 IOUAmount{10
'040'000, 0}));
1089 testAMM([&](AMM& ammAlice, Env&) {
1091 carol,
USD(0), std::nullopt, STAmount{
USD, 2004, -6});
1092 BEAST_EXPECT(ammAlice.expectBalances(
1094 STAmount{USD, 10'080
'16, -2},
1095 IOUAmount{10'040
'000, 0}));
1098 // IOU to IOU + transfer fee
1101 fund(env, gw, {alice}, {USD(20'000),
BTC(0.5)}, Fund::All);
1105 BEAST_EXPECT(ammAlice.expectBalances(
1106 USD(20'000),
BTC(0.5), IOUAmount{100, 0}));
1110 // no transfer fee on deposit
1111 ammAlice.deposit(carol, 10);
1112 BEAST_EXPECT(ammAlice.expectBalances(
1113 USD(22'000),
BTC(0.55), IOUAmount{110, 0}));
1119 testAMM([&](AMM& ammAlice, Env&) {
1120 ammAlice.deposit(
carol, IOUAmount{1, -3});
1121 BEAST_EXPECT(ammAlice.expectBalances(
1122 XRPAmount{10
'000'000
'001},
1123 STAmount{USD, UINT64_C(10'000
'000001), -6},
1124 IOUAmount{10'000
'000'001, -3}));
1125 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{1, -3}));
1127 testAMM([&](AMM& ammAlice, Env&) {
1128 ammAlice.deposit(
carol, XRPAmount{1});
1129 BEAST_EXPECT(ammAlice.expectBalances(
1130 XRPAmount{10
'000'000
'001},
1132 IOUAmount{1
'000'000
'000049999, -8}));
1133 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
1135 testAMM([&](AMM& ammAlice, Env&) {
1136 ammAlice.deposit(carol, STAmount{USD, 1, -10});
1137 BEAST_EXPECT(ammAlice.expectBalances(
1139 STAmount{USD, UINT64_C(10
'000'00000000008), -11},
1140 IOUAmount{10
'000'000
'00000004, -8}));
1141 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
1144 // Issuer create/deposit
1147 env.fund(XRP(30000), gw);
1148 AMM ammGw(env, gw, XRP(10'000), USD(10
'000));
1150 ammGw.expectBalances(XRP(10'000), USD(10
'000), ammGw.tokens()));
1151 ammGw.deposit(gw, 1'000
'000);
1152 BEAST_EXPECT(ammGw.expectBalances(
1153 XRP(11'000), USD(11
'000), IOUAmount{11'000
'000}));
1154 ammGw.deposit(gw, USD(1'000));
1155 BEAST_EXPECT(ammGw.expectBalances(
1157 STAmount{USD, UINT64_C(11'999
'99999999998), -11},
1158 IOUAmount{11'489
'125'29307605, -8}));
1162 testAMM([&](AMM& ammAlice, Env& env) {
1163 ammAlice.deposit(
gw, 1
'000'000);
1164 BEAST_EXPECT(ammAlice.expectBalances(
1165 XRP(11
'000), USD(11'000), IOUAmount{11
'000'000}));
1166 ammAlice.deposit(
gw,
USD(1
'000));
1167 BEAST_EXPECT(ammAlice.expectBalances(
1169 STAmount{
USD, UINT64_C(11
'999'99999999998), -11},
1170 IOUAmount{11
'489'125
'29307605, -8}));
1174 testAMM([&](AMM& ammAlice, Env& env) {
1175 // Equal deposit by tokens
1185 BEAST_EXPECT(ammAlice.expectBalances(
1186 XRP(11'000),
USD(11
'000), IOUAmount{11'000
'000, 0}));
1188 testAMM([&](AMM& ammAlice, Env& env) {
1189 // Equal deposit by asset
1199 BEAST_EXPECT(ammAlice.expectBalances(
1200 XRP(11'000),
USD(11
'000), IOUAmount{11'000
'000, 0}));
1202 testAMM([&](AMM& ammAlice, Env& env) {
1203 // Single deposit by asset
1213 BEAST_EXPECT(ammAlice.expectBalances(
1214 XRP(11'000),
USD(10
'000), IOUAmount{10'488
'088'48170151, -8}));
1216 testAMM([&](AMM& ammAlice, Env& env) {
1227 BEAST_EXPECT(ammAlice.expectBalances(
1229 STAmount{USD, UINT64_C(10'999
'99999999999), -11},
1230 IOUAmount{10'488
'088'48170151, -8}));
1237 testcase(
"Invalid Withdraw");
1239 using namespace jtx;
1286 tfWithdrawAll | tfLPToken,
1298 tfWithdrawAll | tfOneAssetWithdrawAll,
1310 tfOneAssetWithdrawAll,
1354 for (
auto const& it : invalidOptions)
1365 ter(std::get<5>(it)));
1425 ter(tecAMM_BALANCE));
1477 // Carol is not a Liquidity Provider
1490 ter(tecAMM_BALANCE));
1491 // Withdrawing from one side.
1495 IOUAmount(9'999
'999'9999, -4),
1505 ter(tecAMM_BALANCE));
1516 STAmount{
USD, UINT64_C(9
'999'9999999999999), -13},
1526 alice, 10
'000, std::nullopt, std::nullopt, ter(terNO_AMM));
1529 // Globally frozen asset
1530 testAMM([&](AMM& ammAlice, Env& env) {
1531 env(fset(gw, asfGlobalFreeze));
1533 // Can withdraw non-frozen token
1534 ammAlice.withdraw(alice, XRP(100));
1536 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
1538 alice, 1'000, std::nullopt, std::nullopt,
ter(
tecFROZEN));
1548 alice, 1
'000, std::nullopt, std::nullopt, ter(tecFROZEN));
1550 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
1551 env(trust(gw, alice["USD"](0), tfClearFreeze));
1552 // Individually frozen AMM
1555 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
1557 // Can withdraw non-frozen token
1558 ammAlice.withdraw(alice, XRP(100));
1560 alice, 1'000, std::nullopt, std::nullopt,
ter(
tecFROZEN));
1616 ter(tecAMM_FAILED));
1619 // Deposit/Withdraw the same amount with the trading fee
1621 [&](AMM& ammAlice, Env&) {
1622 ammAlice.deposit(carol, USD(1'000));
1628 ter(tecAMM_INVALID_TOKENS));
1633 [&](
AMM& ammAlice,
Env&) {
1645 // Deposit/Withdraw the same amount fails due to the tokens adjustment
1646 testAMM([&](AMM& ammAlice, Env&) {
1647 ammAlice.deposit(carol, STAmount{USD, 1, -6});
1650 STAmount{USD, 1, -6},
1653 ter(tecAMM_INVALID_TOKENS));
1656 // Withdraw close to one side of the pool. Account's LP tokens
1702 testcase(
"Withdraw");
1704 using namespace jtx;
1718 // 30,000 less deposited 1,000 and 10 drops tx fee
1720 expectLedgerEntryRoot(env, carol, XRPAmount{28'999
'999'990}));
1728 expectLedgerEntryRoot(env, carol, XRPAmount{29'999
'999'980}));
1733 testAMM([&](AMM& ammAlice, Env&) {
1734 ammAlice.withdraw(
alice, 1
'000'000);
1735 BEAST_EXPECT(ammAlice.expectBalances(
1736 XRP(9
'000), USD(9'000),
IOUAmount{9
'000'000, 0}));
1744 testAMM([&](AMM& ammAlice, Env&) {
1746 BEAST_EXPECT(ammAlice.expectBalances(
1747 XRP(9
'900), USD(9'900), IOUAmount{9
'900'000, 0}));
1751 testAMM([&](AMM& ammAlice, Env&) {
1753 BEAST_EXPECT(ammAlice.expectBalances(
1754 XRP(9
'900), USD(9'900), IOUAmount{9
'900'000, 0}));
1758 testAMM([&](AMM& ammAlice, Env&) {
1759 ammAlice.withdraw(
alice,
XRP(1
'000));
1760 BEAST_EXPECT(ammAlice.expectBalances(
1761 XRP(9'000),
USD(10
'000), IOUAmount{9'486
'832'98050514, -8}));
1765 testAMM([&](AMM& ammAlice, Env&) {
1766 ammAlice.withdraw(
alice, 10
'000, USD(0));
1767 BEAST_EXPECT(ammAlice.expectBalances(
1768 XRP(10'000),
USD(9980.01), IOUAmount{9
'990'000, 0}));
1772 testAMM([&](AMM& ammAlice, Env& env) {
1773 ammAlice.withdrawAll(
alice);
1774 BEAST_EXPECT(!ammAlice.ammExists());
1777 AMM ammCarol(env,
carol,
XRP(10
'000), USD(10'000));
1778 BEAST_EXPECT(ammCarol.expectBalances(
1779 XRP(10
'000), USD(10'000), IOUAmount{10
'000'000, 0}));
1783 testAMM([&](AMM& ammAlice, Env& env) {
1784 ammAlice.deposit(
carol,
USD(1
'000));
1785 ammAlice.withdrawAll(carol, USD(0));
1786 BEAST_EXPECT(ammAlice.expectBalances(
1787 XRP(10'000),
USD(10
'000), IOUAmount{10'000
'000, 0}));
1789 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
1792 // Single deposit 1000USD, withdraw all tokens in XRP
1793 testAMM([&](AMM& ammAlice, Env&) {
1794 ammAlice.deposit(carol, USD(1'000));
1795 ammAlice.withdrawAll(
carol,
XRP(0));
1796 BEAST_EXPECT(ammAlice.expectBalances(
1797 XRPAmount(9
'090'909
'091),
1798 STAmount{USD, UINT64_C(10'999
'99999999999), -11},
1799 IOUAmount{10'000
'000, 0}));
1802 // Single deposit/withdraw by the same account
1803 testAMM([&](AMM& ammAlice, Env&) {
1804 // Since a smaller amount might be deposited due to
1805 // the lp tokens adjustment, withdrawing by tokens
1806 // is generally preferred to withdrawing by amount.
1807 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
1808 ammAlice.withdraw(
carol, lpTokens,
USD(0));
1809 lpTokens = ammAlice.deposit(
carol, STAmount(
USD, 1, -6));
1810 ammAlice.withdraw(
carol, lpTokens,
USD(0));
1811 lpTokens = ammAlice.deposit(
carol, XRPAmount(1));
1812 ammAlice.withdraw(
carol, lpTokens, XRPAmount(0));
1813 BEAST_EXPECT(ammAlice.expectBalances(
1814 XRP(10
'000), USD(10'000), ammAlice.tokens()));
1815 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{0}));
1820 testAMM([&](AMM& ammAlice, Env&) {
1821 auto const carolTokens = ammAlice.deposit(
carol,
USD(1
'000));
1822 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
1823 ammAlice.withdraw(
alice, aliceTokens,
USD(0));
1824 ammAlice.withdraw(
carol, carolTokens,
USD(0));
1825 BEAST_EXPECT(ammAlice.expectBalances(
1826 XRP(10
'000), USD(10'000), ammAlice.tokens()));
1827 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{0}));
1828 BEAST_EXPECT(ammAlice.expectLPTokens(
alice, ammAlice.tokens()));
1832 testAMM([&](AMM& ammAlice, Env&) {
1833 ammAlice.deposit(
carol, 1
'000'000);
1834 ammAlice.withdrawAll(
carol);
1835 BEAST_EXPECT(ammAlice.expectBalances(
1836 XRP(10
'000), USD(10'000), IOUAmount{10
'000'000, 0}));
1840 testAMM([&](AMM& ammAlice, Env&) {
1841 ammAlice.deposit(
carol, 1
'000'000);
1842 ammAlice.withdrawAll(
carol,
USD(0));
1843 BEAST_EXPECT(ammAlice.expectBalances(
1845 STAmount{USD, UINT64_C(9'090
'909090909092), -12},
1846 IOUAmount{10'000
'000, 0}));
1849 // Equal deposit 10%, withdraw all tokens in XRP
1850 testAMM([&](AMM& ammAlice, Env&) {
1851 ammAlice.deposit(carol, 1'000
'000);
1852 ammAlice.withdrawAll(carol, XRP(0));
1853 BEAST_EXPECT(ammAlice.expectBalances(
1854 XRPAmount(9'090
'909'091),
1856 IOUAmount{10'000
'000, 0}));
1859 // Withdraw with EPrice limit.
1860 testAMM([&](AMM& ammAlice, Env&) {
1861 ammAlice.deposit(carol, 1'000
'000);
1862 ammAlice.withdraw(carol, USD(100), std::nullopt, IOUAmount{520, 0});
1864 ammAlice.expectBalances(
1865 XRPAmount(11'000
'000'000),
1866 STAmount{
USD, UINT64_C(9
'372'781065088757), -12},
1867 IOUAmount{10
'153'846
'15384616, -8}) &&
1868 ammAlice.expectLPTokens(
1869 carol, IOUAmount{153'846
'15384616, -8}));
1870 ammAlice.withdrawAll(carol);
1871 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
1874 // Withdraw with EPrice limit. AssetOut is 0.
1875 testAMM([&](AMM& ammAlice, Env&) {
1876 ammAlice.deposit(carol, 1'000
'000);
1877 ammAlice.withdraw(carol, USD(0), std::nullopt, IOUAmount{520, 0});
1879 ammAlice.expectBalances(
1880 XRPAmount(11'000
'000'000),
1881 STAmount{
USD, UINT64_C(9
'372'781065088757), -12},
1882 IOUAmount{10
'153'846
'15384616, -8}) &&
1883 ammAlice.expectLPTokens(
1884 carol, IOUAmount{153'846
'15384616, -8}));
1887 // IOU to IOU + transfer fee
1890 fund(env, gw, {alice}, {USD(20'000),
BTC(0.5)}, Fund::All);
1895 BEAST_EXPECT(ammAlice.expectBalances(
1896 USD(20'000),
BTC(0.5), IOUAmount{100, 0}));
1900 // no transfer fee on deposit
1901 ammAlice.deposit(carol, 10);
1902 BEAST_EXPECT(ammAlice.expectBalances(
1903 USD(22'000),
BTC(0.55), IOUAmount{110, 0}));
1907 ammAlice.withdraw(
carol, 10);
1908 BEAST_EXPECT(ammAlice.expectBalances(
1909 USD(20
'000), BTC(0.5), IOUAmount{100, 0}));
1910 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
1911 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
1916 testAMM([&](AMM& ammAlice, Env&) {
1918 ammAlice.withdraw(
alice, IOUAmount{1, -3});
1919 BEAST_EXPECT(ammAlice.expectBalances(
1920 XRPAmount{9
'999'999
'999},
1921 STAmount{USD, UINT64_C(9'999
'999999), -6},
1922 IOUAmount{9'999
'999'999, -3}));
1924 testAMM([&](AMM& ammAlice, Env&) {
1926 ammAlice.withdraw(
alice, std::nullopt, XRPAmount{1});
1927 BEAST_EXPECT(ammAlice.expectBalances(
1928 XRPAmount{9
'999'999
'999},
1930 IOUAmount{9
'999'999
'9995, -4}));
1932 testAMM([&](AMM& ammAlice, Env&) {
1934 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
1935 BEAST_EXPECT(ammAlice.expectBalances(
1937 STAmount{USD, UINT64_C(9
'999'9999999999), -10},
1938 IOUAmount{9
'999'999
'99999995, -8}));
1941 // Withdraw close to entire pool
1943 testAMM([&](AMM& ammAlice, Env&) {
1944 ammAlice.withdraw(alice, IOUAmount{9'999
'999'999, -3});
1945 BEAST_EXPECT(ammAlice.expectBalances(
1946 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
1949 testAMM([&](AMM& ammAlice, Env&) {
1950 ammAlice.withdraw(
alice, IOUAmount{9
'999'999},
USD(0));
1951 BEAST_EXPECT(ammAlice.expectBalances(
1952 XRP(10
'000), STAmount{USD, 1, -10}, IOUAmount{1}));
1955 testAMM([&](AMM& ammAlice, Env&) {
1956 ammAlice.withdraw(alice, IOUAmount{9'999
'900}, XRP(0));
1957 BEAST_EXPECT(ammAlice.expectBalances(
1958 XRPAmount{1}, USD(10'000), IOUAmount{100}));
1961 testAMM([&](AMM& ammAlice, Env&) {
1963 alice, STAmount{
USD, UINT64_C(9
'999'99999999999), -11});
1964 BEAST_EXPECT(ammAlice.expectBalances(
1965 XRP(10000), STAmount{
USD, 1, -11}, IOUAmount{316227765, -9}));
1968 testAMM([&](AMM& ammAlice, Env&) {
1969 ammAlice.withdraw(
alice, XRPAmount{9
'999'999
'999});
1970 BEAST_EXPECT(ammAlice.expectBalances(
1971 XRPAmount{1}, USD(10'000), IOUAmount{100}));
1978 testcase(
"Invalid Fee Vote");
1979 using namespace jtx;
1989 ter(temINVALID_FLAG));
2010 ter(terNO_ACCOUNT));
2028 ter(tecAMM_INVALID_TOKENS));
2032 testAMM([&](AMM& ammAlice, Env& env) {
2033 ammAlice.withdrawAll(alice);
2047 testcase(
"Fee Vote");
2048 using namespace jtx;
2053 ammAlice.
vote({}, 1
'000);
2054 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
2059 auto vote = [&](
AMM& ammAlice,
2062 int fundUSD = 100
'000,
2063 std::uint32_t tokens = 10'000
'000,
2064 std::vector<Account>* accounts = nullptr) {
2065 Account a(std::to_string(i));
2066 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2067 ammAlice.deposit(a, tokens);
2068 ammAlice.vote(a, 50 * (i + 1));
2070 accounts->push_back(std::move(a));
2073 // Eight votes fill all voting slots, set fee 0.175%.
2074 testAMM([&](AMM& ammAlice, Env& env) {
2075 for (int i = 0; i < 7; ++i)
2076 vote(ammAlice, env, i, 10'000);
2083 for (
int i = 0; i < 7; ++i)
2084 vote(ammAlice, env, i);
2087 ammAlice.
vote(a, 450);
2093 testAMM([&](AMM& ammAlice, Env& env) {
2094 for (
int i = 0; i < 7; ++i)
2095 vote(ammAlice, env, i);
2096 BEAST_EXPECT(ammAlice.expectTradingFee(175));
2097 vote(ammAlice, env, 7, 100
'000, 20'000
'000);
2098 BEAST_EXPECT(ammAlice.expectTradingFee(244));
2101 // Eight votes fill all voting slots, set fee 0.219%.
2102 // New vote, new account, higher vote weight, set smaller fee 0.206%
2103 testAMM([&](AMM& ammAlice, Env& env) {
2104 for (int i = 7; i > 0; --i)
2105 vote(ammAlice, env, i);
2106 BEAST_EXPECT(ammAlice.expectTradingFee(219));
2107 vote(ammAlice, env, 0, 100'000, 20
'000'000);
2108 BEAST_EXPECT(ammAlice.expectTradingFee(206));
2114 testAMM([&](AMM& ammAlice, Env& env) {
2116 for (
int i = 0; i < 7; ++i)
2117 vote(ammAlice, env, i, 100
'000, 10'000
'000, &accounts);
2118 BEAST_EXPECT(ammAlice.expectTradingFee(175));
2119 for (int i = 0; i < 7; ++i)
2120 ammAlice.withdrawAll(accounts[i]);
2121 ammAlice.deposit(carol, 10'000
'000);
2122 ammAlice.vote(carol, 1'000);
2125 BEAST_EXPECT(ammAlice.expectTradingFee(500));
2131 testAMM([&](AMM& ammAlice, Env& env) {
2133 for (
int i = 0; i < 7; ++i)
2134 vote(ammAlice, env, i, 100
'000, 10'000
'000, &accounts);
2135 BEAST_EXPECT(ammAlice.expectTradingFee(175));
2136 for (int i = 0; i < 7; ++i)
2137 ammAlice.withdraw(accounts[i], 9'000
'000);
2138 ammAlice.deposit(carol, 1'000);
2140 ammAlice.vote(
carol, 1
'000);
2141 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
2142 for (std::uint16_t i = 0; i < info.size(); ++i)
2143 BEAST_EXPECT(info[i][jss::account] != carol.human());
2144 // But the slots are refreshed and the fee is changed
2145 BEAST_EXPECT(ammAlice.expectTradingFee(82));
2152 testcase("Invalid Bid");
2153 using namespace jtx;
2154 using namespace std::chrono;
2156 testAMM([&](AMM& ammAlice, Env& env) {
2166 ter(temINVALID_FLAG));
2168 ammAlice.deposit(carol, 1'000
'000);
2169 // Invalid Bid price <= 0
2170 for (auto bid : {0, -100})
2180 ter(temBAD_AMOUNT));
2189 ter(temBAD_AMOUNT));
2192 // Invlaid Min/Max combination
2201 ter(tecAMM_INVALID_TOKENS));
2214 ter(terNO_ACCOUNT));
2216 // Account is not LP
2217 Account const dan("dan");
2218 env.fund(XRP(1'000), dan);
2282 testAMM([&](AMM& ammAlice, Env& env) {
2283 ammAlice.withdrawAll(
alice);
2296 testAMM([&](AMM& ammAlice, Env& env) {
2298 Account bill(
"bill");
2299 Account scott(
"scott");
2300 Account james(
"james");
2301 env.fund(
XRP(1
'000), bob, ed, bill, scott, james);
2303 ammAlice.deposit(carol, 1'000
'000);
2308 {bob, ed, bill, scott, james},
2315 // Bid price exceeds LP owned tokens
2316 testAMM([&](AMM& ammAlice, Env& env) {
2317 fund(env, gw, {bob}, XRP(1'000), {
USD(100)}, Fund::Acct);
2318 ammAlice.deposit(
carol, 1
'000'000);
2319 ammAlice.deposit(
bob, 10);
2338 ammAlice.bid(
carol, 1
'000);
2339 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2357 auto const lpIssue = amm.lptIssue();
2358 env.trust(STAmount{lpIssue, 100}, alice);
2359 env.trust(STAmount{lpIssue, 50}, bob);
2360 env(pay(gw, alice, STAmount{lpIssue, 100}));
2361 env(pay(gw, bob, STAmount{lpIssue, 50}));
2362 amm.bid(alice, 100);
2363 // Alice doesn't have any more tokens, but
2381 using namespace jtx;
2408 XRP(11
'000), USD(11'000),
IOUAmount{10
'999'814
'5, -1}));
2411 // Start bid at bidMin 110.
2412 testAMM([&](AMM& ammAlice, Env& env) {
2413 ammAlice.deposit(carol, 1'000
'000);
2415 ammAlice.bid(carol, 110);
2416 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2418 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2435 ammAlice.
bid(
carol, std::nullopt, 600);
2439 // Bid Min/MaxSlotPrice fails because the computed price is not in
2449 ter(tecAMM_FAILED));
2450 // Bid Min/MaxSlotPrice succeeds - pay computed price
2451 ammAlice.bid(carol, 100, 600);
2453 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
2461 ammAlice.deposit(bob, 1'000
'000);
2462 BEAST_EXPECT(ammAlice.expectBalances(
2463 XRP(12'000),
USD(12
'000), IOUAmount{12'000
'000, 0}));
2465 // Initial state. Pay bidMin.
2466 ammAlice.bid(carol, 110);
2467 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2469 // 1st Interval after close, price for 0th interval.
2471 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
2473 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
2481 // 20th Interval (expired) after close, price for 10th interval.
2484 AUCTION_SLOT_TIME_INTERVALS * AUCTION_SLOT_INTERVAL_DURATION +
2486 BEAST_EXPECT(ammAlice.expectAuctionSlot(
2487 0, std::nullopt, IOUAmount{127'33875, -5}));
2495 XRP(12
'000), USD(12'000),
IOUAmount{11
'999'678
'91, -2}));
2498 // Pool's
fee 1%. Bid bidMin.
2503 [&](
AMM& ammAlice,
Env& env) {
2506 fund(env,
gw, {
bob, dan, ed}, {
USD(20
'000)}, Fund::Acct);
2507 ammAlice.deposit(bob, 1'000
'000);
2508 ammAlice.deposit(ed, 1'000
'000);
2509 ammAlice.deposit(carol, 500'000);
2510 ammAlice.
deposit(dan, 500
'000);
2511 auto ammTokens = ammAlice.getLPTokensBalance();
2512 ammAlice.bid(carol, 120, std::nullopt, {bob, ed});
2513 auto const slotPrice = IOUAmount{5'200};
2514 ammTokens -= slotPrice;
2517 XRP(13
'000), USD(13'000), ammTokens));
2519 for (
int i = 0; i < 10; ++i)
2531 STAmount(
USD, UINT64_C(29
'499'00572620545), -11));
2534 STAmount(
USD, UINT64_C(18
'999'00572616195), -11));
2537 STAmount(
USD, UINT64_C(18
'999'00572611841), -11));
2541 STAmount(USD, UINT64_C(13'002
'98282151419), -11),
2543 ammTokens = ammAlice.getLPTokensBalance();
2544 // Trade with the fee
2545 for (int i = 0; i < 10; ++i)
2547 auto const tokens = ammAlice.deposit(dan, USD(100));
2548 ammAlice.withdraw(dan, tokens, USD(0));
2550 // dan pays ~9.94USD, which is ~10 times more in fees than
2551 // carol, bob, ed. the discounted fee is 10 times less
2552 // than the trading fee.
2554 env.balance(dan, USD) ==
2555 STAmount(USD, UINT64_C(19'490
'056722744), -9));
2556 // USD pool gains more in dan's fees.
2559 STAmount{USD, UINT64_C(13'012
'92609877019), -11},
2561 // Discounted fee payment
2562 ammAlice.deposit(carol, USD(100));
2563 ammTokens = ammAlice.getLPTokensBalance();
2564 BEAST_EXPECT(ammAlice.expectBalances(
2574 STAmount{USD, UINT64_C(13'012
'92609877019), -11},
2576 // Payment with the trading fee
2577 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
2579 // alice pays ~1.011USD in fees, which is ~10 times more
2582 BEAST_EXPECT(ammAlice.expectBalances(
2583 XRPAmount{13
'000'000
'668},
2584 STAmount{USD, UINT64_C(13'114
'03663047264), -11},
2586 // Auction slot expired, no discounted fee
2587 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
2588 // clock is parent's based
2591 env.balance(carol, USD) ==
2592 STAmount(USD, UINT64_C(29
'399'00572620545), -11));
2593 ammTokens = ammAlice.getLPTokensBalance();
2594 for (int i = 0; i < 10; ++i)
2596 auto const tokens = ammAlice.deposit(carol, USD(100));
2597 ammAlice.withdraw(carol, tokens, USD(0));
2602 env.balance(carol, USD) ==
2603 STAmount(USD, UINT64_C(29
'389'06197177128), -11));
2604 BEAST_EXPECT(ammAlice.expectBalances(
2605 XRPAmount{13
'000'000
'668},
2606 STAmount{USD, UINT64_C(13'123
'98038490681), -11},
2608 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
2610 // carol pays ~1.008XRP in trading fee, which is
2611 // ~10 times more than the discounted fee.
2612 // 99.815876XRP is swapped in for 100USD
2613 BEAST_EXPECT(ammAlice.expectBalances(
2614 XRPAmount(13'100
'824'790),
2615 STAmount{USD, UINT64_C(13
'023'98038490681), -11},
2622 testAMM([&](AMM& ammAlice, Env&) {
2623 // Bid a tiny amount
2624 auto const tiny = Number{STAmount::cMinValue, STAmount::cMinOffset};
2625 ammAlice.bid(alice, IOUAmount{tiny});
2626 // Auction slot purchase price is equal to the tiny amount
2627 // since the minSlotPrice is 0 with no trading fee.
2628 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
2629 // The purchase price is too small to affect the total tokens
2630 BEAST_EXPECT(ammAlice.expectBalances(
2631 XRP(10'000), USD(10
'000), ammAlice.tokens()));
2632 // Bid the tiny amount
2634 alice, IOUAmount{STAmount::cMinValue, STAmount::cMinOffset});
2635 // Pay slightly higher price
2636 BEAST_EXPECT(ammAlice.expectAuctionSlot(
2637 0, 0, IOUAmount{tiny * Number{105, -2}}));
2638 // The purchase price is still too small to affect the total tokens
2639 BEAST_EXPECT(ammAlice.expectBalances(
2640 XRP(10'000), USD(10
'000), ammAlice.tokens()));
2643 // Reset auth account
2644 testAMM([&](AMM& ammAlice, Env& env) {
2645 ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {carol});
2646 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
2647 ammAlice.bid(alice, IOUAmount{100});
2648 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
2651 fund(env, {bob, dan}, XRP(1'000));
2652 ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {bob, dan});
2653 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
2659 fund(env, gw, {alice, bob},
XRP(2
'000), {USD(2'000)});
2660 AMM amm(env,
gw,
XRP(1
'000), USD(1'010),
false, 1
'000);
2661 auto const lpIssue = amm.lptIssue();
2662 env.trust(STAmount{lpIssue, 500}, alice);
2663 env.trust(STAmount{lpIssue, 50}, bob);
2664 env(pay(gw, alice, STAmount{lpIssue, 500}));
2665 env(pay(gw, bob, STAmount{lpIssue, 50}));
2666 // Alice doesn't have anymore lp tokens
2667 amm.bid(
alice, 500);
2668 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{500}));
2673 BEAST_EXPECT(amm.expectBalances(
2676 IOUAmount{1
'004'487
'562112089, -9}));
2677 // Bob pays the full fee ~0.1USD
2678 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
2679 BEAST_EXPECT(amm.expectBalances(
2680 XRPAmount{1'000
'010'011},
2681 STAmount{USD, UINT64_C(1
'010'10090898081), -11},
2687 testInvalidAMMPayment()
2689 testcase("Invalid AMM Payment");
2690 using namespace jtx;
2691 using namespace std::chrono;
2692 using namespace std::literals::chrono_literals;
2694 // Can't pay into AMM account.
2696 for (auto const& acct : {gw, alice})
2700 fund(env, gw, {alice, carol}, XRP(1
'000), {USD(100)});
2701 // XRP balance is below reserve
2702 AMM ammAlice(env, acct, XRP(10), USD(10));
2703 // Pay below reserve
2704 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
2705 ter(tecNO_PERMISSION));
2706 // Pay above reserve
2707 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
2708 ter(tecNO_PERMISSION));
2710 env(pay(carol, ammAlice.ammAccount(), USD(10)),
2711 ter(tecNO_PERMISSION));
2715 fund(env, gw, {alice, carol}, XRP(10'000
'000), {USD(10'000)});
2717 AMM ammAlice(env, acct,
XRP(1
'000'000),
USD(100));
2728 testAMM([&](AMM& ammAlice, Env& env) {
2729 env(escrow(
carol, ammAlice.ammAccount(), XRP(1)),
2731 finish_time(env.now() + 1s),
2732 cancel_time(env.now() + 2s),
2734 ter(tecNO_PERMISSION));
2737 // Can't pay into AMM with paychan.
2738 testAMM([&](AMM& ammAlice, Env& env) {
2740 auto const settleDelay = 100s;
2742 env.current()->info().parentCloseTime + 200s;
2745 ammAlice.ammAccount(),
2750 ter(tecNO_PERMISSION));
2753 // Pay amounts close to one side of the pool
2755 [&](AMM& ammAlice, Env& env) {
2756 // Can't consume whole pool
2759 sendmax(XRP(1
'000'000
'000)),
2760 ter(tecPATH_PARTIAL));
2761 env(pay(alice, carol, XRP(100)),
2763 sendmax(USD(1'000
'000'000)),
2770 sendmax(XRP(1'000
'000'000)),
2776 sendmax(XRP(1'000
'000'000)),
2780 sendmax(
USD(1
'000'000
'000)),
2781 ter(tecPATH_PARTIAL));
2782 // Sender doesn't have enough funds
2785 sendmax(XRP(1
'000'000
'000)),
2786 ter(tecPATH_PARTIAL));
2787 env(pay(alice, carol, STAmount{xrpIssue(), 99'990
'000}),
2789 sendmax(USD(1'000
'000'000)),
2795 testAMM([&](AMM& ammAlice, Env& env) {
2811 testAMM([&](AMM& ammAlice, Env& env) {
2814 STAmount{Issue{
gw[
"USD"].currency, ammAlice.ammAccount()}, 0},
2830 testAMM([&](AMM& ammAlice, Env& env) {
2845 testcase(
"Basic Payment");
2846 using namespace jtx;
2851 [&](
AMM& ammAlice,
Env& env) {
2854 env(pay(bob, carol, USD(100)),
2857 txflags(tfNoRippleDirect));
2859 BEAST_EXPECT(ammAlice.expectBalances(
2860 XRP(10'100),
USD(10
'000), ammAlice.tokens()));
2861 // Initial balance 30,000 + 100
2862 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
2865 env,
bob,
XRP(30
'000) - XRP(100) - txfee(env, 1)));
2867 {{XRP(10'000),
USD(10
'100)}});
2869 // Payment 100USD for 100XRP, use default path.
2871 [&](AMM& ammAlice, Env& env) {
2872 env.fund(jtx::XRP(30'000),
bob);
2877 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
2880 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
2881 BEAST_EXPECT(expectLedgerEntryRoot(
2882 env, bob, XRP(30'000) -
XRP(100) -
txfee(env, 1)));
2884 {{
XRP(10
'000), USD(10'100)}});
2889 [&](
AMM& ammAlice,
Env& env) {
2892 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
2894 BEAST_EXPECT(ammAlice.expectBalances(
2895 XRP(10'100),
USD(10
'000), ammAlice.tokens()));
2896 // Initial balance 30,000 + 100
2897 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
2900 env,
bob,
XRP(30
'000) - XRP(100) - txfee(env, 1)));
2902 {{XRP(10'000),
USD(10
'100)}});
2904 // Payment with limitQuality set.
2906 [&](AMM& ammAlice, Env& env) {
2907 env.fund(jtx::XRP(30'000),
bob);
2918 XRP(10
'010), USD(10'000), ammAlice.
tokens()));
2921 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
2923 BEAST_EXPECT(expectLedgerEntryRoot(
2924 env, bob, XRP(30'000) -
XRP(10) -
txfee(env, 1)));
2936 {{
XRP(10
'000), USD(10'010)}});
2940 [&](
AMM& ammAlice,
Env& env) {
2945 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
2946 // would have been sent has it not been for limitQuality and
2947 // the transfer fee.
2948 env(pay(bob, carol, USD(100)),
2952 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2954 BEAST_EXPECT(ammAlice.expectBalances(
2955 XRP(10'010),
USD(10
'000), ammAlice.tokens()));
2956 // 10USD - 10% transfer fee
2957 BEAST_EXPECT(expectLine(
2960 STAmount{USD, UINT64_C(30'009
'09090909091), -11}));
2961 BEAST_EXPECT(expectLedgerEntryRoot(
2962 env, bob, XRP(30'000) -
XRP(10) -
txfee(env, 1)));
2964 {{
XRP(10
'000), USD(10'010)}});
2968 [&](
AMM& ammAlice,
Env& env) {
2971 env(pay(bob, carol, USD(100)),
2974 txflags(tfNoRippleDirect),
2975 ter(tecPATH_PARTIAL));
2977 {{XRP(10'000),
USD(10
'000)}});
2979 // Non-default path (with AMM) has a better quality than default path.
2980 // The max possible liquidity is taken out of non-default
2981 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
2982 // is taken from the offer.
2986 env, gw, {alice, carol}, {USD(30'000),
EUR(30
'000)}, Fund::All);
2988 env.fund(XRP(1'000),
bob);
2990 auto ammEUR_XRP =
AMM(env,
alice,
XRP(10
'000), EUR(10'000));
2991 auto ammUSD_EUR =
AMM(env,
alice,
EUR(10
'000), USD(10'000));
2999 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3001 STAmount(EUR, UINT64_C(9'970
'007498125468), -12),
3002 ammEUR_XRP.tokens()));
3003 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3004 STAmount(USD, UINT64_C(9'970
'097277662122), -12),
3005 STAmount(EUR, UINT64_C(10'029
'99250187452), -11),
3006 ammUSD_EUR.tokens()));
3007 BEAST_EXPECT(expectOffers(
3012 XRPAmount(30'201
'749),
3013 STAmount(USD, UINT64_C(29'90272233787818), -14)}}}));
3016 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
3017 BEAST_EXPECT(expectLedgerEntryRoot(
3020 XRP(1'000) -
XRPAmount{30
'082'730} - XRPAmount{70
'798'251} -
3027 testAMM([&](AMM& ammAlice, Env& env) {
3028 env.fund(
XRP(1
'000), bob);
3030 env.trust(EUR(2'000),
alice);
3033 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
3035 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
3037 env(pay(bob, carol, USD(100)),
3040 txflags(tfPartialPayment));
3042 BEAST_EXPECT(ammAlice.expectBalances(
3043 XRPAmount(10'050
'238'637),
3044 STAmount(
USD, UINT64_C(9
'950'01249687578), -11),
3045 ammAlice.tokens()));
3051 XRPAmount(50
'487'378),
3052 STAmount(
EUR, UINT64_C(49
'98750312422), -11)},
3054 STAmount(EUR, UINT64_C(49'98750312422), -11),
3055 STAmount(
USD, UINT64_C(49
'98750312422), -11)}}}));
3056 // Initial 30,000 + 99.99999999999
3057 BEAST_EXPECT(expectLine(
3058 env, carol, STAmount{USD, UINT64_C(30'099
'99999999999), -11}));
3059 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
3061 BEAST_EXPECT(expectLedgerEntryRoot(
3064 XRP(1'000) - XRPAmount{50
'238'637} - XRPAmount{50
'512'622} -
3071 [&](AMM& ammAlice, Env& env) {
3080 BEAST_EXPECT(ammAlice.expectBalances(
3081 XRP(10
'100), USD(10'000), ammAlice.tokens()));
3084 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
3085 // - 100(offer) - 10(tx fee) - one reserve
3086 BEAST_EXPECT(expectLedgerEntryRoot(
3089 XRP(30'000) -
XRP(10
'000) - XRP(100) - XRP(100) -
3090 ammCrtFee(env) - txfee(env, 1)));
3091 BEAST_EXPECT(expectOffers(env, bob, 0));
3093 {{XRP(10'000),
USD(10
'100)}});
3095 // Default path with AMM and Order Book offer.
3096 // Order Book offer is consumed first.
3097 // Remaining amount is consumed by AMM.
3100 fund(env, gw, {alice, bob, carol}, XRP(20'000), {
USD(2
'000)});
3101 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
3102 AMM ammAlice(env, alice, XRP(1'000),
USD(1
'050));
3103 env(pay(alice, carol, USD(200)),
3105 txflags(tfPartialPayment));
3106 BEAST_EXPECT(ammAlice.expectBalances(
3107 XRP(1'050),
USD(1
'000), ammAlice.tokens()));
3108 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
3114 [&](AMM& ammAlice, Env& env) {
3117 env(offer(bob, USD(100), XRP(100)));
3119 BEAST_EXPECT(ammAlice.expectBalances(
3120 XRP(10'100),
USD(10
'000), ammAlice.tokens()));
3121 // Initial 1,000 + 100
3122 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
3125 env,
bob,
XRP(30
'000) - XRP(100) - txfee(env, 1)));
3126 BEAST_EXPECT(expectOffers(env, bob, 0));
3128 {{XRP(10'000),
USD(10
'100)}});
3130 // Offer crossing IOU/IOU and transfer rate
3132 [&](AMM& ammAlice, Env& env) {
3133 env(rate(gw, 1.25));
3135 env(offer(carol, EUR(100), GBP(100)));
3138 BEAST_EXPECT(ammAlice.expectBalances(
3139 GBP(1'100),
EUR(1
'000), ammAlice.tokens()));
3140 // Initial 30,000 - 100(offer) - 25% transfer fee
3141 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
3144 BEAST_EXPECT(expectOffers(env, bob, 0));
3146 {{GBP(1'000),
EUR(1
'100)}});
3148 // Payment and transfer fee
3150 // Bob sends 125GBP to pay 80EUR to Carol
3151 // Payment execution:
3152 // bob's 125
GBP/1.25 = 100
GBP
3156 [&](AMM& ammAlice, Env& env) {
3165 BEAST_EXPECT(ammAlice.expectBalances(
3166 GBP(1
'100), EUR(1'000), ammAlice.tokens()));
3170 {{GBP(1'000),
EUR(1
'100)}});
3172 // Payment and transfer fee, multiple steps
3174 // Dan's offer 200CAN/200
GBP
3184 [&](AMM& ammAlice, Env& env) {
3185 Account
const dan(
"dan");
3186 Account
const ed(
"ed");
3187 auto const CAN =
gw[
"CAN"];
3188 fund(env,
gw, {dan}, {CAN(200),
GBP(200)}, Fund::Acct);
3190 fund(env,
gw, {
bob}, {CAN(195.3125)}, Fund::Acct);
3194 env(
offer(dan, CAN(200),
GBP(200)));
3199 sendmax(CAN(195.3125)),
3203 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25),
GBP(43.75)));
3204 BEAST_EXPECT(ammAlice.expectBalances(
3205 GBP(10
'125), EUR(10'000), ammAlice.tokens()));
3209 {{
GBP(10
'000), EUR(10'125)}});
3213 [&](AMM& ammAlice, Env& env) {
3240 auto const ETH =
gw[
"ETH"];
3246 {EUR(50'000),
BTC(50
'000), ETH(50'000),
USD(50
'000)});
3247 fund(env, gw, {carol, bob}, XRP(1'000), {
USD(200)}, Fund::Acct);
3248 AMM xrp_eur(env,
alice,
XRP(10
'100), EUR(10'000));
3249 AMM eur_btc(env,
alice,
EUR(10
'000), BTC(10'200));
3250 AMM btc_usd(env,
alice,
BTC(10
'100), USD(10'000));
3251 AMM xrp_usd(env,
alice,
XRP(10
'150), USD(10'200));
3252 AMM xrp_eth(env,
alice,
XRP(10
'000), ETH(10'100));
3253 AMM eth_eur(env,
alice, ETH(10
'900), EUR(11'000));
3254 AMM eur_usd(env,
alice,
EUR(10
'100), USD(10'000));
3262 BEAST_EXPECT(xrp_eth.expectBalances(
3263 XRPAmount(10
'026'208
'900),
3264 STAmount{ETH, UINT64_C(10'073
'65779244494), -11},
3266 BEAST_EXPECT(eth_eur.expectBalances(
3267 STAmount{ETH, UINT64_C(10'926
'34220755506), -11},
3268 STAmount{EUR, UINT64_C(10'973
'54232078752), -11},
3270 BEAST_EXPECT(eur_usd.expectBalances(
3271 STAmount{EUR, UINT64_C(10'126
'45767921248), -11},
3272 STAmount{USD, UINT64_C(9'973
'93151712086), -11},
3276 // This path provides ~73.9USD/74.1XRP
3277 BEAST_EXPECT(xrp_usd.expectBalances(
3278 XRPAmount(10'224
'106'246),
3279 STAmount{
USD, UINT64_C(10
'126'06848287914), -11},
3288 BEAST_EXPECT(xrp_eur.expectBalances(
3289 XRP(10
'100), EUR(10'000), xrp_eur.tokens()));
3290 BEAST_EXPECT(eur_btc.expectBalances(
3291 EUR(10
'000), BTC(10'200), eur_btc.tokens()));
3292 BEAST_EXPECT(btc_usd.expectBalances(
3293 BTC(10
'100), USD(10'000), btc_usd.tokens()));
3301 auto const ETH =
gw[
"ETH"];
3307 {EUR(50'000),
BTC(50
'000), ETH(50'000),
USD(50
'000)});
3308 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
3309 AMM xrp_eur(env, alice, XRP(10'100),
EUR(10
'000));
3310 AMM eur_btc(env, alice, EUR(10'000),
BTC(10
'200));
3311 AMM btc_usd(env, alice, BTC(10'100),
USD(10
'000));
3312 AMM xrp_eth(env, alice, XRP(10'000), ETH(10
'100));
3313 AMM eth_eur(env, alice, ETH(10'900),
EUR(11
'000));
3314 env(pay(bob, carol, USD(100)),
3315 path(~EUR, ~BTC, ~USD),
3316 path(~ETH, ~EUR, ~BTC, ~USD),
3318 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
3319 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
3320 BEAST_EXPECT(xrp_eur.expectBalances(
3321 XRPAmount(10'118
'738'472),
3322 STAmount{
EUR, UINT64_C(9
'981'544436337968), -12},
3324 BEAST_EXPECT(eur_btc.expectBalances(
3325 STAmount{EUR, UINT64_C(10
'101'16096785173), -11},
3326 STAmount{BTC, UINT64_C(10
'097'91426968066), -11},
3328 BEAST_EXPECT(btc_usd.expectBalances(
3329 STAmount{BTC, UINT64_C(10
'202'08573031934), -11},
3332 BEAST_EXPECT(xrp_eth.expectBalances(
3333 XRPAmount(10'082
'446'397),
3334 STAmount{ETH, UINT64_C(10
'017'41072778012), -11},
3336 BEAST_EXPECT(eth_eur.expectBalances(
3337 STAmount{ETH, UINT64_C(10
'982'58927221988), -11},
3338 STAmount{EUR, UINT64_C(10
'917'2945958103), -10},
3345 testAMM([&](AMM& ammAlice, Env& env) {
3346 env.fund(
XRP(1
'000), bob);
3347 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
3348 env(trust(alice, EUR(200)));
3349 for (int i = 0; i < 30; ++i)
3350 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
3351 // This is worse quality offer than 30 offers above.
3352 // It will not be consumed because of AMM offers limit.
3353 env(offer(alice, EUR(140), XRP(100)));
3354 env(pay(bob, carol, USD(100)),
3357 txflags(tfPartialPayment | tfNoRippleDirect));
3358 // Carol gets ~29.91USD because of the AMM offers limit
3359 BEAST_EXPECT(ammAlice.expectBalances(
3361 STAmount{
USD, UINT64_C(9
'970'089730807577), -12},
3362 ammAlice.tokens()));
3364 env,
carol, STAmount{
USD, UINT64_C(30
'029'91026919241), -11}));
3368 testAMM([&](AMM& ammAlice, Env& env) {
3369 env.fund(
XRP(1
'000), bob);
3370 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
3371 env(trust(alice, EUR(200)));
3372 for (int i = 0; i < 29; ++i)
3373 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
3374 // This is worse quality offer than 30 offers above.
3375 // It will not be consumed because of AMM offers limit.
3376 env(offer(alice, EUR(140), XRP(100)));
3377 env(pay(bob, carol, USD(100)),
3380 txflags(tfPartialPayment | tfNoRippleDirect));
3381 BEAST_EXPECT(ammAlice.expectBalances(
3382 XRPAmount{10'101
'010'102},
USD(9
'900), ammAlice.tokens()));
3383 // Carol gets ~100USD
3384 BEAST_EXPECT(expectLine(
3385 env, carol, STAmount{USD, UINT64_C(30'099
'99999999999), -11}));
3386 BEAST_EXPECT(expectOffers(
3390 {{{STAmount{EUR, 39'1858572, -7}, XRPAmount{27
'989'898}}}}));
3399 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
3401 BEAST_EXPECT(ammAlice.expectBalances(
3402 XRPAmount{10
'049'825
'373},
3403 STAmount{USD, UINT64_C(10'049
'92586949302), -11},
3404 ammAlice.tokens()));
3405 BEAST_EXPECT(expectOffers(
3409 {{{XRPAmount{50'074
'629},
3410 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
3414 // Individually frozen account
3415 testAMM([&](AMM& ammAlice, Env& env) {
3416 env(trust(gw, carol["USD"](0), tfSetFreeze));
3417 env(trust(gw, alice["USD"](0), tfSetFreeze));
3419 env(pay(alice, carol, USD(1)),
3422 txflags(tfNoRippleDirect | tfPartialPayment),
3430 testcase("AMM Tokens");
3431 using namespace jtx;
3433 // Offer crossing with AMM LPTokens and XRP.
3434 testAMM([&](AMM& ammAlice, Env& env) {
3435 auto const token1 = ammAlice.lptIssue();
3436 auto priceXRP = withdrawByTokens(
3437 STAmount{XRPAmount{10'000
'000'000}},
3439 STAmount{token1, 5
'000'000},
3442 env(
offer(
carol, STAmount{token1, 5
'000'000}, priceXRP));
3444 env(
offer(
alice, priceXRP, STAmount{token1, 5
'000'000}));
3446 BEAST_EXPECT(ammAlice.expectBalances(
3447 XRP(10
'000), USD(10'000), IOUAmount{10
'000'000}));
3449 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{5
'000'000}));
3450 BEAST_EXPECT(ammAlice.expectLPTokens(
alice, IOUAmount{5
'000'000}));
3452 ammAlice.vote(
carol, 1
'000);
3453 BEAST_EXPECT(ammAlice.expectTradingFee(500));
3454 ammAlice.vote(carol, 0);
3455 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3457 ammAlice.bid(carol, 100);
3458 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999
'900}));
3459 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
3460 BEAST_EXPECT(accountBalance(env, carol) == "22499999960");
3461 priceXRP = withdrawByTokens(
3462 STAmount{XRPAmount{10'000
'000'000}},
3463 STAmount{token1, 9
'999'900},
3464 STAmount{token1, 4
'999'900},
3467 ammAlice.withdrawAll(
carol,
XRP(0));
3469 BEAST_EXPECT(ammAlice.expectBalances(
3470 XRPAmount{10
'000'000
'000} - priceXRP,
3472 IOUAmount{5
'000'000}));
3473 BEAST_EXPECT(ammAlice.expectLPTokens(
alice, IOUAmount{5
'000'000}));
3474 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{0}));
3478 testAMM([&](AMM& ammAlice, Env& env) {
3479 ammAlice.deposit(
carol, 1
'000'000);
3481 AMM ammAlice1(env, alice, XRP(10'000),
EUR(10
'000));
3482 ammAlice1.deposit(carol, 1'000
'000);
3483 auto const token1 = ammAlice.lptIssue();
3484 auto const token2 = ammAlice1.lptIssue();
3485 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
3486 txflags(tfPassive));
3488 BEAST_EXPECT(expectOffers(env, alice, 1));
3489 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
3492 expectLine(env, alice, STAmount{token1, 10'000
'100}) &&
3493 expectLine(env, alice, STAmount{token2, 9'999
'900}));
3495 expectLine(env, carol, STAmount{token2, 1'000
'100}) &&
3496 expectLine(env, carol, STAmount{token1, 999'900}));
3504 testAMM([&](AMM& ammAlice, Env& env) {
3505 auto const token1 = ammAlice.lptIssue();
3506 env.trust(STAmount{token1, 2
'000'000},
carol);
3508 ammAlice.deposit(
carol, 1
'000'000);
3510 ammAlice.expectLPTokens(
alice, IOUAmount{10
'000'000, 0}) &&
3511 ammAlice.expectLPTokens(
carol, IOUAmount{1
'000'000, 0}));
3518 ammAlice.expectLPTokens(
alice, IOUAmount{9
'999'900, 0}) &&
3520 ammAlice.expectLPTokens(
carol, IOUAmount{1
'000'100, 0}));
3522 env.trust(STAmount{token1, 20
'000'000},
alice);
3528 ammAlice.expectLPTokens(
alice, IOUAmount{10
'000'000, 0}) &&
3529 ammAlice.expectLPTokens(
carol, IOUAmount{1
'000'000, 0}));
3536 testcase(
"Amendment");
3537 using namespace jtx;
3544 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
3546 Env env{*
this, feature};
3548 AMM amm(env, alice, XRP(1'000),
USD(1
'000), ter(temDISABLED));
3556 using namespace jtx;
3558 testAMM([&](AMM& ammAlice, Env& env) {
3559 auto const info = env.rpc(
3563 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
3566 info[jss::result][jss::account_data][jss::Flags].asUInt();
3569 (lsfAMM | lsfDisableMaster | lsfDefaultRipple |
3577 testcase("Rippling");
3578 using namespace jtx;
3580 // Rippling via AMM fails because AMM trust line has 0 limit.
3581 // Set up two issuers, A and B. Have each issue a token called TST.
3582 // Have another account C hold TST from both issuers,
3583 // and create an AMM for this pair.
3584 // Have a fourth account, D, create a trust line to the AMM for TST.
3585 // Send a payment delivering TST.AMM from C to D, using SendMax in
3586 // TST.A (or B) and a path through the AMM account. By normal
3587 // rippling rules, this would have caused the AMM's balances
3594 auto const TSTA = A[
"TST"];
3595 auto const TSTB = B[
"TST"];
3600 env.fund(XRP(10'000), B);
3602 env.fund(XRP(10'000), D);
3604 env.
trust(TSTA(10
'000), C);
3605 env.trust(TSTB(10'000), C);
3606 env(pay(A, C, TSTA(10
'000)));
3607 env(pay(B, C, TSTB(10'000)));
3608 AMM amm(env, C, TSTA(5
'000), TSTB(5'000));
3609 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
3614 env(pay(C, D, STAmount{ammIss, 10}),
3616 path(amm.ammAccount()),
3617 txflags(tfPartialPayment | tfNoRippleDirect),
3625 testcase("AMMAndCLOB, offer quality change");
3626 using namespace jtx;
3627 auto const gw = Account("gw");
3628 auto const TST = gw["TST"];
3629 auto const LP1 = Account("LP1");
3630 auto const LP2 = Account("LP2");
3632 auto prep = [&](auto const& offerCb, auto const& expectCb) {
3634 env.fund(XRP(30'000
'000'000),
gw);
3635 env(offer(
gw,
XRP(11
'500'000
'000), TST(1'000
'000'000)));
3638 env.fund(XRP(10'000), LP2);
3639 env(offer(LP1, TST(25),
XRPAmount(287
'500'000)));
3644 env(offer(LP2, TST(25),
XRPAmount(287
'500'000)));
3657 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
3660 getAccountLines(env, LP2, TST)[
"lines"][0u][
"balance"]
3662 auto const offer = getAccountOffers(env, LP2)[
"offers"][0u];
3663 lp2TakerGets = offer[
"taker_gets"].asString();
3664 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
3671 XRPAmount{18
'095'133},
3672 STAmount{TST, UINT64_C(1
'68737984885388), -14}),
3673 txflags(tfPassive));
3678 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
3680 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
3681 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
3683 lp2TakerPays == offer["taker_pays"]["value"].asString());
3690 testcase("Trading Fee");
3691 using namespace jtx;
3693 // Single Deposit, 1% fee
3695 [&](AMM& ammAlice, Env& env) {
3697 ammAlice.deposit(carol, USD(3'000));
3699 ammAlice.withdrawAll(carol, USD(3'000));
3700 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
3703 ammAlice.vote(alice, 1'000);
3704 BEAST_EXPECT(ammAlice.expectTradingFee(1
'000));
3705 // Carol gets fewer LPToken ~994, because of the single deposit
3707 ammAlice.deposit(carol, USD(3'000));
3708 BEAST_EXPECT(ammAlice.expectLPTokens(
3710 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
3712 ammAlice.vote(alice, 0);
3713 ammAlice.withdrawAll(carol, USD(0));
3715 BEAST_EXPECT(expectLine(
3718 STAmount{USD, UINT64_C(29
'994'96220068281), -11}));
3720 {{
USD(1
'000), EUR(1'000)}});
3725 [&](AMM& ammAlice, Env& env) {
3726 auto const balance = env.balance(
carol,
USD);
3727 auto tokensFee = ammAlice.deposit(
3728 carol,
USD(1
'000), std::nullopt, STAmount{USD, 1, -1});
3729 auto const deposit = balance - env.balance(carol, USD);
3730 ammAlice.withdrawAll(carol, USD(0));
3731 ammAlice.vote(alice, 0);
3732 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3733 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
3734 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
3736 BEAST_EXPECT(tokensFee == IOUAmount(485'636
'0611129, -7));
3737 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644
'85901109, -8));
3745 [&](AMM& ammAlice, Env& env) {
3746 auto const balance = env.balance(
carol,
USD);
3747 auto const tokensFee = ammAlice.deposit(
3748 carol,
USD(200), std::nullopt, STAmount{
USD, 2020, -6});
3749 auto const deposit = balance - env.balance(
carol,
USD);
3750 ammAlice.withdrawAll(
carol,
USD(0));
3751 ammAlice.vote(
alice, 0);
3752 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3753 auto const tokensNoFee = ammAlice.deposit(
carol, deposit);
3756 BEAST_EXPECT(tokensFee == IOUAmount(98
'000'00000002, -8));
3757 BEAST_EXPECT(tokensNoFee == IOUAmount(98
'475'81871545, -8));
3762 // Single Withdrawal, 1% fee
3764 [&](AMM& ammAlice, Env& env) {
3766 ammAlice.deposit(carol, USD(3'000));
3768 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{1
'000}));
3769 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
3771 ammAlice.vote(alice, 1
'000);
3772 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3774 ammAlice.withdrawAll(carol, USD(0));
3775 BEAST_EXPECT(expectLine(
3778 STAmount{USD, UINT64_C(29
'994'97487437186), -11}));
3780 {{
USD(1
'000), EUR(1'000)}});
3784 [&](AMM& ammAlice, Env& env) {
3785 ammAlice.deposit(
carol, 1
'000'000);
3786 auto const tokensFee = ammAlice.withdraw(
3787 carol,
USD(100), std::nullopt, IOUAmount{520, 0});
3789 auto const balanceAfterWithdraw =
3790 STAmount(
USD, UINT64_C(30
'443'43891402715), -11);
3791 BEAST_EXPECT(env.balance(
carol,
USD) == balanceAfterWithdraw);
3793 auto const deposit = balanceAfterWithdraw -
USD(29
'000);
3794 ammAlice.deposit(carol, deposit);
3796 ammAlice.vote(alice, 0);
3797 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3798 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
3800 env.balance(carol, USD) ==
3801 STAmount(USD, UINT64_C(30'443
'43891402717), -11));
3802 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
3804 BEAST_EXPECT(tokensNoFee == IOUAmount(746'579
'80779913, -8));
3805 BEAST_EXPECT(tokensFee == IOUAmount(750'588
'23529411, -8));
3812 [&](AMM& ammAlice, Env& env) {
3818 {USD(1'000),
EUR(1
'000)},
3820 // Alice contributed 1010EUR and 1000USD to the pool
3821 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
3823 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
3832 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
3836 ammAlice.vote(alice, 1'000);
3837 BEAST_EXPECT(ammAlice.expectTradingFee(1
'000));
3838 // Bob pays to Carol with 1% fee
3839 env(pay(bob, carol, USD(10)),
3842 txflags(tfNoRippleDirect));
3844 // Bob sends 10.1~EUR to pay 10USD
3845 BEAST_EXPECT(expectLine(
3846 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
3849 BEAST_EXPECT(ammAlice.expectBalances(
3851 STAmount{
EUR, UINT64_C(1
'010'10101010101), -11},
3852 ammAlice.tokens()));
3854 {{
USD(1
'000), EUR(1'010)}});
3858 [&](AMM& ammAlice, Env& env) {
3863 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
3868 ammAlice.vote(
alice, 500);
3869 BEAST_EXPECT(ammAlice.expectTradingFee(500));
3877 STAmount{
USD, UINT64_C(29
'995'02512562814), -11}));
3881 STAmount{
EUR, UINT64_C(30
'004'97487437186), -11}));
3887 STAmount{
EUR, UINT64_C(5
'025125628140703), -15},
3888 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
3889 BEAST_EXPECT(ammAlice.expectBalances(
3890 STAmount{USD, UINT64_C(1
'004'974874371859), -12},
3891 STAmount{EUR, UINT64_C(1
'005'025125628141), -12},
3892 ammAlice.tokens()));
3894 {{
USD(1
'000), EUR(1'010)}});
3902 Account
const ed(
"ed");
3908 {USD(2'000),
EUR(2
'000)});
3909 env(offer(carol, EUR(5), USD(5)));
3910 AMM ammAlice(env, alice, USD(1'005),
EUR(1
'000));
3911 env(pay(bob, ed, USD(10)),
3914 txflags(tfNoRippleDirect));
3915 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
3917 BEAST_EXPECT(ammAlice.expectBalances(
3918 USD(1'000),
EUR(1
'005), ammAlice.tokens()));
3919 BEAST_EXPECT(expectOffers(env, carol, 0));
3922 // Payment with AMM and CLOB offer. Same as above but with 0.25% fee.
3925 Account const ed("ed");
3929 {alice, bob, carol, ed},
3931 {
USD(2
'000), EUR(2'000)});
3934 AMM ammAlice(env,
alice,
USD(1
'005), EUR(1'000),
false, 250);
3940 BEAST_EXPECT(expectLine(
3941 env, bob, STAmount{EUR, UINT64_C(1'989
'987453007618), -12}));
3942 BEAST_EXPECT(ammAlice.expectBalances(
3944 STAmount{
EUR, UINT64_C(1
'005'012546992382), -12},
3945 ammAlice.tokens()));
3954 Account
const ed(
"ed");
3960 {USD(2'000),
EUR(2
'000)});
3961 env(offer(carol, EUR(10), USD(10)));
3963 AMM ammAlice(env, alice, USD(1'005),
EUR(1
'000), false, 1'000);
3969 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
3970 BEAST_EXPECT(ammAlice.expectBalances(
3971 USD(1
'005), EUR(1'000), ammAlice.tokens()));
3981 Account
const ed(
"ed");
3987 {USD(2'000),
EUR(2
'000)});
3988 env(offer(carol, EUR(9), USD(9)));
3990 AMM ammAlice(env, alice, USD(1'005),
EUR(1
'000), false, 1'000);
3996 BEAST_EXPECT(expectLine(
3997 env, bob, STAmount{EUR, UINT64_C(1'989
'993923296712), -12}));
3998 BEAST_EXPECT(ammAlice.expectBalances(
4000 STAmount{
EUR, UINT64_C(1
'001'006076703288), -12},
4001 ammAlice.tokens()));
4009 testcase(
"Adjusted Deposit/Withdraw Tokens");
4011 using namespace jtx;
4022 Account const nataly(
"nataly");
4026 {
bob, ed, paul, dan, chris, simon, ben, nataly},
4029 for (
int i = 0; i < 10; ++i)
4042 ammAlice.withdrawAll(carol, USD(0));
4043 ammAlice.deposit(ed, USD(10'000));
4046 ammAlice.withdrawAll(paul, USD(0));
4047 ammAlice.deposit(nataly, USD(1'000
'000));
4048 ammAlice.withdrawAll(nataly, USD(0));
4050 // Due to round off some accounts have a tiny gain, while
4051 // other have a tiny loss. The last account to withdraw
4052 // gets everything in the pool.
4053 BEAST_EXPECT(ammAlice.expectBalances(
4066 env, nataly,
STAmount{
USD, UINT64_C(1
'500'000
'000000002), -9}));
4067 ammAlice.withdrawAll(alice);
4068 BEAST_EXPECT(!ammAlice.ammExists());
4069 BEAST_EXPECT(expectLine(
4070 env, alice, STAmount{USD, UINT64_C(30'000
'0000000013), -10}));
4071 // alice XRP balance is 30,000initial - 50 ammcreate fee - 10drops
4073 BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
4076 // Same as above but deposit/withdraw in XRP
4077 testAMM([&](AMM& ammAlice, Env& env) {
4078 Account const bob("bob");
4079 Account const ed("ed");
4080 Account const paul("paul");
4081 Account const dan("dan");
4082 Account const chris("chris");
4083 Account const simon("simon");
4084 Account const ben("ben");
4085 Account const nataly("nataly");
4089 {bob, ed, paul, dan, chris, simon, ben, nataly},
4093 for (int i = 0; i < 10; ++i)
4095 ammAlice.deposit(ben, XRPAmount{1});
4096 ammAlice.withdrawAll(ben, XRP(0));
4097 ammAlice.deposit(simon, XRPAmount(1'000));
4106 ammAlice.withdrawAll(carol, XRP(0));
4107 ammAlice.deposit(ed, XRP(10'000));
4110 ammAlice.withdrawAll(paul, XRP(0));
4111 ammAlice.deposit(nataly, XRP(1'000
'000));
4112 ammAlice.withdrawAll(nataly, XRP(0));
4114 // No round off with XRP in this test
4115 BEAST_EXPECT(ammAlice.expectBalances(
4116 XRP(10'000),
USD(10
'000), IOUAmount{10'000
'000}));
4117 ammAlice.withdrawAll(alice);
4118 BEAST_EXPECT(!ammAlice.ammExists());
4119 // 20,000 initial - (deposit+withdraw) * 10
4120 auto const xrpBalance = (XRP(2'000
'000) - txfee(env, 20)).getText();
4121 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
4122 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
4123 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
4124 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
4125 // 30,000 initial - (deposit+withdraw) * 10
4126 BEAST_EXPECT(accountBalance(env, carol) == "29999999800");
4127 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
4128 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
4129 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
4130 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
4131 BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
4138 testInvalidInstance();
4139 testInstanceCreate();
4140 testInvalidDeposit();
4142 testInvalidWithdraw();
4144 testInvalidFeeVote();
4148 testInvalidAMMPayment();
4149 testBasicPaymentEngine();
4156 testAdjustedTokens();
4166 BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
4169 } // namespace ripple