20#include <test/jtx/AMM.h>
21#include <test/jtx/AMMTest.h>
22#include <test/jtx/PathSet.h>
23#include <test/jtx/amount.h>
24#include <test/jtx/sendmax.h>
25#include <xrpld/app/misc/AMMHelpers.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 <xrpld/rpc/RPCHandler.h>
33#include <xrpld/rpc/detail/RPCHelpers.h>
34#include <xrpl/protocol/AMMCore.h>
35#include <xrpl/protocol/Feature.h>
36#include <xrpl/protocol/STParsedJSON.h>
37#include <xrpl/resource/Fees.h>
55 testcase(
"Incorrect Removal of Funded Offers");
67 Env env{*
this, features};
74 {
USD(200'000),
BTC(2'000)});
97 if (!features[fixAMMv1_1])
100 STAmount{BTC, UINT64_C(1'001'000000374812), -12},
107 STAmount{BTC, UINT64_C(1'001'000000374815), -12},
124 Env env{*
this, features};
129 auto const USD1 = gw1[
"USD"];
130 auto const USD2 = gw2[
"USD"];
138 env(
pay(gw1, dan, USD1(10'000)));
139 env(
pay(gw1,
bob, USD1(50)));
140 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);
166 env(
pay(gw1, dan, USD1(10'050)));
167 env(
pay(gw1,
bob, USD1(50)));
168 env(
pay(gw2,
bob, USD2(50)));
170 AMM ammDan(env, dan,
XRP(10'000), USD1(10'050));
177 XRP(10'050), USD1(10'000), ammDan.
tokens()));
193 auto const startBalance =
XRP(1'000'000);
201 for (
auto const& tweakedFeatures :
202 {features - fix1578, features | fix1578})
205 [&](
AMM& ammAlice,
Env& env) {
207 TER const killedCode{
233 {{
XRP(10'100),
USD(10'000)}},
241 [&](
AMM& ammAlice,
Env& env) {
257 {{
XRP(10'100),
USD(10'000)}},
264 [&](
AMM& ammAlice,
Env& env) {
274 {{
XRP(10'100),
USD(10'000)}},
281 [&](
AMM& ammAlice,
Env& env) {
296 {{
XRP(11'000),
USD(9'000)}},
306 testcase(
"Offer Crossing with XRP, Normal order");
310 Env env{*
this, features};
319 auto const xrpTransferred =
XRPAmount{3'061'224'490};
322 BEAST_EXPECT(ammAlice.expectBalances(
323 XRP(150'000) + xrpTransferred,
329 env,
bob,
XRP(300'000) - xrpTransferred -
txfee(env, 1)));
336 testcase(
"Offer Crossing with Limit Override");
340 Env env{*
this, features};
355 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
358 jrr[jss::node][sfBalance.fieldName] ==
360 (
XRP(200'000) -
XRP(3'000) - env.current()->fees().base * 1)
367 testcase(
"Currency Conversion: Entire Offer");
371 Env env{*
this, features};
393 jrr[jss::node][sfBalance.fieldName] ==
401 testcase(
"Currency Conversion: In Parts");
406 [&](
AMM& ammAlice,
Env& env) {
430 {{
XRP(10'000),
USD(10'000)}},
439 testcase(
"Cross Currency Payment: Start with XRP");
444 [&](
AMM& ammAlice,
Env& env) {
453 {{
XRP(10'000),
USD(10'100)}},
462 testcase(
"Cross Currency Payment: End with XRP");
467 [&](
AMM& ammAlice,
Env& env) {
477 {{
XRP(10'100),
USD(10'000)}},
486 testcase(
"Cross Currency Payment: Bridged");
490 Env env{*
this, features};
492 auto const gw1 =
Account{
"gateway_1"};
493 auto const gw2 =
Account{
"gateway_2"};
494 auto const dan =
Account{
"dan"};
495 auto const USD1 = gw1[
"USD"];
496 auto const EUR1 = gw2[
"EUR"];
506 env(
trust(dan, EUR1(1'000)));
512 env(
pay(gw2, dan, dan[
"EUR"](400)));
515 AMM ammCarol(env,
carol, USD1(5'000),
XRP(50'000));
517 env(
offer(dan,
XRP(500), EUR1(50)));
521 jtp[0u][0u][jss::currency] =
"XRP";
523 json(jss::Paths, jtp),
528 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
537 testcase(
"Offer Fees Consume Funds");
541 Env env{*
this, features};
543 auto const gw1 =
Account{
"gateway_1"};
544 auto const gw2 =
Account{
"gateway_2"};
545 auto const gw3 =
Account{
"gateway_3"};
548 auto const USD1 = gw1[
"USD"];
549 auto const USD2 = gw2[
"USD"];
550 auto const USD3 = gw3[
"USD"];
558 auto const starting_xrp =
XRP(100) +
559 env.current()->fees().accountReserve(3) +
560 env.current()->fees().base * 4;
562 env.fund(starting_xrp, gw1, gw2, gw3,
alice);
563 env.fund(
XRP(2'000),
bob);
573 AMM ammBob(env,
bob,
XRP(1'000), USD1(1'200));
582 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
587 jrr[jss::node][sfBalance.fieldName][jss::value] ==
591 jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
597 testcase(
"Offer Create, then Cross");
601 Env env{*
this, features};
622 jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
628 testcase(
"Offer tfSell: Basic Sell");
633 [&](
AMM& ammAlice,
Env& env) {
643 {{
XRP(9'900),
USD(10'100)}},
652 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
656 Env env{*
this, features};
658 auto const starting_xrp =
659 XRP(100) +
reserve(env, 1) + env.current()->fees().base * 2;
661 env.fund(starting_xrp,
gw,
alice);
662 env.fund(
XRP(2'000),
bob);
686 testcase(
"Client Issue: Gateway Cross Currency");
690 Env env{*
this, features};
692 auto const XTS =
gw[
"XTS"];
693 auto const XXX =
gw[
"XXX"];
695 auto const starting_xrp =
696 XRP(100.1) +
reserve(env, 1) + env.current()->fees().base * 2;
702 {XTS(100), XXX(100)},
705 AMM ammAlice(env,
alice, XTS(100), XXX(100));
709 payment[jss::id] = env.seq(
bob);
710 payment[jss::build_path] =
true;
712 payment[jss::tx_json][jss::Sequence] =
715 ->getFieldU32(sfSequence);
716 payment[jss::tx_json][jss::Fee] =
to_string(env.current()->fees().base);
717 payment[jss::tx_json][jss::SendMax] =
720 auto const jrr = env.rpc(
"json",
"submit",
to_string(payment));
721 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
722 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
723 if (!features[fixAMMv1_1])
725 BEAST_EXPECT(ammAlice.expectBalances(
726 STAmount(XTS, UINT64_C(101'010101010101), -12),
730 env,
bob,
STAmount{XTS, UINT64_C(98'989898989899), -12}));
734 BEAST_EXPECT(ammAlice.expectBalances(
735 STAmount(XTS, UINT64_C(101'0101010101011), -13),
739 env,
bob,
STAmount{XTS, UINT64_C(98'9898989898989), -13}));
752 Env env{*
this, features};
758 {
USD(15'000),
EUR(15'000)},
775 BEAST_EXPECT(ammAlice.expectBalances(
776 XRP(10'100),
USD(10'000), ammAlice.tokens()));
785 Env env{*
this, features};
791 {
USD(15'000),
EUR(15'000)},
810 BEAST_EXPECT(ammAlice.expectBalances(
811 XRP(10'100),
USD(10'000), ammAlice.tokens()));
819 Env env{*
this, features};
825 {
USD(15'000),
EUR(15'000)},
858 testcase(
"Combine tfSell with tfFillOrKill");
863 TER const killedCode{
867 Env env{*
this, features};
875 ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
879 Env env{*
this, features};
886 BEAST_EXPECT(ammBob.expectBalances(
897 Env env{*
this, features};
904 BEAST_EXPECT(ammBob.expectBalances(
918 Env env{*
this, features};
939 [&](
AMM& ammAlice,
Env& env) {
952 {{
XRP(10'000),
USD(10'100)}},
960 [&](
AMM& ammAlice,
Env& env) {
973 {{
XRP(10'100),
USD(10'000)}},
980 Env env{*
this, features};
985 {
USD(15'000),
EUR(15'000)},
1017 Env env{*
this, features};
1022 {
USD(15'000),
EUR(15'000)},
1059 Env env{*
this, features};
1103 Env env{*
this, features};
1149 using namespace jtx;
1151 Env env{*
this, features};
1153 auto const USD_bob =
bob[
"USD"];
1154 auto const f = env.current()->fees().base;
1158 AMM ammBob(env,
bob,
XRP(10'000), USD_bob(10'100));
1164 XRP(10'100), USD_bob(10'000), ammBob.
tokens()));
1177 using namespace jtx;
1181 Env env{*
this, features | featureOwnerPaysFee};
1184 auto const fee = env.current()->fees().base;
1187 auto const ann =
Account(
"ann");
1188 auto const A_BUX = ann[
"BUX"];
1190 auto const cam =
Account(
"cam");
1191 auto const dan =
Account(
"dan");
1192 auto const D_BUX = dan[
"BUX"];
1200 env(
trust(cam, D_BUX(100)));
1202 env(
pay(dan,
bob, D_BUX(100)));
1214 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1218 AMM ammBob(env,
bob, A_BUX(30), D_BUX(30));
1220 env(
trust(ann, D_BUX(100)));
1224 env(
pay(ann, ann, D_BUX(30)),
1233 BEAST_EXPECT(
expectLine(env, ann, D_BUX(0)));
1235 BEAST_EXPECT(
expectLine(env, cam, D_BUX(60)));
1236 BEAST_EXPECT(
expectLine(env, dan, A_BUX(0)));
1250 using namespace jtx;
1252 Env env{*
this, features};
1254 auto const ann =
Account(
"ann");
1256 auto const cam =
Account(
"cam");
1258 auto const A_BUX = ann[
"BUX"];
1259 auto const B_BUX =
bob[
"BUX"];
1261 auto const fee = env.current()->fees().base;
1266 env(
trust(ann, B_BUX(40)));
1267 env(
trust(cam, A_BUX(40)));
1269 env(
trust(cam, B_BUX(40)));
1274 env(
pay(ann, cam, A_BUX(35)));
1275 env(
pay(
bob, cam, B_BUX(35)));
1279 AMM ammCarol(env,
carol, A_BUX(300), B_BUX(330));
1286 env.require(
balance(cam, A_BUX(35)));
1287 env.require(
balance(cam, B_BUX(35)));
1288 env.require(
offers(cam, 1));
1291 env(
offer(cam, B_BUX(30), A_BUX(30)));
1294 if (!features[fixAMMv1_1])
1297 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1298 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1305 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1306 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1311 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1312 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1319 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1320 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1329 using namespace jtx;
1331 Env env{*
this, features};
1333 auto const aliceUSD =
alice[
"USD"];
1334 auto const bobUSD =
bob[
"USD"];
1378 using namespace jtx;
1380 Env env{*
this, features};
1444 using namespace jtx;
1478 using namespace jtx;
1495 BEAST_EXPECT(st.
empty());
1503 BEAST_EXPECT(sa ==
XRP(100'000'000));
1507 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99'9999000001), -10}));
1517 using namespace jtx;
1520 auto const AUD =
gw[
"AUD"];
1536 BEAST_EXPECT(std::get<0>(result).empty());
1543 using namespace jtx;
1544 auto const charlie =
Account(
"charlie");
1549 AMM ammCharlie(env, charlie,
XRP(10),
USD(11));
1552 BEAST_EXPECT(sa ==
XRP(1));
1554 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1556 auto const& pathElem = st[0][0];
1558 pathElem.isOffer() && pathElem.getIssuerID() ==
gw.
id() &&
1566 AMM ammCharlie(env, charlie,
XRP(11),
USD(10));
1570 BEAST_EXPECT(sa ==
USD(1));
1572 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1574 auto const& pathElem = st[0][0];
1576 pathElem.isOffer() &&
1586 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1587 using namespace jtx;
1599 env.
fund(
XRP(1'000), A3, G1, G2, G3);
1603 env.
trust(G1[
"XYZ"](5'000), A1);
1604 env.
trust(G3[
"ABC"](5'000), A1);
1605 env.
trust(G2[
"XYZ"](5'000), A2);
1606 env.
trust(G3[
"ABC"](5'000), A2);
1607 env.
trust(A2[
"ABC"](1'000), A3);
1608 env.
trust(G1[
"XYZ"](100'000), M1);
1609 env.
trust(G2[
"XYZ"](100'000), M1);
1610 env.
trust(G3[
"ABC"](100'000), M1);
1613 env(
pay(G1, A1, G1[
"XYZ"](3'500)));
1614 env(
pay(G3, A1, G3[
"ABC"](1'200)));
1615 env(
pay(G1, M1, G1[
"XYZ"](25'000)));
1616 env(
pay(G2, M1, G2[
"XYZ"](25'000)));
1617 env(
pay(G3, M1, G3[
"ABC"](25'000)));
1620 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1'000), G2[
"XYZ"](1'000));
1621 AMM ammM1_XRP_G3(env, M1,
XRP(10'000), G3[
"ABC"](1'000));
1627 auto const& send_amt =
XRP(10);
1630 BEAST_EXPECT(
equal(da, send_amt));
1631 BEAST_EXPECT(st.
empty());
1637 auto const& send_amt =
XRP(200);
1640 BEAST_EXPECT(
equal(da, send_amt));
1641 BEAST_EXPECT(st.
empty());
1645 auto const& send_amt = G3[
"ABC"](10);
1648 BEAST_EXPECT(
equal(da, send_amt));
1654 auto const& send_amt = A2[
"ABC"](1);
1657 BEAST_EXPECT(
equal(da, send_amt));
1663 auto const& send_amt = A3[
"ABC"](1);
1666 BEAST_EXPECT(
equal(da, send_amt));
1675 testcase(
"Path Find: non-XRP -> XRP");
1676 using namespace jtx;
1683 env.
fund(
XRP(1'000), A1, A2, G3);
1687 env.
trust(G3[
"ABC"](1'000), A1, A2);
1688 env.
trust(G3[
"ABC"](100'000), M1);
1691 env(
pay(G3, A1, G3[
"ABC"](1'000)));
1692 env(
pay(G3, A2, G3[
"ABC"](1'000)));
1693 env(
pay(G3, M1, G3[
"ABC"](1'200)));
1696 AMM ammM1(env, M1, G3[
"ABC"](1'000),
XRP(10'010));
1701 auto const& send_amt =
XRP(10);
1703 find_paths(env, A1, A2, send_amt, std::nullopt, A2[
"ABC"].currency);
1704 BEAST_EXPECT(
equal(da, send_amt));
1705 BEAST_EXPECT(
equal(sa, A1[
"ABC"](1)));
1712 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1713 using namespace jtx;
1726 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1728 env.
fund(
XRP(21'000), M1, M2);
1731 env.
trust(G1[
"HKD"](2'000), A1);
1732 env.
trust(G2[
"HKD"](2'000), A2);
1733 env.
trust(G1[
"HKD"](2'000), A3);
1734 env.
trust(G1[
"HKD"](100'000), M1);
1735 env.
trust(G2[
"HKD"](100'000), M1);
1736 env.
trust(G1[
"HKD"](100'000), M2);
1737 env.
trust(G2[
"HKD"](100'000), M2);
1740 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1741 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1742 env(
pay(G1, A3, G1[
"HKD"](1'000)));
1743 env(
pay(G1, M1, G1[
"HKD"](1'200)));
1744 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1745 env(
pay(G1, M2, G1[
"HKD"](1'200)));
1746 env(
pay(G2, M2, G2[
"HKD"](5'000)));
1749 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1750 AMM ammM2XRP_G2(env, M2,
XRP(10'000), G2[
"HKD"](1'010));
1751 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1'010),
XRP(10'000));
1759 auto const& send_amt = G1[
"HKD"](10);
1761 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1762 BEAST_EXPECT(st.
empty());
1763 BEAST_EXPECT(
equal(da, send_amt));
1764 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1770 auto const& send_amt = A1[
"HKD"](10);
1772 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1773 BEAST_EXPECT(st.
empty());
1774 BEAST_EXPECT(
equal(da, send_amt));
1775 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1781 auto const& send_amt = A3[
"HKD"](10);
1783 env, A1, A3, send_amt, std::nullopt, G1[
"HKD"].currency);
1784 BEAST_EXPECT(
equal(da, send_amt));
1785 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1792 auto const& send_amt = G2[
"HKD"](10);
1794 env, G1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1795 BEAST_EXPECT(
equal(da, send_amt));
1796 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1808 auto const& send_amt = G2[
"HKD"](10);
1810 env, A1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1811 BEAST_EXPECT(
equal(da, send_amt));
1812 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1825 auto const& send_amt = A2[
"HKD"](10);
1827 env, A1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1828 BEAST_EXPECT(
equal(da, send_amt));
1829 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1842 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1843 using namespace jtx;
1853 env.
fund(
XRP(1'000), A1, A2, A3, G1, G2);
1856 env.
trust(G1[
"HKD"](2'000), A1);
1857 env.
trust(G2[
"HKD"](2'000), A2);
1858 env.
trust(A2[
"HKD"](2'000), A3);
1859 env.
trust(G1[
"HKD"](100'000), M1);
1860 env.
trust(G2[
"HKD"](100'000), M1);
1863 env(
pay(G1, A1, G1[
"HKD"](1'000)));
1864 env(
pay(G2, A2, G2[
"HKD"](1'000)));
1865 env(
pay(G1, M1, G1[
"HKD"](5'000)));
1866 env(
pay(G2, M1, G2[
"HKD"](5'000)));
1869 AMM ammM1(env, M1, G1[
"HKD"](1'010), G2[
"HKD"](1'000));
1873 auto const& send_amt = A2[
"HKD"](10);
1877 find_paths(env, G1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1878 BEAST_EXPECT(
equal(da, send_amt));
1879 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1888 using namespace jtx;
1890 Env env(*
this, features);
1896 auto const AMMXRPPool = env.
current()->fees().increment * 2;
1912 AMM ammBob(env,
bob, AMMXRPPool,
USD(150));
1920 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1928 using namespace jtx;
1932 Env env(*
this, features);
1951 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1955 Env env(*
this, features);
1974 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1975 BTC(150),
XRP(100), ammBobBTC_XRP.tokens()));
1981 Env env(*
this, features);
2002 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
2006 Env env(*
this, features);
2026 ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
2030 Env env(*
this, features);
2079 Env env(*
this, features);
2110 auto const flowResult = [&] {
2125 paths.push_back(p1);
2128 paths.push_back(p2);
2146 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2149 if (flowResult.removableOffers.empty())
2152 for (
auto const& o : flowResult.removableOffers)
2175 Env env(*
this, features);
2195 BEAST_EXPECT(ammBob.expectBalances(
2205 using namespace jtx;
2209 Env env(*
this, features);
2232 Env env(*
this, features);
2239 {
USD(1'000),
EUR(1'000)});
2267 Env env(*
this, features);
2274 {
USD(1'000),
EUR(1'000)});
2305 Env env(*
this, features);
2312 {
USD(1'200),
GBP(1'200)});
2326 if (!features[fixAMMv1_1])
2334 STAmount{
GBP, UINT64_C(1'105'555555555555), -12}));
2336 BEAST_EXPECT(amm.expectBalances(
2337 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
2338 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2349 STAmount{
GBP, UINT64_C(1'105'555555555554), -12}));
2351 BEAST_EXPECT(amm.expectBalances(
2352 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
2353 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2362 Env env(*
this, features);
2373 if (!features[fixAMMv1_1])
2376 BEAST_EXPECT(amm.expectBalances(
2377 STAmount{USD, UINT64_C(1'095'238095238095), -12},
2391 BEAST_EXPECT(amm.expectBalances(
2392 STAmount{USD, UINT64_C(1'095'238095238096), -12},
2409 Env env(*
this, features);
2410 auto const USDA =
alice[
"USD"];
2411 auto const USDB =
bob[
"USD"];
2424 AMM ammDan(env, dan,
USD(1'000),
EUR(1'050));
2426 if (!features[fixAMMv1_1])
2451 STAmount{USD, UINT64_C(1'050'000000000001), -12},
2459 env,
bob,
STAmount{USDA, UINT64_C(60'000000000001), -12}));
2469 using namespace jtx;
2473 Env env(*
this, features);
2480 {
USD(1'000),
GBP(1'000)});
2495 if (!features[fixAMMv1_1])
2498 BEAST_EXPECT(amm.expectBalances(
2505 BEAST_EXPECT(amm.expectBalances(
2518 Env env(*
this, features);
2552 BEAST_EXPECT(amm.expectBalances(
2562 Env env(*
this, features);
2575 AMM amm2(env, ed,
EUR(1'000),
USD(1'000));
2584 if (!features[fixAMMv1_1])
2622 Env env(*
this, features);
2634 amm.expectBalances(
USD(1'100),
EUR(1'000), amm.tokens()));
2642 Env env(*
this, features);
2649 {
USD(1'000),
GBP(1'000)});
2669 BEAST_EXPECT(amm.expectBalances(
2670 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2680 Env env(*
this, features);
2687 {
USD(1'200),
GBP(1'200)});
2701 if (!features[fixAMMv1_1])
2708 BEAST_EXPECT(amm.expectBalances(
2709 GBP(1'024),
USD(1'171.875), amm.tokens()));
2719 STAmount{
GBP, UINT64_C(1'169'999999999999), -12}));
2721 BEAST_EXPECT(amm.expectBalances(
2722 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2733 Env env(*
this, features);
2758 if (!features[fixAMMv1_1])
2766 STAmount{
GBP, UINT64_C(1'311'973684210527), -12}));
2773 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2780 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2783 BEAST_EXPECT(amm.expectBalances(
2784 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2785 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2796 STAmount{
GBP, UINT64_C(1'311'973684210525), -12}));
2803 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2810 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2813 BEAST_EXPECT(amm.expectBalances(
2814 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2815 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2825 Env env(*
this, features);
2850 if (!features[fixAMMv1_1])
2858 STAmount{
GBP, UINT64_C(1'329'578947368421), -12}));
2862 BEAST_EXPECT(amm.expectBalances(
2863 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2864 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2875 STAmount{
GBP, UINT64_C(1'329'57894736842), -11}));
2879 BEAST_EXPECT(amm.expectBalances(
2880 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2881 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2890 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2897 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2905 Env env(*
this, features);
2918 AMM amm2(env, ed,
EUR(1'000),
USD(1'400));
2928 if (!features[fixAMMv1_1])
2936 STAmount{
GBP, UINT64_C(1'292'469135802469), -12}));
2939 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2940 STAmount{EUR, UINT64_C(920'78937795562), -11},
2945 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2946 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2957 STAmount{
GBP, UINT64_C(1'292'469135802466), -12}));
2960 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2961 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2966 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2967 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2977 Env env(*
this, features);
2999 if (!features[fixAMMv1_1])
3003 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
3004 STAmount{EUR, UINT64_C(902'4064171122988), -13},
3009 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
3010 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3017 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
3018 STAmount{EUR, UINT64_C(902'4064171122975), -13},
3023 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
3024 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3041 using namespace jtx;
3057 ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
3068 using namespace jtx;
3070 for (
auto const withFix : {
true,
false})
3072 auto const feats = withFix
3077 Env env(*
this, feats);
3092 TER const expectedTer =
3125 auto const JPY =
gw[
"JPY"];
3134 {
USD(200),
EUR(200), JPY(200)},
3139 AMM ammAliceXRP_JPY(env,
alice,
XRP(100), JPY(100));
3154 using namespace jtx;
3155 Env env(*
this, features);
3156 auto const dan =
Account(
"dan");
3157 auto const ed =
Account(
"ed");
3172 if (!features[fixAMMv1_1])
3198 testcase(
"Convert all of an asset using DeliverMin");
3200 using namespace jtx;
3203 Env env(*
this, features);
3238 Env env(*
this, features);
3252 Env env(*
this, features);
3274 auto const dan =
Account(
"dan");
3275 Env env(*
this, features);
3282 AMM ammDan(env, dan,
XRP(1'000),
USD(1'100));
3283 if (!features[fixAMMv1_1])
3307 STAmount{USD, UINT64_C(999'99999909091), -11},
3318 using namespace jtx;
3321 bool const supportsPreauth = {features[featureDepositPreauth]};
3326 Env env(*
this, features);
3355 env(
pay(becky, becky,
USD(10)),
3370 using namespace jtx;
3391 auto failedIouPayments = [
this, &env]() {
3415 failedIouPayments();
3432 failedIouPayments();
3439 failedIouPayments();
3462 using namespace test::jtx;
3463 Env env(*
this, features);
3476 env(
pay(G1,
bob, G1[
"USD"](10)));
3477 env(
pay(G1,
alice, G1[
"USD"](205)));
3480 AMM ammAlice(env,
alice,
XRP(500), G1[
"USD"](105));
3486 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3487 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3488 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3495 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == G1.human());
3496 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3498 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3515 auto affected = env.
meta()->getJson(
3520 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3522 ff[sfLowLimit.fieldName] ==
3533 auto affected = env.
meta()->getJson(
3538 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3540 ff[sfHighLimit.fieldName] ==
3545 BEAST_EXPECT(ff[sfBalance.fieldName] == amt);
3548 XRP(525), G1[
"USD"](100), ammAlice.
tokens()));
3567 for (
auto const& it :
lines[jss::lines])
3569 if (it[jss::account] ==
bob.
human())
3575 if (!BEAST_EXPECT(bobLine))
3577 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3578 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3585 for (
auto const& it :
lines[jss::lines])
3587 if (it[jss::account] == G1.human())
3593 if (!BEAST_EXPECT(g1Line))
3595 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3596 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3603 auto affected = env.
meta()->getJson(
3608 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3610 ff[sfLowLimit.fieldName] ==
3612 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3623 using namespace test::jtx;
3624 Env env(*
this, features);
3634 env.
fund(
XRP(20'000), A2, A3, A4);
3637 env.
trust(G1[
"USD"](1'200), A1);
3638 env.
trust(G1[
"USD"](200), A2);
3639 env.
trust(G1[
"BTC"](100), A3);
3640 env.
trust(G1[
"BTC"](100), A4);
3643 env(
pay(G1, A1, G1[
"USD"](1'000)));
3644 env(
pay(G1, A2, G1[
"USD"](100)));
3645 env(
pay(G1, A3, G1[
"BTC"](100)));
3646 env(
pay(G1, A4, G1[
"BTC"](100)));
3649 AMM ammG1(env, G1,
XRP(10'000), G1[
"USD"](100));
3661 "XRP")[jss::result][jss::offers];
3669 BEAST_EXPECT(accounts.
find(A2.human()) !=
std::end(accounts));
3675 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3683 BEAST_EXPECT(accounts.
find(A1.human()) !=
std::end(accounts));
3690 AMM ammA3(env, A3, G1[
"BTC"](1),
XRP(1));
3696 env(
pay(G1, A2, G1[
"USD"](1)));
3699 env(
pay(A2, G1, G1[
"USD"](1)));
3702 env(
pay(A2, A1, G1[
"USD"](1)));
3705 env(
pay(A1, A2, G1[
"USD"](1)));
3731 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3738 "XRP")[jss::result][jss::offers];
3746 env(
pay(G1, A2, G1[
"USD"](1)));
3749 env(
pay(A2, G1, G1[
"USD"](1)));
3759 testcase(
"Offers for Frozen Trust Lines");
3761 using namespace test::jtx;
3762 Env env(*
this, features);
3769 env.
fund(
XRP(2'000), G1, A3, A4);
3773 env.
trust(G1[
"USD"](1'000), A2);
3774 env.
trust(G1[
"USD"](2'000), A3);
3775 env.
trust(G1[
"USD"](2'001), A4);
3778 env(
pay(G1, A3, G1[
"USD"](2'000)));
3779 env(
pay(G1, A4, G1[
"USD"](2'001)));
3782 AMM ammA3(env, A3,
XRP(1'000), G1[
"USD"](1'001));
3793 env(
offer(A4,
XRP(999), G1[
"USD"](999)));
3804 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3810 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3812 ff[sfHighLimit.fieldName] ==
3814 BEAST_EXPECT(!(ff[jss::Flags].asUInt() &
lsfLowFreeze));
3832 ff = affected[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3834 ff[sfLowLimit.fieldName] ==
3841 env(
offer(A2, G1[
"USD"](999),
XRP(999)));
3846 auto created = affected[0u][sfCreatedNode.fieldName];
3848 created[sfNewFields.fieldName][jss::Account] == A2.human());
3860 testcase(
"Multisign AMM Transactions");
3862 using namespace jtx;
3863 Env env{*
this, features};
3878 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3881 const msig ms{becky, bogie};
3907 ammAlice.
vote({}, 1'000);
3910 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).close();
3922 using namespace jtx;
3926 Env env(*
this, features);
3933 {
USD(2'000),
EUR(1'000)});
3949 using namespace jtx;
3953 Env env(*
this, features);
3954 auto const BobUSD =
bob[
"USD"];
3955 auto const BobEUR =
bob[
"EUR"];
3963 {BobUSD(100), BobEUR(100)},
3966 AMM ammBobXRP_USD(env,
bob,
XRP(100), BobUSD(100));
3969 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3972 Path const p = [&] {
3989 Env env(*
this, features);
4003 Env env(*
this, features);
4022 using namespace jtx;
4024 auto const CNY =
gw[
"CNY"];
4027 Env env(*
this, features);
4046 Env env(*
this, features);
4059 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
4085 using namespace jtx;
4103 using namespace jtx;
4112 using namespace jtx;
4130 using namespace test::jtx;
4140 using namespace jtx;
4144 all - featureMultiSignReserve - featureExpandedSignerList);
4152 using namespace jtx;
4175BEAST_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