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/amount.h>
31 #include <test/jtx/sendmax.h>
46 testcase(
"Instance Create");
60 USD(20
'000), BTC(0.5), IOUAmount{100, 0}));
62 {{USD(20'000),
BTC(0.5)}});
67 fund(env,
gw, {
alice}, {
USD(20
'000), BTC(0.5)}, Fund::All);
70 // no transfer fee on create
71 AMM ammAlice(env, alice, USD(20'000),
BTC(0.5));
72 BEAST_EXPECT(ammAlice.expectBalances(
73 USD(20
'000), BTC(0.5), IOUAmount{100, 0}));
74 BEAST_EXPECT(expectLine(env, alice, USD(0)));
75 BEAST_EXPECT(expectLine(env, alice, BTC(0)));
78 // Require authorization is set, account is authorized
81 env.fund(XRP(30'000),
gw,
alice);
85 env.trust(
USD(30
'000), alice);
87 env(trust(gw, alice["USD"](30'000)), txflags(
tfSetfAuth));
91 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000));
94 // Cleared global freeze
97 env.fund(XRP(30'000),
gw,
alice);
99 env.trust(
USD(30
'000), alice);
101 env(pay(gw, alice, USD(10'000)));
109 AMM ammAlice(env,
alice, XRP(10
'000), USD(10'000));
114 [&](AMM& amm, Env&) {
115 BEAST_EXPECT(
amm.expectTradingFee(1
'000));
116 BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0}));
125 testcase(
"Invalid Instance");
134 env, alice, XRP(10'000),
XRP(10
'000), ter(temBAD_AMM_TOKENS));
135 BEAST_EXPECT(!ammAlice.ammExists());
138 // Can't have both tokens the
same IOU
143 env, alice, USD(10'000),
USD(10
'000), ter(temBAD_AMM_TOKENS));
144 BEAST_EXPECT(!ammAlice.ammExists());
147 // Can't have zero or negative amounts
152 BEAST_EXPECT(!ammAlice.ammExists());
153 AMM ammAlice1(env,
alice,
XRP(10
'000), USD(0), ter(temBAD_AMOUNT));
154 BEAST_EXPECT(!ammAlice1.ammExists());
156 env, alice, XRP(10'000),
USD(-10
'000), ter(temBAD_AMOUNT));
157 BEAST_EXPECT(!ammAlice2.ammExists());
159 env, alice, XRP(-10'000),
USD(10
'000), ter(temBAD_AMOUNT));
160 BEAST_EXPECT(!ammAlice3.ammExists());
166 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
169 BEAST_EXPECT(!ammAlice.ammExists());
177 env, alice, XRP(10'000),
USD(40
'000), ter(tecUNFUNDED_AMM));
178 BEAST_EXPECT(!ammAlice.ammExists());
181 // Insufficient XRP balance
184 fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
187 BEAST_EXPECT(!ammAlice.ammExists());
206 BEAST_EXPECT(!ammAlice.ammExists());
230 ter(temINVALID_FLAG));
231 BEAST_EXPECT(!ammAlice.ammExists());
251 BEAST_EXPECT(!ammAlice.ammExists());
254 // Require authorization is set
257 env.fund(XRP(30'000),
gw,
alice);
261 env(trust(
gw,
alice[
"USD"](30
'000)));
263 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000), ter(tecNO_AUTH));
264 BEAST_EXPECT(!ammAlice.ammExists());
270 env.fund(XRP(30'000),
gw,
alice);
274 env(trust(
gw,
alice[
"USD"](30
'000)));
276 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000), ter(tecFROZEN));
277 BEAST_EXPECT(!ammAlice.ammExists());
280 // Individually frozen
283 env.fund(XRP(30'000),
gw,
alice);
285 env(trust(
gw,
alice[
"USD"](30
'000)));
287 env(trust(gw, alice["USD"](0), tfSetFreeze));
289 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'000), ter(tecFROZEN));
290 BEAST_EXPECT(!ammAlice.ammExists());
293 // Insufficient reserve, XRP/IOU
296 auto const starting_xrp =
298 env.
fund(starting_xrp,
gw);
302 env(pay(gw, alice, USD(2'000)));
313 auto const starting_xrp =
315 env.
fund(starting_xrp,
gw);
318 env.trust(EUR(2'000),
alice);
321 env(pay(gw, alice, EUR(2'000)));
340 ammCrtFee(env).drops() - 1,
344 ter(telINSUF_FEE_P));
349 // AMM with one LPToken from another AMM.
350 testAMM([&](AMM& ammAlice, Env& env) {
351 fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
356 STAmount{ammAlice.lptIssue(), 1'000
'000},
357 ter(tecAMM_INVALID_TOKENS));
361 STAmount{ammAlice.lptIssue(), 1'000
'000},
369 AMM ammAlice1(env, alice, XRP(10'000),
EUR(10
'000));
370 auto const token1 = ammAlice.lptIssue();
371 auto const token2 = ammAlice1.lptIssue();
375 STAmount{token1, 1'000
'000},
376 STAmount{token2, 1'000
'000},
377 ter(tecAMM_INVALID_TOKENS));
380 // Issuer has DefaultRipple disabled
383 env.fund(XRP(30'000),
gw);
386 env.fund(
XRP(30
'000), alice);
387 env.trust(USD(30'000),
alice);
390 env, alice, XRP(10'000),
USD(10
'000), ter(terNO_RIPPLE));
391 Account const gw1("gw1");
392 env.fund(XRP(30'000), gw1);
394 env.trust(
USD(30
'000), gw1);
395 env(pay(gw, gw1, USD(30'000)));
396 auto const USD1 = gw1[
"USD"];
398 env.trust(USD1(30
'000), alice);
399 env(pay(gw1, alice, USD1(30'000)));
408 env(fset(gw, asfAllowTrustLineClawback));
409 fund(env, gw, {alice}, XRP(1'000), {
USD(1
'000)}, Fund::Acct);
411 AMM amm(env, gw, XRP(100), USD(100), ter(tecNO_PERMISSION));
412 AMM amm1(env, alice, USD(100), XRP(100), ter(tecNO_PERMISSION));
413 env(fclear(gw, asfAllowTrustLineClawback));
423 testcase(
"Invalid Deposit");
462 STAmount{USD, 1, -1},
468 STAmount{USD, 1, -1},
498 STAmount{USD, 1, -1},
516 STAmount{USD, 1, -1},
552 STAmount{USD, 1, -1},
582 STAmount{USD, 1, -1},
603 for (
auto const& it : invalidOptions)
645 // Depositing mismatched token, invalid Asset1In.issue
652 ter(temBAD_AMM_TOKENS));
654 // Depositing mismatched token, invalid Asset2In.issue
661 ter(temBAD_AMM_TOKENS));
663 // Depositing mismatched token, Asset1In.issue == Asset2In.issue
670 ter(temBAD_AMM_TOKENS));
672 // Invalid amount value
732 // Single deposit: 100000 tokens worth of USD
733 // Amount to deposit exceeds Max
760 // Deposit amount is invalid
761 // Calculated amount to deposit is 98,000,000
766 STAmount{USD, 1, -1},
768 ter(tecUNFUNDED_AMM));
769 // Calculated amount is 0
774 STAmount{USD, 2'000, -6},
807 alice, 10
'000, std::nullopt, std::nullopt, ter(terNO_AMM));
810 // Globally frozen asset
811 testAMM([&](AMM& ammAlice, Env& env) {
812 env(fset(gw, asfGlobalFreeze));
813 // Can deposit non-frozen token
814 ammAlice.deposit(carol, XRP(100));
823 carol, 1'000
'000, std::nullopt, std::nullopt, ter(tecFROZEN));
826 // Individually frozen (AMM) account
827 testAMM([&](AMM& ammAlice, Env& env) {
828 env(trust(gw, carol["USD"](0), tfSetFreeze));
830 // Can deposit non-frozen token
831 ammAlice.deposit(carol, XRP(100));
833 carol, 1'000
'000, std::nullopt, std::nullopt, ter(tecFROZEN));
841 env(trust(gw, carol["USD"](0), tfClearFreeze));
842 // Individually frozen AMM
845 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
848 // Can deposit non-frozen token
849 ammAlice.deposit(carol, XRP(100));
851 carol, 1'000
'000, std::nullopt, std::nullopt, ter(tecFROZEN));
861 // Insufficient XRP balance
862 testAMM([&](AMM& ammAlice, Env& env) {
863 env.fund(XRP(1'000),
bob);
873 ter(tecUNFUNDED_AMM));
876 // Insufficient USD balance
877 testAMM([&](AMM& ammAlice, Env& env) {
878 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
886 ter(tecUNFUNDED_AMM));
889 // Insufficient USD balance by tokens
890 testAMM([&](AMM& ammAlice, Env& env) {
891 fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
907 testAMM([&](AMM& ammAlice, Env& env) {
908 env.fund(XRP(1
'000), bob);
909 env.trust(USD(100'000),
bob);
923 ter(tecUNFUNDED_AMM));
926 // Insufficient reserve, XRP/IOU
929 auto const starting_xrp =
930 reserve(env, 4) + env.current()->fees().base * 4;
931 env.fund(XRP(10'000),
gw);
932 env.fund(XRP(10
'000), alice);
933 env.fund(starting_xrp, carol);
934 env.trust(USD(2'000),
alice);
935 env.trust(
USD(2
'000), carol);
937 env(pay(gw, alice, USD(2'000)));
940 env(offer(carol, XRP(100), USD(101)));
941 env(offer(carol, XRP(100), USD(102)));
942 AMM ammAlice(env, alice, XRP(1'000),
USD(1
'000));
949 ter(tecINSUF_RESERVE_LINE));
952 // Insufficient reserve, IOU/IOU
955 auto const starting_xrp =
956 reserve(env, 4) + env.current()->fees().base * 4;
957 env.fund(XRP(10'000),
gw);
958 env.fund(XRP(10
'000), alice);
959 env.fund(starting_xrp, carol);
960 env.trust(USD(2'000),
alice);
961 env.trust(
EUR(2
'000), alice);
962 env.trust(USD(2'000),
carol);
963 env.trust(
EUR(2
'000), carol);
965 env(pay(gw, alice, USD(2'000)));
967 env(pay(gw, carol, USD(2'000)));
970 env(offer(carol, XRP(100), USD(101)));
971 env(offer(carol, XRP(100), USD(102)));
972 AMM ammAlice(env, alice, XRP(1'000),
USD(1
'000));
979 ter(tecINSUF_RESERVE_LINE));
983 testAMM([&](AMM& ammAlice, Env& env) {
984 // min tokens can't be <= zero
1011 ter(temBAD_AMOUNT));
1034 ter(temBAD_CURRENCY));
1035 // min amount bad token pair
1057 ter(temBAD_AMM_TOKENS));
1061 testAMM([&](AMM& ammAlice, Env& env) {
1062 // Equal deposit by tokens
1073 ter(tecAMM_FAILED));
1084 ter(tecAMM_FAILED));
1085 // Equal deposit by asset
1115 testcase(
"Deposit");
1117 using namespace jtx;
1126 // 30,000 less deposited 1,000 and 10 drops tx fee
1128 expectLedgerEntryRoot(env, carol, XRPAmount{28'999
'999'990}));
1144 testAMM([&](AMM& ammAlice, Env&) {
1145 ammAlice.deposit(
carol,
USD(200), XRP(100));
1146 BEAST_EXPECT(ammAlice.expectBalances(
1147 XRP(10
'100), USD(10'100), IOUAmount{10
'100'000, 0}));
1150 testAMM([&](AMM& ammAlice, Env&) {
1152 BEAST_EXPECT(ammAlice.expectBalances(
1153 XRP(10
'100), USD(10'100), IOUAmount{10
'100'000, 0}));
1157 testAMM([&](AMM& ammAlice, Env&) {
1158 ammAlice.deposit(
carol,
USD(1
'000));
1159 BEAST_EXPECT(ammAlice.expectBalances(
1161 STAmount{
USD, UINT64_C(10
'999'99999999999), -11},
1162 IOUAmount{10
'488'088
'48170151, -8}));
1165 // Single deposit: 1000 XRP
1166 testAMM([&](AMM& ammAlice, Env&) {
1167 ammAlice.deposit(carol, XRP(1'000));
1168 BEAST_EXPECT(ammAlice.expectBalances(
1169 XRP(11
'000), USD(10'000), IOUAmount{10
'488'088
'48170151, -8}));
1172 // Single deposit: 100000 tokens worth of USD
1173 testAMM([&](AMM& ammAlice, Env&) {
1174 ammAlice.deposit(carol, 100000, USD(205));
1175 BEAST_EXPECT(ammAlice.expectBalances(
1176 XRP(10'000),
USD(10
'201), IOUAmount{10'100
'000, 0}));
1179 // Single deposit: 100000 tokens worth of XRP
1180 testAMM([&](AMM& ammAlice, Env&) {
1181 ammAlice.deposit(carol, 100'000,
XRP(205));
1182 BEAST_EXPECT(ammAlice.expectBalances(
1183 XRP(10
'201), USD(10'000), IOUAmount{10
'100'000, 0}));
1188 testAMM([&](AMM& ammAlice, Env&) {
1190 carol,
USD(1
'000), std::nullopt, STAmount{USD, 1, -1});
1191 BEAST_EXPECT(ammAlice.expectBalances(
1193 STAmount{
USD, UINT64_C(10
'999'99999999999), -11},
1194 IOUAmount{10
'488'088
'48170151, -8}));
1197 // Single deposit with EP not exceeding specified:
1198 // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
1199 testAMM([&](AMM& ammAlice, Env&) {
1201 carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
1202 BEAST_EXPECT(ammAlice.expectBalances(
1204 STAmount{
USD, 10
'080'16, -2},
1205 IOUAmount{10
'040'000, 0}));
1210 testAMM([&](AMM& ammAlice, Env&) {
1212 carol,
USD(0), std::nullopt, STAmount{
USD, 2004, -6});
1213 BEAST_EXPECT(ammAlice.expectBalances(
1215 STAmount{USD, 10'080
'16, -2},
1216 IOUAmount{10'040
'000, 0}));
1219 // IOU to IOU + transfer fee
1222 fund(env, gw, {alice}, {USD(20'000),
BTC(0.5)}, Fund::All);
1226 BEAST_EXPECT(ammAlice.expectBalances(
1227 USD(20'000),
BTC(0.5), IOUAmount{100, 0}));
1231 // no transfer fee on deposit
1232 ammAlice.deposit(carol, 10);
1233 BEAST_EXPECT(ammAlice.expectBalances(
1234 USD(22'000),
BTC(0.55), IOUAmount{110, 0}));
1240 testAMM([&](AMM& ammAlice, Env&) {
1241 ammAlice.deposit(
carol, IOUAmount{1, -3});
1242 BEAST_EXPECT(ammAlice.expectBalances(
1243 XRPAmount{10
'000'000
'001},
1244 STAmount{USD, UINT64_C(10'000
'000001), -6},
1245 IOUAmount{10'000
'000'001, -3}));
1246 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{1, -3}));
1248 testAMM([&](AMM& ammAlice, Env&) {
1249 ammAlice.deposit(
carol, XRPAmount{1});
1250 BEAST_EXPECT(ammAlice.expectBalances(
1251 XRPAmount{10
'000'000
'001},
1253 IOUAmount{1
'000'000
'000049999, -8}));
1254 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
1256 testAMM([&](AMM& ammAlice, Env&) {
1257 ammAlice.deposit(carol, STAmount{USD, 1, -10});
1258 BEAST_EXPECT(ammAlice.expectBalances(
1260 STAmount{USD, UINT64_C(10
'000'00000000008), -11},
1261 IOUAmount{10
'000'000
'00000004, -8}));
1262 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
1265 // Issuer create/deposit
1268 env.fund(XRP(30000), gw);
1269 AMM ammGw(env, gw, XRP(10'000), USD(10
'000));
1271 ammGw.expectBalances(XRP(10'000), USD(10
'000), ammGw.tokens()));
1272 ammGw.deposit(gw, 1'000
'000);
1273 BEAST_EXPECT(ammGw.expectBalances(
1274 XRP(11'000), USD(11
'000), IOUAmount{11'000
'000}));
1275 ammGw.deposit(gw, USD(1'000));
1276 BEAST_EXPECT(ammGw.expectBalances(
1278 STAmount{USD, UINT64_C(11'999
'99999999998), -11},
1279 IOUAmount{11'489
'125'29307605, -8}));
1283 testAMM([&](AMM& ammAlice, Env& env) {
1284 ammAlice.deposit(
gw, 1
'000'000);
1285 BEAST_EXPECT(ammAlice.expectBalances(
1286 XRP(11
'000), USD(11'000), IOUAmount{11
'000'000}));
1287 ammAlice.deposit(
gw,
USD(1
'000));
1288 BEAST_EXPECT(ammAlice.expectBalances(
1290 STAmount{
USD, UINT64_C(11
'999'99999999998), -11},
1291 IOUAmount{11
'489'125
'29307605, -8}));
1295 testAMM([&](AMM& ammAlice, Env& env) {
1296 // Equal deposit by tokens
1306 BEAST_EXPECT(ammAlice.expectBalances(
1307 XRP(11'000),
USD(11
'000), IOUAmount{11'000
'000, 0}));
1309 testAMM([&](AMM& ammAlice, Env& env) {
1310 // Equal deposit by asset
1320 BEAST_EXPECT(ammAlice.expectBalances(
1321 XRP(11'000),
USD(11
'000), IOUAmount{11'000
'000, 0}));
1323 testAMM([&](AMM& ammAlice, Env& env) {
1324 // Single deposit by asset
1334 BEAST_EXPECT(ammAlice.expectBalances(
1335 XRP(11'000),
USD(10
'000), IOUAmount{10'488
'088'48170151, -8}));
1337 testAMM([&](AMM& ammAlice, Env& env) {
1348 BEAST_EXPECT(ammAlice.expectBalances(
1350 STAmount{USD, UINT64_C(10'999
'99999999999), -11},
1351 IOUAmount{10'488
'088'48170151, -8}));
1358 testcase(
"Invalid Withdraw");
1360 using namespace jtx;
1417 tfWithdrawAll | tfLPToken,
1429 tfWithdrawAll | tfOneAssetWithdrawAll,
1441 tfOneAssetWithdrawAll,
1485 for (
auto const& it : invalidOptions)
1496 ter(std::get<5>(it)));
1556 ter(tecAMM_BALANCE));
1608 // Carol is not a Liquidity Provider
1621 ter(tecAMM_BALANCE));
1622 // Withdrawing from one side.
1626 IOUAmount(9'999
'999'9999, -4),
1636 ter(tecAMM_BALANCE));
1647 STAmount{
USD, UINT64_C(9
'999'9999999999999), -13},
1657 alice, 10
'000, std::nullopt, std::nullopt, ter(terNO_AMM));
1660 // Globally frozen asset
1661 testAMM([&](AMM& ammAlice, Env& env) {
1662 env(fset(gw, asfGlobalFreeze));
1664 // Can withdraw non-frozen token
1665 ammAlice.withdraw(alice, XRP(100));
1667 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
1669 alice, 1'000, std::nullopt, std::nullopt,
ter(
tecFROZEN));
1679 alice, 1
'000, std::nullopt, std::nullopt, ter(tecFROZEN));
1681 alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
1682 env(trust(gw, alice["USD"](0), tfClearFreeze));
1683 // Individually frozen AMM
1686 STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
1688 // Can withdraw non-frozen token
1689 ammAlice.withdraw(alice, XRP(100));
1691 alice, 1'000, std::nullopt, std::nullopt,
ter(
tecFROZEN));
1747 ter(tecAMM_FAILED));
1750 // Deposit/Withdraw the same amount with the trading fee
1752 [&](AMM& ammAlice, Env&) {
1753 ammAlice.deposit(carol, USD(1'000));
1759 ter(tecAMM_INVALID_TOKENS));
1764 [&](
AMM& ammAlice,
Env&) {
1776 // Deposit/Withdraw the same amount fails due to the tokens adjustment
1777 testAMM([&](AMM& ammAlice, Env&) {
1778 ammAlice.deposit(carol, STAmount{USD, 1, -6});
1781 STAmount{USD, 1, -6},
1784 ter(tecAMM_INVALID_TOKENS));
1787 // Withdraw close to one side of the pool. Account's LP tokens
1833 testcase(
"Withdraw");
1835 using namespace jtx;
1849 // 30,000 less deposited 1,000 and 10 drops tx fee
1851 expectLedgerEntryRoot(env, carol, XRPAmount{28'999
'999'990}));
1859 expectLedgerEntryRoot(env, carol, XRPAmount{29'999
'999'980}));
1864 testAMM([&](AMM& ammAlice, Env&) {
1865 ammAlice.withdraw(
alice, 1
'000'000);
1866 BEAST_EXPECT(ammAlice.expectBalances(
1867 XRP(9
'000), USD(9'000),
IOUAmount{9
'000'000, 0}));
1875 testAMM([&](AMM& ammAlice, Env&) {
1877 BEAST_EXPECT(ammAlice.expectBalances(
1878 XRP(9
'900), USD(9'900), IOUAmount{9
'900'000, 0}));
1882 testAMM([&](AMM& ammAlice, Env&) {
1884 BEAST_EXPECT(ammAlice.expectBalances(
1885 XRP(9
'900), USD(9'900), IOUAmount{9
'900'000, 0}));
1889 testAMM([&](AMM& ammAlice, Env&) {
1890 ammAlice.withdraw(
alice,
XRP(1
'000));
1891 BEAST_EXPECT(ammAlice.expectBalances(
1892 XRP(9'000),
USD(10
'000), IOUAmount{9'486
'832'98050514, -8}));
1896 testAMM([&](AMM& ammAlice, Env&) {
1897 ammAlice.withdraw(
alice, 10
'000, USD(0));
1898 BEAST_EXPECT(ammAlice.expectBalances(
1899 XRP(10'000),
USD(9980.01), IOUAmount{9
'990'000, 0}));
1903 testAMM([&](AMM& ammAlice, Env& env) {
1904 env(
trust(
carol, STAmount{ammAlice.lptIssue(), 10
'000}));
1905 // Can SetTrust only for AMM LP tokens
1909 Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}),
1912 ammAlice.withdrawAll(
alice);
1913 BEAST_EXPECT(!ammAlice.ammExists());
1918 AMM ammCarol(env,
carol,
XRP(10
'000), USD(10'000));
1919 BEAST_EXPECT(ammCarol.expectBalances(
1920 XRP(10
'000), USD(10'000), IOUAmount{10
'000'000, 0}));
1924 testAMM([&](AMM& ammAlice, Env& env) {
1925 ammAlice.deposit(
carol,
USD(1
'000));
1926 ammAlice.withdrawAll(carol, USD(0));
1927 BEAST_EXPECT(ammAlice.expectBalances(
1928 XRP(10'000),
USD(10
'000), IOUAmount{10'000
'000, 0}));
1930 ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
1933 // Single deposit 1000USD, withdraw all tokens in XRP
1934 testAMM([&](AMM& ammAlice, Env&) {
1935 ammAlice.deposit(carol, USD(1'000));
1936 ammAlice.withdrawAll(
carol,
XRP(0));
1937 BEAST_EXPECT(ammAlice.expectBalances(
1938 XRPAmount(9
'090'909
'091),
1939 STAmount{USD, UINT64_C(10'999
'99999999999), -11},
1940 IOUAmount{10'000
'000, 0}));
1943 // Single deposit/withdraw by the same account
1944 testAMM([&](AMM& ammAlice, Env&) {
1945 // Since a smaller amount might be deposited due to
1946 // the lp tokens adjustment, withdrawing by tokens
1947 // is generally preferred to withdrawing by amount.
1948 auto lpTokens = ammAlice.deposit(carol, USD(1'000));
1949 ammAlice.withdraw(
carol, lpTokens,
USD(0));
1950 lpTokens = ammAlice.deposit(
carol, STAmount(
USD, 1, -6));
1951 ammAlice.withdraw(
carol, lpTokens,
USD(0));
1952 lpTokens = ammAlice.deposit(
carol, XRPAmount(1));
1953 ammAlice.withdraw(
carol, lpTokens, XRPAmount(0));
1954 BEAST_EXPECT(ammAlice.expectBalances(
1955 XRP(10
'000), USD(10'000), ammAlice.tokens()));
1956 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{0}));
1961 testAMM([&](AMM& ammAlice, Env&) {
1962 auto const carolTokens = ammAlice.deposit(
carol,
USD(1
'000));
1963 auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
1964 ammAlice.withdraw(
alice, aliceTokens,
USD(0));
1965 ammAlice.withdraw(
carol, carolTokens,
USD(0));
1966 BEAST_EXPECT(ammAlice.expectBalances(
1967 XRP(10
'000), USD(10'000), ammAlice.tokens()));
1968 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{0}));
1969 BEAST_EXPECT(ammAlice.expectLPTokens(
alice, ammAlice.tokens()));
1973 testAMM([&](AMM& ammAlice, Env&) {
1974 ammAlice.deposit(
carol, 1
'000'000);
1975 ammAlice.withdrawAll(
carol);
1976 BEAST_EXPECT(ammAlice.expectBalances(
1977 XRP(10
'000), USD(10'000), IOUAmount{10
'000'000, 0}));
1981 testAMM([&](AMM& ammAlice, Env&) {
1982 ammAlice.deposit(
carol, 1
'000'000);
1983 ammAlice.withdrawAll(
carol,
USD(0));
1984 BEAST_EXPECT(ammAlice.expectBalances(
1986 STAmount{USD, UINT64_C(9'090
'909090909092), -12},
1987 IOUAmount{10'000
'000, 0}));
1990 // Equal deposit 10%, withdraw all tokens in XRP
1991 testAMM([&](AMM& ammAlice, Env&) {
1992 ammAlice.deposit(carol, 1'000
'000);
1993 ammAlice.withdrawAll(carol, XRP(0));
1994 BEAST_EXPECT(ammAlice.expectBalances(
1995 XRPAmount(9'090
'909'091),
1997 IOUAmount{10'000
'000, 0}));
2000 // Withdraw with EPrice limit.
2001 testAMM([&](AMM& ammAlice, Env&) {
2002 ammAlice.deposit(carol, 1'000
'000);
2003 ammAlice.withdraw(carol, USD(100), std::nullopt, IOUAmount{520, 0});
2005 ammAlice.expectBalances(
2006 XRPAmount(11'000
'000'000),
2007 STAmount{
USD, UINT64_C(9
'372'781065088757), -12},
2008 IOUAmount{10
'153'846
'15384616, -8}) &&
2009 ammAlice.expectLPTokens(
2010 carol, IOUAmount{153'846
'15384616, -8}));
2011 ammAlice.withdrawAll(carol);
2012 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
2015 // Withdraw with EPrice limit. AssetOut is 0.
2016 testAMM([&](AMM& ammAlice, Env&) {
2017 ammAlice.deposit(carol, 1'000
'000);
2018 ammAlice.withdraw(carol, USD(0), std::nullopt, IOUAmount{520, 0});
2020 ammAlice.expectBalances(
2021 XRPAmount(11'000
'000'000),
2022 STAmount{
USD, UINT64_C(9
'372'781065088757), -12},
2023 IOUAmount{10
'153'846
'15384616, -8}) &&
2024 ammAlice.expectLPTokens(
2025 carol, IOUAmount{153'846
'15384616, -8}));
2028 // IOU to IOU + transfer fee
2031 fund(env, gw, {alice}, {USD(20'000),
BTC(0.5)}, Fund::All);
2036 BEAST_EXPECT(ammAlice.expectBalances(
2037 USD(20'000),
BTC(0.5), IOUAmount{100, 0}));
2041 // no transfer fee on deposit
2042 ammAlice.deposit(carol, 10);
2043 BEAST_EXPECT(ammAlice.expectBalances(
2044 USD(22'000),
BTC(0.55), IOUAmount{110, 0}));
2048 ammAlice.withdraw(
carol, 10);
2049 BEAST_EXPECT(ammAlice.expectBalances(
2050 USD(20
'000), BTC(0.5), IOUAmount{100, 0}));
2051 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
2052 BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
2057 testAMM([&](AMM& ammAlice, Env&) {
2059 ammAlice.withdraw(
alice, IOUAmount{1, -3});
2060 BEAST_EXPECT(ammAlice.expectBalances(
2061 XRPAmount{9
'999'999
'999},
2062 STAmount{USD, UINT64_C(9'999
'999999), -6},
2063 IOUAmount{9'999
'999'999, -3}));
2065 testAMM([&](AMM& ammAlice, Env&) {
2067 ammAlice.withdraw(
alice, std::nullopt, XRPAmount{1});
2068 BEAST_EXPECT(ammAlice.expectBalances(
2069 XRPAmount{9
'999'999
'999},
2071 IOUAmount{9
'999'999
'9995, -4}));
2073 testAMM([&](AMM& ammAlice, Env&) {
2075 ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
2076 BEAST_EXPECT(ammAlice.expectBalances(
2078 STAmount{USD, UINT64_C(9
'999'9999999999), -10},
2079 IOUAmount{9
'999'999
'99999995, -8}));
2082 // Withdraw close to entire pool
2084 testAMM([&](AMM& ammAlice, Env&) {
2085 ammAlice.withdraw(alice, IOUAmount{9'999
'999'999, -3});
2086 BEAST_EXPECT(ammAlice.expectBalances(
2087 XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
2090 testAMM([&](AMM& ammAlice, Env&) {
2091 ammAlice.withdraw(
alice, IOUAmount{9
'999'999},
USD(0));
2092 BEAST_EXPECT(ammAlice.expectBalances(
2093 XRP(10
'000), STAmount{USD, 1, -10}, IOUAmount{1}));
2096 testAMM([&](AMM& ammAlice, Env&) {
2097 ammAlice.withdraw(alice, IOUAmount{9'999
'900}, XRP(0));
2098 BEAST_EXPECT(ammAlice.expectBalances(
2099 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2102 testAMM([&](AMM& ammAlice, Env&) {
2104 alice, STAmount{
USD, UINT64_C(9
'999'99999999999), -11});
2105 BEAST_EXPECT(ammAlice.expectBalances(
2106 XRP(10000), STAmount{
USD, 1, -11}, IOUAmount{316227765, -9}));
2109 testAMM([&](AMM& ammAlice, Env&) {
2110 ammAlice.withdraw(
alice, XRPAmount{9
'999'999
'999});
2111 BEAST_EXPECT(ammAlice.expectBalances(
2112 XRPAmount{1}, USD(10'000), IOUAmount{100}));
2119 testcase(
"Invalid Fee Vote");
2120 using namespace jtx;
2130 ter(temINVALID_FLAG));
2151 ter(terNO_ACCOUNT));
2169 ter(tecAMM_INVALID_TOKENS));
2173 testAMM([&](AMM& ammAlice, Env& env) {
2174 ammAlice.withdrawAll(alice);
2188 testcase(
"Fee Vote");
2189 using namespace jtx;
2194 ammAlice.
vote({}, 1
'000);
2195 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
2200 auto vote = [&](
AMM& ammAlice,
2203 int fundUSD = 100
'000,
2204 std::uint32_t tokens = 10'000
'000,
2205 std::vector<Account>* accounts = nullptr) {
2206 Account a(std::to_string(i));
2207 fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2208 ammAlice.deposit(a, tokens);
2209 ammAlice.vote(a, 50 * (i + 1));
2211 accounts->push_back(std::move(a));
2214 // Eight votes fill all voting slots, set fee 0.175%.
2215 testAMM([&](AMM& ammAlice, Env& env) {
2216 for (int i = 0; i < 7; ++i)
2217 vote(ammAlice, env, i, 10'000);
2224 for (
int i = 0; i < 7; ++i)
2225 vote(ammAlice, env, i);
2228 ammAlice.
vote(a, 450);
2234 testAMM([&](AMM& ammAlice, Env& env) {
2235 for (
int i = 0; i < 7; ++i)
2236 vote(ammAlice, env, i);
2237 BEAST_EXPECT(ammAlice.expectTradingFee(175));
2238 vote(ammAlice, env, 7, 100
'000, 20'000
'000);
2239 BEAST_EXPECT(ammAlice.expectTradingFee(244));
2242 // Eight votes fill all voting slots, set fee 0.219%.
2243 // New vote, new account, higher vote weight, set smaller fee 0.206%
2244 testAMM([&](AMM& ammAlice, Env& env) {
2245 for (int i = 7; i > 0; --i)
2246 vote(ammAlice, env, i);
2247 BEAST_EXPECT(ammAlice.expectTradingFee(219));
2248 vote(ammAlice, env, 0, 100'000, 20
'000'000);
2249 BEAST_EXPECT(ammAlice.expectTradingFee(206));
2255 testAMM([&](AMM& ammAlice, Env& env) {
2257 for (
int i = 0; i < 7; ++i)
2258 vote(ammAlice, env, i, 100
'000, 10'000
'000, &accounts);
2259 BEAST_EXPECT(ammAlice.expectTradingFee(175));
2260 for (int i = 0; i < 7; ++i)
2261 ammAlice.withdrawAll(accounts[i]);
2262 ammAlice.deposit(carol, 10'000
'000);
2263 ammAlice.vote(carol, 1'000);
2266 BEAST_EXPECT(ammAlice.expectTradingFee(500));
2272 testAMM([&](AMM& ammAlice, Env& env) {
2274 for (
int i = 0; i < 7; ++i)
2275 vote(ammAlice, env, i, 100
'000, 10'000
'000, &accounts);
2276 BEAST_EXPECT(ammAlice.expectTradingFee(175));
2277 for (int i = 0; i < 7; ++i)
2278 ammAlice.withdraw(accounts[i], 9'000
'000);
2279 ammAlice.deposit(carol, 1'000);
2281 ammAlice.vote(
carol, 1
'000);
2282 auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
2283 for (std::uint16_t i = 0; i < info.size(); ++i)
2284 BEAST_EXPECT(info[i][jss::account] != carol.human());
2285 // But the slots are refreshed and the fee is changed
2286 BEAST_EXPECT(ammAlice.expectTradingFee(82));
2293 testcase("Invalid Bid");
2294 using namespace jtx;
2295 using namespace std::chrono;
2297 testAMM([&](AMM& ammAlice, Env& env) {
2307 ter(temINVALID_FLAG));
2309 ammAlice.deposit(carol, 1'000
'000);
2310 // Invalid Bid price <= 0
2311 for (auto bid : {0, -100})
2321 ter(temBAD_AMOUNT));
2330 ter(temBAD_AMOUNT));
2333 // Invlaid Min/Max combination
2342 ter(tecAMM_INVALID_TOKENS));
2355 ter(terNO_ACCOUNT));
2357 // Account is not LP
2358 Account const dan("dan");
2359 env.fund(XRP(1'000), dan);
2423 testAMM([&](AMM& ammAlice, Env& env) {
2424 ammAlice.withdrawAll(
alice);
2437 testAMM([&](AMM& ammAlice, Env& env) {
2439 Account bill(
"bill");
2440 Account scott(
"scott");
2441 Account james(
"james");
2442 env.fund(
XRP(1
'000), bob, ed, bill, scott, james);
2444 ammAlice.deposit(carol, 1'000
'000);
2449 {bob, ed, bill, scott, james},
2456 // Bid price exceeds LP owned tokens
2457 testAMM([&](AMM& ammAlice, Env& env) {
2458 fund(env, gw, {bob}, XRP(1'000), {
USD(100)}, Fund::Acct);
2459 ammAlice.deposit(
carol, 1
'000'000);
2460 ammAlice.deposit(
bob, 10);
2479 ammAlice.bid(
carol, 1
'000);
2480 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2498 auto const lpIssue = amm.lptIssue();
2499 env.trust(STAmount{lpIssue, 100}, alice);
2500 env.trust(STAmount{lpIssue, 50}, bob);
2501 env(pay(gw, alice, STAmount{lpIssue, 100}));
2502 env(pay(gw, bob, STAmount{lpIssue, 50}));
2503 amm.bid(alice, 100);
2504 // Alice doesn't have any more tokens, but
2522 using namespace jtx;
2549 XRP(11
'000), USD(11'000),
IOUAmount{10
'999'814
'5, -1}));
2552 // Start bid at bidMin 110.
2553 testAMM([&](AMM& ammAlice, Env& env) {
2554 ammAlice.deposit(carol, 1'000
'000);
2556 ammAlice.bid(carol, 110);
2557 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2559 fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2576 ammAlice.
bid(
carol, std::nullopt, 600);
2580 // Bid Min/MaxSlotPrice fails because the computed price is not in
2590 ter(tecAMM_FAILED));
2591 // Bid Min/MaxSlotPrice succeeds - pay computed price
2592 ammAlice.bid(carol, 100, 600);
2594 ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
2602 ammAlice.deposit(bob, 1'000
'000);
2603 BEAST_EXPECT(ammAlice.expectBalances(
2604 XRP(12'000),
USD(12
'000), IOUAmount{12'000
'000, 0}));
2606 // Initial state. Pay bidMin.
2607 ammAlice.bid(carol, 110);
2608 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2610 // 1st Interval after close, price for 0th interval.
2612 env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
2614 ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
2622 // 20th Interval (expired) after close, price for 10th interval.
2625 AUCTION_SLOT_TIME_INTERVALS * AUCTION_SLOT_INTERVAL_DURATION +
2627 BEAST_EXPECT(ammAlice.expectAuctionSlot(
2628 0, std::nullopt, IOUAmount{127'33875, -5}));
2636 XRP(12
'000), USD(12'000),
IOUAmount{11
'999'678
'91, -2}));
2639 // Pool's
fee 1%. Bid bidMin.
2644 [&](
AMM& ammAlice,
Env& env) {
2647 fund(env,
gw, {
bob, dan, ed}, {
USD(20
'000)}, Fund::Acct);
2648 ammAlice.deposit(bob, 1'000
'000);
2649 ammAlice.deposit(ed, 1'000
'000);
2650 ammAlice.deposit(carol, 500'000);
2651 ammAlice.
deposit(dan, 500
'000);
2652 auto ammTokens = ammAlice.getLPTokensBalance();
2653 ammAlice.bid(carol, 120, std::nullopt, {bob, ed});
2654 auto const slotPrice = IOUAmount{5'200};
2655 ammTokens -= slotPrice;
2658 XRP(13
'000), USD(13'000), ammTokens));
2660 for (
int i = 0; i < 10; ++i)
2672 STAmount(
USD, UINT64_C(29
'499'00572620545), -11));
2675 STAmount(
USD, UINT64_C(18
'999'00572616195), -11));
2678 STAmount(
USD, UINT64_C(18
'999'00572611841), -11));
2682 STAmount(USD, UINT64_C(13'002
'98282151419), -11),
2684 ammTokens = ammAlice.getLPTokensBalance();
2685 // Trade with the fee
2686 for (int i = 0; i < 10; ++i)
2688 auto const tokens = ammAlice.deposit(dan, USD(100));
2689 ammAlice.withdraw(dan, tokens, USD(0));
2691 // dan pays ~9.94USD, which is ~10 times more in fees than
2692 // carol, bob, ed. the discounted fee is 10 times less
2693 // than the trading fee.
2695 env.balance(dan, USD) ==
2696 STAmount(USD, UINT64_C(19'490
'056722744), -9));
2697 // USD pool gains more in dan's fees.
2700 STAmount{USD, UINT64_C(13'012
'92609877019), -11},
2702 // Discounted fee payment
2703 ammAlice.deposit(carol, USD(100));
2704 ammTokens = ammAlice.getLPTokensBalance();
2705 BEAST_EXPECT(ammAlice.expectBalances(
2715 STAmount{USD, UINT64_C(13'012
'92609877019), -11},
2717 // Payment with the trading fee
2718 env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
2720 // alice pays ~1.011USD in fees, which is ~10 times more
2723 BEAST_EXPECT(ammAlice.expectBalances(
2724 XRPAmount{13
'000'000
'668},
2725 STAmount{USD, UINT64_C(13'114
'03663047264), -11},
2727 // Auction slot expired, no discounted fee
2728 env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
2729 // clock is parent's based
2732 env.balance(carol, USD) ==
2733 STAmount(USD, UINT64_C(29
'399'00572620545), -11));
2734 ammTokens = ammAlice.getLPTokensBalance();
2735 for (int i = 0; i < 10; ++i)
2737 auto const tokens = ammAlice.deposit(carol, USD(100));
2738 ammAlice.withdraw(carol, tokens, USD(0));
2743 env.balance(carol, USD) ==
2744 STAmount(USD, UINT64_C(29
'389'06197177128), -11));
2745 BEAST_EXPECT(ammAlice.expectBalances(
2746 XRPAmount{13
'000'000
'668},
2747 STAmount{USD, UINT64_C(13'123
'98038490681), -11},
2749 env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
2751 // carol pays ~1.008XRP in trading fee, which is
2752 // ~10 times more than the discounted fee.
2753 // 99.815876XRP is swapped in for 100USD
2754 BEAST_EXPECT(ammAlice.expectBalances(
2755 XRPAmount(13'100
'824'790),
2756 STAmount{USD, UINT64_C(13
'023'98038490681), -11},
2763 testAMM([&](AMM& ammAlice, Env&) {
2764 // Bid a tiny amount
2765 auto const tiny = Number{STAmount::cMinValue, STAmount::cMinOffset};
2766 ammAlice.bid(alice, IOUAmount{tiny});
2767 // Auction slot purchase price is equal to the tiny amount
2768 // since the minSlotPrice is 0 with no trading fee.
2769 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
2770 // The purchase price is too small to affect the total tokens
2771 BEAST_EXPECT(ammAlice.expectBalances(
2772 XRP(10'000), USD(10
'000), ammAlice.tokens()));
2773 // Bid the tiny amount
2775 alice, IOUAmount{STAmount::cMinValue, STAmount::cMinOffset});
2776 // Pay slightly higher price
2777 BEAST_EXPECT(ammAlice.expectAuctionSlot(
2778 0, 0, IOUAmount{tiny * Number{105, -2}}));
2779 // The purchase price is still too small to affect the total tokens
2780 BEAST_EXPECT(ammAlice.expectBalances(
2781 XRP(10'000), USD(10
'000), ammAlice.tokens()));
2784 // Reset auth account
2785 testAMM([&](AMM& ammAlice, Env& env) {
2786 ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {carol});
2787 BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
2788 ammAlice.bid(alice, IOUAmount{100});
2789 BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
2792 fund(env, {bob, dan}, XRP(1'000));
2793 ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {bob, dan});
2794 BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
2800 fund(env, gw, {alice, bob},
XRP(2
'000), {USD(2'000)});
2801 AMM amm(env,
gw,
XRP(1
'000), USD(1'010),
false, 1
'000);
2802 auto const lpIssue = amm.lptIssue();
2803 env.trust(STAmount{lpIssue, 500}, alice);
2804 env.trust(STAmount{lpIssue, 50}, bob);
2805 env(pay(gw, alice, STAmount{lpIssue, 500}));
2806 env(pay(gw, bob, STAmount{lpIssue, 50}));
2807 // Alice doesn't have anymore lp tokens
2808 amm.bid(
alice, 500);
2809 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{500}));
2814 BEAST_EXPECT(amm.expectBalances(
2817 IOUAmount{1
'004'487
'562112089, -9}));
2818 // Bob pays the full fee ~0.1USD
2819 env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
2820 BEAST_EXPECT(amm.expectBalances(
2821 XRPAmount{1'000
'010'011},
2822 STAmount{USD, UINT64_C(1
'010'10090898081), -11},
2828 testInvalidAMMPayment()
2830 testcase("Invalid AMM Payment");
2831 using namespace jtx;
2832 using namespace std::chrono;
2833 using namespace std::literals::chrono_literals;
2835 // Can't pay into AMM account.
2837 for (auto const& acct : {gw, alice})
2841 fund(env, gw, {alice, carol}, XRP(1
'000), {USD(100)});
2842 // XRP balance is below reserve
2843 AMM ammAlice(env, acct, XRP(10), USD(10));
2844 // Pay below reserve
2845 env(pay(carol, ammAlice.ammAccount(), XRP(10)),
2846 ter(tecNO_PERMISSION));
2847 // Pay above reserve
2848 env(pay(carol, ammAlice.ammAccount(), XRP(300)),
2849 ter(tecNO_PERMISSION));
2851 env(pay(carol, ammAlice.ammAccount(), USD(10)),
2852 ter(tecNO_PERMISSION));
2856 fund(env, gw, {alice, carol}, XRP(10'000
'000), {USD(10'000)});
2858 AMM ammAlice(env, acct,
XRP(1
'000'000),
USD(100));
2869 testAMM([&](AMM& ammAlice, Env& env) {
2870 env(escrow(
carol, ammAlice.ammAccount(), XRP(1)),
2872 finish_time(env.now() + 1s),
2873 cancel_time(env.now() + 2s),
2875 ter(tecNO_PERMISSION));
2878 // Can't pay into AMM with paychan.
2879 testAMM([&](AMM& ammAlice, Env& env) {
2881 auto const settleDelay = 100s;
2883 env.current()->info().parentCloseTime + 200s;
2886 ammAlice.ammAccount(),
2891 ter(tecNO_PERMISSION));
2894 // Can't pay into AMM with checks.
2895 testAMM([&](AMM& ammAlice, Env& env) {
2896 env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)),
2902 [&](AMM& ammAlice, Env& env) {
2906 sendmax(XRP(1
'000'000
'000)),
2907 ter(tecPATH_PARTIAL));
2908 env(pay(alice, carol, XRP(100)),
2910 sendmax(USD(1'000
'000'000)),
2915 STAmount{
USD, UINT64_C(99
'999999999), -9}),
2917 sendmax(XRP(1'000
'000'000)),
2921 STAmount{
USD, UINT64_C(999
'99999999), -8}),
2923 sendmax(XRP(1'000
'000'000)),
2927 sendmax(
USD(1
'000'000
'000)),
2928 ter(tecPATH_PARTIAL));
2929 // Sender doesn't have enough funds
2932 sendmax(
XRP(1
'000'000
'000)),
2933 ter(tecPATH_PARTIAL));
2934 env(pay(alice, carol, STAmount{xrpIssue(), 99'990
'000}),
2936 sendmax(USD(1'000
'000'000)),
2942 testAMM([&](AMM& ammAlice, Env& env) {
2958 testAMM([&](AMM& ammAlice, Env& env) {
2961 STAmount{Issue{
gw[
"USD"].currency, ammAlice.ammAccount()}, 0},
2977 testAMM([&](AMM& ammAlice, Env& env) {
2992 testcase(
"Basic Payment");
2993 using namespace jtx;
2998 [&](
AMM& ammAlice,
Env& env) {
3001 env(pay(bob, carol, USD(100)),
3004 txflags(tfNoRippleDirect));
3006 BEAST_EXPECT(ammAlice.expectBalances(
3007 XRP(10'100),
USD(10
'000), ammAlice.tokens()));
3008 // Initial balance 30,000 + 100
3009 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
3012 env,
bob,
XRP(30
'000) - XRP(100) - txfee(env, 1)));
3014 {{XRP(10'000),
USD(10
'100)}});
3016 // Payment 100USD for 100XRP, use default path.
3018 [&](AMM& ammAlice, Env& env) {
3019 env.fund(jtx::XRP(30'000),
bob);
3024 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
3027 // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
3028 BEAST_EXPECT(expectLedgerEntryRoot(
3029 env, bob, XRP(30'000) -
XRP(100) -
txfee(env, 1)));
3031 {{
XRP(10
'000), USD(10'100)}});
3036 [&](
AMM& ammAlice,
Env& env) {
3039 env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
3041 BEAST_EXPECT(ammAlice.expectBalances(
3042 XRP(10'100),
USD(10
'000), ammAlice.tokens()));
3043 // Initial balance 30,000 + 100
3044 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
3047 env,
bob,
XRP(30
'000) - XRP(100) - txfee(env, 1)));
3049 {{XRP(10'000),
USD(10
'100)}});
3051 // Payment with limitQuality set.
3053 [&](AMM& ammAlice, Env& env) {
3054 env.fund(jtx::XRP(30'000),
bob);
3065 XRP(10
'010), USD(10'000), ammAlice.
tokens()));
3068 // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
3070 BEAST_EXPECT(expectLedgerEntryRoot(
3071 env, bob, XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3083 {{
XRP(10
'000), USD(10'010)}});
3087 [&](
AMM& ammAlice,
Env& env) {
3092 // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
3093 // would have been sent has it not been for limitQuality and
3094 // the transfer fee.
3095 env(pay(bob, carol, USD(100)),
3099 tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
3101 BEAST_EXPECT(ammAlice.expectBalances(
3102 XRP(10'010),
USD(10
'000), ammAlice.tokens()));
3103 // 10USD - 10% transfer fee
3104 BEAST_EXPECT(expectLine(
3107 STAmount{USD, UINT64_C(30'009
'09090909091), -11}));
3108 BEAST_EXPECT(expectLedgerEntryRoot(
3109 env, bob, XRP(30'000) -
XRP(10) -
txfee(env, 1)));
3111 {{
XRP(10
'000), USD(10'010)}});
3115 [&](
AMM& ammAlice,
Env& env) {
3118 env(pay(bob, carol, USD(100)),
3121 txflags(tfNoRippleDirect),
3122 ter(tecPATH_PARTIAL));
3124 {{XRP(10'000),
USD(10
'000)}});
3126 // Non-default path (with AMM) has a better quality than default path.
3127 // The max possible liquidity is taken out of non-default
3128 // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
3129 // is taken from the offer.
3133 env, gw, {alice, carol}, {USD(30'000),
EUR(30
'000)}, Fund::All);
3135 env.fund(XRP(1'000),
bob);
3137 auto ammEUR_XRP =
AMM(env,
alice,
XRP(10
'000), EUR(10'000));
3138 auto ammUSD_EUR =
AMM(env,
alice,
EUR(10
'000), USD(10'000));
3146 BEAST_EXPECT(ammEUR_XRP.expectBalances(
3148 STAmount(EUR, UINT64_C(9'970
'007498125468), -12),
3149 ammEUR_XRP.tokens()));
3150 BEAST_EXPECT(ammUSD_EUR.expectBalances(
3151 STAmount(USD, UINT64_C(9'970
'097277662122), -12),
3152 STAmount(EUR, UINT64_C(10'029
'99250187452), -11),
3153 ammUSD_EUR.tokens()));
3154 BEAST_EXPECT(expectOffers(
3159 XRPAmount(30'201
'749),
3160 STAmount(USD, UINT64_C(29'90272233787818), -14)}}}));
3163 // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
3164 BEAST_EXPECT(expectLedgerEntryRoot(
3167 XRP(1'000) -
XRPAmount{30
'082'730} - XRPAmount{70
'798'251} -
3174 testAMM([&](AMM& ammAlice, Env& env) {
3175 env.fund(
XRP(1
'000), bob);
3177 env.trust(EUR(2'000),
alice);
3180 env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
3182 env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
3184 env(pay(bob, carol, USD(100)),
3187 txflags(tfPartialPayment));
3189 BEAST_EXPECT(ammAlice.expectBalances(
3190 XRPAmount(10'050
'238'637),
3191 STAmount(
USD, UINT64_C(9
'950'01249687578), -11),
3192 ammAlice.tokens()));
3198 XRPAmount(50
'487'378),
3199 STAmount(
EUR, UINT64_C(49
'98750312422), -11)},
3201 STAmount(EUR, UINT64_C(49'98750312422), -11),
3202 STAmount(
USD, UINT64_C(49
'98750312422), -11)}}}));
3203 // Initial 30,000 + 99.99999999999
3204 BEAST_EXPECT(expectLine(
3205 env, carol, STAmount{USD, UINT64_C(30'099
'99999999999), -11}));
3206 // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
3208 BEAST_EXPECT(expectLedgerEntryRoot(
3211 XRP(1'000) - XRPAmount{50
'238'637} - XRPAmount{50
'512'622} -
3218 [&](AMM& ammAlice, Env& env) {
3227 BEAST_EXPECT(ammAlice.expectBalances(
3228 XRP(10
'100), USD(10'000), ammAlice.tokens()));
3231 // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
3232 // - 100(offer) - 10(tx fee) - one reserve
3233 BEAST_EXPECT(expectLedgerEntryRoot(
3236 XRP(30'000) -
XRP(10
'000) - XRP(100) - XRP(100) -
3237 ammCrtFee(env) - txfee(env, 1)));
3238 BEAST_EXPECT(expectOffers(env, bob, 0));
3240 {{XRP(10'000),
USD(10
'100)}});
3242 // Default path with AMM and Order Book offer.
3243 // Order Book offer is consumed first.
3244 // Remaining amount is consumed by AMM.
3247 fund(env, gw, {alice, bob, carol}, XRP(20'000), {
USD(2
'000)});
3248 env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
3249 AMM ammAlice(env, alice, XRP(1'000),
USD(1
'050));
3250 env(pay(alice, carol, USD(200)),
3252 txflags(tfPartialPayment));
3253 BEAST_EXPECT(ammAlice.expectBalances(
3254 XRP(1'050),
USD(1
'000), ammAlice.tokens()));
3255 BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
3261 [&](AMM& ammAlice, Env& env) {
3264 env(offer(bob, USD(100), XRP(100)));
3266 BEAST_EXPECT(ammAlice.expectBalances(
3267 XRP(10'100),
USD(10
'000), ammAlice.tokens()));
3268 // Initial 1,000 + 100
3269 BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
3272 env,
bob,
XRP(30
'000) - XRP(100) - txfee(env, 1)));
3273 BEAST_EXPECT(expectOffers(env, bob, 0));
3275 {{XRP(10'000),
USD(10
'100)}});
3277 // Offer crossing IOU/IOU and transfer rate
3279 [&](AMM& ammAlice, Env& env) {
3280 env(rate(gw, 1.25));
3282 env(offer(carol, EUR(100), GBP(100)));
3285 BEAST_EXPECT(ammAlice.expectBalances(
3286 GBP(1'100),
EUR(1
'000), ammAlice.tokens()));
3287 // Initial 30,000 - 100(offer) - 25% transfer fee
3288 BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
3291 BEAST_EXPECT(expectOffers(env, bob, 0));
3293 {{GBP(1'000),
EUR(1
'100)}});
3295 // Payment and transfer fee
3297 // Bob sends 125GBP to pay 80EUR to Carol
3298 // Payment execution:
3299 // bob's 125
GBP/1.25 = 100
GBP
3303 [&](AMM& ammAlice, Env& env) {
3312 BEAST_EXPECT(ammAlice.expectBalances(
3313 GBP(1
'100), EUR(1'000), ammAlice.tokens()));
3317 {{GBP(1'000),
EUR(1
'100)}});
3319 // Payment and transfer fee, multiple steps
3321 // Dan's offer 200CAN/200
GBP
3331 [&](AMM& ammAlice, Env& env) {
3332 Account
const dan(
"dan");
3333 Account
const ed(
"ed");
3334 auto const CAN =
gw[
"CAN"];
3335 fund(env,
gw, {dan}, {CAN(200),
GBP(200)}, Fund::Acct);
3337 fund(env,
gw, {
bob}, {CAN(195.3125)}, Fund::Acct);
3341 env(
offer(dan, CAN(200),
GBP(200)));
3346 sendmax(CAN(195.3125)),
3350 BEAST_EXPECT(
expectLine(env, dan, CAN(356.25),
GBP(43.75)));
3351 BEAST_EXPECT(ammAlice.expectBalances(
3352 GBP(10
'125), EUR(10'000), ammAlice.tokens()));
3356 {{
GBP(10
'000), EUR(10'125)}});
3360 [&](AMM& ammAlice, Env& env) {
3387 auto const ETH =
gw[
"ETH"];
3393 {EUR(50'000),
BTC(50
'000), ETH(50'000),
USD(50
'000)});
3394 fund(env, gw, {carol, bob}, XRP(1'000), {
USD(200)}, Fund::Acct);
3395 AMM xrp_eur(env,
alice,
XRP(10
'100), EUR(10'000));
3396 AMM eur_btc(env,
alice,
EUR(10
'000), BTC(10'200));
3397 AMM btc_usd(env,
alice,
BTC(10
'100), USD(10'000));
3398 AMM xrp_usd(env,
alice,
XRP(10
'150), USD(10'200));
3399 AMM xrp_eth(env,
alice,
XRP(10
'000), ETH(10'100));
3400 AMM eth_eur(env,
alice, ETH(10
'900), EUR(11'000));
3401 AMM eur_usd(env,
alice,
EUR(10
'100), USD(10'000));
3409 BEAST_EXPECT(xrp_eth.expectBalances(
3410 XRPAmount(10
'026'208
'900),
3411 STAmount{ETH, UINT64_C(10'073
'65779244494), -11},
3413 BEAST_EXPECT(eth_eur.expectBalances(
3414 STAmount{ETH, UINT64_C(10'926
'34220755506), -11},
3415 STAmount{EUR, UINT64_C(10'973
'54232078752), -11},
3417 BEAST_EXPECT(eur_usd.expectBalances(
3418 STAmount{EUR, UINT64_C(10'126
'45767921248), -11},
3419 STAmount{USD, UINT64_C(9'973
'93151712086), -11},
3423 // This path provides ~73.9USD/74.1XRP
3424 BEAST_EXPECT(xrp_usd.expectBalances(
3425 XRPAmount(10'224
'106'246),
3426 STAmount{
USD, UINT64_C(10
'126'06848287914), -11},
3435 BEAST_EXPECT(xrp_eur.expectBalances(
3436 XRP(10
'100), EUR(10'000), xrp_eur.tokens()));
3437 BEAST_EXPECT(eur_btc.expectBalances(
3438 EUR(10
'000), BTC(10'200), eur_btc.tokens()));
3439 BEAST_EXPECT(btc_usd.expectBalances(
3440 BTC(10
'100), USD(10'000), btc_usd.tokens()));
3448 auto const ETH =
gw[
"ETH"];
3454 {EUR(50'000),
BTC(50
'000), ETH(50'000),
USD(50
'000)});
3455 fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
3456 AMM xrp_eur(env, alice, XRP(10'100),
EUR(10
'000));
3457 AMM eur_btc(env, alice, EUR(10'000),
BTC(10
'200));
3458 AMM btc_usd(env, alice, BTC(10'100),
USD(10
'000));
3459 AMM xrp_eth(env, alice, XRP(10'000), ETH(10
'100));
3460 AMM eth_eur(env, alice, ETH(10'900),
EUR(11
'000));
3461 env(pay(bob, carol, USD(100)),
3462 path(~EUR, ~BTC, ~USD),
3463 path(~ETH, ~EUR, ~BTC, ~USD),
3465 // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
3466 // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
3467 BEAST_EXPECT(xrp_eur.expectBalances(
3468 XRPAmount(10'118
'738'472),
3469 STAmount{
EUR, UINT64_C(9
'981'544436337968), -12},
3471 BEAST_EXPECT(eur_btc.expectBalances(
3472 STAmount{EUR, UINT64_C(10
'101'16096785173), -11},
3473 STAmount{BTC, UINT64_C(10
'097'91426968066), -11},
3475 BEAST_EXPECT(btc_usd.expectBalances(
3476 STAmount{BTC, UINT64_C(10
'202'08573031934), -11},
3479 BEAST_EXPECT(xrp_eth.expectBalances(
3480 XRPAmount(10'082
'446'397),
3481 STAmount{ETH, UINT64_C(10
'017'41072778012), -11},
3483 BEAST_EXPECT(eth_eur.expectBalances(
3484 STAmount{ETH, UINT64_C(10
'982'58927221988), -11},
3485 STAmount{EUR, UINT64_C(10
'917'2945958103), -10},
3492 testAMM([&](AMM& ammAlice, Env& env) {
3493 env.fund(
XRP(1
'000), bob);
3494 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
3495 env(trust(alice, EUR(200)));
3496 for (int i = 0; i < 30; ++i)
3497 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
3498 // This is worse quality offer than 30 offers above.
3499 // It will not be consumed because of AMM offers limit.
3500 env(offer(alice, EUR(140), XRP(100)));
3501 env(pay(bob, carol, USD(100)),
3504 txflags(tfPartialPayment | tfNoRippleDirect));
3505 // Carol gets ~29.91USD because of the AMM offers limit
3506 BEAST_EXPECT(ammAlice.expectBalances(
3508 STAmount{
USD, UINT64_C(9
'970'089730807577), -12},
3509 ammAlice.tokens()));
3511 env,
carol, STAmount{
USD, UINT64_C(30
'029'91026919241), -11}));
3515 testAMM([&](AMM& ammAlice, Env& env) {
3516 env.fund(
XRP(1
'000), bob);
3517 fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
3518 env(trust(alice, EUR(200)));
3519 for (int i = 0; i < 29; ++i)
3520 env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
3521 // This is worse quality offer than 30 offers above.
3522 // It will not be consumed because of AMM offers limit.
3523 env(offer(alice, EUR(140), XRP(100)));
3524 env(pay(bob, carol, USD(100)),
3527 txflags(tfPartialPayment | tfNoRippleDirect));
3528 BEAST_EXPECT(ammAlice.expectBalances(
3529 XRPAmount{10'101
'010'102},
USD(9
'900), ammAlice.tokens()));
3530 // Carol gets ~100USD
3531 BEAST_EXPECT(expectLine(
3532 env, carol, STAmount{USD, UINT64_C(30'099
'99999999999), -11}));
3533 BEAST_EXPECT(expectOffers(
3537 {{{STAmount{EUR, 39'1858572, -7}, XRPAmount{27
'989'898}}}}));
3546 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
3548 BEAST_EXPECT(ammAlice.expectBalances(
3549 XRPAmount{10
'049'825
'373},
3550 STAmount{USD, UINT64_C(10'049
'92586949302), -11},
3551 ammAlice.tokens()));
3552 BEAST_EXPECT(expectOffers(
3556 {{{XRPAmount{50'074
'629},
3557 STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
3561 // Individually frozen account
3562 testAMM([&](AMM& ammAlice, Env& env) {
3563 env(trust(gw, carol["USD"](0), tfSetFreeze));
3564 env(trust(gw, alice["USD"](0), tfSetFreeze));
3566 env(pay(alice, carol, USD(1)),
3569 txflags(tfNoRippleDirect | tfPartialPayment),
3577 testcase("AMM Tokens");
3578 using namespace jtx;
3580 // Offer crossing with AMM LPTokens and XRP.
3581 testAMM([&](AMM& ammAlice, Env& env) {
3582 auto const token1 = ammAlice.lptIssue();
3583 auto priceXRP = withdrawByTokens(
3584 STAmount{XRPAmount{10'000
'000'000}},
3586 STAmount{token1, 5
'000'000},
3589 env(
offer(
carol, STAmount{token1, 5
'000'000}, priceXRP));
3591 env(
offer(
alice, priceXRP, STAmount{token1, 5
'000'000}));
3593 BEAST_EXPECT(ammAlice.expectBalances(
3594 XRP(10
'000), USD(10'000), IOUAmount{10
'000'000}));
3596 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{5
'000'000}));
3597 BEAST_EXPECT(ammAlice.expectLPTokens(
alice, IOUAmount{5
'000'000}));
3599 ammAlice.vote(
carol, 1
'000);
3600 BEAST_EXPECT(ammAlice.expectTradingFee(500));
3601 ammAlice.vote(carol, 0);
3602 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3604 ammAlice.bid(carol, 100);
3605 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999
'900}));
3606 BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
3607 BEAST_EXPECT(accountBalance(env, carol) == "22499999960");
3608 priceXRP = withdrawByTokens(
3609 STAmount{XRPAmount{10'000
'000'000}},
3610 STAmount{token1, 9
'999'900},
3611 STAmount{token1, 4
'999'900},
3614 ammAlice.withdrawAll(
carol,
XRP(0));
3616 BEAST_EXPECT(ammAlice.expectBalances(
3617 XRPAmount{10
'000'000
'000} - priceXRP,
3619 IOUAmount{5
'000'000}));
3620 BEAST_EXPECT(ammAlice.expectLPTokens(
alice, IOUAmount{5
'000'000}));
3621 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{0}));
3625 testAMM([&](AMM& ammAlice, Env& env) {
3626 ammAlice.deposit(
carol, 1
'000'000);
3628 AMM ammAlice1(env, alice, XRP(10'000),
EUR(10
'000));
3629 ammAlice1.deposit(carol, 1'000
'000);
3630 auto const token1 = ammAlice.lptIssue();
3631 auto const token2 = ammAlice1.lptIssue();
3632 env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
3633 txflags(tfPassive));
3635 BEAST_EXPECT(expectOffers(env, alice, 1));
3636 env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
3639 expectLine(env, alice, STAmount{token1, 10'000
'100}) &&
3640 expectLine(env, alice, STAmount{token2, 9'999
'900}));
3642 expectLine(env, carol, STAmount{token2, 1'000
'100}) &&
3643 expectLine(env, carol, STAmount{token1, 999'900}));
3651 testAMM([&](AMM& ammAlice, Env& env) {
3652 auto const token1 = ammAlice.lptIssue();
3653 env.trust(STAmount{token1, 2
'000'000},
carol);
3655 ammAlice.deposit(
carol, 1
'000'000);
3657 ammAlice.expectLPTokens(
alice, IOUAmount{10
'000'000, 0}) &&
3658 ammAlice.expectLPTokens(
carol, IOUAmount{1
'000'000, 0}));
3665 ammAlice.expectLPTokens(
alice, IOUAmount{9
'999'900, 0}) &&
3667 ammAlice.expectLPTokens(
carol, IOUAmount{1
'000'100, 0}));
3669 env.trust(STAmount{token1, 20
'000'000},
alice);
3675 ammAlice.expectLPTokens(
alice, IOUAmount{10
'000'000, 0}) &&
3676 ammAlice.expectLPTokens(
carol, IOUAmount{1
'000'000, 0}));
3683 testcase(
"Amendment");
3684 using namespace jtx;
3691 for (
auto const& feature : {noAMM, noNumber, noAMMAndNumber})
3693 Env env{*
this, feature};
3695 AMM amm(env, alice, XRP(1'000),
USD(1
'000), ter(temDISABLED));
3703 using namespace jtx;
3705 testAMM([&](AMM& ammAlice, Env& env) {
3706 auto const info = env.rpc(
3710 "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
3713 info[jss::result][jss::account_data][jss::Flags].asUInt();
3716 (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
3723 testcase("Rippling");
3724 using namespace jtx;
3726 // Rippling via AMM fails because AMM trust line has 0 limit.
3727 // Set up two issuers, A and B. Have each issue a token called TST.
3728 // Have another account C hold TST from both issuers,
3729 // and create an AMM for this pair.
3730 // Have a fourth account, D, create a trust line to the AMM for TST.
3731 // Send a payment delivering TST.AMM from C to D, using SendMax in
3732 // TST.A (or B) and a path through the AMM account. By normal
3733 // rippling rules, this would have caused the AMM's balances
3740 auto const TSTA = A[
"TST"];
3741 auto const TSTB = B[
"TST"];
3746 env.fund(XRP(10'000), B);
3748 env.fund(XRP(10'000), D);
3750 env.
trust(TSTA(10
'000), C);
3751 env.trust(TSTB(10'000), C);
3752 env(pay(A, C, TSTA(10
'000)));
3753 env(pay(B, C, TSTB(10'000)));
3754 AMM amm(env, C, TSTA(5
'000), TSTB(5'000));
3755 auto const ammIss =
Issue(TSTA.currency, amm.ammAccount());
3758 env(trust(D,
STAmount{ammIss, 10
'000}), ter(tecNO_PERMISSION));
3761 // The payment would fail because of above, but check just in case
3762 env(pay(C, D, STAmount{ammIss, 10}),
3764 path(amm.ammAccount()),
3765 txflags(tfPartialPayment | tfNoRippleDirect),
3773 testcase("AMMAndCLOB, offer quality change");
3774 using namespace jtx;
3775 auto const gw = Account("gw");
3776 auto const TST = gw["TST"];
3777 auto const LP1 = Account("LP1");
3778 auto const LP2 = Account("LP2");
3780 auto prep = [&](auto const& offerCb, auto const& expectCb) {
3782 env.fund(XRP(30'000
'000'000),
gw);
3783 env(offer(
gw,
XRP(11
'500'000
'000), TST(1'000
'000'000)));
3786 env.fund(XRP(10'000), LP2);
3787 env(offer(LP1, TST(25),
XRPAmount(287
'500'000)));
3792 env(offer(LP2, TST(25),
XRPAmount(287
'500'000)));
3805 [&](
Env& env) {
AMM amm(env, LP1, TST(25),
XRP(250)); },
3808 getAccountLines(env, LP2, TST)[
"lines"][0u][
"balance"]
3810 auto const offer = getAccountOffers(env, LP2)[
"offers"][0u];
3811 lp2TakerGets = offer[
"taker_gets"].asString();
3812 lp2TakerPays = offer[
"taker_pays"][
"value"].asString();
3819 XRPAmount{18
'095'133},
3820 STAmount{TST, UINT64_C(1
'68737984885388), -14}),
3821 txflags(tfPassive));
3826 getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
3828 auto const offer = getAccountOffers(env, LP2)["offers"][0u];
3829 BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
3831 lp2TakerPays == offer["taker_pays"]["value"].asString());
3838 testcase("Trading Fee");
3839 using namespace jtx;
3841 // Single Deposit, 1% fee
3843 [&](AMM& ammAlice, Env& env) {
3845 ammAlice.deposit(carol, USD(3'000));
3847 ammAlice.withdrawAll(carol, USD(3'000));
3848 BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
3851 ammAlice.vote(alice, 1'000);
3852 BEAST_EXPECT(ammAlice.expectTradingFee(1
'000));
3853 // Carol gets fewer LPToken ~994, because of the single deposit
3855 ammAlice.deposit(carol, USD(3'000));
3856 BEAST_EXPECT(ammAlice.expectLPTokens(
3858 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
3860 ammAlice.vote(alice, 0);
3861 ammAlice.withdrawAll(carol, USD(0));
3863 BEAST_EXPECT(expectLine(
3866 STAmount{USD, UINT64_C(29
'994'96220068281), -11}));
3868 {{
USD(1
'000), EUR(1'000)}});
3873 [&](AMM& ammAlice, Env& env) {
3874 auto const balance = env.balance(
carol,
USD);
3875 auto tokensFee = ammAlice.deposit(
3876 carol,
USD(1
'000), std::nullopt, STAmount{USD, 1, -1});
3877 auto const deposit = balance - env.balance(carol, USD);
3878 ammAlice.withdrawAll(carol, USD(0));
3879 ammAlice.vote(alice, 0);
3880 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3881 auto const tokensNoFee = ammAlice.deposit(carol, deposit);
3882 // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
3884 BEAST_EXPECT(tokensFee == IOUAmount(485'636
'0611129, -7));
3885 BEAST_EXPECT(tokensNoFee == IOUAmount(487'644
'85901109, -8));
3893 [&](AMM& ammAlice, Env& env) {
3894 auto const balance = env.balance(
carol,
USD);
3895 auto const tokensFee = ammAlice.deposit(
3896 carol,
USD(200), std::nullopt, STAmount{
USD, 2020, -6});
3897 auto const deposit = balance - env.balance(
carol,
USD);
3898 ammAlice.withdrawAll(
carol,
USD(0));
3899 ammAlice.vote(
alice, 0);
3900 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3901 auto const tokensNoFee = ammAlice.deposit(
carol, deposit);
3904 BEAST_EXPECT(tokensFee == IOUAmount(98
'000'00000002, -8));
3905 BEAST_EXPECT(tokensNoFee == IOUAmount(98
'475'81871545, -8));
3910 // Single Withdrawal, 1% fee
3912 [&](AMM& ammAlice, Env& env) {
3914 ammAlice.deposit(carol, USD(3'000));
3916 BEAST_EXPECT(ammAlice.expectLPTokens(
carol, IOUAmount{1
'000}));
3917 BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
3919 ammAlice.vote(alice, 1
'000);
3920 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3922 ammAlice.withdrawAll(carol, USD(0));
3923 BEAST_EXPECT(expectLine(
3926 STAmount{USD, UINT64_C(29
'994'97487437186), -11}));
3928 {{
USD(1
'000), EUR(1'000)}});
3932 [&](AMM& ammAlice, Env& env) {
3933 ammAlice.deposit(
carol, 1
'000'000);
3934 auto const tokensFee = ammAlice.withdraw(
3935 carol,
USD(100), std::nullopt, IOUAmount{520, 0});
3937 auto const balanceAfterWithdraw =
3938 STAmount(
USD, UINT64_C(30
'443'43891402715), -11);
3939 BEAST_EXPECT(env.balance(
carol,
USD) == balanceAfterWithdraw);
3941 auto const deposit = balanceAfterWithdraw -
USD(29
'000);
3942 ammAlice.deposit(carol, deposit);
3944 ammAlice.vote(alice, 0);
3945 BEAST_EXPECT(ammAlice.expectTradingFee(0));
3946 auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
3948 env.balance(carol, USD) ==
3949 STAmount(USD, UINT64_C(30'443
'43891402717), -11));
3950 // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
3952 BEAST_EXPECT(tokensNoFee == IOUAmount(746'579
'80779913, -8));
3953 BEAST_EXPECT(tokensFee == IOUAmount(750'588
'23529411, -8));
3960 [&](AMM& ammAlice, Env& env) {
3966 {USD(1'000),
EUR(1
'000)},
3968 // Alice contributed 1010EUR and 1000USD to the pool
3969 BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
3971 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
3980 BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
3984 ammAlice.vote(alice, 1'000);
3985 BEAST_EXPECT(ammAlice.expectTradingFee(1
'000));
3986 // Bob pays to Carol with 1% fee
3987 env(pay(bob, carol, USD(10)),
3990 txflags(tfNoRippleDirect));
3992 // Bob sends 10.1~EUR to pay 10USD
3993 BEAST_EXPECT(expectLine(
3994 env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
3997 BEAST_EXPECT(ammAlice.expectBalances(
3999 STAmount{
EUR, UINT64_C(1
'010'10101010101), -11},
4000 ammAlice.tokens()));
4002 {{
USD(1
'000), EUR(1'010)}});
4006 [&](AMM& ammAlice, Env& env) {
4011 BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
4016 ammAlice.vote(
alice, 500);
4017 BEAST_EXPECT(ammAlice.expectTradingFee(500));
4025 STAmount{
USD, UINT64_C(29
'995'02512562814), -11}));
4029 STAmount{
EUR, UINT64_C(30
'004'97487437186), -11}));
4035 STAmount{
EUR, UINT64_C(5
'025125628140703), -15},
4036 STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
4037 BEAST_EXPECT(ammAlice.expectBalances(
4038 STAmount{USD, UINT64_C(1
'004'974874371859), -12},
4039 STAmount{EUR, UINT64_C(1
'005'025125628141), -12},
4040 ammAlice.tokens()));
4042 {{
USD(1
'000), EUR(1'010)}});
4050 Account
const ed(
"ed");
4056 {USD(2'000),
EUR(2
'000)});
4057 env(offer(carol, EUR(5), USD(5)));
4058 AMM ammAlice(env, alice, USD(1'005),
EUR(1
'000));
4059 env(pay(bob, ed, USD(10)),
4062 txflags(tfNoRippleDirect));
4063 BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
4065 BEAST_EXPECT(ammAlice.expectBalances(
4066 USD(1'000),
EUR(1
'005), ammAlice.tokens()));
4067 BEAST_EXPECT(expectOffers(env, carol, 0));
4070 // Payment with AMM and CLOB offer. Same as above but with 0.25% fee.
4073 Account const ed("ed");
4077 {alice, bob, carol, ed},
4079 {
USD(2
'000), EUR(2'000)});
4082 AMM ammAlice(env,
alice,
USD(1
'005), EUR(1'000),
false, 250);
4088 BEAST_EXPECT(expectLine(
4089 env, bob, STAmount{EUR, UINT64_C(1'989
'987453007618), -12}));
4090 BEAST_EXPECT(ammAlice.expectBalances(
4092 STAmount{
EUR, UINT64_C(1
'005'012546992382), -12},
4093 ammAlice.tokens()));
4102 Account
const ed(
"ed");
4108 {USD(2'000),
EUR(2
'000)});
4109 env(offer(carol, EUR(10), USD(10)));
4111 AMM ammAlice(env, alice, USD(1'005),
EUR(1
'000), false, 1'000);
4117 BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
4118 BEAST_EXPECT(ammAlice.expectBalances(
4119 USD(1
'005), EUR(1'000), ammAlice.tokens()));
4129 Account
const ed(
"ed");
4135 {USD(2'000),
EUR(2
'000)});
4136 env(offer(carol, EUR(9), USD(9)));
4138 AMM ammAlice(env, alice, USD(1'005),
EUR(1
'000), false, 1'000);
4144 BEAST_EXPECT(expectLine(
4145 env, bob, STAmount{EUR, UINT64_C(1'989
'993923296712), -12}));
4146 BEAST_EXPECT(ammAlice.expectBalances(
4148 STAmount{
EUR, UINT64_C(1
'001'006076703288), -12},
4149 ammAlice.tokens()));
4157 testcase(
"Adjusted Deposit/Withdraw Tokens");
4159 using namespace jtx;
4170 Account const nataly(
"nataly");
4174 {
bob, ed, paul, dan, chris, simon, ben, nataly},
4177 for (
int i = 0; i < 10; ++i)
4190 ammAlice.withdrawAll(carol, USD(0));
4191 ammAlice.deposit(ed, USD(10'000));
4194 ammAlice.withdrawAll(paul, USD(0));
4195 ammAlice.deposit(nataly, USD(1'000
'000));
4196 ammAlice.withdrawAll(nataly, USD(0));
4198 // Due to round off some accounts have a tiny gain, while
4199 // other have a tiny loss. The last account to withdraw
4200 // gets everything in the pool.
4201 BEAST_EXPECT(ammAlice.expectBalances(
4214 env, nataly,
STAmount{
USD, UINT64_C(1
'500'000
'000000002), -9}));
4215 ammAlice.withdrawAll(alice);
4216 BEAST_EXPECT(!ammAlice.ammExists());
4217 BEAST_EXPECT(expectLine(
4218 env, alice, STAmount{USD, UINT64_C(30'000
'0000000013), -10}));
4219 // alice XRP balance is 30,000initial - 50 ammcreate fee - 10drops
4221 BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
4224 // Same as above but deposit/withdraw in XRP
4225 testAMM([&](AMM& ammAlice, Env& env) {
4226 Account const bob("bob");
4227 Account const ed("ed");
4228 Account const paul("paul");
4229 Account const dan("dan");
4230 Account const chris("chris");
4231 Account const simon("simon");
4232 Account const ben("ben");
4233 Account const nataly("nataly");
4237 {bob, ed, paul, dan, chris, simon, ben, nataly},
4241 for (int i = 0; i < 10; ++i)
4243 ammAlice.deposit(ben, XRPAmount{1});
4244 ammAlice.withdrawAll(ben, XRP(0));
4245 ammAlice.deposit(simon, XRPAmount(1'000));
4254 ammAlice.withdrawAll(carol, XRP(0));
4255 ammAlice.deposit(ed, XRP(10'000));
4258 ammAlice.withdrawAll(paul, XRP(0));
4259 ammAlice.deposit(nataly, XRP(1'000
'000));
4260 ammAlice.withdrawAll(nataly, XRP(0));
4262 // No round off with XRP in this test
4263 BEAST_EXPECT(ammAlice.expectBalances(
4264 XRP(10'000),
USD(10
'000), IOUAmount{10'000
'000}));
4265 ammAlice.withdrawAll(alice);
4266 BEAST_EXPECT(!ammAlice.ammExists());
4267 // 20,000 initial - (deposit+withdraw) * 10
4268 auto const xrpBalance = (XRP(2'000
'000) - txfee(env, 20)).getText();
4269 BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
4270 BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
4271 BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
4272 BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
4273 // 30,000 initial - (deposit+withdraw) * 10
4274 BEAST_EXPECT(accountBalance(env, carol) == "29999999800");
4275 BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
4276 BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
4277 BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
4278 // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
4279 BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
4286 testcase("Auto Delete");
4288 using namespace jtx;
4289 FeatureBitset const all{supported_amendments()};
4294 envconfig([](std::unique_ptr<Config> cfg) {
4295 cfg->FEES.reference_fee = XRPAmount(1);
4299 fund(env, gw, {alice}, XRP(20'000), {
USD(10
'000)});
4300 AMM amm(env, gw, XRP(10'000),
USD(10
'000));
4301 for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i)
4303 Account const a{std::to_string(i)};
4304 env.fund(XRP(1'000), a);
4305 env(trust(a,
STAmount{amm.lptIssue(), 10
'000}));
4308 // The trustlines are partially deleted,
4309 // AMM is set to an empty state.
4310 amm.withdrawAll(gw);
4311 BEAST_EXPECT(amm.ammExists());
4313 // Bid,Vote,Deposit,Withdraw,SetTrust failing with
4314 // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option.
4332 alice, 100, std::nullopt, std::nullopt, ter(tecAMM_EMPTY));
4340 env(trust(alice, STAmount{amm.lptIssue(), 10'000}),
4355 amm.expectBalances(XRP(10'000),
USD(10
'000), amm.tokens()));
4356 BEAST_EXPECT(amm.expectTradingFee(1'000));
4357 BEAST_EXPECT(amm.expectAuctionSlot(100, 0,
IOUAmount{0}));
4361 amm.withdrawAll(
alice);
4362 BEAST_EXPECT(!amm.ammExists());
4374 fund(env,
gw, {
alice}, XRP(20
'000), {USD(10'000)});
4380 env(trust(a, STAmount{amm.lptIssue(), 10'000}));
4384 amm.withdrawAll(
gw);
4385 BEAST_EXPECT(
amm.ammExists());
4389 BEAST_EXPECT(
amm.ammExists());
4392 BEAST_EXPECT(!
amm.ammExists());
4400 testcase(
"Clawback");
4401 using namespace jtx;
4404 env.fund(XRP(2'000),
alice);
4405 AMM amm(env,
gw,
XRP(1
'000), USD(1'000));
4413 using namespace jtx;
4415 amm.setClose(
false);
4416 auto const info = env.
rpc(
4420 "{\"account\": \"" +
to_string(amm.ammAccount()) +
"\"}"));
4424 info[jss::result][jss::account_data][jss::AMMID]
4425 .asString() == to_string(amm.ammID()));
4431 amm.deposit(
carol, 1
'000);
4432 auto affected = env.meta()->getJson(
4433 JsonOptions::none)[sfAffectedNodes.fieldName];
4437 for (auto const& node : affected)
4439 if (node.isMember(sfModifiedNode.fieldName) &&
4440 node[sfModifiedNode.fieldName]
4441 [sfLedgerEntryType.fieldName]
4442 .asString() == "AccountRoot" &&
4443 node[sfModifiedNode.fieldName][sfFinalFields.fieldName]
4445 .asString() == to_string(amm.ammAccount()))
4447 found = node[sfModifiedNode.fieldName]
4448 [sfFinalFields.fieldName][jss::AMMID]
4449 .asString() == to_string(amm.ammID());
4453 BEAST_EXPECT(found);
4465 testcase("Offer/Strand Selection");
4466 using namespace jtx;
4467 Account const ed("ed");
4468 Account const gw1("gw1");
4469 auto const ETH = gw1["ETH"];
4470 auto const CAN = gw1["CAN"];
4472 // These tests are expected to fail if the OwnerPaysFee feature
4473 // is ever supported. Updates will need to be made to AMM handling
4474 // in the payment engine, and these tests will need to be updated.
4476 auto prep = [&](Env& env, auto gwRate, auto gw1Rate) {
4477 fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {
USD(2
'000)});
4478 env.fund(XRP(2'000), gw1);
4483 {ETH(2
'000), CAN(2'000)},
4486 env(
rate(gw1, gw1Rate));
4490 for (
auto const& rates :
4505 for (
auto i = 0; i < 3; ++i)
4508 prep(env, rates.first, rates.second);
4510 if (i == 0 || i == 2)
4512 env(offer(ed, ETH(400),
USD(400)), txflags(
tfPassive));
4516 amm.emplace(env, ed,
USD(1
'000), ETH(1'000));
4524 BEAST_EXPECT(amm->expectBalances(
4525 USD(1
'000), ETH(1'000), amm->tokens()));
4528 q[i] = Quality(Amounts{
4532 // CLOB is better quality than AMM
4533 BEAST_EXPECT(q[0] > q[1]);
4534 // AMM is not selected with CLOB
4535 BEAST_EXPECT(q[0] == q[2]);
4537 // Offer crossing: AMM has the same spot price quality
4538 // as CLOB's offer and can
't generate a better quality offer.
4539 // The transfer fee in this case doesn't change the CLOB quality
4541 for (
auto i = 0; i < 3; ++i)
4544 prep(env, rates.first, rates.second);
4546 if (i == 0 || i == 2)
4552 amm.emplace(env, ed,
USD(1
'000), ETH(1'000));
4558 BEAST_EXPECT(
amm->expectBalances(
4559 USD(1
'000), ETH(1'000),
amm->tokens()));
4561 if (i == 0 || i == 2)
4570 env,
alice, 1, {Amounts{
USD(400), ETH(400)}}));
4581 for (
auto i = 0; i < 3; ++i)
4584 prep(env, rates.first, rates.second);
4586 if (i == 0 || i == 2)
4592 amm.emplace(env, ed,
USD(1
'000), ETH(1'000));
4600 BEAST_EXPECT(!
amm->expectBalances(
4601 USD(1
'000), ETH(1'000),
amm->tokens()));
4605 if (rates.first == 1.5)
4613 ETH, UINT64_C(378
'6327949540823), -13},
4616 UINT64_C(283'9745962155617),
4627 ETH, UINT64_C(325
'299461620749), -12},
4630 UINT64_C(243'9745962155617),
4635 q[i] = Quality(Amounts{
4639 // AMM is better quality
4640 BEAST_EXPECT(q[1] > q[0]);
4641 // AMM and CLOB produce better quality
4642 BEAST_EXPECT(q[2] > q[1]);
4645 // Same as the offer-crossing but reduced offer quality
4646 for (auto i = 0; i < 3; ++i)
4649 prep(env, rates.first, rates.second);
4650 std::optional<AMM> amm;
4651 if (i == 0 || i == 2)
4653 env(offer(ed, ETH(400), USD(250)), txflags(tfPassive));
4657 amm.emplace(env, ed, USD(1'000), ETH(1
'000));
4658 env(offer(alice, USD(250), ETH(400)));
4660 // AMM is selected in both cases
4663 BEAST_EXPECT(!amm->expectBalances(
4664 USD(1'000), ETH(1
'000), amm->tokens()));
4666 // Partially crosses, AMM is selected, CLOB fails limitQuality
4669 if (rates.first == 1.5)
4671 BEAST_EXPECT(expectOffers(
4672 env, ed, 1, {{Amounts{ETH(400), USD(250)}}}));
4673 BEAST_EXPECT(expectOffers(
4678 STAmount{USD, UINT64_C(40'5694150420947), -13},
4679 STAmount{ETH, UINT64_C(64
'91106406735152), -14},
4684 // Ed offer is partially crossed.
4685 BEAST_EXPECT(expectOffers(
4690 STAmount{ETH, UINT64_C(335'0889359326485), -13},
4691 STAmount{
USD, UINT64_C(209
'4305849579053), -13},
4693 BEAST_EXPECT(expectOffers(env, alice, 0));
4700 // Two book steps strand quality is 1.
4701 // AMM strand's best quality is
equal to AMM
's spot price
4702 // quality, which is 1. Both strands (steps) are adjusted
4703 // for the transfer fee in qualityUpperBound. In case
4704 // of two strands, AMM offers have better quality and are consumed
4705 // first, remaining liquidity is generated by CLOB offers.
4706 // Liquidity from two strands is better in this case than in case
4707 // of one strand with two book steps. Liquidity from one strand
4708 // with AMM has better quality than either one strand with two book
4709 // steps or two strands. It may appear unintuitive, but one strand
4710 // with AMM is optimized and generates one AMM offer, while in case
4711 // of two strands, multiple AMM offers are generated, which results
4712 // in slightly worse overall quality.
4714 std::array<Quality, 3> q;
4715 for (auto i = 0; i < 3; ++i)
4718 prep(env, rates.first, rates.second);
4719 std::optional<AMM> amm;
4721 if (i == 0 || i == 2)
4723 env(offer(ed, ETH(400), CAN(400)), txflags(tfPassive));
4724 env(offer(ed, CAN(400), USD(400))), txflags(tfPassive);
4729 amm.emplace(env, ed, ETH(1'000),
USD(1
'000));
4731 env(pay(carol, bob, USD(100)),
4737 BEAST_EXPECT(expectLine(env, bob, USD(2'100)));
4741 if (rates.first == 1.5)
4744 BEAST_EXPECT(
amm->expectBalances(
4745 STAmount{ETH, UINT64_C(1
'176'66038955758), -11},
4751 BEAST_EXPECT(
amm->expectBalances(
4753 ETH, UINT64_C(1
'179'540094339627), -12},
4754 STAmount{USD, UINT64_C(847
'7880529867501), -13},
4756 BEAST_EXPECT(expectOffers(
4763 UINT64_C(343'3179205198749),
4767 UINT64_C(343
'3179205198749),
4773 UINT64_C(362'2119470132499),
4777 UINT64_C(362
'2119470132499),
4782 q[i] = Quality(Amounts{
4783 ETH(2'000) - env.balance(carol, ETH),
4784 env.balance(bob, USD) - USD(2
'000)});
4786 BEAST_EXPECT(q[1] > q[0]);
4787 BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]);
4793 testFixDefaultInnerObj()
4795 testcase("Fix Default Inner Object");
4796 using namespace jtx;
4797 FeatureBitset const all{supported_amendments()};
4799 auto test = [&](FeatureBitset features,
4806 std::optional<std::uint16_t> extra = std::nullopt) {
4807 Env env(*this, features);
4808 fund(env, gw, {alice}, XRP(1'000), {USD(10)});
4814 {.tfee = tfee, .close = closeLedger});
4817 amm.withdraw(WithdrawArg{
4818 .account =
gw, .asset1Out =
USD(1), .err = ter(err2)});
4824 amm.vote(VoteArg{.account =
alice, .tfee = 20, .err = ter(err3)});
4825 amm.withdraw(WithdrawArg{
4826 .account =
gw, .asset1Out =
USD(2), .err = ter(err4)});