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"];
135 env(
pay(gw1, dan, USD1(10'000)));
136 env(
pay(gw1,
bob, USD1(50)));
137 env(
pay(gw2,
bob, USD2(50)));
139 AMM ammDan(env, dan,
XRP(10'000), USD1(10'000));
150 Env env{*
this, features};
155 auto const USD1 = gw1[
"USD"];
156 auto const USD2 = gw2[
"USD"];
159 env.fund(
XRP(20'000), dan);
163 env(
pay(gw1, dan, USD1(10'050)));
164 env(
pay(gw1,
bob, USD1(50)));
165 env(
pay(gw2,
bob, USD2(50)));
167 AMM ammDan(env, dan,
XRP(10'000), USD1(10'050));
174 XRP(10'050), USD1(10'000), ammDan.
tokens()));
190 auto const startBalance =
XRP(1'000'000);
198 for (
auto const& tweakedFeatures :
199 {features - fix1578, features | fix1578})
202 [&](
AMM& ammAlice,
Env& env) {
204 TER const killedCode{
230 {{
XRP(10'100),
USD(10'000)}},
238 [&](
AMM& ammAlice,
Env& env) {
254 {{
XRP(10'100),
USD(10'000)}},
261 [&](
AMM& ammAlice,
Env& env) {
271 {{
XRP(10'100),
USD(10'000)}},
278 [&](
AMM& ammAlice,
Env& env) {
293 {{
XRP(11'000),
USD(9'000)}},
303 testcase(
"Offer Crossing with XRP, Normal order");
307 Env env{*
this, features};
316 auto const xrpTransferred =
XRPAmount{3'061'224'490};
319 BEAST_EXPECT(ammAlice.expectBalances(
320 XRP(150'000) + xrpTransferred,
326 env,
bob,
XRP(300'000) - xrpTransferred -
txfee(env, 1)));
333 testcase(
"Offer Crossing with Limit Override");
337 Env env{*
this, features};
352 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
355 jrr[jss::node][sfBalance.fieldName] ==
357 (
XRP(200'000) -
XRP(3'000) - env.current()->fees().base * 1)
364 testcase(
"Currency Conversion: Entire Offer");
368 Env env{*
this, features};
390 jrr[jss::node][sfBalance.fieldName] ==
398 testcase(
"Currency Conversion: In Parts");
403 [&](
AMM& ammAlice,
Env& env) {
427 {{
XRP(10'000),
USD(10'000)}},
436 testcase(
"Cross Currency Payment: Start with XRP");
441 [&](
AMM& ammAlice,
Env& env) {
450 {{
XRP(10'000),
USD(10'100)}},
459 testcase(
"Cross Currency Payment: End with XRP");
464 [&](
AMM& ammAlice,
Env& env) {
474 {{
XRP(10'100),
USD(10'000)}},
483 testcase(
"Cross Currency Payment: Bridged");
487 Env env{*
this, features};
489 auto const gw1 =
Account{
"gateway_1"};
490 auto const gw2 =
Account{
"gateway_2"};
491 auto const dan =
Account{
"dan"};
492 auto const USD1 = gw1[
"USD"];
493 auto const EUR1 = gw2[
"EUR"];
503 env(
trust(dan, EUR1(1'000)));
509 env(
pay(gw2, dan, dan[
"EUR"](400)));
512 AMM ammCarol(env,
carol, USD1(5'000),
XRP(50'000));
514 env(
offer(dan,
XRP(500), EUR1(50)));
518 jtp[0u][0u][jss::currency] =
"XRP";
520 json(jss::Paths, jtp),
525 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
534 testcase(
"Offer Fees Consume Funds");
538 Env env{*
this, features};
540 auto const gw1 =
Account{
"gateway_1"};
541 auto const gw2 =
Account{
"gateway_2"};
542 auto const gw3 =
Account{
"gateway_3"};
545 auto const USD1 = gw1[
"USD"];
546 auto const USD2 = gw2[
"USD"];
547 auto const USD3 = gw3[
"USD"];
555 auto const starting_xrp =
XRP(100) +
556 env.current()->fees().accountReserve(3) +
557 env.current()->fees().base * 4;
559 env.fund(starting_xrp, gw1, gw2, gw3,
alice);
560 env.fund(
XRP(2'000),
bob);
570 AMM ammBob(env,
bob,
XRP(1'000), USD1(1'200));
579 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
584 jrr[jss::node][sfBalance.fieldName][jss::value] ==
588 jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
594 testcase(
"Offer Create, then Cross");
598 Env env{*
this, features};
619 jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
625 testcase(
"Offer tfSell: Basic Sell");
630 [&](
AMM& ammAlice,
Env& env) {
640 {{
XRP(9'900),
USD(10'100)}},
649 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
653 Env env{*
this, features};
655 auto const starting_xrp =
656 XRP(100) +
reserve(env, 1) + env.current()->fees().base * 2;
658 env.fund(starting_xrp,
gw,
alice);
659 env.fund(
XRP(2'000),
bob);
683 testcase(
"Client Issue: Gateway Cross Currency");
687 Env env{*
this, features};
689 auto const XTS =
gw[
"XTS"];
690 auto const XXX =
gw[
"XXX"];
692 auto const starting_xrp =
693 XRP(100.1) +
reserve(env, 1) + env.current()->fees().base * 2;
699 {XTS(100), XXX(100)},
702 AMM ammAlice(env,
alice, XTS(100), XXX(100));
706 payment[jss::id] = env.seq(
bob);
707 payment[jss::build_path] =
true;
709 payment[jss::tx_json][jss::Sequence] =
712 ->getFieldU32(sfSequence);
713 payment[jss::tx_json][jss::Fee] =
to_string(env.current()->fees().base);
714 payment[jss::tx_json][jss::SendMax] =
717 auto const jrr = env.rpc(
"json",
"submit",
to_string(payment));
718 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
719 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
720 if (!features[fixAMMv1_1])
722 BEAST_EXPECT(ammAlice.expectBalances(
723 STAmount(XTS, UINT64_C(101'010101010101), -12),
727 env,
bob,
STAmount{XTS, UINT64_C(98'989898989899), -12}));
731 BEAST_EXPECT(ammAlice.expectBalances(
732 STAmount(XTS, UINT64_C(101'0101010101011), -13),
736 env,
bob,
STAmount{XTS, UINT64_C(98'9898989898989), -13}));
749 Env env{*
this, features};
755 {
USD(15'000),
EUR(15'000)},
772 BEAST_EXPECT(ammAlice.expectBalances(
773 XRP(10'100),
USD(10'000), ammAlice.tokens()));
782 Env env{*
this, features};
788 {
USD(15'000),
EUR(15'000)},
807 BEAST_EXPECT(ammAlice.expectBalances(
808 XRP(10'100),
USD(10'000), ammAlice.tokens()));
816 Env env{*
this, features};
822 {
USD(15'000),
EUR(15'000)},
855 testcase(
"Combine tfSell with tfFillOrKill");
860 TER const killedCode{
864 Env env{*
this, features};
872 ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
876 Env env{*
this, features};
883 BEAST_EXPECT(ammBob.expectBalances(
894 Env env{*
this, features};
901 BEAST_EXPECT(ammBob.expectBalances(
915 Env env{*
this, features};
936 [&](
AMM& ammAlice,
Env& env) {
949 {{
XRP(10'000),
USD(10'100)}},
957 [&](
AMM& ammAlice,
Env& env) {
970 {{
XRP(10'100),
USD(10'000)}},
977 Env env{*
this, features};
982 {
USD(15'000),
EUR(15'000)},
1014 Env env{*
this, features};
1019 {
USD(15'000),
EUR(15'000)},
1056 Env env{*
this, features};
1100 Env env{*
this, features};
1146 using namespace jtx;
1148 Env env{*
this, features};
1150 auto const USD_bob =
bob[
"USD"];
1151 auto const f = env.current()->fees().base;
1155 AMM ammBob(env,
bob,
XRP(10'000), USD_bob(10'100));
1161 XRP(10'100), USD_bob(10'000), ammBob.
tokens()));
1174 using namespace jtx;
1178 Env env{*
this, features | featureOwnerPaysFee};
1181 auto const fee = env.current()->fees().base;
1184 auto const ann =
Account(
"ann");
1185 auto const A_BUX = ann[
"BUX"];
1187 auto const cam =
Account(
"cam");
1188 auto const dan =
Account(
"dan");
1189 auto const D_BUX = dan[
"BUX"];
1197 env(
trust(cam, D_BUX(100)));
1199 env(
pay(dan,
bob, D_BUX(100)));
1211 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1215 AMM ammBob(env,
bob, A_BUX(30), D_BUX(30));
1217 env(
trust(ann, D_BUX(100)));
1221 env(
pay(ann, ann, D_BUX(30)),
1230 BEAST_EXPECT(
expectLine(env, ann, D_BUX(0)));
1232 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1233 BEAST_EXPECT(
expectLine(env, dan, A_BUX(0)));
1247 using namespace jtx;
1249 Env env{*
this, features};
1251 auto const ann =
Account(
"ann");
1253 auto const cam =
Account(
"cam");
1255 auto const A_BUX = ann[
"BUX"];
1256 auto const B_BUX =
bob[
"BUX"];
1258 auto const fee = env.current()->fees().base;
1263 env(
trust(ann, B_BUX(40)));
1264 env(
trust(cam, A_BUX(40)));
1266 env(
trust(cam, B_BUX(40)));
1271 env(
pay(ann, cam, A_BUX(35)));
1272 env(
pay(
bob, cam, B_BUX(35)));
1276 AMM ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1283 env.require(
balance(cam, A_BUX(35)));
1284 env.require(
balance(cam, B_BUX(35)));
1285 env.require(
offers(cam, 1));
1288 env(
offer(cam, B_BUX(30), A_BUX(30)));
1291 if (!features[fixAMMv1_1])
1294 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1295 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1302 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1303 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1308 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1309 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1316 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1317 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1326 using namespace jtx;
1328 Env env{*
this, features};
1330 auto const aliceUSD =
alice[
"USD"];
1331 auto const bobUSD =
bob[
"USD"];
1375 using namespace jtx;
1377 Env env{*
this, features};
1441 using namespace jtx;
1475 using namespace jtx;
1492 BEAST_EXPECT(st.
empty());
1500 BEAST_EXPECT(sa ==
XRP(100'000'000));
1504 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1514 using namespace jtx;
1517 auto const AUD =
gw[
"AUD"];
1533 BEAST_EXPECT(std::get<0>(result).empty());
1540 using namespace jtx;
1541 auto const charlie =
Account(
"charlie");
1546 AMM ammCharlie(env, charlie,
XRP(10),
USD(11));
1549 BEAST_EXPECT(sa ==
XRP(1));
1551 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1553 auto const& pathElem = st[0][0];
1555 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1563 AMM ammCharlie(env, charlie,
XRP(11),
USD(10));
1567 BEAST_EXPECT(sa ==
USD(1));
1569 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1571 auto const& pathElem = st[0][0];
1573 pathElem.isOffer() &&
1583 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1584 using namespace jtx;
1596 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1600 env.
trust(G1[
"XYZ"](5'000), A1);
1601 env.
trust(G3[
"ABC"](5'000), A1);
1602 env.
trust(G2[
"XYZ"](5'000), A2);
1603 env.
trust(G3[
"ABC"](5'000), A2);
1604 env.
trust(A2[
"ABC"](1'000), A3);
1605 env.
trust(G1[
"XYZ"](100'000), M1);
1606 env.
trust(G2[
"XYZ"](100'000), M1);
1607 env.
trust(G3[
"ABC"](100'000), M1);
1610 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1611 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1612 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1613 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1614 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1617 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1618 AMM ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1624 auto const& send_amt =
XRP(10);
1627 BEAST_EXPECT(
equal(da, send_amt));
1628 BEAST_EXPECT(st.
empty());
1634 auto const& send_amt =
XRP(200);
1637 BEAST_EXPECT(
equal(da, send_amt));
1638 BEAST_EXPECT(st.
empty());
1642 auto const& send_amt = G3[
"ABC"](10);
1645 BEAST_EXPECT(
equal(da, send_amt));
1651 auto const& send_amt = A2[
"ABC"](1);
1654 BEAST_EXPECT(
equal(da, send_amt));
1660 auto const& send_amt = A3[
"ABC"](1);
1663 BEAST_EXPECT(
equal(da, send_amt));
1672 testcase(
"Path Find: non-XRP -> XRP");
1673 using namespace jtx;
1680 env.
fund(
XRP(1'000), A1, A2, G3);
1684 env.
trust(G3[
"ABC"](1'000), A1, A2);
1685 env.
trust(G3[
"ABC"](100'000), M1);
1688 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1689 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1690 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1693 AMM ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1698 auto const& send_amt =
XRP(10);
1700 find_paths(env, A1, A2, send_amt, std::nullopt, A2[
"ABC"].currency);
1701 BEAST_EXPECT(
equal(da, send_amt));
1702 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1709 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1710 using namespace jtx;
1723 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1725 env.
fund(
XRP(21'000), M1, M2);
1728 env.
trust(G1[
"HKD"](2'000), A1);
1729 env.
trust(G2[
"HKD"](2'000), A2);
1730 env.
trust(G1[
"HKD"](2'000), A3);
1731 env.
trust(G1[
"HKD"](100'000), M1);
1732 env.
trust(G2[
"HKD"](100'000), M1);
1733 env.
trust(G1[
"HKD"](100'000), M2);
1734 env.
trust(G2[
"HKD"](100'000), M2);
1737 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1738 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1739 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1740 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1741 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1742 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1743 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1746 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1747 AMM ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1748 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1756 auto const& send_amt = G1[
"HKD"](10);
1758 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1759 BEAST_EXPECT(st.
empty());
1760 BEAST_EXPECT(
equal(da, send_amt));
1761 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1767 auto const& send_amt = A1[
"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 = A3[
"HKD"](10);
1780 env, A1, A3, send_amt, std::nullopt, G1[
"HKD"].currency);
1781 BEAST_EXPECT(
equal(da, send_amt));
1782 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1789 auto const& send_amt = G2[
"HKD"](10);
1791 env, G1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1792 BEAST_EXPECT(
equal(da, send_amt));
1793 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1805 auto const& send_amt = G2[
"HKD"](10);
1807 env, A1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1808 BEAST_EXPECT(
equal(da, send_amt));
1809 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1822 auto const& send_amt = A2[
"HKD"](10);
1824 env, A1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1825 BEAST_EXPECT(
equal(da, send_amt));
1826 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1839 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1840 using namespace jtx;
1850 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1853 env.
trust(G1[
"HKD"](2'000), A1);
1854 env.
trust(G2[
"HKD"](2'000), A2);
1855 env.
trust(A2[
"HKD"](2'000), A3);
1856 env.
trust(G1[
"HKD"](100'000), M1);
1857 env.
trust(G2[
"HKD"](100'000), M1);
1860 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1861 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1862 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1863 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1866 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1870 auto const& send_amt = A2[
"HKD"](10);
1874 find_paths(env, G1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1875 BEAST_EXPECT(
equal(da, send_amt));
1876 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1885 using namespace jtx;
1887 Env env(*
this, features);
1893 auto const AMMXRPPool = env.
current()->fees().increment * 2;
1909 AMM ammBob(env,
bob, AMMXRPPool,
USD(150));
1917 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1925 using namespace jtx;
1929 Env env(*
this, features);
1948 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1952 Env env(*
this, features);
1971 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1972 BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1978 Env env(*
this, features);
1999 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
2003 Env env(*
this, features);
2023 ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
2027 Env env(*
this, features);
2076 Env env(*
this, features);
2107 auto const flowResult = [&] {
2122 paths.push_back(p1);
2125 paths.push_back(p2);
2143 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2146 if (flowResult.removableOffers.empty())
2149 for (
auto const& o : flowResult.removableOffers)
2172 Env env(*
this, features);
2192 BEAST_EXPECT(ammBob.expectBalances(
2202 using namespace jtx;
2206 Env env(*
this, features);
2229 Env env(*
this, features);
2236 {
USD(1'000),
EUR(1'000)});
2264 Env env(*
this, features);
2271 {
USD(1'000),
EUR(1'000)});
2302 Env env(*
this, features);
2309 {
USD(1'200),
GBP(1'200)});
2323 if (!features[fixAMMv1_1])
2331 STAmount{
GBP, UINT64_C(1'105'555555555555), -12}));
2333 BEAST_EXPECT(amm.expectBalances(
2334 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
2335 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2346 STAmount{
GBP, UINT64_C(1'105'555555555554), -12}));
2348 BEAST_EXPECT(amm.expectBalances(
2349 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
2350 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2359 Env env(*
this, features);
2370 if (!features[fixAMMv1_1])
2373 BEAST_EXPECT(amm.expectBalances(
2374 STAmount{USD, UINT64_C(1'095'238095238095), -12},
2388 BEAST_EXPECT(amm.expectBalances(
2389 STAmount{USD, UINT64_C(1'095'238095238096), -12},
2406 Env env(*
this, features);
2407 auto const USDA =
alice[
"USD"];
2408 auto const USDB =
bob[
"USD"];
2421 AMM ammDan(env, dan,
USD(1'000),
EUR(1'050));
2423 if (!features[fixAMMv1_1])
2448 STAmount{USD, UINT64_C(1'050'000000000001), -12},
2456 env,
bob,
STAmount{USDA, UINT64_C(60'000000000001), -12}));
2466 using namespace jtx;
2470 Env env(*
this, features);
2477 {
USD(1'000),
GBP(1'000)});
2492 if (!features[fixAMMv1_1])
2495 BEAST_EXPECT(amm.expectBalances(
2502 BEAST_EXPECT(amm.expectBalances(
2515 Env env(*
this, features);
2549 BEAST_EXPECT(amm.expectBalances(
2559 Env env(*
this, features);
2572 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2581 if (!features[fixAMMv1_1])
2619 Env env(*
this, features);
2631 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2639 Env env(*
this, features);
2646 {
USD(1'000),
GBP(1'000)});
2666 BEAST_EXPECT(amm.expectBalances(
2667 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2677 Env env(*
this, features);
2684 {
USD(1'200),
GBP(1'200)});
2698 if (!features[fixAMMv1_1])
2705 BEAST_EXPECT(amm.expectBalances(
2706 GBP(1'024),
USD(1'171.875), amm.tokens()));
2716 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2718 BEAST_EXPECT(amm.expectBalances(
2719 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2730 Env env(*
this, features);
2755 if (!features[fixAMMv1_1])
2763 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2770 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2777 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2780 BEAST_EXPECT(amm.expectBalances(
2781 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2782 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2793 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2800 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2807 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2810 BEAST_EXPECT(amm.expectBalances(
2811 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2812 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2822 Env env(*
this, features);
2847 if (!features[fixAMMv1_1])
2855 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2859 BEAST_EXPECT(amm.expectBalances(
2860 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2861 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2872 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2876 BEAST_EXPECT(amm.expectBalances(
2877 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2878 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2887 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2894 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2902 Env env(*
this, features);
2915 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2925 if (!features[fixAMMv1_1])
2933 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2936 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2937 STAmount{EUR, UINT64_C(920'78937795562), -11},
2942 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2943 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2954 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2957 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2958 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2963 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2964 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2974 Env env(*
this, features);
2996 if (!features[fixAMMv1_1])
3000 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
3001 STAmount{EUR, UINT64_C(902'4064171122988), -13},
3006 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
3007 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3014 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
3015 STAmount{EUR, UINT64_C(902'4064171122975), -13},
3020 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
3021 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3038 using namespace jtx;
3054 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
3065 using namespace jtx;
3067 for (
auto const withFix : {
true,
false})
3069 auto const feats = withFix
3074 Env env(*
this, feats);
3089 TER const expectedTer =
3122 auto const JPY =
gw[
"JPY"];
3131 {
USD(200),
EUR(200), JPY(200)},
3136 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
3151 using namespace jtx;
3152 Env env(*
this, features);
3153 auto const dan =
Account(
"dan");
3154 auto const ed =
Account(
"ed");
3169 if (!features[fixAMMv1_1])
3195 testcase(
"Convert all of an asset using DeliverMin");
3197 using namespace jtx;
3200 Env env(*
this, features);
3235 Env env(*
this, features);
3249 Env env(*
this, features);
3271 auto const dan =
Account(
"dan");
3272 Env env(*
this, features);
3279 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3280 if (!features[fixAMMv1_1])
3304 STAmount{USD, UINT64_C(999'99999909091), -11},
3315 using namespace jtx;
3318 bool const supportsPreauth = {features[featureDepositPreauth]};
3323 Env env(*
this, features);
3352 env(
pay(becky, becky,
USD(10)),
3367 using namespace jtx;
3388 auto failedIouPayments = [
this, &env]() {
3412 failedIouPayments();
3429 failedIouPayments();
3436 failedIouPayments();
3459 using namespace test::jtx;
3460 Env env(*
this, features);
3473 env(
pay(G1,
bob, G1[
"USD"](10)));
3474 env(
pay(G1,
alice, G1[
"USD"](205)));
3477 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3483 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3484 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3485 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3492 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3493 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3495 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3512 auto affected = env.
meta()->getJson(
3517 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3519 ff[sfLowLimit.fieldName] ==
3530 auto affected = env.
meta()->getJson(
3535 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3537 ff[sfHighLimit.fieldName] ==
3542 BEAST_EXPECT(ff[sfBalance.fieldName] == amt);
3545 XRP(525), G1[
"USD"](100), ammAlice.
tokens()));
3564 for (
auto const& it :
lines[jss::lines])
3566 if (it[jss::account] ==
bob.
human())
3572 if (!BEAST_EXPECT(bobLine))
3574 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3575 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3582 for (
auto const& it :
lines[jss::lines])
3584 if (it[jss::account] == G1.human())
3590 if (!BEAST_EXPECT(g1Line))
3592 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3593 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3600 auto affected = env.
meta()->getJson(
3605 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3607 ff[sfLowLimit.fieldName] ==
3609 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3620 using namespace test::jtx;
3621 Env env(*
this, features);
3631 env.
fund(
XRP(20'000), A2, A3, A4);
3634 env.
trust(G1[
"USD"](1'200), A1);
3635 env.
trust(G1[
"USD"](200), A2);
3636 env.
trust(G1[
"BTC"](100), A3);
3637 env.
trust(G1[
"BTC"](100), A4);
3640 env(
pay(G1, A1, G1[
"USD"](1'000)));
3641 env(
pay(G1, A2, G1[
"USD"](100)));
3642 env(
pay(G1, A3, G1[
"BTC"](100)));
3643 env(
pay(G1, A4, G1[
"BTC"](100)));
3646 AMM ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3658 "XRP")[jss::result][jss::offers];
3666 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3672 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3680 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3687 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3693 env(
pay(G1, A2, G1[
"USD"](1)));
3696 env(
pay(A2, G1, G1[
"USD"](1)));
3699 env(
pay(A2, A1, G1[
"USD"](1)));
3702 env(
pay(A1, A2, G1[
"USD"](1)));
3728 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3735 "XRP")[jss::result][jss::offers];
3743 env(
pay(G1, A2, G1[
"USD"](1)));
3746 env(
pay(A2, G1, G1[
"USD"](1)));
3756 testcase(
"Offers for Frozen Trust Lines");
3758 using namespace test::jtx;
3759 Env env(*
this, features);
3766 env.
fund(
XRP(2'000), G1, A3, A4);
3770 env.
trust(G1[
"USD"](1'000), A2);
3771 env.
trust(G1[
"USD"](2'000), A3);
3772 env.
trust(G1[
"USD"](2'001), A4);
3775 env(
pay(G1, A3, G1[
"USD"](2'000)));
3776 env(
pay(G1, A4, G1[
"USD"](2'001)));
3779 AMM ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3790 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3801 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3807 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3809 ff[sfHighLimit.fieldName] ==
3811 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3829 ff = affected[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3831 ff[sfLowLimit.fieldName] ==
3838 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3843 auto created = affected[0u][sfCreatedNode.fieldName];
3845 created[sfNewFields.fieldName][jss::Account] == A2.human());
3857 testcase(
"Multisign AMM Transactions");
3859 using namespace jtx;
3860 Env env{*
this, features};
3875 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3878 const msig ms{becky, bogie};
3904 ammAlice.
vote({}, 1'000);
3907 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3919 using namespace jtx;
3923 Env env(*
this, features);
3930 {
USD(2'000),
EUR(1'000)});
3946 using namespace jtx;
3950 Env env(*
this, features);
3951 auto const BobUSD =
bob[
"USD"];
3952 auto const BobEUR =
bob[
"EUR"];
3960 {BobUSD(100), BobEUR(100)},
3963 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3966 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3969 Path const p = [&] {
3986 Env env(*
this, features);
4000 Env env(*
this, features);
4019 using namespace jtx;
4021 auto const CNY =
gw[
"CNY"];
4024 Env env(*
this, features);
4043 Env env(*
this, features);
4056 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
4082 using namespace jtx;
4100 using namespace jtx;
4109 using namespace jtx;
4127 using namespace test::jtx;
4137 using namespace jtx;
4141 all - featureMultiSignReserve - featureExpandedSignerList);
4149 using namespace jtx;
4172BEAST_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.
Json::Value noop(Account const &account)
The null transaction.
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.
AccountID const & noAccount()
A placeholder for empty accounts.
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