2#include <test/jtx/AMM.h>
3#include <test/jtx/AMMTest.h>
4#include <test/jtx/PathSet.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/sendmax.h>
8#include <xrpld/app/misc/AMMUtils.h>
9#include <xrpld/app/paths/AMMContext.h>
10#include <xrpld/app/paths/AMMOffer.h>
11#include <xrpld/app/paths/Flow.h>
12#include <xrpld/app/paths/detail/StrandFlow.h>
14#include <xrpl/ledger/PaymentSandbox.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/STParsedJSON.h>
33 testcase(
"Incorrect Removal of Funded Offers");
45 Env env{*
this, features};
52 {
USD(200'000),
BTC(2'000)});
75 if (!features[fixAMMv1_1])
78 STAmount{BTC, UINT64_C(1'001'000000374812), -12},
85 STAmount{BTC, UINT64_C(1'001'000000374815), -12},
102 Env env{*
this, features};
107 auto const USD1 = gw1[
"USD"];
108 auto const USD2 = gw2[
"USD"];
118 env(
pay(gw1, dan, USD1(10'000)));
119 env(
pay(gw1,
bob, USD1(50)));
120 env(
pay(gw2,
bob, USD2(50)));
123 AMM ammDan(env, dan,
XRP(10'000), USD1(10'000));
134 Env env{*
this, features};
139 auto const USD1 = gw1[
"USD"];
140 auto const USD2 = gw2[
"USD"];
143 env.fund(
XRP(20'000), dan);
149 env(
pay(gw1, dan, USD1(10'050)));
150 env(
pay(gw1,
bob, USD1(50)));
151 env(
pay(gw2,
bob, USD2(50)));
154 AMM ammDan(env, dan,
XRP(10'000), USD1(10'050));
161 XRP(10'050), USD1(10'000), ammDan.
tokens()));
177 auto const startBalance =
XRP(1'000'000);
183 [&](
AMM& ammAlice,
Env& env) {
209 {{
XRP(10'100),
USD(10'000)}},
217 [&](
AMM& ammAlice,
Env& env) {
233 {{
XRP(10'100),
USD(10'000)}},
240 [&](
AMM& ammAlice,
Env& env) {
250 {{
XRP(10'100),
USD(10'000)}},
257 [&](
AMM& ammAlice,
Env& env) {
272 {{
XRP(11'000),
USD(9'000)}},
281 testcase(
"Offer Crossing with XRP, Normal order");
285 Env env{*
this, features};
294 auto const xrpTransferred =
XRPAmount{3'061'224'490};
297 BEAST_EXPECT(ammAlice.expectBalances(
298 XRP(150'000) + xrpTransferred,
304 env,
bob,
XRP(300'000) - xrpTransferred -
txfee(env, 1)));
311 testcase(
"Offer Crossing with Limit Override");
315 Env env{*
this, features};
331 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
334 jrr[jss::node][sfBalance.fieldName] ==
336 (
XRP(200'000) -
XRP(3'000) - env.current()->fees().base * 1)
343 testcase(
"Currency Conversion: Entire Offer");
347 Env env{*
this, features};
369 jrr[jss::node][sfBalance.fieldName] ==
377 testcase(
"Currency Conversion: In Parts");
382 [&](
AMM& ammAlice,
Env& env) {
406 {{
XRP(10'000),
USD(10'000)}},
415 testcase(
"Cross Currency Payment: Start with XRP");
420 [&](
AMM& ammAlice,
Env& env) {
430 {{
XRP(10'000),
USD(10'100)}},
439 testcase(
"Cross Currency Payment: End with XRP");
444 [&](
AMM& ammAlice,
Env& env) {
455 {{
XRP(10'100),
USD(10'000)}},
464 testcase(
"Cross Currency Payment: Bridged");
468 Env env{*
this, features};
470 auto const gw1 =
Account{
"gateway_1"};
471 auto const gw2 =
Account{
"gateway_2"};
472 auto const dan =
Account{
"dan"};
473 auto const USD1 = gw1[
"USD"];
474 auto const EUR1 = gw2[
"EUR"];
483 env(
trust(dan, EUR1(1'000)));
489 env(
pay(gw2, dan, dan[
"EUR"](400)));
492 AMM ammCarol(env,
carol, USD1(5'000),
XRP(50'000));
494 env(
offer(dan,
XRP(500), EUR1(50)));
498 jtp[0u][0u][jss::currency] =
"XRP";
500 json(jss::Paths, jtp),
505 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
514 testcase(
"Offer Fees Consume Funds");
518 Env env{*
this, features};
520 auto const gw1 =
Account{
"gateway_1"};
521 auto const gw2 =
Account{
"gateway_2"};
522 auto const gw3 =
Account{
"gateway_3"};
525 auto const USD1 = gw1[
"USD"];
526 auto const USD2 = gw2[
"USD"];
527 auto const USD3 = gw3[
"USD"];
535 auto const starting_xrp =
XRP(100) +
536 env.current()->fees().accountReserve(3) +
537 env.current()->fees().base * 4;
539 env.fund(starting_xrp, gw1, gw2, gw3,
alice);
540 env.fund(
XRP(2'000),
bob);
551 AMM ammBob(env,
bob,
XRP(1'000), USD1(1'200));
560 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
565 jrr[jss::node][sfBalance.fieldName][jss::value] ==
569 jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
575 testcase(
"Offer Create, then Cross");
579 Env env{*
this, features};
600 jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
606 testcase(
"Offer tfSell: Basic Sell");
611 [&](
AMM& ammAlice,
Env& env) {
621 {{
XRP(9'900),
USD(10'100)}},
630 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
634 Env env{*
this, features};
636 auto const starting_xrp =
637 XRP(100) +
reserve(env, 1) + env.current()->fees().base * 2;
639 env.fund(starting_xrp,
gw,
alice);
640 env.fund(
XRP(2'000),
bob);
665 testcase(
"Client Issue: Gateway Cross Currency");
669 Env env{*
this, features};
671 auto const XTS =
gw[
"XTS"];
672 auto const XXX =
gw[
"XXX"];
674 auto const starting_xrp =
675 XRP(100.1) +
reserve(env, 1) + env.current()->fees().base * 2;
681 {XTS(100), XXX(100)},
684 AMM ammAlice(env,
alice, XTS(100), XXX(100));
688 payment[jss::id] = env.seq(
bob);
689 payment[jss::build_path] =
true;
691 payment[jss::tx_json][jss::Sequence] =
694 ->getFieldU32(sfSequence);
695 payment[jss::tx_json][jss::Fee] =
to_string(env.current()->fees().base);
696 payment[jss::tx_json][jss::SendMax] =
699 auto const jrr = env.rpc(
"json",
"submit",
to_string(payment));
700 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
701 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
702 if (!features[fixAMMv1_1])
704 BEAST_EXPECT(ammAlice.expectBalances(
705 STAmount(XTS, UINT64_C(101'010101010101), -12),
709 env,
bob,
STAmount{XTS, UINT64_C(98'989898989899), -12}));
713 BEAST_EXPECT(ammAlice.expectBalances(
714 STAmount(XTS, UINT64_C(101'0101010101011), -13),
718 env,
bob,
STAmount{XTS, UINT64_C(98'9898989898989), -13}));
731 Env env{*
this, features};
737 {
USD(15'000),
EUR(15'000)},
754 BEAST_EXPECT(ammAlice.expectBalances(
755 XRP(10'100),
USD(10'000), ammAlice.tokens()));
764 Env env{*
this, features};
770 {
USD(15'000),
EUR(15'000)},
789 BEAST_EXPECT(ammAlice.expectBalances(
790 XRP(10'100),
USD(10'000), ammAlice.tokens()));
798 Env env{*
this, features};
804 {
USD(15'000),
EUR(15'000)},
837 testcase(
"Combine tfSell with tfFillOrKill");
845 Env env{*
this, features};
853 ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
857 Env env{*
this, features};
864 BEAST_EXPECT(ammBob.expectBalances(
875 Env env{*
this, features};
882 BEAST_EXPECT(ammBob.expectBalances(
896 Env env{*
this, features};
917 [&](
AMM& ammAlice,
Env& env) {
930 {{
XRP(10'000),
USD(10'100)}},
938 [&](
AMM& ammAlice,
Env& env) {
951 {{
XRP(10'100),
USD(10'000)}},
958 Env env{*
this, features};
963 {
USD(15'000),
EUR(15'000)},
995 Env env{*
this, features};
1000 {
USD(15'000),
EUR(15'000)},
1037 Env env{*
this, features};
1081 Env env{*
this, features};
1127 using namespace jtx;
1129 Env env{*
this, features};
1131 auto const USD_bob =
bob[
"USD"];
1132 auto const f = env.current()->fees().base;
1136 AMM ammBob(env,
bob,
XRP(10'000), USD_bob(10'100));
1142 XRP(10'100), USD_bob(10'000), ammBob.
tokens()));
1155 using namespace jtx;
1157 Env env{*
this, features};
1160 auto const fee = env.current()->fees().base;
1163 auto const ann =
Account(
"ann");
1164 auto const A_BUX = ann[
"BUX"];
1166 auto const cam =
Account(
"cam");
1167 auto const dan =
Account(
"dan");
1168 auto const D_BUX = dan[
"BUX"];
1176 env(
trust(cam, D_BUX(100)));
1178 env(
pay(dan,
bob, D_BUX(100)));
1194 AMM ammBob(env,
bob, A_BUX(30), D_BUX(30));
1196 env(
trust(ann, D_BUX(100)));
1200 env(
pay(ann, ann, D_BUX(30)),
1226 using namespace jtx;
1228 Env env{*
this, features};
1230 auto const ann =
Account(
"ann");
1232 auto const cam =
Account(
"cam");
1234 auto const A_BUX = ann[
"BUX"];
1235 auto const B_BUX =
bob[
"BUX"];
1237 auto const fee = env.current()->fees().base;
1242 env(
trust(ann, B_BUX(40)));
1243 env(
trust(cam, A_BUX(40)));
1245 env(
trust(cam, B_BUX(40)));
1250 env(
pay(ann, cam, A_BUX(35)));
1251 env(
pay(
bob, cam, B_BUX(35)));
1255 AMM ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1262 env.require(
balance(cam, A_BUX(35)));
1263 env.require(
balance(cam, B_BUX(35)));
1264 env.require(
offers(cam, 1));
1267 env(
offer(cam, B_BUX(30), A_BUX(30)));
1270 if (!features[fixAMMv1_1])
1273 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1274 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1281 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1282 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1287 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1288 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1295 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1296 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1305 using namespace jtx;
1307 Env env{*
this, features};
1309 auto const aliceUSD =
alice[
"USD"];
1310 auto const bobUSD =
bob[
"USD"];
1354 using namespace jtx;
1356 Env env{*
this, features};
1420 using namespace jtx;
1454 using namespace jtx;
1471 BEAST_EXPECT(st.
empty());
1479 BEAST_EXPECT(sa ==
XRP(100'000'000));
1483 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1493 using namespace jtx;
1496 auto const AUD =
gw[
"AUD"];
1520 using namespace jtx;
1521 auto const charlie =
Account(
"charlie");
1526 AMM ammCharlie(env, charlie,
XRP(10),
USD(11));
1529 BEAST_EXPECT(sa ==
XRP(1));
1531 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1533 auto const& pathElem = st[0][0];
1535 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1543 AMM ammCharlie(env, charlie,
XRP(11),
USD(10));
1547 BEAST_EXPECT(sa ==
USD(1));
1549 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1551 auto const& pathElem = st[0][0];
1553 pathElem.isOffer() &&
1563 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1564 using namespace jtx;
1576 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1580 env.
trust(G1[
"XYZ"](5'000), A1);
1581 env.
trust(G3[
"ABC"](5'000), A1);
1582 env.
trust(G2[
"XYZ"](5'000), A2);
1583 env.
trust(G3[
"ABC"](5'000), A2);
1584 env.
trust(A2[
"ABC"](1'000), A3);
1585 env.
trust(G1[
"XYZ"](100'000), M1);
1586 env.
trust(G2[
"XYZ"](100'000), M1);
1587 env.
trust(G3[
"ABC"](100'000), M1);
1590 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1591 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1592 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1593 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1594 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1597 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1598 AMM ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1604 auto const& send_amt =
XRP(10);
1607 BEAST_EXPECT(
equal(da, send_amt));
1608 BEAST_EXPECT(st.
empty());
1614 auto const& send_amt =
XRP(200);
1617 BEAST_EXPECT(
equal(da, send_amt));
1618 BEAST_EXPECT(st.
empty());
1622 auto const& send_amt = G3[
"ABC"](10);
1625 BEAST_EXPECT(
equal(da, send_amt));
1631 auto const& send_amt = A2[
"ABC"](1);
1634 BEAST_EXPECT(
equal(da, send_amt));
1640 auto const& send_amt = A3[
"ABC"](1);
1643 BEAST_EXPECT(
equal(da, send_amt));
1652 testcase(
"Path Find: non-XRP -> XRP");
1653 using namespace jtx;
1660 env.
fund(
XRP(1'000), A1, A2, G3);
1664 env.
trust(G3[
"ABC"](1'000), A1, A2);
1665 env.
trust(G3[
"ABC"](100'000), M1);
1668 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1669 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1670 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1673 AMM ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1678 auto const& send_amt =
XRP(10);
1681 BEAST_EXPECT(
equal(da, send_amt));
1682 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1689 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1690 using namespace jtx;
1703 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1705 env.
fund(
XRP(21'000), M1, M2);
1708 env.
trust(G1[
"HKD"](2'000), A1);
1709 env.
trust(G2[
"HKD"](2'000), A2);
1710 env.
trust(G1[
"HKD"](2'000), A3);
1711 env.
trust(G1[
"HKD"](100'000), M1);
1712 env.
trust(G2[
"HKD"](100'000), M1);
1713 env.
trust(G1[
"HKD"](100'000), M2);
1714 env.
trust(G2[
"HKD"](100'000), M2);
1717 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1718 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1719 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1720 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1721 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1722 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1723 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1726 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1727 AMM ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1728 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1736 auto const& send_amt = G1[
"HKD"](10);
1738 env, A1, G1, send_amt,
std::nullopt, G1[
"HKD"].currency);
1739 BEAST_EXPECT(st.
empty());
1740 BEAST_EXPECT(
equal(da, send_amt));
1741 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1747 auto const& send_amt = A1[
"HKD"](10);
1749 env, A1, G1, send_amt,
std::nullopt, G1[
"HKD"].currency);
1750 BEAST_EXPECT(st.
empty());
1751 BEAST_EXPECT(
equal(da, send_amt));
1752 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1758 auto const& send_amt = A3[
"HKD"](10);
1760 env, A1, A3, send_amt,
std::nullopt, G1[
"HKD"].currency);
1761 BEAST_EXPECT(
equal(da, send_amt));
1762 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1769 auto const& send_amt = G2[
"HKD"](10);
1771 env, G1, G2, send_amt,
std::nullopt, G1[
"HKD"].currency);
1772 BEAST_EXPECT(
equal(da, send_amt));
1773 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1785 auto const& send_amt = G2[
"HKD"](10);
1787 env, A1, G2, send_amt,
std::nullopt, G1[
"HKD"].currency);
1788 BEAST_EXPECT(
equal(da, send_amt));
1789 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1802 auto const& send_amt = A2[
"HKD"](10);
1804 env, A1, A2, send_amt,
std::nullopt, G1[
"HKD"].currency);
1805 BEAST_EXPECT(
equal(da, send_amt));
1806 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1819 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1820 using namespace jtx;
1830 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1833 env.
trust(G1[
"HKD"](2'000), A1);
1834 env.
trust(G2[
"HKD"](2'000), A2);
1835 env.
trust(A2[
"HKD"](2'000), A3);
1836 env.
trust(G1[
"HKD"](100'000), M1);
1837 env.
trust(G2[
"HKD"](100'000), M1);
1840 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1841 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1842 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1843 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1846 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1850 auto const& send_amt = A2[
"HKD"](10);
1855 BEAST_EXPECT(
equal(da, send_amt));
1856 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1865 using namespace jtx;
1867 Env env(*
this, features);
1873 auto const AMMXRPPool = env.
current()->fees().increment * 2;
1890 AMM ammBob(env,
bob, AMMXRPPool,
USD(150));
1898 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1906 using namespace jtx;
1910 Env env(*
this, features);
1929 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1933 Env env(*
this, features);
1952 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1953 BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1959 Env env(*
this, features);
1980 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
1984 Env env(*
this, features);
2004 ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
2008 Env env(*
this, features);
2061 Env env(*
this, features);
2095 auto const flowResult = [&] {
2110 paths.push_back(p1);
2113 paths.push_back(p2);
2132 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2135 if (flowResult.removableOffers.empty())
2138 for (
auto const& o : flowResult.removableOffers)
2161 Env env(*
this, features);
2184 BEAST_EXPECT(ammBob.expectBalances(
2193 using namespace jtx;
2197 Env env(*
this, features);
2204 {
USD(1'000),
GBP(1'000)});
2219 if (!features[fixAMMv1_1])
2222 BEAST_EXPECT(amm.expectBalances(
2229 BEAST_EXPECT(amm.expectBalances(
2242 Env env(*
this, features);
2276 BEAST_EXPECT(amm.expectBalances(
2286 Env env(*
this, features);
2299 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2308 if (!features[fixAMMv1_1])
2346 Env env(*
this, features);
2358 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2366 Env env(*
this, features);
2373 {
USD(1'000),
GBP(1'000)});
2393 BEAST_EXPECT(amm.expectBalances(
2394 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2404 Env env(*
this, features);
2411 {
USD(1'200),
GBP(1'200)});
2425 if (!features[fixAMMv1_1])
2432 BEAST_EXPECT(amm.expectBalances(
2433 GBP(1'024),
USD(1'171.875), amm.tokens()));
2443 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2445 BEAST_EXPECT(amm.expectBalances(
2446 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2457 Env env(*
this, features);
2482 if (!features[fixAMMv1_1])
2490 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2497 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2504 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2507 BEAST_EXPECT(amm.expectBalances(
2508 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2509 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2520 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2527 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2534 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2537 BEAST_EXPECT(amm.expectBalances(
2538 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2539 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2549 Env env(*
this, features);
2574 if (!features[fixAMMv1_1])
2582 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2586 BEAST_EXPECT(amm.expectBalances(
2587 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2588 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2599 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2603 BEAST_EXPECT(amm.expectBalances(
2604 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2605 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2614 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2621 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2629 Env env(*
this, features);
2642 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2652 if (!features[fixAMMv1_1])
2660 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2663 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2664 STAmount{EUR, UINT64_C(920'78937795562), -11},
2669 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2670 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2681 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2684 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2685 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2690 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2691 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2701 Env env(*
this, features);
2723 if (!features[fixAMMv1_1])
2727 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
2728 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2733 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
2734 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2741 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
2742 STAmount{EUR, UINT64_C(902'4064171122975), -13},
2747 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
2748 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2765 using namespace jtx;
2781 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
2792 using namespace jtx;
2842 auto const JPY =
gw[
"JPY"];
2851 {
USD(200),
EUR(200), JPY(200)},
2856 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
2871 using namespace jtx;
2872 Env env(*
this, features);
2873 auto const dan =
Account(
"dan");
2874 auto const ed =
Account(
"ed");
2890 if (!features[fixAMMv1_1])
2916 testcase(
"Convert all of an asset using DeliverMin");
2918 using namespace jtx;
2921 Env env(*
this, features);
2953 drops(10'000'000'000 - env.
current()->fees().base.drops())));
2958 Env env(*
this, features);
2972 Env env(*
this, features);
2994 auto const dan =
Account(
"dan");
2995 Env env(*
this, features);
3003 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3004 if (!features[fixAMMv1_1])
3028 STAmount{USD, UINT64_C(999'99999909091), -11},
3039 using namespace jtx;
3045 Env env(*
this, features);
3070 env(
pay(becky, becky,
USD(10)),
3085 using namespace jtx;
3106 auto failedIouPayments = [
this, &env]() {
3130 failedIouPayments();
3147 failedIouPayments();
3154 failedIouPayments();
3177 using namespace test::jtx;
3178 Env env(*
this, features);
3191 env(
pay(G1,
bob, G1[
"USD"](10)));
3192 env(
pay(G1,
alice, G1[
"USD"](205)));
3195 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3201 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3202 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3203 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3210 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3211 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3213 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3235 XRP(525), G1[
"USD"](100), ammAlice.
tokens()));
3254 for (
auto const& it :
lines[jss::lines])
3256 if (it[jss::account] ==
bob.
human())
3262 if (!BEAST_EXPECT(bobLine))
3264 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3265 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3272 for (
auto const& it :
lines[jss::lines])
3274 if (it[jss::account] == G1.human())
3280 if (!BEAST_EXPECT(g1Line))
3282 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3283 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3290 auto affected = env.
meta()->getJson(
3295 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3297 ff[sfLowLimit.fieldName] ==
3299 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3310 using namespace test::jtx;
3311 Env env(*
this, features);
3321 env.
fund(
XRP(20'000), A2, A3, A4);
3324 env.
trust(G1[
"USD"](1'200), A1);
3325 env.
trust(G1[
"USD"](200), A2);
3326 env.
trust(G1[
"BTC"](100), A3);
3327 env.
trust(G1[
"BTC"](100), A4);
3330 env(
pay(G1, A1, G1[
"USD"](1'000)));
3331 env(
pay(G1, A2, G1[
"USD"](100)));
3332 env(
pay(G1, A3, G1[
"BTC"](100)));
3333 env(
pay(G1, A4, G1[
"BTC"](100)));
3336 AMM ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3348 "XRP")[jss::result][jss::offers];
3356 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3362 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3370 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3377 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3383 env(
pay(G1, A2, G1[
"USD"](1)));
3386 env(
pay(A2, G1, G1[
"USD"](1)));
3389 env(
pay(A2, A1, G1[
"USD"](1)));
3392 env(
pay(A1, A2, G1[
"USD"](1)));
3418 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3425 "XRP")[jss::result][jss::offers];
3433 env(
pay(G1, A2, G1[
"USD"](1)));
3436 env(
pay(A2, G1, G1[
"USD"](1)));
3446 testcase(
"Offers for Frozen Trust Lines");
3448 using namespace test::jtx;
3449 Env env(*
this, features);
3456 env.
fund(
XRP(2'000), G1, A3, A4);
3460 env.
trust(G1[
"USD"](1'000), A2);
3461 env.
trust(G1[
"USD"](2'000), A3);
3462 env.
trust(G1[
"USD"](2'001), A4);
3465 env(
pay(G1, A3, G1[
"USD"](2'000)));
3466 env(
pay(G1, A4, G1[
"USD"](2'001)));
3469 AMM ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3480 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3491 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3507 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3519 testcase(
"Multisign AMM Transactions");
3521 using namespace jtx;
3522 Env env{*
this, features};
3539 msig const ms{becky, bogie};
3565 ammAlice.
vote({}, 1'000);
3568 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3580 using namespace jtx;
3584 Env env(*
this, features);
3591 {
USD(2'000),
EUR(1'000)});
3607 using namespace jtx;
3611 Env env(*
this, features);
3612 auto const BobUSD =
bob[
"USD"];
3613 auto const BobEUR =
bob[
"EUR"];
3622 {BobUSD(100), BobEUR(100)},
3626 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3629 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3632 Path const p = [&] {
3649 Env env(*
this, features);
3663 Env env(*
this, features);
3682 using namespace jtx;
3684 auto const CNY =
gw[
"CNY"];
3687 Env env(*
this, features);
3708 Env env(*
this, features);
3722 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
3748 using namespace jtx;
3762 using namespace jtx;
3771 using namespace jtx;
3787 using namespace test::jtx;
3825BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app,
ripple, 1);
A generic endpoint for log messages.
testcase_t testcase
Memberspace for declaring test cases.
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