21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
23#include <test/jtx/PathSet.h>
24#include <test/jtx/amount.h>
25#include <test/jtx/sendmax.h>
27#include <xrpld/app/misc/AMMUtils.h>
28#include <xrpld/app/paths/AMMContext.h>
29#include <xrpld/app/paths/AMMOffer.h>
30#include <xrpld/app/paths/Flow.h>
31#include <xrpld/app/paths/detail/StrandFlow.h>
33#include <xrpl/ledger/PaymentSandbox.h>
34#include <xrpl/protocol/Feature.h>
35#include <xrpl/protocol/STParsedJSON.h>
52 testcase(
"Incorrect Removal of Funded Offers");
64 Env env{*
this, features};
71 {
USD(200'000),
BTC(2'000)});
94 if (!features[fixAMMv1_1])
97 STAmount{BTC, UINT64_C(1'001'000000374812), -12},
104 STAmount{BTC, UINT64_C(1'001'000000374815), -12},
121 Env env{*
this, features};
126 auto const USD1 = gw1[
"USD"];
127 auto const USD2 = gw2[
"USD"];
137 env(
pay(gw1, dan, USD1(10'000)));
138 env(
pay(gw1,
bob, USD1(50)));
139 env(
pay(gw2,
bob, USD2(50)));
142 AMM ammDan(env, dan,
XRP(10'000), USD1(10'000));
153 Env env{*
this, features};
158 auto const USD1 = gw1[
"USD"];
159 auto const USD2 = gw2[
"USD"];
162 env.fund(
XRP(20'000), dan);
168 env(
pay(gw1, dan, USD1(10'050)));
169 env(
pay(gw1,
bob, USD1(50)));
170 env(
pay(gw2,
bob, USD2(50)));
173 AMM ammDan(env, dan,
XRP(10'000), USD1(10'050));
180 XRP(10'050), USD1(10'000), ammDan.
tokens()));
196 auto const startBalance =
XRP(1'000'000);
202 [&](
AMM& ammAlice,
Env& env) {
228 {{
XRP(10'100),
USD(10'000)}},
236 [&](
AMM& ammAlice,
Env& env) {
252 {{
XRP(10'100),
USD(10'000)}},
259 [&](
AMM& ammAlice,
Env& env) {
269 {{
XRP(10'100),
USD(10'000)}},
276 [&](
AMM& ammAlice,
Env& env) {
291 {{
XRP(11'000),
USD(9'000)}},
300 testcase(
"Offer Crossing with XRP, Normal order");
304 Env env{*
this, features};
313 auto const xrpTransferred =
XRPAmount{3'061'224'490};
316 BEAST_EXPECT(ammAlice.expectBalances(
317 XRP(150'000) + xrpTransferred,
323 env,
bob,
XRP(300'000) - xrpTransferred -
txfee(env, 1)));
330 testcase(
"Offer Crossing with Limit Override");
334 Env env{*
this, features};
350 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
353 jrr[jss::node][sfBalance.fieldName] ==
355 (
XRP(200'000) -
XRP(3'000) - env.current()->fees().base * 1)
362 testcase(
"Currency Conversion: Entire Offer");
366 Env env{*
this, features};
388 jrr[jss::node][sfBalance.fieldName] ==
396 testcase(
"Currency Conversion: In Parts");
401 [&](
AMM& ammAlice,
Env& env) {
425 {{
XRP(10'000),
USD(10'000)}},
434 testcase(
"Cross Currency Payment: Start with XRP");
439 [&](
AMM& ammAlice,
Env& env) {
449 {{
XRP(10'000),
USD(10'100)}},
458 testcase(
"Cross Currency Payment: End with XRP");
463 [&](
AMM& ammAlice,
Env& env) {
474 {{
XRP(10'100),
USD(10'000)}},
483 testcase(
"Cross Currency Payment: Bridged");
487 Env env{*
this, features};
489 auto const gw1 =
Account{
"gateway_1"};
490 auto const gw2 =
Account{
"gateway_2"};
491 auto const dan =
Account{
"dan"};
492 auto const USD1 = gw1[
"USD"];
493 auto const EUR1 = gw2[
"EUR"];
502 env(
trust(dan, EUR1(1'000)));
508 env(
pay(gw2, dan, dan[
"EUR"](400)));
511 AMM ammCarol(env,
carol, USD1(5'000),
XRP(50'000));
513 env(
offer(dan,
XRP(500), EUR1(50)));
517 jtp[0u][0u][jss::currency] =
"XRP";
519 json(jss::Paths, jtp),
524 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
533 testcase(
"Offer Fees Consume Funds");
537 Env env{*
this, features};
539 auto const gw1 =
Account{
"gateway_1"};
540 auto const gw2 =
Account{
"gateway_2"};
541 auto const gw3 =
Account{
"gateway_3"};
544 auto const USD1 = gw1[
"USD"];
545 auto const USD2 = gw2[
"USD"];
546 auto const USD3 = gw3[
"USD"];
554 auto const starting_xrp =
XRP(100) +
555 env.current()->fees().accountReserve(3) +
556 env.current()->fees().base * 4;
558 env.fund(starting_xrp, gw1, gw2, gw3,
alice);
559 env.fund(
XRP(2'000),
bob);
570 AMM ammBob(env,
bob,
XRP(1'000), USD1(1'200));
579 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
584 jrr[jss::node][sfBalance.fieldName][jss::value] ==
588 jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
594 testcase(
"Offer Create, then Cross");
598 Env env{*
this, features};
619 jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
625 testcase(
"Offer tfSell: Basic Sell");
630 [&](
AMM& ammAlice,
Env& env) {
640 {{
XRP(9'900),
USD(10'100)}},
649 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
653 Env env{*
this, features};
655 auto const starting_xrp =
656 XRP(100) +
reserve(env, 1) + env.current()->fees().base * 2;
658 env.fund(starting_xrp,
gw,
alice);
659 env.fund(
XRP(2'000),
bob);
684 testcase(
"Client Issue: Gateway Cross Currency");
688 Env env{*
this, features};
690 auto const XTS =
gw[
"XTS"];
691 auto const XXX =
gw[
"XXX"];
693 auto const starting_xrp =
694 XRP(100.1) +
reserve(env, 1) + env.current()->fees().base * 2;
700 {XTS(100), XXX(100)},
703 AMM ammAlice(env,
alice, XTS(100), XXX(100));
707 payment[jss::id] = env.seq(
bob);
708 payment[jss::build_path] =
true;
710 payment[jss::tx_json][jss::Sequence] =
713 ->getFieldU32(sfSequence);
714 payment[jss::tx_json][jss::Fee] =
to_string(env.current()->fees().base);
715 payment[jss::tx_json][jss::SendMax] =
718 auto const jrr = env.rpc(
"json",
"submit",
to_string(payment));
719 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
720 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
721 if (!features[fixAMMv1_1])
723 BEAST_EXPECT(ammAlice.expectBalances(
724 STAmount(XTS, UINT64_C(101'010101010101), -12),
728 env,
bob,
STAmount{XTS, UINT64_C(98'989898989899), -12}));
732 BEAST_EXPECT(ammAlice.expectBalances(
733 STAmount(XTS, UINT64_C(101'0101010101011), -13),
737 env,
bob,
STAmount{XTS, UINT64_C(98'9898989898989), -13}));
750 Env env{*
this, features};
756 {
USD(15'000),
EUR(15'000)},
773 BEAST_EXPECT(ammAlice.expectBalances(
774 XRP(10'100),
USD(10'000), ammAlice.tokens()));
783 Env env{*
this, features};
789 {
USD(15'000),
EUR(15'000)},
808 BEAST_EXPECT(ammAlice.expectBalances(
809 XRP(10'100),
USD(10'000), ammAlice.tokens()));
817 Env env{*
this, features};
823 {
USD(15'000),
EUR(15'000)},
856 testcase(
"Combine tfSell with tfFillOrKill");
864 Env env{*
this, features};
872 ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
876 Env env{*
this, features};
883 BEAST_EXPECT(ammBob.expectBalances(
894 Env env{*
this, features};
901 BEAST_EXPECT(ammBob.expectBalances(
915 Env env{*
this, features};
936 [&](
AMM& ammAlice,
Env& env) {
949 {{
XRP(10'000),
USD(10'100)}},
957 [&](
AMM& ammAlice,
Env& env) {
970 {{
XRP(10'100),
USD(10'000)}},
977 Env env{*
this, features};
982 {
USD(15'000),
EUR(15'000)},
1014 Env env{*
this, features};
1019 {
USD(15'000),
EUR(15'000)},
1056 Env env{*
this, features};
1100 Env env{*
this, features};
1146 using namespace jtx;
1148 Env env{*
this, features};
1150 auto const USD_bob =
bob[
"USD"];
1151 auto const f = env.current()->fees().base;
1155 AMM ammBob(env,
bob,
XRP(10'000), USD_bob(10'100));
1161 XRP(10'100), USD_bob(10'000), ammBob.
tokens()));
1174 using namespace jtx;
1176 Env env{*
this, features};
1179 auto const fee = env.current()->fees().base;
1182 auto const ann =
Account(
"ann");
1183 auto const A_BUX = ann[
"BUX"];
1185 auto const cam =
Account(
"cam");
1186 auto const dan =
Account(
"dan");
1187 auto const D_BUX = dan[
"BUX"];
1195 env(
trust(cam, D_BUX(100)));
1197 env(
pay(dan,
bob, D_BUX(100)));
1213 AMM ammBob(env,
bob, A_BUX(30), D_BUX(30));
1215 env(
trust(ann, D_BUX(100)));
1219 env(
pay(ann, ann, D_BUX(30)),
1245 using namespace jtx;
1247 Env env{*
this, features};
1249 auto const ann =
Account(
"ann");
1251 auto const cam =
Account(
"cam");
1253 auto const A_BUX = ann[
"BUX"];
1254 auto const B_BUX =
bob[
"BUX"];
1256 auto const fee = env.current()->fees().base;
1261 env(
trust(ann, B_BUX(40)));
1262 env(
trust(cam, A_BUX(40)));
1264 env(
trust(cam, B_BUX(40)));
1269 env(
pay(ann, cam, A_BUX(35)));
1270 env(
pay(
bob, cam, B_BUX(35)));
1274 AMM ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1281 env.require(
balance(cam, A_BUX(35)));
1282 env.require(
balance(cam, B_BUX(35)));
1283 env.require(
offers(cam, 1));
1286 env(
offer(cam, B_BUX(30), A_BUX(30)));
1289 if (!features[fixAMMv1_1])
1292 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1293 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1300 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1301 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1306 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1307 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1314 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1315 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1324 using namespace jtx;
1326 Env env{*
this, features};
1328 auto const aliceUSD =
alice[
"USD"];
1329 auto const bobUSD =
bob[
"USD"];
1373 using namespace jtx;
1375 Env env{*
this, features};
1439 using namespace jtx;
1473 using namespace jtx;
1490 BEAST_EXPECT(st.
empty());
1498 BEAST_EXPECT(sa ==
XRP(100'000'000));
1502 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1512 using namespace jtx;
1515 auto const AUD =
gw[
"AUD"];
1539 using namespace jtx;
1540 auto const charlie =
Account(
"charlie");
1545 AMM ammCharlie(env, charlie,
XRP(10),
USD(11));
1548 BEAST_EXPECT(sa ==
XRP(1));
1550 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1552 auto const& pathElem = st[0][0];
1554 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1562 AMM ammCharlie(env, charlie,
XRP(11),
USD(10));
1566 BEAST_EXPECT(sa ==
USD(1));
1568 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1570 auto const& pathElem = st[0][0];
1572 pathElem.isOffer() &&
1582 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1583 using namespace jtx;
1595 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1599 env.
trust(G1[
"XYZ"](5'000), A1);
1600 env.
trust(G3[
"ABC"](5'000), A1);
1601 env.
trust(G2[
"XYZ"](5'000), A2);
1602 env.
trust(G3[
"ABC"](5'000), A2);
1603 env.
trust(A2[
"ABC"](1'000), A3);
1604 env.
trust(G1[
"XYZ"](100'000), M1);
1605 env.
trust(G2[
"XYZ"](100'000), M1);
1606 env.
trust(G3[
"ABC"](100'000), M1);
1609 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1610 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1611 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1612 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1613 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1616 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1617 AMM ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1623 auto const& send_amt =
XRP(10);
1626 BEAST_EXPECT(
equal(da, send_amt));
1627 BEAST_EXPECT(st.
empty());
1633 auto const& send_amt =
XRP(200);
1636 BEAST_EXPECT(
equal(da, send_amt));
1637 BEAST_EXPECT(st.
empty());
1641 auto const& send_amt = G3[
"ABC"](10);
1644 BEAST_EXPECT(
equal(da, send_amt));
1650 auto const& send_amt = A2[
"ABC"](1);
1653 BEAST_EXPECT(
equal(da, send_amt));
1659 auto const& send_amt = A3[
"ABC"](1);
1662 BEAST_EXPECT(
equal(da, send_amt));
1671 testcase(
"Path Find: non-XRP -> XRP");
1672 using namespace jtx;
1679 env.
fund(
XRP(1'000), A1, A2, G3);
1683 env.
trust(G3[
"ABC"](1'000), A1, A2);
1684 env.
trust(G3[
"ABC"](100'000), M1);
1687 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1688 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1689 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1692 AMM ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1697 auto const& send_amt =
XRP(10);
1700 BEAST_EXPECT(
equal(da, send_amt));
1701 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1708 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1709 using namespace jtx;
1722 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1724 env.
fund(
XRP(21'000), M1, M2);
1727 env.
trust(G1[
"HKD"](2'000), A1);
1728 env.
trust(G2[
"HKD"](2'000), A2);
1729 env.
trust(G1[
"HKD"](2'000), A3);
1730 env.
trust(G1[
"HKD"](100'000), M1);
1731 env.
trust(G2[
"HKD"](100'000), M1);
1732 env.
trust(G1[
"HKD"](100'000), M2);
1733 env.
trust(G2[
"HKD"](100'000), M2);
1736 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1737 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1738 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1739 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1740 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1741 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1742 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1745 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1746 AMM ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1747 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1755 auto const& send_amt = G1[
"HKD"](10);
1757 env, A1, G1, send_amt,
std::nullopt, G1[
"HKD"].currency);
1758 BEAST_EXPECT(st.
empty());
1759 BEAST_EXPECT(
equal(da, send_amt));
1760 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1766 auto const& send_amt = A1[
"HKD"](10);
1768 env, A1, G1, send_amt,
std::nullopt, G1[
"HKD"].currency);
1769 BEAST_EXPECT(st.
empty());
1770 BEAST_EXPECT(
equal(da, send_amt));
1771 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1777 auto const& send_amt = A3[
"HKD"](10);
1779 env, A1, A3, send_amt,
std::nullopt, G1[
"HKD"].currency);
1780 BEAST_EXPECT(
equal(da, send_amt));
1781 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1788 auto const& send_amt = G2[
"HKD"](10);
1790 env, G1, G2, send_amt,
std::nullopt, G1[
"HKD"].currency);
1791 BEAST_EXPECT(
equal(da, send_amt));
1792 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1804 auto const& send_amt = G2[
"HKD"](10);
1806 env, A1, G2, send_amt,
std::nullopt, G1[
"HKD"].currency);
1807 BEAST_EXPECT(
equal(da, send_amt));
1808 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1821 auto const& send_amt = A2[
"HKD"](10);
1823 env, A1, A2, send_amt,
std::nullopt, G1[
"HKD"].currency);
1824 BEAST_EXPECT(
equal(da, send_amt));
1825 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1838 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1839 using namespace jtx;
1849 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1852 env.
trust(G1[
"HKD"](2'000), A1);
1853 env.
trust(G2[
"HKD"](2'000), A2);
1854 env.
trust(A2[
"HKD"](2'000), A3);
1855 env.
trust(G1[
"HKD"](100'000), M1);
1856 env.
trust(G2[
"HKD"](100'000), M1);
1859 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1860 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1861 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1862 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1865 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1869 auto const& send_amt = A2[
"HKD"](10);
1874 BEAST_EXPECT(
equal(da, send_amt));
1875 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1884 using namespace jtx;
1886 Env env(*
this, features);
1892 auto const AMMXRPPool = env.
current()->fees().increment * 2;
1909 AMM ammBob(env,
bob, AMMXRPPool,
USD(150));
1917 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1925 using namespace jtx;
1929 Env env(*
this, features);
1948 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1952 Env env(*
this, features);
1971 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1972 BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1978 Env env(*
this, features);
1999 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
2003 Env env(*
this, features);
2023 ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
2027 Env env(*
this, features);
2080 Env env(*
this, features);
2114 auto const flowResult = [&] {
2129 paths.push_back(p1);
2132 paths.push_back(p2);
2151 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2154 if (flowResult.removableOffers.empty())
2157 for (
auto const& o : flowResult.removableOffers)
2180 Env env(*
this, features);
2203 BEAST_EXPECT(ammBob.expectBalances(
2212 using namespace jtx;
2216 Env env(*
this, features);
2223 {
USD(1'000),
GBP(1'000)});
2238 if (!features[fixAMMv1_1])
2241 BEAST_EXPECT(amm.expectBalances(
2248 BEAST_EXPECT(amm.expectBalances(
2261 Env env(*
this, features);
2295 BEAST_EXPECT(amm.expectBalances(
2305 Env env(*
this, features);
2318 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2327 if (!features[fixAMMv1_1])
2365 Env env(*
this, features);
2377 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2385 Env env(*
this, features);
2392 {
USD(1'000),
GBP(1'000)});
2412 BEAST_EXPECT(amm.expectBalances(
2413 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2423 Env env(*
this, features);
2430 {
USD(1'200),
GBP(1'200)});
2444 if (!features[fixAMMv1_1])
2451 BEAST_EXPECT(amm.expectBalances(
2452 GBP(1'024),
USD(1'171.875), amm.tokens()));
2462 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2464 BEAST_EXPECT(amm.expectBalances(
2465 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2476 Env env(*
this, features);
2501 if (!features[fixAMMv1_1])
2509 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2516 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2523 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2526 BEAST_EXPECT(amm.expectBalances(
2527 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2528 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2539 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2546 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2553 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2556 BEAST_EXPECT(amm.expectBalances(
2557 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2558 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2568 Env env(*
this, features);
2593 if (!features[fixAMMv1_1])
2601 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2605 BEAST_EXPECT(amm.expectBalances(
2606 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2607 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2618 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2622 BEAST_EXPECT(amm.expectBalances(
2623 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2624 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2633 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2640 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2648 Env env(*
this, features);
2661 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2671 if (!features[fixAMMv1_1])
2679 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2682 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2683 STAmount{EUR, UINT64_C(920'78937795562), -11},
2688 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2689 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2700 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2703 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2704 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2709 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2710 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2720 Env env(*
this, features);
2742 if (!features[fixAMMv1_1])
2746 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
2747 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2752 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
2753 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2760 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
2761 STAmount{EUR, UINT64_C(902'4064171122975), -13},
2766 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
2767 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2784 using namespace jtx;
2800 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
2811 using namespace jtx;
2861 auto const JPY =
gw[
"JPY"];
2870 {
USD(200),
EUR(200), JPY(200)},
2875 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
2890 using namespace jtx;
2891 Env env(*
this, features);
2892 auto const dan =
Account(
"dan");
2893 auto const ed =
Account(
"ed");
2909 if (!features[fixAMMv1_1])
2935 testcase(
"Convert all of an asset using DeliverMin");
2937 using namespace jtx;
2940 Env env(*
this, features);
2972 drops(10'000'000'000 - env.
current()->fees().base.drops())));
2977 Env env(*
this, features);
2991 Env env(*
this, features);
3013 auto const dan =
Account(
"dan");
3014 Env env(*
this, features);
3022 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3023 if (!features[fixAMMv1_1])
3047 STAmount{USD, UINT64_C(999'99999909091), -11},
3058 using namespace jtx;
3061 bool const supportsPreauth = {features[featureDepositPreauth]};
3066 Env env(*
this, features);
3095 env(
pay(becky, becky,
USD(10)),
3110 using namespace jtx;
3131 auto failedIouPayments = [
this, &env]() {
3155 failedIouPayments();
3172 failedIouPayments();
3179 failedIouPayments();
3202 using namespace test::jtx;
3203 Env env(*
this, features);
3216 env(
pay(G1,
bob, G1[
"USD"](10)));
3217 env(
pay(G1,
alice, G1[
"USD"](205)));
3220 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3226 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3227 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3228 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3235 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3236 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3238 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3260 XRP(525), G1[
"USD"](100), ammAlice.
tokens()));
3279 for (
auto const& it :
lines[jss::lines])
3281 if (it[jss::account] ==
bob.
human())
3287 if (!BEAST_EXPECT(bobLine))
3289 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3290 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3297 for (
auto const& it :
lines[jss::lines])
3299 if (it[jss::account] == G1.human())
3305 if (!BEAST_EXPECT(g1Line))
3307 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3308 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3315 auto affected = env.
meta()->getJson(
3320 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3322 ff[sfLowLimit.fieldName] ==
3324 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3335 using namespace test::jtx;
3336 Env env(*
this, features);
3346 env.
fund(
XRP(20'000), A2, A3, A4);
3349 env.
trust(G1[
"USD"](1'200), A1);
3350 env.
trust(G1[
"USD"](200), A2);
3351 env.
trust(G1[
"BTC"](100), A3);
3352 env.
trust(G1[
"BTC"](100), A4);
3355 env(
pay(G1, A1, G1[
"USD"](1'000)));
3356 env(
pay(G1, A2, G1[
"USD"](100)));
3357 env(
pay(G1, A3, G1[
"BTC"](100)));
3358 env(
pay(G1, A4, G1[
"BTC"](100)));
3361 AMM ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3373 "XRP")[jss::result][jss::offers];
3381 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3387 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3395 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3402 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3408 env(
pay(G1, A2, G1[
"USD"](1)));
3411 env(
pay(A2, G1, G1[
"USD"](1)));
3414 env(
pay(A2, A1, G1[
"USD"](1)));
3417 env(
pay(A1, A2, G1[
"USD"](1)));
3443 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3450 "XRP")[jss::result][jss::offers];
3458 env(
pay(G1, A2, G1[
"USD"](1)));
3461 env(
pay(A2, G1, G1[
"USD"](1)));
3471 testcase(
"Offers for Frozen Trust Lines");
3473 using namespace test::jtx;
3474 Env env(*
this, features);
3481 env.
fund(
XRP(2'000), G1, A3, A4);
3485 env.
trust(G1[
"USD"](1'000), A2);
3486 env.
trust(G1[
"USD"](2'000), A3);
3487 env.
trust(G1[
"USD"](2'001), A4);
3490 env(
pay(G1, A3, G1[
"USD"](2'000)));
3491 env(
pay(G1, A4, G1[
"USD"](2'001)));
3494 AMM ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3505 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3516 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3532 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3544 testcase(
"Multisign AMM Transactions");
3546 using namespace jtx;
3547 Env env{*
this, features};
3562 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3565 msig const ms{becky, bogie};
3591 ammAlice.
vote({}, 1'000);
3594 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3606 using namespace jtx;
3610 Env env(*
this, features);
3617 {
USD(2'000),
EUR(1'000)});
3633 using namespace jtx;
3637 Env env(*
this, features);
3638 auto const BobUSD =
bob[
"USD"];
3639 auto const BobEUR =
bob[
"EUR"];
3648 {BobUSD(100), BobEUR(100)},
3652 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3655 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3658 Path const p = [&] {
3675 Env env(*
this, features);
3689 Env env(*
this, features);
3708 using namespace jtx;
3710 auto const CNY =
gw[
"CNY"];
3713 Env env(*
this, features);
3734 Env env(*
this, features);
3748 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
3774 using namespace jtx;
3788 using namespace jtx;
3797 using namespace jtx;
3815 using namespace test::jtx;
3825 using namespace jtx;
3829 all - featureMultiSignReserve - featureExpandedSignerList);
3837 using namespace jtx;
3860BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app,
ripple, 1);
A generic endpoint for log messages.
testcase_t testcase
Memberspace for declaring test cases.
bool expect(Condition const &shouldBeTrue)
Evaluate a test condition.
virtual OpenLedger & openLedger()=0
Floating point representation of amounts with high dynamic range.
A currency issued by an account.
beast::Journal journal(std::string const &name)
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.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Path & push_back(Issue const &iss)
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&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
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)
XRPAmount ammCrtFee(jtx::Env &env) const
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Convenience class to test AMM functionality.
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.
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
AccountID const & ammAccount() const
bool expectTradingFee(std::uint16_t fee) const
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)
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
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)
Json::Value bid(BidArg const &arg)
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
void require(Args const &... args)
Check a set of requirements.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
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.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
ripple::Currency currency
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 account(AccountID const &id) noexcept
AccountID root.
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
bool checkArraySize(Json::Value const &val, unsigned int size)
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Json::Value ledgerEntryRoot(Env &env, Account const &acct)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
PrettyAmount xrpMinusFee(Env const &env, std::int64_t xrpAmount)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
bool same(STPathSet const &st1, Args const &... args)
Json::Value ledgerEntryState(Env &env, Account const &acct_a, Account const &acct_b, std::string const ¤cy)
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
void n_offers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
FeatureBitset testable_amendments()
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
STPathElement IPE(Issue const &iss)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
bool expectHolding(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
STPathElement cpe(Currency const &c)
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
STPathElement allpe(AccountID const &a, Issue const &iss)
XRP_t const XRP
Converts to XRP Issue or STAmount.
XRPAmount txfee(Env const &env, std::uint16_t n)
STPath stpath(Args const &... args)
Json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
bool equal(std::unique_ptr< Step > const &s1, DirectStepInfo const &dsi)
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 toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
constexpr std::uint32_t asfGlobalFreeze
constexpr std::uint32_t asfDepositAuth
AccountID const & xrpAccount()
Compute AccountID from public key.
constexpr std::uint32_t asfNoFreeze
constexpr std::uint32_t tfFillOrKill
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.
constexpr std::uint32_t tfPassive
constexpr std::uint32_t tfImmediateOrCancel
constexpr std::uint32_t asfDisableMaster
constexpr std::uint32_t tfPartialPayment
constexpr std::uint32_t tfSetfAuth
Currency const & xrpCurrency()
XRP currency.
constexpr std::uint32_t tfClearFreeze
constexpr std::uint32_t tfNoRippleDirect
constexpr std::uint32_t tfLimitQuality
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr std::uint32_t tfSell
constexpr std::uint32_t asfRequireAuth
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
constexpr std::uint32_t tfSetFreeze
constexpr std::uint32_t tfSetNoRipple
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
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 testGlobalFreeze(FeatureBitset features)
void testOfferCrossWithXRP(FeatureBitset features)
void testCurrencyConversionEntire(FeatureBitset features)
void testCrossingLimits()
void testFalseDry(FeatureBitset features)
void testOfferCrossWithLimitOverride(FeatureBitset features)
void testTransferRateOffer(FeatureBitset features)
void testBookStep(FeatureBitset features)
void testBridgedCross(FeatureBitset features)
void test_convert_all_of_an_asset(FeatureBitset features)
void testGatewayCrossCurrency(FeatureBitset features)
void testRequireAuth(FeatureBitset features)
void testPayment(FeatureBitset features)
void testOfferFeesConsumeFunds(FeatureBitset features)
void testOffersWhenFrozen(FeatureBitset features)
void testSellFlagExceedLimit(FeatureBitset features)
void testCrossCurrencyBridged(FeatureBitset features)
void testBadPathAssert(FeatureBitset features)
void testLoop(FeatureBitset features)
void via_offers_via_gateway()
void testOfferCreateThenCross(FeatureBitset features)
void testToStrand(FeatureBitset features)
void run() override
Runs the suite.
void testFillModes(FeatureBitset features)
void testMissingAuth(FeatureBitset features)
void path_find_consume_all()
void testRIPD1373(FeatureBitset features)
void testCrossCurrencyEndXRP(FeatureBitset features)
void testCurrencyConversionInParts(FeatureBitset features)
void testTransferRateNoOwnerFee(FeatureBitset features)
void testRippleState(FeatureBitset features)
void testRmFundedOffer(FeatureBitset features)
void testSelfIssueOffer(FeatureBitset features)
void testDirectToDirectPath(FeatureBitset features)
void testStepLimit(FeatureBitset features)
void testEnforceNoRipple(FeatureBitset features)
void testCrossCurrencyStartXRP(FeatureBitset features)
void testSellWithFillOrKill(FeatureBitset features)
void testTxMultisign(FeatureBitset features)
void testSellFlagBasic(FeatureBitset features)
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...
STAmount const & value() const