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;
1188 Env env{*
this, features | featureOwnerPaysFee};
1191 auto const fee = env.current()->fees().base;
1194 auto const ann =
Account(
"ann");
1195 auto const A_BUX = ann[
"BUX"];
1197 auto const cam =
Account(
"cam");
1198 auto const dan =
Account(
"dan");
1199 auto const D_BUX = dan[
"BUX"];
1207 env(
trust(cam, D_BUX(100)));
1209 env(
pay(dan,
bob, D_BUX(100)));
1221 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1225 AMM ammBob(env,
bob, A_BUX(30), D_BUX(30));
1227 env(
trust(ann, D_BUX(100)));
1231 env(
pay(ann, ann, D_BUX(30)),
1240 BEAST_EXPECT(
expectLine(env, ann, D_BUX(0)));
1242 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1243 BEAST_EXPECT(
expectLine(env, dan, A_BUX(0)));
1257 using namespace jtx;
1259 Env env{*
this, features};
1261 auto const ann =
Account(
"ann");
1263 auto const cam =
Account(
"cam");
1265 auto const A_BUX = ann[
"BUX"];
1266 auto const B_BUX =
bob[
"BUX"];
1268 auto const fee = env.current()->fees().base;
1273 env(
trust(ann, B_BUX(40)));
1274 env(
trust(cam, A_BUX(40)));
1276 env(
trust(cam, B_BUX(40)));
1281 env(
pay(ann, cam, A_BUX(35)));
1282 env(
pay(
bob, cam, B_BUX(35)));
1286 AMM ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1293 env.require(
balance(cam, A_BUX(35)));
1294 env.require(
balance(cam, B_BUX(35)));
1295 env.require(
offers(cam, 1));
1298 env(
offer(cam, B_BUX(30), A_BUX(30)));
1301 if (!features[fixAMMv1_1])
1304 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1305 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1312 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1313 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1318 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1319 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1326 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1327 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1336 using namespace jtx;
1338 Env env{*
this, features};
1340 auto const aliceUSD =
alice[
"USD"];
1341 auto const bobUSD =
bob[
"USD"];
1385 using namespace jtx;
1387 Env env{*
this, features};
1451 using namespace jtx;
1485 using namespace jtx;
1502 BEAST_EXPECT(st.
empty());
1510 BEAST_EXPECT(sa ==
XRP(100'000'000));
1514 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1524 using namespace jtx;
1527 auto const AUD =
gw[
"AUD"];
1544 BEAST_EXPECT(std::get<0>(result).empty());
1551 using namespace jtx;
1552 auto const charlie =
Account(
"charlie");
1557 AMM ammCharlie(env, charlie,
XRP(10),
USD(11));
1560 BEAST_EXPECT(sa ==
XRP(1));
1562 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1564 auto const& pathElem = st[0][0];
1566 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1574 AMM ammCharlie(env, charlie,
XRP(11),
USD(10));
1578 BEAST_EXPECT(sa ==
USD(1));
1580 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1582 auto const& pathElem = st[0][0];
1584 pathElem.isOffer() &&
1594 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1595 using namespace jtx;
1607 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1611 env.
trust(G1[
"XYZ"](5'000), A1);
1612 env.
trust(G3[
"ABC"](5'000), A1);
1613 env.
trust(G2[
"XYZ"](5'000), A2);
1614 env.
trust(G3[
"ABC"](5'000), A2);
1615 env.
trust(A2[
"ABC"](1'000), A3);
1616 env.
trust(G1[
"XYZ"](100'000), M1);
1617 env.
trust(G2[
"XYZ"](100'000), M1);
1618 env.
trust(G3[
"ABC"](100'000), M1);
1621 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1622 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1623 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1624 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1625 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1628 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1629 AMM ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1635 auto const& send_amt =
XRP(10);
1638 BEAST_EXPECT(
equal(da, send_amt));
1639 BEAST_EXPECT(st.
empty());
1645 auto const& send_amt =
XRP(200);
1648 BEAST_EXPECT(
equal(da, send_amt));
1649 BEAST_EXPECT(st.
empty());
1653 auto const& send_amt = G3[
"ABC"](10);
1656 BEAST_EXPECT(
equal(da, send_amt));
1662 auto const& send_amt = A2[
"ABC"](1);
1665 BEAST_EXPECT(
equal(da, send_amt));
1671 auto const& send_amt = A3[
"ABC"](1);
1674 BEAST_EXPECT(
equal(da, send_amt));
1683 testcase(
"Path Find: non-XRP -> XRP");
1684 using namespace jtx;
1691 env.
fund(
XRP(1'000), A1, A2, G3);
1695 env.
trust(G3[
"ABC"](1'000), A1, A2);
1696 env.
trust(G3[
"ABC"](100'000), M1);
1699 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1700 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1701 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1704 AMM ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1709 auto const& send_amt =
XRP(10);
1711 find_paths(env, A1, A2, send_amt, std::nullopt, A2[
"ABC"].currency);
1712 BEAST_EXPECT(
equal(da, send_amt));
1713 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1720 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1721 using namespace jtx;
1734 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1736 env.
fund(
XRP(21'000), M1, M2);
1739 env.
trust(G1[
"HKD"](2'000), A1);
1740 env.
trust(G2[
"HKD"](2'000), A2);
1741 env.
trust(G1[
"HKD"](2'000), A3);
1742 env.
trust(G1[
"HKD"](100'000), M1);
1743 env.
trust(G2[
"HKD"](100'000), M1);
1744 env.
trust(G1[
"HKD"](100'000), M2);
1745 env.
trust(G2[
"HKD"](100'000), M2);
1748 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1749 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1750 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1751 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1752 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1753 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1754 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1757 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1758 AMM ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1759 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1767 auto const& send_amt = G1[
"HKD"](10);
1769 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1770 BEAST_EXPECT(st.
empty());
1771 BEAST_EXPECT(
equal(da, send_amt));
1772 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1778 auto const& send_amt = A1[
"HKD"](10);
1780 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1781 BEAST_EXPECT(st.
empty());
1782 BEAST_EXPECT(
equal(da, send_amt));
1783 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1789 auto const& send_amt = A3[
"HKD"](10);
1791 env, A1, A3, send_amt, std::nullopt, G1[
"HKD"].currency);
1792 BEAST_EXPECT(
equal(da, send_amt));
1793 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1800 auto const& send_amt = G2[
"HKD"](10);
1802 env, G1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1803 BEAST_EXPECT(
equal(da, send_amt));
1804 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1816 auto const& send_amt = G2[
"HKD"](10);
1818 env, A1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1819 BEAST_EXPECT(
equal(da, send_amt));
1820 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1833 auto const& send_amt = A2[
"HKD"](10);
1835 env, A1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1836 BEAST_EXPECT(
equal(da, send_amt));
1837 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1850 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1851 using namespace jtx;
1861 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1864 env.
trust(G1[
"HKD"](2'000), A1);
1865 env.
trust(G2[
"HKD"](2'000), A2);
1866 env.
trust(A2[
"HKD"](2'000), A3);
1867 env.
trust(G1[
"HKD"](100'000), M1);
1868 env.
trust(G2[
"HKD"](100'000), M1);
1871 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1872 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1873 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1874 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1877 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1881 auto const& send_amt = A2[
"HKD"](10);
1885 find_paths(env, G1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1886 BEAST_EXPECT(
equal(da, send_amt));
1887 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1896 using namespace jtx;
1898 Env env(*
this, features);
1904 auto const AMMXRPPool = env.
current()->fees().increment * 2;
1921 AMM ammBob(env,
bob, AMMXRPPool,
USD(150));
1929 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1937 using namespace jtx;
1941 Env env(*
this, features);
1960 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1964 Env env(*
this, features);
1983 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1984 BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1990 Env env(*
this, features);
2011 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
2015 Env env(*
this, features);
2035 ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
2039 Env env(*
this, features);
2092 Env env(*
this, features);
2126 auto const flowResult = [&] {
2141 paths.push_back(p1);
2144 paths.push_back(p2);
2163 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2166 if (flowResult.removableOffers.empty())
2169 for (
auto const& o : flowResult.removableOffers)
2192 Env env(*
this, features);
2215 BEAST_EXPECT(ammBob.expectBalances(
2225 using namespace jtx;
2229 Env env(*
this, features);
2252 Env env(*
this, features);
2259 {
USD(1'000),
EUR(1'000)});
2287 Env env(*
this, features);
2294 {
USD(1'000),
EUR(1'000)});
2325 Env env(*
this, features);
2332 {
USD(1'200),
GBP(1'200)});
2346 if (!features[fixAMMv1_1])
2354 STAmount{
GBP, UINT64_C(1'105'555555555555), -12}));
2356 BEAST_EXPECT(amm.expectBalances(
2357 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
2358 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2369 STAmount{
GBP, UINT64_C(1'105'555555555554), -12}));
2371 BEAST_EXPECT(amm.expectBalances(
2372 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
2373 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2382 Env env(*
this, features);
2393 if (!features[fixAMMv1_1])
2396 BEAST_EXPECT(amm.expectBalances(
2397 STAmount{USD, UINT64_C(1'095'238095238095), -12},
2411 BEAST_EXPECT(amm.expectBalances(
2412 STAmount{USD, UINT64_C(1'095'238095238096), -12},
2429 Env env(*
this, features);
2430 auto const USDA =
alice[
"USD"];
2431 auto const USDB =
bob[
"USD"];
2444 AMM ammDan(env, dan,
USD(1'000),
EUR(1'050));
2446 if (!features[fixAMMv1_1])
2471 STAmount{USD, UINT64_C(1'050'000000000001), -12},
2479 env,
bob,
STAmount{USDA, UINT64_C(60'000000000001), -12}));
2489 using namespace jtx;
2493 Env env(*
this, features);
2500 {
USD(1'000),
GBP(1'000)});
2515 if (!features[fixAMMv1_1])
2518 BEAST_EXPECT(amm.expectBalances(
2525 BEAST_EXPECT(amm.expectBalances(
2538 Env env(*
this, features);
2572 BEAST_EXPECT(amm.expectBalances(
2582 Env env(*
this, features);
2595 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2604 if (!features[fixAMMv1_1])
2642 Env env(*
this, features);
2654 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2662 Env env(*
this, features);
2669 {
USD(1'000),
GBP(1'000)});
2689 BEAST_EXPECT(amm.expectBalances(
2690 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2700 Env env(*
this, features);
2707 {
USD(1'200),
GBP(1'200)});
2721 if (!features[fixAMMv1_1])
2728 BEAST_EXPECT(amm.expectBalances(
2729 GBP(1'024),
USD(1'171.875), amm.tokens()));
2739 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2741 BEAST_EXPECT(amm.expectBalances(
2742 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2753 Env env(*
this, features);
2778 if (!features[fixAMMv1_1])
2786 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2793 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2800 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2803 BEAST_EXPECT(amm.expectBalances(
2804 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2805 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2816 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2823 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2830 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2833 BEAST_EXPECT(amm.expectBalances(
2834 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2835 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2845 Env env(*
this, features);
2870 if (!features[fixAMMv1_1])
2878 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2882 BEAST_EXPECT(amm.expectBalances(
2883 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2884 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2895 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2899 BEAST_EXPECT(amm.expectBalances(
2900 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2901 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2910 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2917 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2925 Env env(*
this, features);
2938 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2948 if (!features[fixAMMv1_1])
2956 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2959 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2960 STAmount{EUR, UINT64_C(920'78937795562), -11},
2965 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2966 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2977 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2980 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2981 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2986 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2987 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2997 Env env(*
this, features);
3019 if (!features[fixAMMv1_1])
3023 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
3024 STAmount{EUR, UINT64_C(902'4064171122988), -13},
3029 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
3030 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3037 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
3038 STAmount{EUR, UINT64_C(902'4064171122975), -13},
3043 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
3044 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3061 using namespace jtx;
3077 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
3088 using namespace jtx;
3090 for (
auto const withFix : {
true,
false})
3092 auto const feats = withFix
3097 Env env(*
this, feats);
3112 TER const expectedTer =
3145 auto const JPY =
gw[
"JPY"];
3154 {
USD(200),
EUR(200), JPY(200)},
3159 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
3174 using namespace jtx;
3175 Env env(*
this, features);
3176 auto const dan =
Account(
"dan");
3177 auto const ed =
Account(
"ed");
3193 if (!features[fixAMMv1_1])
3219 testcase(
"Convert all of an asset using DeliverMin");
3221 using namespace jtx;
3224 Env env(*
this, features);
3256 drops(10'000'000'000 - env.
current()->fees().base.drops())));
3261 Env env(*
this, features);
3275 Env env(*
this, features);
3297 auto const dan =
Account(
"dan");
3298 Env env(*
this, features);
3306 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3307 if (!features[fixAMMv1_1])
3331 STAmount{USD, UINT64_C(999'99999909091), -11},
3342 using namespace jtx;
3345 bool const supportsPreauth = {features[featureDepositPreauth]};
3350 Env env(*
this, features);
3379 env(
pay(becky, becky,
USD(10)),
3394 using namespace jtx;
3415 auto failedIouPayments = [
this, &env]() {
3439 failedIouPayments();
3456 failedIouPayments();
3463 failedIouPayments();
3486 using namespace test::jtx;
3487 Env env(*
this, features);
3500 env(
pay(G1,
bob, G1[
"USD"](10)));
3501 env(
pay(G1,
alice, G1[
"USD"](205)));
3504 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3510 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3511 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3512 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3519 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3520 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3522 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3544 XRP(525), G1[
"USD"](100), ammAlice.
tokens()));
3563 for (
auto const& it :
lines[jss::lines])
3565 if (it[jss::account] ==
bob.
human())
3571 if (!BEAST_EXPECT(bobLine))
3573 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3574 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3581 for (
auto const& it :
lines[jss::lines])
3583 if (it[jss::account] == G1.human())
3589 if (!BEAST_EXPECT(g1Line))
3591 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3592 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3599 auto affected = env.
meta()->getJson(
3604 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3606 ff[sfLowLimit.fieldName] ==
3608 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3619 using namespace test::jtx;
3620 Env env(*
this, features);
3630 env.
fund(
XRP(20'000), A2, A3, A4);
3633 env.
trust(G1[
"USD"](1'200), A1);
3634 env.
trust(G1[
"USD"](200), A2);
3635 env.
trust(G1[
"BTC"](100), A3);
3636 env.
trust(G1[
"BTC"](100), A4);
3639 env(
pay(G1, A1, G1[
"USD"](1'000)));
3640 env(
pay(G1, A2, G1[
"USD"](100)));
3641 env(
pay(G1, A3, G1[
"BTC"](100)));
3642 env(
pay(G1, A4, G1[
"BTC"](100)));
3645 AMM ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3657 "XRP")[jss::result][jss::offers];
3665 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3671 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3679 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3686 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3692 env(
pay(G1, A2, G1[
"USD"](1)));
3695 env(
pay(A2, G1, G1[
"USD"](1)));
3698 env(
pay(A2, A1, G1[
"USD"](1)));
3701 env(
pay(A1, A2, G1[
"USD"](1)));
3727 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3734 "XRP")[jss::result][jss::offers];
3742 env(
pay(G1, A2, G1[
"USD"](1)));
3745 env(
pay(A2, G1, G1[
"USD"](1)));
3755 testcase(
"Offers for Frozen Trust Lines");
3757 using namespace test::jtx;
3758 Env env(*
this, features);
3765 env.
fund(
XRP(2'000), G1, A3, A4);
3769 env.
trust(G1[
"USD"](1'000), A2);
3770 env.
trust(G1[
"USD"](2'000), A3);
3771 env.
trust(G1[
"USD"](2'001), A4);
3774 env(
pay(G1, A3, G1[
"USD"](2'000)));
3775 env(
pay(G1, A4, G1[
"USD"](2'001)));
3778 AMM ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3789 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3800 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3816 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3828 testcase(
"Multisign AMM Transactions");
3830 using namespace jtx;
3831 Env env{*
this, features};
3846 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3849 msig const ms{becky, bogie};
3875 ammAlice.
vote({}, 1'000);
3878 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3890 using namespace jtx;
3894 Env env(*
this, features);
3901 {
USD(2'000),
EUR(1'000)});
3917 using namespace jtx;
3921 Env env(*
this, features);
3922 auto const BobUSD =
bob[
"USD"];
3923 auto const BobEUR =
bob[
"EUR"];
3932 {BobUSD(100), BobEUR(100)},
3936 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3939 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3942 Path const p = [&] {
3959 Env env(*
this, features);
3973 Env env(*
this, features);
3992 using namespace jtx;
3994 auto const CNY =
gw[
"CNY"];
3997 Env env(*
this, features);
4018 Env env(*
this, features);
4032 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
4058 using namespace jtx;
4076 using namespace jtx;
4085 using namespace jtx;
4103 using namespace test::jtx;
4113 using namespace jtx;
4117 all - featureMultiSignReserve - featureExpandedSignerList);
4125 using namespace jtx;
4148BEAST_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={supported_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)
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)
FeatureBitset supported_amendments()
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 testTransferRate(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