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);
2162 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2165 if (flowResult.removableOffers.empty())
2168 for (
auto const& o : flowResult.removableOffers)
2191 Env env(*
this, features);
2214 BEAST_EXPECT(ammBob.expectBalances(
2224 using namespace jtx;
2228 Env env(*
this, features);
2251 Env env(*
this, features);
2258 {
USD(1'000),
EUR(1'000)});
2286 Env env(*
this, features);
2293 {
USD(1'000),
EUR(1'000)});
2324 Env env(*
this, features);
2331 {
USD(1'200),
GBP(1'200)});
2345 if (!features[fixAMMv1_1])
2353 STAmount{
GBP, UINT64_C(1'105'555555555555), -12}));
2355 BEAST_EXPECT(amm.expectBalances(
2356 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
2357 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2368 STAmount{
GBP, UINT64_C(1'105'555555555554), -12}));
2370 BEAST_EXPECT(amm.expectBalances(
2371 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
2372 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2381 Env env(*
this, features);
2392 if (!features[fixAMMv1_1])
2395 BEAST_EXPECT(amm.expectBalances(
2396 STAmount{USD, UINT64_C(1'095'238095238095), -12},
2410 BEAST_EXPECT(amm.expectBalances(
2411 STAmount{USD, UINT64_C(1'095'238095238096), -12},
2428 Env env(*
this, features);
2429 auto const USDA =
alice[
"USD"];
2430 auto const USDB =
bob[
"USD"];
2443 AMM ammDan(env, dan,
USD(1'000),
EUR(1'050));
2445 if (!features[fixAMMv1_1])
2470 STAmount{USD, UINT64_C(1'050'000000000001), -12},
2478 env,
bob,
STAmount{USDA, UINT64_C(60'000000000001), -12}));
2488 using namespace jtx;
2492 Env env(*
this, features);
2499 {
USD(1'000),
GBP(1'000)});
2514 if (!features[fixAMMv1_1])
2517 BEAST_EXPECT(amm.expectBalances(
2524 BEAST_EXPECT(amm.expectBalances(
2537 Env env(*
this, features);
2571 BEAST_EXPECT(amm.expectBalances(
2581 Env env(*
this, features);
2594 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2603 if (!features[fixAMMv1_1])
2641 Env env(*
this, features);
2653 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2661 Env env(*
this, features);
2668 {
USD(1'000),
GBP(1'000)});
2688 BEAST_EXPECT(amm.expectBalances(
2689 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2699 Env env(*
this, features);
2706 {
USD(1'200),
GBP(1'200)});
2720 if (!features[fixAMMv1_1])
2727 BEAST_EXPECT(amm.expectBalances(
2728 GBP(1'024),
USD(1'171.875), amm.tokens()));
2738 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2740 BEAST_EXPECT(amm.expectBalances(
2741 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2752 Env env(*
this, features);
2777 if (!features[fixAMMv1_1])
2785 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2792 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2799 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2802 BEAST_EXPECT(amm.expectBalances(
2803 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2804 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2815 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2822 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2829 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2832 BEAST_EXPECT(amm.expectBalances(
2833 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2834 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2844 Env env(*
this, features);
2869 if (!features[fixAMMv1_1])
2877 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2881 BEAST_EXPECT(amm.expectBalances(
2882 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2883 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2894 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2898 BEAST_EXPECT(amm.expectBalances(
2899 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2900 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2909 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2916 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2924 Env env(*
this, features);
2937 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2947 if (!features[fixAMMv1_1])
2955 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2958 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2959 STAmount{EUR, UINT64_C(920'78937795562), -11},
2964 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2965 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2976 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2979 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2980 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2985 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2986 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2996 Env env(*
this, features);
3018 if (!features[fixAMMv1_1])
3022 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
3023 STAmount{EUR, UINT64_C(902'4064171122988), -13},
3028 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
3029 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3036 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
3037 STAmount{EUR, UINT64_C(902'4064171122975), -13},
3042 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
3043 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3060 using namespace jtx;
3076 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
3087 using namespace jtx;
3089 for (
auto const withFix : {
true,
false})
3091 auto const feats = withFix
3096 Env env(*
this, feats);
3111 TER const expectedTer =
3144 auto const JPY =
gw[
"JPY"];
3153 {
USD(200),
EUR(200), JPY(200)},
3158 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
3173 using namespace jtx;
3174 Env env(*
this, features);
3175 auto const dan =
Account(
"dan");
3176 auto const ed =
Account(
"ed");
3192 if (!features[fixAMMv1_1])
3218 testcase(
"Convert all of an asset using DeliverMin");
3220 using namespace jtx;
3223 Env env(*
this, features);
3255 drops(10'000'000'000 - env.
current()->fees().base.drops())));
3260 Env env(*
this, features);
3274 Env env(*
this, features);
3296 auto const dan =
Account(
"dan");
3297 Env env(*
this, features);
3305 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3306 if (!features[fixAMMv1_1])
3330 STAmount{USD, UINT64_C(999'99999909091), -11},
3341 using namespace jtx;
3344 bool const supportsPreauth = {features[featureDepositPreauth]};
3349 Env env(*
this, features);
3378 env(
pay(becky, becky,
USD(10)),
3393 using namespace jtx;
3414 auto failedIouPayments = [
this, &env]() {
3438 failedIouPayments();
3455 failedIouPayments();
3462 failedIouPayments();
3485 using namespace test::jtx;
3486 Env env(*
this, features);
3499 env(
pay(G1,
bob, G1[
"USD"](10)));
3500 env(
pay(G1,
alice, G1[
"USD"](205)));
3503 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3509 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3510 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3511 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3518 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3519 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3521 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3543 XRP(525), G1[
"USD"](100), ammAlice.
tokens()));
3562 for (
auto const& it :
lines[jss::lines])
3564 if (it[jss::account] ==
bob.
human())
3570 if (!BEAST_EXPECT(bobLine))
3572 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3573 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3580 for (
auto const& it :
lines[jss::lines])
3582 if (it[jss::account] == G1.human())
3588 if (!BEAST_EXPECT(g1Line))
3590 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3591 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3598 auto affected = env.
meta()->getJson(
3603 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3605 ff[sfLowLimit.fieldName] ==
3607 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3618 using namespace test::jtx;
3619 Env env(*
this, features);
3629 env.
fund(
XRP(20'000), A2, A3, A4);
3632 env.
trust(G1[
"USD"](1'200), A1);
3633 env.
trust(G1[
"USD"](200), A2);
3634 env.
trust(G1[
"BTC"](100), A3);
3635 env.
trust(G1[
"BTC"](100), A4);
3638 env(
pay(G1, A1, G1[
"USD"](1'000)));
3639 env(
pay(G1, A2, G1[
"USD"](100)));
3640 env(
pay(G1, A3, G1[
"BTC"](100)));
3641 env(
pay(G1, A4, G1[
"BTC"](100)));
3644 AMM ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3656 "XRP")[jss::result][jss::offers];
3664 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3670 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3678 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3685 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3691 env(
pay(G1, A2, G1[
"USD"](1)));
3694 env(
pay(A2, G1, G1[
"USD"](1)));
3697 env(
pay(A2, A1, G1[
"USD"](1)));
3700 env(
pay(A1, A2, G1[
"USD"](1)));
3726 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3733 "XRP")[jss::result][jss::offers];
3741 env(
pay(G1, A2, G1[
"USD"](1)));
3744 env(
pay(A2, G1, G1[
"USD"](1)));
3754 testcase(
"Offers for Frozen Trust Lines");
3756 using namespace test::jtx;
3757 Env env(*
this, features);
3764 env.
fund(
XRP(2'000), G1, A3, A4);
3768 env.
trust(G1[
"USD"](1'000), A2);
3769 env.
trust(G1[
"USD"](2'000), A3);
3770 env.
trust(G1[
"USD"](2'001), A4);
3773 env(
pay(G1, A3, G1[
"USD"](2'000)));
3774 env(
pay(G1, A4, G1[
"USD"](2'001)));
3777 AMM ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3788 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3799 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3815 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3827 testcase(
"Multisign AMM Transactions");
3829 using namespace jtx;
3830 Env env{*
this, features};
3845 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3848 msig const ms{becky, bogie};
3874 ammAlice.
vote({}, 1'000);
3877 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3889 using namespace jtx;
3893 Env env(*
this, features);
3900 {
USD(2'000),
EUR(1'000)});
3916 using namespace jtx;
3920 Env env(*
this, features);
3921 auto const BobUSD =
bob[
"USD"];
3922 auto const BobEUR =
bob[
"EUR"];
3931 {BobUSD(100), BobEUR(100)},
3935 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3938 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3941 Path const p = [&] {
3958 Env env(*
this, features);
3972 Env env(*
this, features);
3991 using namespace jtx;
3993 auto const CNY =
gw[
"CNY"];
3996 Env env(*
this, features);
4017 Env env(*
this, features);
4031 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
4057 using namespace jtx;
4075 using namespace jtx;
4084 using namespace jtx;
4102 using namespace test::jtx;
4112 using namespace jtx;
4116 all - featureMultiSignReserve - featureExpandedSignerList);
4124 using namespace jtx;
4147BEAST_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