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>
32#include <xrpld/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);
204 for (
auto const& tweakedFeatures :
205 {features - fix1578, features | fix1578})
208 [&](
AMM& ammAlice,
Env& env) {
210 TER const killedCode{
236 {{
XRP(10'100),
USD(10'000)}},
244 [&](
AMM& ammAlice,
Env& env) {
260 {{
XRP(10'100),
USD(10'000)}},
267 [&](
AMM& ammAlice,
Env& env) {
277 {{
XRP(10'100),
USD(10'000)}},
284 [&](
AMM& ammAlice,
Env& env) {
299 {{
XRP(11'000),
USD(9'000)}},
309 testcase(
"Offer Crossing with XRP, Normal order");
313 Env env{*
this, features};
322 auto const xrpTransferred =
XRPAmount{3'061'224'490};
325 BEAST_EXPECT(ammAlice.expectBalances(
326 XRP(150'000) + xrpTransferred,
332 env,
bob,
XRP(300'000) - xrpTransferred -
txfee(env, 1)));
339 testcase(
"Offer Crossing with Limit Override");
343 Env env{*
this, features};
359 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
362 jrr[jss::node][sfBalance.fieldName] ==
364 (
XRP(200'000) -
XRP(3'000) - env.current()->fees().base * 1)
371 testcase(
"Currency Conversion: Entire Offer");
375 Env env{*
this, features};
397 jrr[jss::node][sfBalance.fieldName] ==
405 testcase(
"Currency Conversion: In Parts");
410 [&](
AMM& ammAlice,
Env& env) {
434 {{
XRP(10'000),
USD(10'000)}},
443 testcase(
"Cross Currency Payment: Start with XRP");
448 [&](
AMM& ammAlice,
Env& env) {
458 {{
XRP(10'000),
USD(10'100)}},
467 testcase(
"Cross Currency Payment: End with XRP");
472 [&](
AMM& ammAlice,
Env& env) {
483 {{
XRP(10'100),
USD(10'000)}},
492 testcase(
"Cross Currency Payment: Bridged");
496 Env env{*
this, features};
498 auto const gw1 =
Account{
"gateway_1"};
499 auto const gw2 =
Account{
"gateway_2"};
500 auto const dan =
Account{
"dan"};
501 auto const USD1 = gw1[
"USD"];
502 auto const EUR1 = gw2[
"EUR"];
511 env(
trust(dan, EUR1(1'000)));
517 env(
pay(gw2, dan, dan[
"EUR"](400)));
520 AMM ammCarol(env,
carol, USD1(5'000),
XRP(50'000));
522 env(
offer(dan,
XRP(500), EUR1(50)));
526 jtp[0u][0u][jss::currency] =
"XRP";
528 json(jss::Paths, jtp),
533 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
542 testcase(
"Offer Fees Consume Funds");
546 Env env{*
this, features};
548 auto const gw1 =
Account{
"gateway_1"};
549 auto const gw2 =
Account{
"gateway_2"};
550 auto const gw3 =
Account{
"gateway_3"};
553 auto const USD1 = gw1[
"USD"];
554 auto const USD2 = gw2[
"USD"];
555 auto const USD3 = gw3[
"USD"];
563 auto const starting_xrp =
XRP(100) +
564 env.current()->fees().accountReserve(3) +
565 env.current()->fees().base * 4;
567 env.fund(starting_xrp, gw1, gw2, gw3,
alice);
568 env.fund(
XRP(2'000),
bob);
579 AMM ammBob(env,
bob,
XRP(1'000), USD1(1'200));
588 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
593 jrr[jss::node][sfBalance.fieldName][jss::value] ==
597 jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
603 testcase(
"Offer Create, then Cross");
607 Env env{*
this, features};
628 jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
634 testcase(
"Offer tfSell: Basic Sell");
639 [&](
AMM& ammAlice,
Env& env) {
649 {{
XRP(9'900),
USD(10'100)}},
658 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
662 Env env{*
this, features};
664 auto const starting_xrp =
665 XRP(100) +
reserve(env, 1) + env.current()->fees().base * 2;
667 env.fund(starting_xrp,
gw,
alice);
668 env.fund(
XRP(2'000),
bob);
693 testcase(
"Client Issue: Gateway Cross Currency");
697 Env env{*
this, features};
699 auto const XTS =
gw[
"XTS"];
700 auto const XXX =
gw[
"XXX"];
702 auto const starting_xrp =
703 XRP(100.1) +
reserve(env, 1) + env.current()->fees().base * 2;
709 {XTS(100), XXX(100)},
712 AMM ammAlice(env,
alice, XTS(100), XXX(100));
716 payment[jss::id] = env.seq(
bob);
717 payment[jss::build_path] =
true;
719 payment[jss::tx_json][jss::Sequence] =
722 ->getFieldU32(sfSequence);
723 payment[jss::tx_json][jss::Fee] =
to_string(env.current()->fees().base);
724 payment[jss::tx_json][jss::SendMax] =
727 auto const jrr = env.rpc(
"json",
"submit",
to_string(payment));
728 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
729 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
730 if (!features[fixAMMv1_1])
732 BEAST_EXPECT(ammAlice.expectBalances(
733 STAmount(XTS, UINT64_C(101'010101010101), -12),
737 env,
bob,
STAmount{XTS, UINT64_C(98'989898989899), -12}));
741 BEAST_EXPECT(ammAlice.expectBalances(
742 STAmount(XTS, UINT64_C(101'0101010101011), -13),
746 env,
bob,
STAmount{XTS, UINT64_C(98'9898989898989), -13}));
759 Env env{*
this, features};
765 {
USD(15'000),
EUR(15'000)},
782 BEAST_EXPECT(ammAlice.expectBalances(
783 XRP(10'100),
USD(10'000), ammAlice.tokens()));
792 Env env{*
this, features};
798 {
USD(15'000),
EUR(15'000)},
817 BEAST_EXPECT(ammAlice.expectBalances(
818 XRP(10'100),
USD(10'000), ammAlice.tokens()));
826 Env env{*
this, features};
832 {
USD(15'000),
EUR(15'000)},
865 testcase(
"Combine tfSell with tfFillOrKill");
870 TER const killedCode{
874 Env env{*
this, features};
882 ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
886 Env env{*
this, features};
893 BEAST_EXPECT(ammBob.expectBalances(
904 Env env{*
this, features};
911 BEAST_EXPECT(ammBob.expectBalances(
925 Env env{*
this, features};
946 [&](
AMM& ammAlice,
Env& env) {
959 {{
XRP(10'000),
USD(10'100)}},
967 [&](
AMM& ammAlice,
Env& env) {
980 {{
XRP(10'100),
USD(10'000)}},
987 Env env{*
this, features};
992 {
USD(15'000),
EUR(15'000)},
1024 Env env{*
this, features};
1029 {
USD(15'000),
EUR(15'000)},
1066 Env env{*
this, features};
1110 Env env{*
this, features};
1156 using namespace jtx;
1158 Env env{*
this, features};
1160 auto const USD_bob =
bob[
"USD"];
1161 auto const f = env.current()->fees().base;
1165 AMM ammBob(env,
bob,
XRP(10'000), USD_bob(10'100));
1171 XRP(10'100), USD_bob(10'000), ammBob.
tokens()));
1184 using namespace jtx;
1186 Env env{*
this, features};
1189 auto const fee = env.current()->fees().base;
1192 auto const ann =
Account(
"ann");
1193 auto const A_BUX = ann[
"BUX"];
1195 auto const cam =
Account(
"cam");
1196 auto const dan =
Account(
"dan");
1197 auto const D_BUX = dan[
"BUX"];
1205 env(
trust(cam, D_BUX(100)));
1207 env(
pay(dan,
bob, D_BUX(100)));
1219 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1223 AMM ammBob(env,
bob, A_BUX(30), D_BUX(30));
1225 env(
trust(ann, D_BUX(100)));
1229 env(
pay(ann, ann, D_BUX(30)),
1238 BEAST_EXPECT(
expectLine(env, ann, D_BUX(0)));
1240 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1241 BEAST_EXPECT(
expectLine(env, dan, A_BUX(0)));
1255 using namespace jtx;
1257 Env env{*
this, features};
1259 auto const ann =
Account(
"ann");
1261 auto const cam =
Account(
"cam");
1263 auto const A_BUX = ann[
"BUX"];
1264 auto const B_BUX =
bob[
"BUX"];
1266 auto const fee = env.current()->fees().base;
1271 env(
trust(ann, B_BUX(40)));
1272 env(
trust(cam, A_BUX(40)));
1274 env(
trust(cam, B_BUX(40)));
1279 env(
pay(ann, cam, A_BUX(35)));
1280 env(
pay(
bob, cam, B_BUX(35)));
1284 AMM ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1291 env.require(
balance(cam, A_BUX(35)));
1292 env.require(
balance(cam, B_BUX(35)));
1293 env.require(
offers(cam, 1));
1296 env(
offer(cam, B_BUX(30), A_BUX(30)));
1299 if (!features[fixAMMv1_1])
1302 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1303 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1310 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1311 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1316 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1317 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1324 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1325 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1334 using namespace jtx;
1336 Env env{*
this, features};
1338 auto const aliceUSD =
alice[
"USD"];
1339 auto const bobUSD =
bob[
"USD"];
1383 using namespace jtx;
1385 Env env{*
this, features};
1449 using namespace jtx;
1483 using namespace jtx;
1500 BEAST_EXPECT(st.
empty());
1508 BEAST_EXPECT(sa ==
XRP(100'000'000));
1512 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1522 using namespace jtx;
1525 auto const AUD =
gw[
"AUD"];
1542 BEAST_EXPECT(std::get<0>(result).empty());
1549 using namespace jtx;
1550 auto const charlie =
Account(
"charlie");
1555 AMM ammCharlie(env, charlie,
XRP(10),
USD(11));
1558 BEAST_EXPECT(sa ==
XRP(1));
1560 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1562 auto const& pathElem = st[0][0];
1564 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1572 AMM ammCharlie(env, charlie,
XRP(11),
USD(10));
1576 BEAST_EXPECT(sa ==
USD(1));
1578 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1580 auto const& pathElem = st[0][0];
1582 pathElem.isOffer() &&
1592 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1593 using namespace jtx;
1605 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1609 env.
trust(G1[
"XYZ"](5'000), A1);
1610 env.
trust(G3[
"ABC"](5'000), A1);
1611 env.
trust(G2[
"XYZ"](5'000), A2);
1612 env.
trust(G3[
"ABC"](5'000), A2);
1613 env.
trust(A2[
"ABC"](1'000), A3);
1614 env.
trust(G1[
"XYZ"](100'000), M1);
1615 env.
trust(G2[
"XYZ"](100'000), M1);
1616 env.
trust(G3[
"ABC"](100'000), M1);
1619 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1620 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1621 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1622 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1623 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1626 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1627 AMM ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1633 auto const& send_amt =
XRP(10);
1636 BEAST_EXPECT(
equal(da, send_amt));
1637 BEAST_EXPECT(st.
empty());
1643 auto const& send_amt =
XRP(200);
1646 BEAST_EXPECT(
equal(da, send_amt));
1647 BEAST_EXPECT(st.
empty());
1651 auto const& send_amt = G3[
"ABC"](10);
1654 BEAST_EXPECT(
equal(da, send_amt));
1660 auto const& send_amt = A2[
"ABC"](1);
1663 BEAST_EXPECT(
equal(da, send_amt));
1669 auto const& send_amt = A3[
"ABC"](1);
1672 BEAST_EXPECT(
equal(da, send_amt));
1681 testcase(
"Path Find: non-XRP -> XRP");
1682 using namespace jtx;
1689 env.
fund(
XRP(1'000), A1, A2, G3);
1693 env.
trust(G3[
"ABC"](1'000), A1, A2);
1694 env.
trust(G3[
"ABC"](100'000), M1);
1697 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1698 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1699 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1702 AMM ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1707 auto const& send_amt =
XRP(10);
1709 find_paths(env, A1, A2, send_amt, std::nullopt, A2[
"ABC"].currency);
1710 BEAST_EXPECT(
equal(da, send_amt));
1711 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1718 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1719 using namespace jtx;
1732 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1734 env.
fund(
XRP(21'000), M1, M2);
1737 env.
trust(G1[
"HKD"](2'000), A1);
1738 env.
trust(G2[
"HKD"](2'000), A2);
1739 env.
trust(G1[
"HKD"](2'000), A3);
1740 env.
trust(G1[
"HKD"](100'000), M1);
1741 env.
trust(G2[
"HKD"](100'000), M1);
1742 env.
trust(G1[
"HKD"](100'000), M2);
1743 env.
trust(G2[
"HKD"](100'000), M2);
1746 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1747 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1748 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1749 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1750 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1751 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1752 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1755 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1756 AMM ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1757 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1765 auto const& send_amt = G1[
"HKD"](10);
1767 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1768 BEAST_EXPECT(st.
empty());
1769 BEAST_EXPECT(
equal(da, send_amt));
1770 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1776 auto const& send_amt = A1[
"HKD"](10);
1778 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1779 BEAST_EXPECT(st.
empty());
1780 BEAST_EXPECT(
equal(da, send_amt));
1781 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1787 auto const& send_amt = A3[
"HKD"](10);
1789 env, A1, A3, send_amt, std::nullopt, G1[
"HKD"].currency);
1790 BEAST_EXPECT(
equal(da, send_amt));
1791 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1798 auto const& send_amt = G2[
"HKD"](10);
1800 env, G1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1801 BEAST_EXPECT(
equal(da, send_amt));
1802 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1814 auto const& send_amt = G2[
"HKD"](10);
1816 env, A1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1817 BEAST_EXPECT(
equal(da, send_amt));
1818 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1831 auto const& send_amt = A2[
"HKD"](10);
1833 env, A1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1834 BEAST_EXPECT(
equal(da, send_amt));
1835 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1848 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1849 using namespace jtx;
1859 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1862 env.
trust(G1[
"HKD"](2'000), A1);
1863 env.
trust(G2[
"HKD"](2'000), A2);
1864 env.
trust(A2[
"HKD"](2'000), A3);
1865 env.
trust(G1[
"HKD"](100'000), M1);
1866 env.
trust(G2[
"HKD"](100'000), M1);
1869 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1870 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1871 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1872 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1875 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1879 auto const& send_amt = A2[
"HKD"](10);
1883 find_paths(env, G1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1884 BEAST_EXPECT(
equal(da, send_amt));
1885 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1894 using namespace jtx;
1896 Env env(*
this, features);
1902 auto const AMMXRPPool = env.
current()->fees().increment * 2;
1919 AMM ammBob(env,
bob, AMMXRPPool,
USD(150));
1927 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1935 using namespace jtx;
1939 Env env(*
this, features);
1958 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1962 Env env(*
this, features);
1981 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1982 BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1988 Env env(*
this, features);
2009 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
2013 Env env(*
this, features);
2033 ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
2037 Env env(*
this, features);
2090 Env env(*
this, features);
2124 auto const flowResult = [&] {
2139 paths.push_back(p1);
2142 paths.push_back(p2);
2161 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2164 if (flowResult.removableOffers.empty())
2167 for (
auto const& o : flowResult.removableOffers)
2190 Env env(*
this, features);
2213 BEAST_EXPECT(ammBob.expectBalances(
2222 using namespace jtx;
2226 Env env(*
this, features);
2233 {
USD(1'000),
GBP(1'000)});
2248 if (!features[fixAMMv1_1])
2251 BEAST_EXPECT(amm.expectBalances(
2258 BEAST_EXPECT(amm.expectBalances(
2271 Env env(*
this, features);
2305 BEAST_EXPECT(amm.expectBalances(
2315 Env env(*
this, features);
2328 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2337 if (!features[fixAMMv1_1])
2375 Env env(*
this, features);
2387 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2395 Env env(*
this, features);
2402 {
USD(1'000),
GBP(1'000)});
2422 BEAST_EXPECT(amm.expectBalances(
2423 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2433 Env env(*
this, features);
2440 {
USD(1'200),
GBP(1'200)});
2454 if (!features[fixAMMv1_1])
2461 BEAST_EXPECT(amm.expectBalances(
2462 GBP(1'024),
USD(1'171.875), amm.tokens()));
2472 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2474 BEAST_EXPECT(amm.expectBalances(
2475 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2486 Env env(*
this, features);
2511 if (!features[fixAMMv1_1])
2519 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2526 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2533 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2536 BEAST_EXPECT(amm.expectBalances(
2537 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2538 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2549 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2556 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2563 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2566 BEAST_EXPECT(amm.expectBalances(
2567 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2568 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2578 Env env(*
this, features);
2603 if (!features[fixAMMv1_1])
2611 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2615 BEAST_EXPECT(amm.expectBalances(
2616 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2617 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2628 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2632 BEAST_EXPECT(amm.expectBalances(
2633 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2634 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2643 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2650 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2658 Env env(*
this, features);
2671 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2681 if (!features[fixAMMv1_1])
2689 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2692 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2693 STAmount{EUR, UINT64_C(920'78937795562), -11},
2698 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2699 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2710 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2713 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2714 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2719 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2720 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2730 Env env(*
this, features);
2752 if (!features[fixAMMv1_1])
2756 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
2757 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2762 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
2763 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2770 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
2771 STAmount{EUR, UINT64_C(902'4064171122975), -13},
2776 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
2777 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2794 using namespace jtx;
2810 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
2821 using namespace jtx;
2823 for (
auto const withFix : {
true,
false})
2825 auto const feats = withFix
2830 Env env(*
this, feats);
2845 TER const expectedTer =
2878 auto const JPY =
gw[
"JPY"];
2887 {
USD(200),
EUR(200), JPY(200)},
2892 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
2907 using namespace jtx;
2908 Env env(*
this, features);
2909 auto const dan =
Account(
"dan");
2910 auto const ed =
Account(
"ed");
2926 if (!features[fixAMMv1_1])
2952 testcase(
"Convert all of an asset using DeliverMin");
2954 using namespace jtx;
2957 Env env(*
this, features);
2989 drops(10'000'000'000 - env.
current()->fees().base.drops())));
2994 Env env(*
this, features);
3008 Env env(*
this, features);
3030 auto const dan =
Account(
"dan");
3031 Env env(*
this, features);
3039 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3040 if (!features[fixAMMv1_1])
3064 STAmount{USD, UINT64_C(999'99999909091), -11},
3075 using namespace jtx;
3078 bool const supportsPreauth = {features[featureDepositPreauth]};
3083 Env env(*
this, features);
3112 env(
pay(becky, becky,
USD(10)),
3127 using namespace jtx;
3148 auto failedIouPayments = [
this, &env]() {
3172 failedIouPayments();
3189 failedIouPayments();
3196 failedIouPayments();
3219 using namespace test::jtx;
3220 Env env(*
this, features);
3233 env(
pay(G1,
bob, G1[
"USD"](10)));
3234 env(
pay(G1,
alice, G1[
"USD"](205)));
3237 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3243 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3244 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3245 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3252 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3253 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3255 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3277 XRP(525), G1[
"USD"](100), ammAlice.
tokens()));
3296 for (
auto const& it :
lines[jss::lines])
3298 if (it[jss::account] ==
bob.
human())
3304 if (!BEAST_EXPECT(bobLine))
3306 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3307 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3314 for (
auto const& it :
lines[jss::lines])
3316 if (it[jss::account] == G1.human())
3322 if (!BEAST_EXPECT(g1Line))
3324 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3325 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3332 auto affected = env.
meta()->getJson(
3337 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3339 ff[sfLowLimit.fieldName] ==
3341 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3352 using namespace test::jtx;
3353 Env env(*
this, features);
3363 env.
fund(
XRP(20'000), A2, A3, A4);
3366 env.
trust(G1[
"USD"](1'200), A1);
3367 env.
trust(G1[
"USD"](200), A2);
3368 env.
trust(G1[
"BTC"](100), A3);
3369 env.
trust(G1[
"BTC"](100), A4);
3372 env(
pay(G1, A1, G1[
"USD"](1'000)));
3373 env(
pay(G1, A2, G1[
"USD"](100)));
3374 env(
pay(G1, A3, G1[
"BTC"](100)));
3375 env(
pay(G1, A4, G1[
"BTC"](100)));
3378 AMM ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3390 "XRP")[jss::result][jss::offers];
3398 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3404 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3412 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3419 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3425 env(
pay(G1, A2, G1[
"USD"](1)));
3428 env(
pay(A2, G1, G1[
"USD"](1)));
3431 env(
pay(A2, A1, G1[
"USD"](1)));
3434 env(
pay(A1, A2, G1[
"USD"](1)));
3460 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3467 "XRP")[jss::result][jss::offers];
3475 env(
pay(G1, A2, G1[
"USD"](1)));
3478 env(
pay(A2, G1, G1[
"USD"](1)));
3488 testcase(
"Offers for Frozen Trust Lines");
3490 using namespace test::jtx;
3491 Env env(*
this, features);
3498 env.
fund(
XRP(2'000), G1, A3, A4);
3502 env.
trust(G1[
"USD"](1'000), A2);
3503 env.
trust(G1[
"USD"](2'000), A3);
3504 env.
trust(G1[
"USD"](2'001), A4);
3507 env(
pay(G1, A3, G1[
"USD"](2'000)));
3508 env(
pay(G1, A4, G1[
"USD"](2'001)));
3511 AMM ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3522 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3533 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3549 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3561 testcase(
"Multisign AMM Transactions");
3563 using namespace jtx;
3564 Env env{*
this, features};
3579 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3582 msig const ms{becky, bogie};
3608 ammAlice.
vote({}, 1'000);
3611 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3623 using namespace jtx;
3627 Env env(*
this, features);
3634 {
USD(2'000),
EUR(1'000)});
3650 using namespace jtx;
3654 Env env(*
this, features);
3655 auto const BobUSD =
bob[
"USD"];
3656 auto const BobEUR =
bob[
"EUR"];
3665 {BobUSD(100), BobEUR(100)},
3669 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3672 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3675 Path const p = [&] {
3692 Env env(*
this, features);
3706 Env env(*
this, features);
3725 using namespace jtx;
3727 auto const CNY =
gw[
"CNY"];
3730 Env env(*
this, features);
3751 Env env(*
this, features);
3765 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
3791 using namespace jtx;
3805 using namespace jtx;
3814 using namespace jtx;
3832 using namespace test::jtx;
3842 using namespace jtx;
3846 all - featureMultiSignReserve - featureExpandedSignerList);
3854 using namespace jtx;
3877BEAST_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.
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.
bool expectLine(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
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.
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
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
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
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