2#include <test/jtx/AMM.h>
3#include <test/jtx/AMMTest.h>
4#include <test/jtx/PathSet.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/sendmax.h>
8#include <xrpl/ledger/PaymentSandbox.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/STParsedJSON.h>
11#include <xrpl/tx/paths/AMMOffer.h>
12#include <xrpl/tx/paths/Flow.h>
13#include <xrpl/tx/paths/detail/StrandFlow.h>
14#include <xrpl/tx/transactors/dex/AMMContext.h>
15#include <xrpl/tx/transactors/dex/AMMUtils.h>
35 testcase(
"Incorrect Removal of Funded Offers");
48 Env env{*
this, features};
73 if (!features[fixAMMv1_1])
76 STAmount{BTC, UINT64_C(1'001'000000374812), -12},
USD(100'000), ammCarol.
tokens()));
81 STAmount{BTC, UINT64_C(1'001'000000374815), -12},
USD(100'000), ammCarol.
tokens()));
96 Env env{*
this, features};
101 auto const USD1 = gw1[
"USD"];
102 auto const USD2 = gw2[
"USD"];
107 env(
trust(
bob, USD1(1'000), tfSetNoRipple));
109 env(
trust(
bob, USD2(1'000), tfSetNoRipple));
112 env(
pay(gw1, dan, USD1(10'000)));
113 env(
pay(gw1,
bob, USD1(50)));
114 env(
pay(gw2,
bob, USD2(50)));
117 AMM const ammDan(env, dan,
XRP(10'000), USD1(10'000));
128 Env env{*
this, features};
133 auto const USD1 = gw1[
"USD"];
134 auto const USD2 = gw2[
"USD"];
137 env.fund(
XRP(20'000), dan);
143 env(
pay(gw1, dan, USD1(10'050)));
144 env(
pay(gw1,
bob, USD1(50)));
145 env(
pay(gw2,
bob, USD2(50)));
148 AMM const ammDan(env, dan,
XRP(10'000), USD1(10'050));
169 auto const startBalance =
XRP(1'000'000);
175 [&](
AMM& ammAlice,
Env& env) {
194 {{
XRP(10'100),
USD(10'000)}},
202 [&](
AMM& ammAlice,
Env& env) {
217 {{
XRP(10'100),
USD(10'000)}},
224 [&](
AMM& ammAlice,
Env& env) {
233 {{
XRP(10'100),
USD(10'000)}},
240 [&](
AMM& ammAlice,
Env& env) {
255 {{
XRP(11'000),
USD(9'000)}},
264 testcase(
"Offer Crossing with XRP, Normal order");
268 Env env{*
this, features};
277 auto const xrpTransferred =
XRPAmount{3'061'224'490};
280 BEAST_EXPECT(ammAlice.expectBalances(
281 XRP(150'000) + xrpTransferred,
USD(49),
IOUAmount{273'861'278752583, -8}));
292 testcase(
"Offer Crossing with Limit Override");
296 Env env{*
this, features};
311 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
314 jrr[jss::node][sfBalance.fieldName] ==
315 to_string((
XRP(200'000) -
XRP(3'000) - env.current()->fees().base * 1).xrp()));
321 testcase(
"Currency Conversion: Entire Offer");
325 Env env{*
this, features};
346 jrr[jss::node][sfBalance.fieldName] ==
347 to_string((
XRP(10'000) +
XRP(500) - env.current()->fees().base * 2).xrp()));
353 testcase(
"Currency Conversion: In Parts");
358 [&](
AMM& ammAlice,
Env& env) {
378 {{
XRP(10'000),
USD(10'000)}},
387 testcase(
"Cross Currency Payment: Start with XRP");
392 [&](
AMM& ammAlice,
Env& env) {
401 {{
XRP(10'000),
USD(10'100)}},
410 testcase(
"Cross Currency Payment: End with XRP");
415 [&](
AMM& ammAlice,
Env& env) {
425 {{
XRP(10'100),
USD(10'000)}},
434 testcase(
"Cross Currency Payment: Bridged");
438 Env env{*
this, features};
440 auto const gw1 =
Account{
"gateway_1"};
441 auto const gw2 =
Account{
"gateway_2"};
442 auto const dan =
Account{
"dan"};
443 auto const USD1 = gw1[
"USD"];
444 auto const EUR1 = gw2[
"EUR"];
453 env(
trust(dan, EUR1(1'000)));
459 env(
pay(gw2, dan, dan[
"EUR"](400)));
462 AMM const ammCarol(env,
carol, USD1(5'000),
XRP(50'000));
464 env(
offer(dan,
XRP(500), EUR1(50)));
468 jtp[0u][0u][jss::currency] =
"XRP";
472 XRP(49'700),
STAmount{USD1, UINT64_C(5'030'181086519115), -12}, ammCarol.
tokens()));
480 testcase(
"Offer Fees Consume Funds");
484 Env env{*
this, features};
486 auto const gw1 =
Account{
"gateway_1"};
487 auto const gw2 =
Account{
"gateway_2"};
488 auto const gw3 =
Account{
"gateway_3"};
491 auto const USD1 = gw1[
"USD"];
492 auto const USD2 = gw2[
"USD"];
493 auto const USD3 = gw3[
"USD"];
501 auto const starting_xrp =
502 XRP(100) + env.current()->fees().accountReserve(3) + env.current()->fees().base * 4;
504 env.fund(starting_xrp, gw1, gw2, gw3,
alice);
505 env.fund(
XRP(2'000),
bob);
516 AMM const ammBob(env,
bob,
XRP(1'000), USD1(1'200));
524 XRP(1'100),
STAmount{USD1, UINT64_C(1'090'909090909091), -12}, ammBob.
tokens()));
527 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"109.090909090909");
529 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
535 testcase(
"Offer Create, then Cross");
539 Env env{*
this, features};
558 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
564 testcase(
"Offer tfSell: Basic Sell");
569 [&](
AMM& ammAlice,
Env& env) {
578 {{
XRP(9'900),
USD(10'100)}},
587 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
591 Env env{*
this, features};
593 auto const starting_xrp =
XRP(100) +
reserve(env, 1) + env.current()->fees().base * 2;
595 env.fund(starting_xrp,
gw,
alice);
596 env.fund(
XRP(2'000),
bob);
620 testcase(
"Client Issue: Gateway Cross Currency");
624 Env env{*
this, features};
626 auto const XTS =
gw[
"XTS"];
627 auto const XXX =
gw[
"XXX"];
629 auto const starting_xrp =
XRP(100.1) +
reserve(env, 1) + env.current()->fees().base * 2;
630 fund(env,
gw, {
alice,
bob}, starting_xrp, {XTS(100), XXX(100)}, Fund::All);
632 AMM const ammAlice(env,
alice, XTS(100), XXX(100));
636 payment[jss::id] = env.seq(
bob);
637 payment[jss::build_path] =
true;
639 payment[jss::tx_json][jss::Sequence] =
641 payment[jss::tx_json][jss::Fee] =
to_string(env.current()->fees().base);
643 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
644 auto const jrr = env.rpc(
"json",
"submit",
to_string(payment));
645 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
646 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
647 if (!features[fixAMMv1_1])
649 BEAST_EXPECT(ammAlice.expectBalances(
650 STAmount(XTS, UINT64_C(101'010101010101), -12), XXX(99), ammAlice.tokens()));
655 BEAST_EXPECT(ammAlice.expectBalances(
656 STAmount(XTS, UINT64_C(101'0101010101011), -13), XXX(99), ammAlice.tokens()));
670 Env env{*
this, features};
688 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'100),
USD(10'000), ammAlice.tokens()));
696 Env env{*
this, features};
716 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'100),
USD(10'000), ammAlice.tokens()));
724 Env env{*
this, features};
757 testcase(
"Combine tfSell with tfFillOrKill");
765 Env env{*
this, features};
771 BEAST_EXPECT(ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
775 Env env{*
this, features};
782 BEAST_EXPECT(ammBob.expectBalances(
783 XRP(20'220),
STAmount{
USD, UINT64_C(197'8239366963403), -13}, ammBob.tokens()));
791 Env env{*
this, features};
798 BEAST_EXPECT(ammBob.expectBalances(
799 XRP(21'500),
STAmount{
USD, UINT64_C(186'046511627907), -12}, ammBob.tokens()));
810 Env env{*
this, features};
830 [&](
AMM& ammAlice,
Env& env) {
842 {{
XRP(10'000),
USD(10'100)}},
850 [&](
AMM& ammAlice,
Env& env) {
862 {{
XRP(10'100),
USD(10'000)}},
869 Env env{*
this, features};
900 Env env{*
this, features};
935 Env env{*
this, features};
978 Env env{*
this, features};
1023 using namespace jtx;
1025 Env env{*
this, features};
1027 auto const USD_bob =
bob[
"USD"];
1028 auto const f = env.current()->fees().base;
1032 AMM const ammBob(env,
bob,
XRP(10'000), USD_bob(10'100));
1050 using namespace jtx;
1052 Env env{*
this, features};
1055 auto const fee = env.current()->fees().base;
1058 auto const ann =
Account(
"ann");
1059 auto const A_BUX = ann[
"BUX"];
1061 auto const cam =
Account(
"cam");
1062 auto const dan =
Account(
"dan");
1063 auto const D_BUX = dan[
"BUX"];
1071 env(
trust(cam, D_BUX(100)));
1073 env(
pay(dan,
bob, D_BUX(100)));
1089 AMM const ammBob(env,
bob, A_BUX(30), D_BUX(30));
1091 env(
trust(ann, D_BUX(100)));
1117 using namespace jtx;
1119 Env env{*
this, features};
1121 auto const ann =
Account(
"ann");
1123 auto const cam =
Account(
"cam");
1125 auto const A_BUX = ann[
"BUX"];
1126 auto const B_BUX =
bob[
"BUX"];
1128 auto const fee = env.current()->fees().base;
1133 env(
trust(ann, B_BUX(40)));
1134 env(
trust(cam, A_BUX(40)));
1136 env(
trust(cam, B_BUX(40)));
1141 env(
pay(ann, cam, A_BUX(35)));
1142 env(
pay(
bob, cam, B_BUX(35)));
1146 AMM const ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1151 env(
offer(cam, A_BUX(29), B_BUX(30), tfPassive));
1153 env.require(
balance(cam, A_BUX(35)));
1154 env.require(
balance(cam, B_BUX(35)));
1155 env.require(
offers(cam, 1));
1158 env(
offer(cam, B_BUX(30), A_BUX(30)));
1161 if (!features[fixAMMv1_1])
1164 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1165 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1172 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1173 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1178 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1179 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1186 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1187 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1196 using namespace jtx;
1198 Env env{*
this, features};
1200 auto const aliceUSD =
alice[
"USD"];
1201 auto const bobUSD =
bob[
"USD"];
1207 env(
fset(
gw, asfRequireAuth));
1244 using namespace jtx;
1246 Env env{*
this, features};
1256 env(
fset(
gw, asfRequireAuth));
1308 using namespace jtx;
1346 using namespace jtx;
1359 BEAST_EXPECT(st.
empty());
1363 BEAST_EXPECT(sa ==
XRP(100'000'000));
1366 BEAST_EXPECT(
equal(da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1376 using namespace jtx;
1379 auto const AUD =
gw[
"AUD"];
1401 using namespace jtx;
1402 auto const charlie =
Account(
"charlie");
1407 AMM const ammCharlie(env, charlie,
XRP(10),
USD(11));
1409 BEAST_EXPECT(sa ==
XRP(1));
1411 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1413 auto const& pathElem = st[0][0];
1415 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1423 AMM const ammCharlie(env, charlie,
XRP(11),
USD(10));
1426 BEAST_EXPECT(sa ==
USD(1));
1428 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1430 auto const& pathElem = st[0][0];
1432 pathElem.isOffer() && pathElem.getIssuerID() ==
xrpAccount() &&
1441 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1442 using namespace jtx;
1454 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1458 env.
trust(G1[
"XYZ"](5'000), A1);
1459 env.
trust(G3[
"ABC"](5'000), A1);
1460 env.
trust(G2[
"XYZ"](5'000), A2);
1461 env.
trust(G3[
"ABC"](5'000), A2);
1462 env.
trust(A2[
"ABC"](1'000), A3);
1463 env.
trust(G1[
"XYZ"](100'000), M1);
1464 env.
trust(G2[
"XYZ"](100'000), M1);
1465 env.
trust(G3[
"ABC"](100'000), M1);
1468 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1469 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1470 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1471 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1472 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1475 AMM const ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1476 AMM const ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1482 auto const& send_amt =
XRP(10);
1484 BEAST_EXPECT(
equal(da, send_amt));
1485 BEAST_EXPECT(st.
empty());
1491 auto const& send_amt =
XRP(200);
1494 BEAST_EXPECT(
equal(da, send_amt));
1495 BEAST_EXPECT(st.
empty());
1499 auto const& send_amt = G3[
"ABC"](10);
1501 BEAST_EXPECT(
equal(da, send_amt));
1507 auto const& send_amt = A2[
"ABC"](1);
1509 BEAST_EXPECT(
equal(da, send_amt));
1515 auto const& send_amt = A3[
"ABC"](1);
1517 BEAST_EXPECT(
equal(da, send_amt));
1526 testcase(
"Path Find: non-XRP -> XRP");
1527 using namespace jtx;
1534 env.
fund(
XRP(1'000), A1, A2, G3);
1538 env.
trust(G3[
"ABC"](1'000), A1, A2);
1539 env.
trust(G3[
"ABC"](100'000), M1);
1542 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1543 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1544 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1547 AMM const ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1552 auto const& send_amt =
XRP(10);
1554 BEAST_EXPECT(
equal(da, send_amt));
1555 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1562 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1563 using namespace jtx;
1576 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1578 env.
fund(
XRP(21'000), M1, M2);
1581 env.
trust(G1[
"HKD"](2'000), A1);
1582 env.
trust(G2[
"HKD"](2'000), A2);
1583 env.
trust(G1[
"HKD"](2'000), A3);
1584 env.
trust(G1[
"HKD"](100'000), M1);
1585 env.
trust(G2[
"HKD"](100'000), M1);
1586 env.
trust(G1[
"HKD"](100'000), M2);
1587 env.
trust(G2[
"HKD"](100'000), M2);
1590 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1591 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1592 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1593 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1594 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1595 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1596 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1599 AMM const ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1600 AMM const ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1601 AMM const ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1609 auto const& send_amt = G1[
"HKD"](10);
1612 BEAST_EXPECT(st.
empty());
1613 BEAST_EXPECT(
equal(da, send_amt));
1614 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1620 auto const& send_amt = A1[
"HKD"](10);
1623 BEAST_EXPECT(st.
empty());
1624 BEAST_EXPECT(
equal(da, send_amt));
1625 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1631 auto const& send_amt = A3[
"HKD"](10);
1634 BEAST_EXPECT(
equal(da, send_amt));
1635 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1642 auto const& send_amt = G2[
"HKD"](10);
1645 BEAST_EXPECT(
equal(da, send_amt));
1646 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1658 auto const& send_amt = G2[
"HKD"](10);
1661 BEAST_EXPECT(
equal(da, send_amt));
1662 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1675 auto const& send_amt = A2[
"HKD"](10);
1678 BEAST_EXPECT(
equal(da, send_amt));
1679 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1692 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1693 using namespace jtx;
1703 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1706 env.
trust(G1[
"HKD"](2'000), A1);
1707 env.
trust(G2[
"HKD"](2'000), A2);
1708 env.
trust(A2[
"HKD"](2'000), A3);
1709 env.
trust(G1[
"HKD"](100'000), M1);
1710 env.
trust(G2[
"HKD"](100'000), M1);
1713 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1714 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1715 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1716 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1719 AMM const ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1723 auto const& send_amt = A2[
"HKD"](10);
1727 BEAST_EXPECT(
equal(da, send_amt));
1728 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1737 using namespace jtx;
1739 Env env(*
this, features);
1745 auto const ammXrpPool = env.
current()->fees().increment * 2;
1762 AMM const ammBob(env,
bob, ammXrpPool,
USD(150));
1767 txflags(tfNoRippleDirect | tfPartialPayment));
1770 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1778 using namespace jtx;
1782 Env env(*
this, features);
1794 BEAST_EXPECT(ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1798 Env env(*
this, features);
1811 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1816 Env env(*
this, features);
1828 BEAST_EXPECT(ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
1832 Env env(*
this, features);
1844 BEAST_EXPECT(ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
1848 Env env(*
this, features);
1897 Env env(*
this, features);
1930 auto const flowResult = [&] {
1945 paths.push_back(p1);
1948 paths.push_back(p2);
1967 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
1969 if (flowResult.removableOffers.empty())
1972 for (
auto const& o : flowResult.removableOffers)
1996 Env env(*
this, features);
2016 txflags(tfNoRippleDirect | tfPartialPayment));
2019 BEAST_EXPECT(ammBob.expectBalances(
USD(8.4),
XRPAmount{20}, ammBob.tokens()));
2027 using namespace jtx;
2031 Env env(*
this, features);
2042 txflags(tfNoRippleDirect | tfPartialPayment));
2048 if (!features[fixAMMv1_1])
2051 BEAST_EXPECT(amm.expectBalances(
2052 GBP(1'120),
STAmount{
USD, UINT64_C(892'8571428571428), -13}, amm.tokens()));
2056 BEAST_EXPECT(amm.expectBalances(
2057 GBP(1'120),
STAmount{
USD, UINT64_C(892'8571428571429), -13}, amm.tokens()));
2067 Env env(*
this, features);
2083 txflags(tfNoRippleDirect | tfPartialPayment));
2096 BEAST_EXPECT(amm.expectBalances(
2097 EUR(1'096),
STAmount{
USD, UINT64_C(912'4087591240876), -13}, amm.tokens()));
2104 Env env(*
this, features);
2113 AMM const amm2(env, ed,
EUR(1'000),
USD(1'000));
2118 txflags(tfNoRippleDirect | tfPartialPayment));
2122 if (!features[fixAMMv1_1])
2156 Env env(*
this, features);
2167 BEAST_EXPECT(amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2175 Env env(*
this, features);
2188 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2197 BEAST_EXPECT(amm.expectBalances(
2198 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
USD(875), amm.tokens()));
2206 Env env(*
this, features);
2219 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2222 if (!features[fixAMMv1_1])
2229 BEAST_EXPECT(amm.expectBalances(
GBP(1'024),
USD(1'171.875), amm.tokens()));
2239 BEAST_EXPECT(amm.expectBalances(
2240 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2251 Env env(*
this, features);
2269 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2272 if (!features[fixAMMv1_1])
2285 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2292 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2295 BEAST_EXPECT(amm.expectBalances(
2296 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2297 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2313 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2320 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2323 BEAST_EXPECT(amm.expectBalances(
2324 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2325 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2335 Env env(*
this, features);
2353 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2356 if (!features[fixAMMv1_1])
2366 BEAST_EXPECT(amm.expectBalances(
2367 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2368 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2381 BEAST_EXPECT(amm.expectBalances(
2382 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2383 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2392 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2399 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2407 Env env(*
this, features);
2416 AMM const amm2(env, ed,
EUR(1'000),
USD(1'400));
2423 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2426 if (!features[fixAMMv1_1])
2435 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2436 STAmount{EUR, UINT64_C(920'78937795562), -11},
2441 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2442 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2454 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2455 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2460 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2461 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2471 Env env(*
this, features);
2485 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2488 if (!features[fixAMMv1_1])
2492 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
2493 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2498 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
2499 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2506 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
2507 STAmount{EUR, UINT64_C(902'4064171122975), -13},
2512 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
2513 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2530 using namespace jtx;
2543 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2545 BEAST_EXPECT(ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
2556 using namespace jtx;
2594 auto const JPY =
gw[
"JPY"];
2602 AMM const ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
2617 using namespace jtx;
2618 Env env(*
this, features);
2619 auto const dan =
Account(
"dan");
2620 auto const ed =
Account(
"ed");
2631 AMM const ammEd(env, ed,
XRP(9),
USD(11));
2636 if (!features[fixAMMv1_1])
2664 testcase(
"Convert all of an asset using DeliverMin");
2666 using namespace jtx;
2669 Env env(*
this, features);
2702 Env env(*
this, features);
2716 Env env(*
this, features);
2737 auto const dan =
Account(
"dan");
2738 Env env(*
this, features);
2746 AMM const ammDan(env, dan,
XRP(1'000),
USD(1'100));
2747 if (!features[fixAMMv1_1])
2769 STAmount{USD, UINT64_C(999'99999909091), -11},
2780 using namespace jtx;
2786 Env env(*
this, features);
2806 env(
fset(becky, asfDepositAuth));
2822 using namespace jtx;
2843 auto failedIouPayments = [
this, &env]() {
2865 failedIouPayments();
2882 failedIouPayments();
2889 failedIouPayments();
2911 using namespace test::jtx;
2912 Env env(*
this, features);
2925 env(
pay(G1,
bob, G1[
"USD"](10)));
2926 env(
pay(G1,
alice, G1[
"USD"](205)));
2929 AMM const ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
2935 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
2936 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
2937 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
2944 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
2945 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
2947 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
2960 env(
trust(G1,
bob[
"USD"](0), tfSetFreeze));
2987 for (
auto const& it :
lines[jss::lines])
2989 if (it[jss::account] ==
bob.
human())
2995 if (!BEAST_EXPECT(bobLine))
2997 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
2998 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3005 for (
auto const& it :
lines[jss::lines])
3007 if (it[jss::account] == G1.human())
3013 if (!BEAST_EXPECT(g1Line))
3015 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3016 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3022 env(
trust(G1,
bob[
"USD"](0), tfClearFreeze));
3026 auto ff = affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3029 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3030 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3040 using namespace test::jtx;
3041 Env env(*
this, features);
3051 env.
fund(
XRP(20'000), A2, A3, A4);
3054 env.
trust(G1[
"USD"](1'200), A1);
3055 env.
trust(G1[
"USD"](200), A2);
3056 env.
trust(G1[
"BTC"](100), A3);
3057 env.
trust(G1[
"BTC"](100), A4);
3060 env(
pay(G1, A1, G1[
"USD"](1'000)));
3061 env(
pay(G1, A2, G1[
"USD"](100)));
3062 env(
pay(G1, A3, G1[
"BTC"](100)));
3063 env(
pay(G1, A4, G1[
"BTC"](100)));
3066 AMM const ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3076 "book_offers",
std::string(
"USD/") + G1.human(),
"XRP")[jss::result][jss::offers];
3084 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3088 "book_offers",
"XRP",
std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3096 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3103 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3109 env(
pay(G1, A2, G1[
"USD"](1)));
3112 env(
pay(A2, G1, G1[
"USD"](1)));
3115 env(
pay(A2, A1, G1[
"USD"](1)));
3118 env(
pay(A1, A2, G1[
"USD"](1)));
3127 env(
fset(G1, asfGlobalFreeze));
3142 "book_offers",
"XRP",
std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3147 "book_offers",
std::string(
"USD/") + G1.human(),
"XRP")[jss::result][jss::offers];
3155 env(
pay(G1, A2, G1[
"USD"](1)));
3158 env(
pay(A2, G1, G1[
"USD"](1)));
3168 testcase(
"Offers for Frozen Trust Lines");
3170 using namespace test::jtx;
3171 Env env(*
this, features);
3178 env.
fund(
XRP(2'000), G1, A3, A4);
3182 env.
trust(G1[
"USD"](1'000), A2);
3183 env.
trust(G1[
"USD"](2'000), A3);
3184 env.
trust(G1[
"USD"](2'001), A4);
3187 env(
pay(G1, A3, G1[
"USD"](2'000)));
3188 env(
pay(G1, A4, G1[
"USD"](2'001)));
3191 AMM const ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3201 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3208 env(
trust(G1, a3am, tfSetFreeze));
3210 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3221 env(
trust(G1, A4[
"USD"](0), tfSetFreeze));
3225 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3237 testcase(
"Multisign AMM Transactions");
3239 using namespace jtx;
3240 Env env{*
this, features};
3257 msig const ms{becky, bogie};
3280 ammAlice.
vote({}, 1'000);
3283 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3294 using namespace jtx;
3298 Env env(*
this, features);
3302 AMM const bobXRP_USD(env,
bob,
XRP(1'000),
USD(1'000));
3303 AMM const bobUSD_EUR(env,
bob,
USD(1'000),
EUR(1'000));
3316 using namespace jtx;
3320 Env env(*
this, features);
3321 auto const BobUSD =
bob[
"USD"];
3322 auto const BobEUR =
bob[
"EUR"];
3327 fund(env,
bob, {
alice,
gw}, {BobUSD(100), BobEUR(100)}, Fund::IOUOnly);
3330 AMM const ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3333 AMM const ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3336 Path const p = [&] {
3348 txflags(tfNoRippleDirect | tfPartialPayment),
3353 Env env(*
this, features);
3367 Env env(*
this, features);
3386 using namespace jtx;
3388 auto const CNY =
gw[
"CNY"];
3391 Env env(*
this, features);
3412 Env env(*
this, features);
3426 AMM const ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
3452 using namespace jtx;
3469 using namespace jtx;
3481 using namespace jtx;
3504 using namespace test::jtx;
3545BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app,
xrpl, 1);
A generic endpoint for log messages.
testcase_t testcase
Memberspace for declaring test cases.
Floating point representation of amounts with high dynamic range.
A currency issued by an account.
Sets the new scale and restores the old scale when it leaves scope.
bool modify(modify_type const &f)
Modify the open ledger.
Writable ledger view that accumulates state and tx changes.
A wrapper which makes credits unavailable to balances.
Discardable, editable view to a ledger.
virtual beast::Journal getJournal(std::string const &name)=0
virtual OpenLedger & getOpenLedger()=0
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Path & push_back(Issue const &iss)
static FeatureBitset testable_amendments()
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> const &cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={testable_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
static XRPAmount ammCrtFee(jtx::Env &env)
Convenience class to test AMM functionality.
bool expectTradingFee(std::uint16_t fee) const
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
AccountID const & ammAccount() const
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Json::Value bid(BidArg const &arg)
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Immutable cryptographic account descriptor.
std::string const & human() const
Returns the human readable public key.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
void require(Args const &... args)
Check a set of requirements.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Sets the DeliverMin on a JTx.
Set a multisignature on a JTx.
Match clear account flags.
Match the number of items in the account's owner directory.
Set Paths, SendMax on a JTx.
Sets the QualityIn on a trust JTx.
Sets the QualityOut on a trust JTx as a percentage.
Check a set of conditions.
Sets the SendMax on a JTx.
Set the regular signature on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
@ arrayValue
array value (ordered list)
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Keylet account(AccountID const &id) noexcept
AccountID root.
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
Json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
PrettyAmount xrpMinusFee(Env const &env, std::int64_t xrpAmount)
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
bool expectHolding(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Json::Value ledgerEntryState(Env &env, Account const &acct_a, Account const &acct_b, std::string const ¤cy)
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
bool same(STPathSet const &st1, Args const &... args)
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
XRPAmount txfee(Env const &env, std::uint16_t n)
XRP_t const XRP
Converts to XRP Issue or STAmount.
STPathElement allPathElements(AccountID const &a, Issue const &iss)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
STPathElement IPE(Issue const &iss)
STPathElement cpe(Currency const &c)
Json::Value ledgerEntryRoot(Env &env, Account const &acct)
FeatureBitset testable_amendments()
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
bool checkArraySize(Json::Value const &val, unsigned int size)
STPath stpath(Args const &... args)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
void n_offers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
bool equal(std::unique_ptr< Step > const &s1, DirectStepInfo const &dsi)
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
std::string to_string(base_uint< Bits, Tag > const &a)
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Currency const & xrpCurrency()
XRP currency.
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
AccountID const & xrpAccount()
Compute AccountID from public key.
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Tests of AMM that use offers too.
void testRippleState(FeatureBitset features)
void testOfferFeesConsumeFunds(FeatureBitset features)
void testTransferRateOffer(FeatureBitset features)
void testCrossCurrencyBridged(FeatureBitset features)
void testOffersWhenFrozen(FeatureBitset features)
void testDirectToDirectPath(FeatureBitset features)
void testTransferRateNoOwnerFee(FeatureBitset features)
void testFillModes(FeatureBitset features)
void testSellFlagExceedLimit(FeatureBitset features)
void testRmFundedOffer(FeatureBitset features)
void testSelfIssueOffer(FeatureBitset features)
void test_convert_all_of_an_asset(FeatureBitset features)
void testStepLimit(FeatureBitset features)
void testSellFlagBasic(FeatureBitset features)
void testTxMultisign(FeatureBitset features)
void via_offers_via_gateway()
void testEnforceNoRipple(FeatureBitset features)
void testGlobalFreeze(FeatureBitset features)
void testLoop(FeatureBitset features)
void testBadPathAssert(FeatureBitset features)
void run() override
Runs the suite.
void testBridgedCross(FeatureBitset features)
void testCrossingLimits()
void testMissingAuth(FeatureBitset features)
void path_find_consume_all()
void testCurrencyConversionEntire(FeatureBitset features)
void testCurrencyConversionInParts(FeatureBitset features)
void testToStrand(FeatureBitset features)
NumberMantissaScaleGuard const sg_
void testCrossCurrencyEndXRP(FeatureBitset features)
void testFalseDry(FeatureBitset features)
void testOfferCreateThenCross(FeatureBitset features)
void testOfferCrossWithXRP(FeatureBitset features)
void testBookStep(FeatureBitset features)
void testGatewayCrossCurrency(FeatureBitset features)
void testSellWithFillOrKill(FeatureBitset features)
void testRequireAuth(FeatureBitset features)
void testPayment(FeatureBitset features)
void testRIPD1373(FeatureBitset features)
void testOfferCrossWithLimitOverride(FeatureBitset features)
void testCrossCurrencyStartXRP(FeatureBitset features)
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...
STAmount const & value() const