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>
26#include <xrpld/app/misc/AMMUtils.h>
27#include <xrpld/app/paths/AMMContext.h>
28#include <xrpld/app/paths/AMMOffer.h>
29#include <xrpld/app/paths/Flow.h>
30#include <xrpld/app/paths/detail/StrandFlow.h>
31#include <xrpld/ledger/PaymentSandbox.h>
32#include <xrpl/protocol/Feature.h>
33#include <xrpl/protocol/STParsedJSON.h>
50 testcase(
"Incorrect Removal of Funded Offers");
62 Env env{*
this, features};
69 {
USD(200'000),
BTC(2'000)});
92 if (!features[fixAMMv1_1])
95 STAmount{BTC, UINT64_C(1'001'000000374812), -12},
102 STAmount{BTC, UINT64_C(1'001'000000374815), -12},
119 Env env{*
this, features};
124 auto const USD1 = gw1[
"USD"];
125 auto const USD2 = gw2[
"USD"];
133 env(
pay(gw1, dan, USD1(10'000)));
134 env(
pay(gw1,
bob, USD1(50)));
135 env(
pay(gw2,
bob, USD2(50)));
137 AMM ammDan(env, dan,
XRP(10'000), USD1(10'000));
148 Env env{*
this, features};
153 auto const USD1 = gw1[
"USD"];
154 auto const USD2 = gw2[
"USD"];
157 env.fund(
XRP(20'000), dan);
161 env(
pay(gw1, dan, USD1(10'050)));
162 env(
pay(gw1,
bob, USD1(50)));
163 env(
pay(gw2,
bob, USD2(50)));
165 AMM ammDan(env, dan,
XRP(10'000), USD1(10'050));
172 XRP(10'050), USD1(10'000), ammDan.
tokens()));
188 auto const startBalance =
XRP(1'000'000);
196 for (
auto const& tweakedFeatures :
197 {features - fix1578, features | fix1578})
200 [&](
AMM& ammAlice,
Env& env) {
202 TER const killedCode{
228 {{
XRP(10'100),
USD(10'000)}},
236 [&](
AMM& ammAlice,
Env& env) {
252 {{
XRP(10'100),
USD(10'000)}},
259 [&](
AMM& ammAlice,
Env& env) {
269 {{
XRP(10'100),
USD(10'000)}},
276 [&](
AMM& ammAlice,
Env& env) {
291 {{
XRP(11'000),
USD(9'000)}},
301 testcase(
"Offer Crossing with XRP, Normal order");
305 Env env{*
this, features};
314 auto const xrpTransferred =
XRPAmount{3'061'224'490};
317 BEAST_EXPECT(ammAlice.expectBalances(
318 XRP(150'000) + xrpTransferred,
324 env,
bob,
XRP(300'000) - xrpTransferred -
txfee(env, 1)));
331 testcase(
"Offer Crossing with Limit Override");
335 Env env{*
this, features};
350 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
353 jrr[jss::node][sfBalance.fieldName] ==
355 (
XRP(200'000) -
XRP(3'000) - env.current()->fees().base * 1)
362 testcase(
"Currency Conversion: Entire Offer");
366 Env env{*
this, features};
388 jrr[jss::node][sfBalance.fieldName] ==
396 testcase(
"Currency Conversion: In Parts");
401 [&](
AMM& ammAlice,
Env& env) {
425 {{
XRP(10'000),
USD(10'000)}},
434 testcase(
"Cross Currency Payment: Start with XRP");
439 [&](
AMM& ammAlice,
Env& env) {
448 {{
XRP(10'000),
USD(10'100)}},
457 testcase(
"Cross Currency Payment: End with XRP");
462 [&](
AMM& ammAlice,
Env& env) {
472 {{
XRP(10'100),
USD(10'000)}},
481 testcase(
"Cross Currency Payment: Bridged");
485 Env env{*
this, features};
487 auto const gw1 =
Account{
"gateway_1"};
488 auto const gw2 =
Account{
"gateway_2"};
489 auto const dan =
Account{
"dan"};
490 auto const USD1 = gw1[
"USD"];
491 auto const EUR1 = gw2[
"EUR"];
501 env(
trust(dan, EUR1(1'000)));
507 env(
pay(gw2, dan, dan[
"EUR"](400)));
510 AMM ammCarol(env,
carol, USD1(5'000),
XRP(50'000));
512 env(
offer(dan,
XRP(500), EUR1(50)));
516 jtp[0u][0u][jss::currency] =
"XRP";
518 json(jss::Paths, jtp),
523 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
532 testcase(
"Offer Fees Consume Funds");
536 Env env{*
this, features};
538 auto const gw1 =
Account{
"gateway_1"};
539 auto const gw2 =
Account{
"gateway_2"};
540 auto const gw3 =
Account{
"gateway_3"};
543 auto const USD1 = gw1[
"USD"];
544 auto const USD2 = gw2[
"USD"];
545 auto const USD3 = gw3[
"USD"];
553 auto const starting_xrp =
XRP(100) +
554 env.current()->fees().accountReserve(3) +
555 env.current()->fees().base * 4;
557 env.fund(starting_xrp, gw1, gw2, gw3,
alice);
558 env.fund(
XRP(2'000),
bob);
568 AMM ammBob(env,
bob,
XRP(1'000), USD1(1'200));
577 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
582 jrr[jss::node][sfBalance.fieldName][jss::value] ==
586 jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
592 testcase(
"Offer Create, then Cross");
596 Env env{*
this, features};
617 jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
623 testcase(
"Offer tfSell: Basic Sell");
628 [&](
AMM& ammAlice,
Env& env) {
638 {{
XRP(9'900),
USD(10'100)}},
647 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
651 Env env{*
this, features};
653 auto const starting_xrp =
654 XRP(100) +
reserve(env, 1) + env.current()->fees().base * 2;
656 env.fund(starting_xrp,
gw,
alice);
657 env.fund(
XRP(2'000),
bob);
681 testcase(
"Client Issue: Gateway Cross Currency");
685 Env env{*
this, features};
687 auto const XTS =
gw[
"XTS"];
688 auto const XXX =
gw[
"XXX"];
690 auto const starting_xrp =
691 XRP(100.1) +
reserve(env, 1) + env.current()->fees().base * 2;
697 {XTS(100), XXX(100)},
700 AMM ammAlice(env,
alice, XTS(100), XXX(100));
704 payment[jss::id] = env.seq(
bob);
705 payment[jss::build_path] =
true;
707 payment[jss::tx_json][jss::Sequence] =
710 ->getFieldU32(sfSequence);
711 payment[jss::tx_json][jss::Fee] =
to_string(env.current()->fees().base);
712 payment[jss::tx_json][jss::SendMax] =
715 auto const jrr = env.rpc(
"json",
"submit",
to_string(payment));
716 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
717 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
718 if (!features[fixAMMv1_1])
720 BEAST_EXPECT(ammAlice.expectBalances(
721 STAmount(XTS, UINT64_C(101'010101010101), -12),
725 env,
bob,
STAmount{XTS, UINT64_C(98'989898989899), -12}));
729 BEAST_EXPECT(ammAlice.expectBalances(
730 STAmount(XTS, UINT64_C(101'0101010101011), -13),
734 env,
bob,
STAmount{XTS, UINT64_C(98'9898989898989), -13}));
747 Env env{*
this, features};
753 {
USD(15'000),
EUR(15'000)},
770 BEAST_EXPECT(ammAlice.expectBalances(
771 XRP(10'100),
USD(10'000), ammAlice.tokens()));
780 Env env{*
this, features};
786 {
USD(15'000),
EUR(15'000)},
805 BEAST_EXPECT(ammAlice.expectBalances(
806 XRP(10'100),
USD(10'000), ammAlice.tokens()));
814 Env env{*
this, features};
820 {
USD(15'000),
EUR(15'000)},
853 testcase(
"Combine tfSell with tfFillOrKill");
858 TER const killedCode{
862 Env env{*
this, features};
870 ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
874 Env env{*
this, features};
881 BEAST_EXPECT(ammBob.expectBalances(
892 Env env{*
this, features};
899 BEAST_EXPECT(ammBob.expectBalances(
913 Env env{*
this, features};
934 [&](
AMM& ammAlice,
Env& env) {
947 {{
XRP(10'000),
USD(10'100)}},
955 [&](
AMM& ammAlice,
Env& env) {
968 {{
XRP(10'100),
USD(10'000)}},
975 Env env{*
this, features};
980 {
USD(15'000),
EUR(15'000)},
1012 Env env{*
this, features};
1017 {
USD(15'000),
EUR(15'000)},
1054 Env env{*
this, features};
1098 Env env{*
this, features};
1144 using namespace jtx;
1146 Env env{*
this, features};
1148 auto const USD_bob =
bob[
"USD"];
1149 auto const f = env.current()->fees().base;
1153 AMM ammBob(env,
bob,
XRP(10'000), USD_bob(10'100));
1159 XRP(10'100), USD_bob(10'000), ammBob.
tokens()));
1172 using namespace jtx;
1176 Env env{*
this, features | featureOwnerPaysFee};
1179 auto const fee = env.current()->fees().base;
1182 auto const ann =
Account(
"ann");
1183 auto const A_BUX = ann[
"BUX"];
1185 auto const cam =
Account(
"cam");
1186 auto const dan =
Account(
"dan");
1187 auto const D_BUX = dan[
"BUX"];
1195 env(
trust(cam, D_BUX(100)));
1197 env(
pay(dan,
bob, D_BUX(100)));
1209 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1213 AMM ammBob(env,
bob, A_BUX(30), D_BUX(30));
1215 env(
trust(ann, D_BUX(100)));
1219 env(
pay(ann, ann, D_BUX(30)),
1228 BEAST_EXPECT(
expectLine(env, ann, D_BUX(0)));
1230 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1231 BEAST_EXPECT(
expectLine(env, dan, A_BUX(0)));
1245 using namespace jtx;
1247 Env env{*
this, features};
1249 auto const ann =
Account(
"ann");
1251 auto const cam =
Account(
"cam");
1253 auto const A_BUX = ann[
"BUX"];
1254 auto const B_BUX =
bob[
"BUX"];
1256 auto const fee = env.current()->fees().base;
1261 env(
trust(ann, B_BUX(40)));
1262 env(
trust(cam, A_BUX(40)));
1264 env(
trust(cam, B_BUX(40)));
1269 env(
pay(ann, cam, A_BUX(35)));
1270 env(
pay(
bob, cam, B_BUX(35)));
1274 AMM ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1281 env.require(
balance(cam, A_BUX(35)));
1282 env.require(
balance(cam, B_BUX(35)));
1283 env.require(
offers(cam, 1));
1286 env(
offer(cam, B_BUX(30), A_BUX(30)));
1289 if (!features[fixAMMv1_1])
1292 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1293 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1300 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1301 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1306 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1307 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1314 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1315 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1324 using namespace jtx;
1326 Env env{*
this, features};
1328 auto const aliceUSD =
alice[
"USD"];
1329 auto const bobUSD =
bob[
"USD"];
1373 using namespace jtx;
1375 Env env{*
this, features};
1439 using namespace jtx;
1473 using namespace jtx;
1490 BEAST_EXPECT(st.
empty());
1498 BEAST_EXPECT(sa ==
XRP(100'000'000));
1502 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1512 using namespace jtx;
1515 auto const AUD =
gw[
"AUD"];
1531 BEAST_EXPECT(std::get<0>(result).empty());
1538 using namespace jtx;
1539 auto const charlie =
Account(
"charlie");
1544 AMM ammCharlie(env, charlie,
XRP(10),
USD(11));
1547 BEAST_EXPECT(sa ==
XRP(1));
1549 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1551 auto const& pathElem = st[0][0];
1553 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1561 AMM ammCharlie(env, charlie,
XRP(11),
USD(10));
1565 BEAST_EXPECT(sa ==
USD(1));
1567 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1569 auto const& pathElem = st[0][0];
1571 pathElem.isOffer() &&
1581 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1582 using namespace jtx;
1594 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1598 env.
trust(G1[
"XYZ"](5'000), A1);
1599 env.
trust(G3[
"ABC"](5'000), A1);
1600 env.
trust(G2[
"XYZ"](5'000), A2);
1601 env.
trust(G3[
"ABC"](5'000), A2);
1602 env.
trust(A2[
"ABC"](1'000), A3);
1603 env.
trust(G1[
"XYZ"](100'000), M1);
1604 env.
trust(G2[
"XYZ"](100'000), M1);
1605 env.
trust(G3[
"ABC"](100'000), M1);
1608 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1609 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1610 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1611 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1612 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1615 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1616 AMM ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1622 auto const& send_amt =
XRP(10);
1625 BEAST_EXPECT(
equal(da, send_amt));
1626 BEAST_EXPECT(st.
empty());
1632 auto const& send_amt =
XRP(200);
1635 BEAST_EXPECT(
equal(da, send_amt));
1636 BEAST_EXPECT(st.
empty());
1640 auto const& send_amt = G3[
"ABC"](10);
1643 BEAST_EXPECT(
equal(da, send_amt));
1649 auto const& send_amt = A2[
"ABC"](1);
1652 BEAST_EXPECT(
equal(da, send_amt));
1658 auto const& send_amt = A3[
"ABC"](1);
1661 BEAST_EXPECT(
equal(da, send_amt));
1670 testcase(
"Path Find: non-XRP -> XRP");
1671 using namespace jtx;
1678 env.
fund(
XRP(1'000), A1, A2, G3);
1682 env.
trust(G3[
"ABC"](1'000), A1, A2);
1683 env.
trust(G3[
"ABC"](100'000), M1);
1686 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1687 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1688 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1691 AMM ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1696 auto const& send_amt =
XRP(10);
1698 find_paths(env, A1, A2, send_amt, std::nullopt, A2[
"ABC"].currency);
1699 BEAST_EXPECT(
equal(da, send_amt));
1700 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1707 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1708 using namespace jtx;
1721 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1723 env.
fund(
XRP(21'000), M1, M2);
1726 env.
trust(G1[
"HKD"](2'000), A1);
1727 env.
trust(G2[
"HKD"](2'000), A2);
1728 env.
trust(G1[
"HKD"](2'000), A3);
1729 env.
trust(G1[
"HKD"](100'000), M1);
1730 env.
trust(G2[
"HKD"](100'000), M1);
1731 env.
trust(G1[
"HKD"](100'000), M2);
1732 env.
trust(G2[
"HKD"](100'000), M2);
1735 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1736 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1737 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1738 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1739 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1740 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1741 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1744 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1745 AMM ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1746 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1754 auto const& send_amt = G1[
"HKD"](10);
1756 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1757 BEAST_EXPECT(st.
empty());
1758 BEAST_EXPECT(
equal(da, send_amt));
1759 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1765 auto const& send_amt = A1[
"HKD"](10);
1767 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1768 BEAST_EXPECT(st.
empty());
1769 BEAST_EXPECT(
equal(da, send_amt));
1770 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1776 auto const& send_amt = A3[
"HKD"](10);
1778 env, A1, A3, send_amt, std::nullopt, G1[
"HKD"].currency);
1779 BEAST_EXPECT(
equal(da, send_amt));
1780 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1787 auto const& send_amt = G2[
"HKD"](10);
1789 env, G1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1790 BEAST_EXPECT(
equal(da, send_amt));
1791 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1803 auto const& send_amt = G2[
"HKD"](10);
1805 env, A1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1806 BEAST_EXPECT(
equal(da, send_amt));
1807 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1820 auto const& send_amt = A2[
"HKD"](10);
1822 env, A1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1823 BEAST_EXPECT(
equal(da, send_amt));
1824 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1837 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1838 using namespace jtx;
1848 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1851 env.
trust(G1[
"HKD"](2'000), A1);
1852 env.
trust(G2[
"HKD"](2'000), A2);
1853 env.
trust(A2[
"HKD"](2'000), A3);
1854 env.
trust(G1[
"HKD"](100'000), M1);
1855 env.
trust(G2[
"HKD"](100'000), M1);
1858 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1859 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1860 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1861 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1864 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1868 auto const& send_amt = A2[
"HKD"](10);
1872 find_paths(env, G1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1873 BEAST_EXPECT(
equal(da, send_amt));
1874 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1883 using namespace jtx;
1885 Env env(*
this, features);
1891 auto const AMMXRPPool = env.
current()->fees().increment * 2;
1907 AMM ammBob(env,
bob, AMMXRPPool,
USD(150));
1915 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1923 using namespace jtx;
1927 Env env(*
this, features);
1946 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1950 Env env(*
this, features);
1969 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1970 BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1976 Env env(*
this, features);
1997 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
2001 Env env(*
this, features);
2021 ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
2025 Env env(*
this, features);
2074 Env env(*
this, features);
2105 auto const flowResult = [&] {
2120 paths.push_back(p1);
2123 paths.push_back(p2);
2141 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2144 if (flowResult.removableOffers.empty())
2147 for (
auto const& o : flowResult.removableOffers)
2170 Env env(*
this, features);
2190 BEAST_EXPECT(ammBob.expectBalances(
2200 using namespace jtx;
2204 Env env(*
this, features);
2227 Env env(*
this, features);
2234 {
USD(1'000),
EUR(1'000)});
2262 Env env(*
this, features);
2269 {
USD(1'000),
EUR(1'000)});
2300 Env env(*
this, features);
2307 {
USD(1'200),
GBP(1'200)});
2321 if (!features[fixAMMv1_1])
2329 STAmount{
GBP, UINT64_C(1'105'555555555555), -12}));
2331 BEAST_EXPECT(amm.expectBalances(
2332 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
2333 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2344 STAmount{
GBP, UINT64_C(1'105'555555555554), -12}));
2346 BEAST_EXPECT(amm.expectBalances(
2347 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
2348 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2357 Env env(*
this, features);
2368 if (!features[fixAMMv1_1])
2371 BEAST_EXPECT(amm.expectBalances(
2372 STAmount{USD, UINT64_C(1'095'238095238095), -12},
2386 BEAST_EXPECT(amm.expectBalances(
2387 STAmount{USD, UINT64_C(1'095'238095238096), -12},
2404 Env env(*
this, features);
2405 auto const USDA =
alice[
"USD"];
2406 auto const USDB =
bob[
"USD"];
2419 AMM ammDan(env, dan,
USD(1'000),
EUR(1'050));
2421 if (!features[fixAMMv1_1])
2446 STAmount{USD, UINT64_C(1'050'000000000001), -12},
2454 env,
bob,
STAmount{USDA, UINT64_C(60'000000000001), -12}));
2464 using namespace jtx;
2468 Env env(*
this, features);
2475 {
USD(1'000),
GBP(1'000)});
2490 if (!features[fixAMMv1_1])
2493 BEAST_EXPECT(amm.expectBalances(
2500 BEAST_EXPECT(amm.expectBalances(
2513 Env env(*
this, features);
2547 BEAST_EXPECT(amm.expectBalances(
2557 Env env(*
this, features);
2570 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2579 if (!features[fixAMMv1_1])
2617 Env env(*
this, features);
2629 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2637 Env env(*
this, features);
2644 {
USD(1'000),
GBP(1'000)});
2664 BEAST_EXPECT(amm.expectBalances(
2665 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2675 Env env(*
this, features);
2682 {
USD(1'200),
GBP(1'200)});
2696 if (!features[fixAMMv1_1])
2703 BEAST_EXPECT(amm.expectBalances(
2704 GBP(1'024),
USD(1'171.875), amm.tokens()));
2714 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2716 BEAST_EXPECT(amm.expectBalances(
2717 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2728 Env env(*
this, features);
2753 if (!features[fixAMMv1_1])
2761 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2768 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2775 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2778 BEAST_EXPECT(amm.expectBalances(
2779 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2780 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2791 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2798 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2805 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2808 BEAST_EXPECT(amm.expectBalances(
2809 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2810 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2820 Env env(*
this, features);
2845 if (!features[fixAMMv1_1])
2853 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2857 BEAST_EXPECT(amm.expectBalances(
2858 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2859 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2870 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2874 BEAST_EXPECT(amm.expectBalances(
2875 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2876 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2885 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2892 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2900 Env env(*
this, features);
2913 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2923 if (!features[fixAMMv1_1])
2931 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2934 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2935 STAmount{EUR, UINT64_C(920'78937795562), -11},
2940 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2941 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2952 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2955 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2956 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2961 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2962 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2972 Env env(*
this, features);
2994 if (!features[fixAMMv1_1])
2998 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
2999 STAmount{EUR, UINT64_C(902'4064171122988), -13},
3004 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
3005 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3012 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
3013 STAmount{EUR, UINT64_C(902'4064171122975), -13},
3018 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
3019 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3036 using namespace jtx;
3052 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
3063 using namespace jtx;
3065 for (
auto const withFix : {
true,
false})
3067 auto const feats = withFix
3072 Env env(*
this, feats);
3087 TER const expectedTer =
3120 auto const JPY =
gw[
"JPY"];
3129 {
USD(200),
EUR(200), JPY(200)},
3134 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
3149 using namespace jtx;
3150 Env env(*
this, features);
3151 auto const dan =
Account(
"dan");
3152 auto const ed =
Account(
"ed");
3167 if (!features[fixAMMv1_1])
3193 testcase(
"Convert all of an asset using DeliverMin");
3195 using namespace jtx;
3198 Env env(*
this, features);
3233 Env env(*
this, features);
3247 Env env(*
this, features);
3269 auto const dan =
Account(
"dan");
3270 Env env(*
this, features);
3277 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3278 if (!features[fixAMMv1_1])
3302 STAmount{USD, UINT64_C(999'99999909091), -11},
3313 using namespace jtx;
3316 bool const supportsPreauth = {features[featureDepositPreauth]};
3321 Env env(*
this, features);
3350 env(
pay(becky, becky,
USD(10)),
3365 using namespace jtx;
3386 auto failedIouPayments = [
this, &env]() {
3410 failedIouPayments();
3427 failedIouPayments();
3434 failedIouPayments();
3457 using namespace test::jtx;
3458 Env env(*
this, features);
3471 env(
pay(G1,
bob, G1[
"USD"](10)));
3472 env(
pay(G1,
alice, G1[
"USD"](205)));
3475 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3481 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3482 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3483 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3490 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3491 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3493 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3510 auto affected = env.
meta()->getJson(
3515 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3517 ff[sfLowLimit.fieldName] ==
3528 auto affected = env.
meta()->getJson(
3533 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3535 ff[sfHighLimit.fieldName] ==
3540 BEAST_EXPECT(ff[sfBalance.fieldName] == amt);
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());
3805 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3807 ff[sfHighLimit.fieldName] ==
3809 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3827 ff = affected[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3829 ff[sfLowLimit.fieldName] ==
3836 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3841 auto created = affected[0u][sfCreatedNode.fieldName];
3843 created[sfNewFields.fieldName][jss::Account] == A2.human());
3855 testcase(
"Multisign AMM Transactions");
3857 using namespace jtx;
3858 Env env{*
this, features};
3873 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3876 const msig ms{becky, bogie};
3902 ammAlice.
vote({}, 1'000);
3905 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3917 using namespace jtx;
3921 Env env(*
this, features);
3928 {
USD(2'000),
EUR(1'000)});
3944 using namespace jtx;
3948 Env env(*
this, features);
3949 auto const BobUSD =
bob[
"USD"];
3950 auto const BobEUR =
bob[
"EUR"];
3958 {BobUSD(100), BobEUR(100)},
3961 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3964 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3967 Path const p = [&] {
3984 Env env(*
this, features);
3998 Env env(*
this, features);
4017 using namespace jtx;
4019 auto const CNY =
gw[
"CNY"];
4022 Env env(*
this, features);
4041 Env env(*
this, features);
4054 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
4080 using namespace jtx;
4098 using namespace jtx;
4107 using namespace jtx;
4125 using namespace test::jtx;
4135 using namespace jtx;
4139 all - featureMultiSignReserve - featureExpandedSignerList);
4147 using namespace jtx;
4170BEAST_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