1#include <test/jtx/AMM.h>
2#include <test/jtx/AMMTest.h>
3#include <test/jtx/Account.h>
4#include <test/jtx/Env.h>
5#include <test/jtx/PathSet.h>
6#include <test/jtx/TestHelpers.h>
7#include <test/jtx/amount.h>
8#include <test/jtx/balance.h>
9#include <test/jtx/delivermin.h>
10#include <test/jtx/fee.h>
11#include <test/jtx/flags.h>
12#include <test/jtx/jtx_json.h>
13#include <test/jtx/multisign.h>
14#include <test/jtx/noop.h>
15#include <test/jtx/offer.h>
16#include <test/jtx/owners.h>
17#include <test/jtx/paths.h>
18#include <test/jtx/pay.h>
19#include <test/jtx/quality.h>
20#include <test/jtx/rate.h>
21#include <test/jtx/regkey.h>
22#include <test/jtx/require.h>
23#include <test/jtx/sendmax.h>
24#include <test/jtx/sig.h>
25#include <test/jtx/tags.h>
26#include <test/jtx/ter.h>
27#include <test/jtx/trust.h>
28#include <test/jtx/txflags.h>
30#include <xrpl/basics/Number.h>
31#include <xrpl/beast/unit_test/suite.h>
32#include <xrpl/json/json_value.h>
33#include <xrpl/json/to_string.h>
34#include <xrpl/ledger/ApplyView.h>
35#include <xrpl/ledger/OpenView.h>
36#include <xrpl/ledger/PaymentSandbox.h>
37#include <xrpl/ledger/Sandbox.h>
38#include <xrpl/ledger/helpers/OfferHelpers.h>
39#include <xrpl/protocol/AccountID.h>
40#include <xrpl/protocol/Feature.h>
41#include <xrpl/protocol/Indexes.h>
42#include <xrpl/protocol/Issue.h>
43#include <xrpl/protocol/KeyType.h>
44#include <xrpl/protocol/LedgerFormats.h>
45#include <xrpl/protocol/SField.h>
46#include <xrpl/protocol/STPathSet.h>
47#include <xrpl/protocol/Seed.h>
48#include <xrpl/protocol/TER.h>
49#include <xrpl/protocol/TxFlags.h>
50#include <xrpl/protocol/UintTypes.h>
51#include <xrpl/protocol/XRPAmount.h>
52#include <xrpl/protocol/jss.h>
53#include <xrpl/tx/paths/Flow.h>
54#include <xrpl/tx/paths/detail/Steps.h>
82 testcase(
"Incorrect Removal of Funded Offers");
95 Env env{*
this, features};
120 if (!features[fixAMMv1_1])
123 STAmount{BTC, UINT64_C(1'001'000000374812), -12},
USD(100'000), ammCarol.
tokens()));
128 STAmount{BTC, UINT64_C(1'001'000000374815), -12},
USD(100'000), ammCarol.
tokens()));
143 Env env{*
this, features};
148 auto const usD1 = gw1[
"USD"];
149 auto const usD2 = gw2[
"USD"];
154 env(
trust(
bob_, usD1(1'000), tfSetNoRipple));
156 env(
trust(
bob_, usD2(1'000), tfSetNoRipple));
159 env(
pay(gw1, dan, usD1(10'000)));
164 AMM const ammDan(env, dan,
XRP(10'000), usD1(10'000));
175 Env env{*
this, features};
180 auto const usD1 = gw1[
"USD"];
181 auto const usD2 = gw2[
"USD"];
190 env(
pay(gw1, dan, usD1(10'050)));
195 AMM const ammDan(env, dan,
XRP(10'000), usD1(10'050));
216 auto const startBalance =
XRP(1'000'000);
222 [&](
AMM& ammAlice,
Env& env) {
241 {{
XRP(10'100),
USD(10'000)}},
249 [&](
AMM& ammAlice,
Env& env) {
264 {{
XRP(10'100),
USD(10'000)}},
271 [&](
AMM& ammAlice,
Env& env) {
280 {{
XRP(10'100),
USD(10'000)}},
287 [&](
AMM& ammAlice,
Env& env) {
302 {{
XRP(11'000),
USD(9'000)}},
311 testcase(
"Offer Crossing with XRP, Normal order");
315 Env env{*
this, features};
324 auto const xrpTransferred =
XRPAmount{3'061'224'490};
327 BEAST_EXPECT(ammAlice.expectBalances(
328 XRP(150'000) + xrpTransferred,
USD(49),
IOUAmount{273'861'278752583, -8}));
339 testcase(
"Offer Crossing with Limit Override");
343 Env env{*
this, features};
358 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-1");
361 jrr[jss::node][sfBalance.fieldName] ==
368 testcase(
"Currency Conversion: Entire Offer");
372 Env env{*
this, features};
393 jrr[jss::node][sfBalance.fieldName] ==
400 testcase(
"Currency Conversion: In Parts");
405 [&](
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"];
500 env(
trust(dan, euR1(1'000)));
506 env(
pay(gw2, dan, dan[
"EUR"](400)));
509 AMM const ammCarol(env,
carol_, usD1(5'000),
XRP(50'000));
511 env(
offer(dan,
XRP(500), euR1(50)));
515 jtp[0u][0u][jss::currency] =
"XRP";
519 XRP(49'700),
STAmount{usD1, UINT64_C(5'030'181086519115), -12}, ammCarol.
tokens()));
527 testcase(
"Offer Fees Consume Funds");
531 Env env{*
this, features};
533 auto const gw1 =
Account{
"gateway_1"};
534 auto const gw2 =
Account{
"gateway_2"};
535 auto const gw3 =
Account{
"gateway_3"};
536 auto const localAlice =
Account{
"alice"};
537 auto const localBob =
Account{
"bob"};
538 auto const usD1 = gw1[
"USD"];
539 auto const usD2 = gw2[
"USD"];
540 auto const usD3 = gw3[
"USD"];
548 auto const startingXrp =
549 XRP(100) + env.
current()->fees().accountReserve(3) + env.
current()->fees().base * 4;
551 env.
fund(startingXrp, gw1, gw2, gw3, localAlice);
552 env.
fund(
XRP(2'000), localBob);
555 env(
trust(localAlice, usD1(1'000)));
556 env(
trust(localAlice, usD2(1'000)));
557 env(
trust(localAlice, usD3(1'000)));
558 env(
trust(localBob, usD1(1'200)));
559 env(
trust(localBob, usD2(1'100)));
561 env(
pay(gw1, localBob, localBob[
"USD"](1'200)));
563 AMM const ammBob(env, localBob,
XRP(1'000), usD1(1'200));
566 env(
offer(localAlice, usD1(200),
XRP(200)));
571 XRP(1'100),
STAmount{usD1, UINT64_C(1'090'909090909091), -12}, ammBob.
tokens()));
574 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"109.090909090909");
576 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName] ==
XRP(350).value().getText());
582 testcase(
"Offer Create, then Cross");
586 Env env{*
this, features};
605 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] ==
"-0.8995000001");
611 testcase(
"Offer tfSell: Basic Sell");
616 [&](
AMM& ammAlice,
Env& env) {
625 {{
XRP(9'900),
USD(10'100)}},
634 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
638 Env env{*
this, features};
640 auto const startingXrp =
XRP(100) +
reserve(env, 1) + env.
current()->fees().base * 2;
667 testcase(
"Client Issue: Gateway Cross Currency");
671 Env env{*
this, features};
673 auto const xts =
gw_[
"XTS"];
674 auto const xxx =
gw_[
"XXX"];
676 auto const startingXrp =
XRP(100.1) +
reserve(env, 1) + env.
current()->fees().base * 2;
679 AMM const ammAlice(env,
alice_, xts(100), xxx(100));
683 payment[jss::id] = env.
seq(
bob_);
684 payment[jss::build_path] =
true;
686 payment[jss::tx_json][jss::Sequence] =
689 payment[jss::tx_json][jss::SendMax] =
691 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
692 auto const jrr = env.
rpc(
"json",
"submit",
to_string(payment));
693 BEAST_EXPECT(jrr[jss::result][jss::status] ==
"success");
694 BEAST_EXPECT(jrr[jss::result][jss::engine_result] ==
"tesSUCCESS");
695 if (!features[fixAMMv1_1])
697 BEAST_EXPECT(ammAlice.expectBalances(
698 STAmount(xts, UINT64_C(101'010101010101), -12), xxx(99), ammAlice.tokens()));
703 BEAST_EXPECT(ammAlice.expectBalances(
704 STAmount(xts, UINT64_C(101'0101010101011), -13), xxx(99), ammAlice.tokens()));
718 Env env{*
this, features};
736 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'100),
USD(10'000), ammAlice.tokens()));
744 Env env{*
this, features};
764 BEAST_EXPECT(ammAlice.expectBalances(
XRP(10'100),
USD(10'000), ammAlice.tokens()));
772 Env env{*
this, features};
805 testcase(
"Combine tfSell with tfFillOrKill");
813 Env env{*
this, features};
819 BEAST_EXPECT(ammBob.expectBalances(
XRP(20'000),
USD(200), ammBob.tokens()));
823 Env env{*
this, features};
830 BEAST_EXPECT(ammBob.expectBalances(
831 XRP(20'220),
STAmount{
USD, UINT64_C(197'8239366963403), -13}, ammBob.tokens()));
839 Env env{*
this, features};
846 BEAST_EXPECT(ammBob.expectBalances(
847 XRP(21'500),
STAmount{
USD, UINT64_C(186'046511627907), -12}, ammBob.tokens()));
858 Env env{*
this, features};
878 [&](
AMM& ammAlice,
Env& env) {
890 {{
XRP(10'000),
USD(10'100)}},
898 [&](
AMM& ammAlice,
Env& env) {
910 {{
XRP(10'100),
USD(10'000)}},
917 Env env{*
this, features};
948 Env env{*
this, features};
983 Env env{*
this, features};
1026 Env env{*
this, features};
1071 using namespace jtx;
1073 Env env{*
this, features};
1075 auto const usdBob =
bob_[
"USD"];
1076 auto const f = env.
current()->fees().base;
1080 AMM const ammBob(env,
bob_,
XRP(10'000), usdBob(10'100));
1098 using namespace jtx;
1100 Env env{*
this, features};
1103 auto const fee = env.
current()->fees().base;
1106 auto const ann =
Account(
"ann");
1107 auto const aBux = ann[
"BUX"];
1108 auto const localBob =
Account(
"bob");
1109 auto const cam =
Account(
"cam");
1110 auto const dan =
Account(
"dan");
1111 auto const dBux = dan[
"BUX"];
1114 env.
fund(
reserve(env, 4) + (fee * 4), ann, localBob, cam, dan);
1117 env(
trust(localBob, aBux(400)));
1119 env(
trust(cam, dBux(100)));
1121 env(
pay(dan, localBob, dBux(100)));
1125 env(
pay(ann, cam, dBux(60)),
Path(localBob, dan),
Sendmax(aBux(200)));
1137 AMM const ammBob(env, localBob, aBux(30), dBux(30));
1139 env(
trust(ann, dBux(100)));
1165 using namespace jtx;
1167 Env env{*
this, features};
1169 auto const ann =
Account(
"ann");
1170 auto const localBob =
Account(
"bob");
1171 auto const cam =
Account(
"cam");
1172 auto const carol =
Account(
"carol");
1173 auto const aBux = ann[
"BUX"];
1174 auto const bBux = localBob[
"BUX"];
1176 auto const fee = env.
current()->fees().base;
1178 env.
fund(
reserve(env, 4) + (fee * 5), ann, localBob, cam);
1181 env(
trust(ann, bBux(40)));
1182 env(
trust(cam, aBux(40)));
1183 env(
trust(localBob, aBux(30)));
1184 env(
trust(cam, bBux(40)));
1185 env(
trust(carol, bBux(400)));
1186 env(
trust(carol, aBux(400)));
1189 env(
pay(ann, cam, aBux(35)));
1190 env(
pay(localBob, cam, bBux(35)));
1191 env(
pay(localBob, carol, bBux(400)));
1192 env(
pay(ann, carol, aBux(400)));
1194 AMM const ammCarol(env, carol, aBux(300), bBux(330));
1199 env(
offer(cam, aBux(29), bBux(30), tfPassive));
1206 env(
offer(cam, bBux(30), aBux(30)));
1209 if (!features[fixAMMv1_1])
1212 STAmount{aBux, UINT64_C(309'3541659651605), -13},
1213 STAmount{bBux, UINT64_C(320'0215509984417), -13},
1220 STAmount{bBux, UINT64_C(20'0215509984417), -13},
1221 STAmount{aBux, UINT64_C(20'0215509984417), -13}}}}));
1226 STAmount{aBux, UINT64_C(309'3541659651604), -13},
1227 STAmount{bBux, UINT64_C(320'0215509984419), -13},
1234 STAmount{bBux, UINT64_C(20'0215509984419), -13},
1235 STAmount{aBux, UINT64_C(20'0215509984419), -13}}}}));
1244 using namespace jtx;
1246 Env env{*
this, features};
1248 auto const aliceUSD =
alice_[
"USD"];
1249 auto const bobUSD =
bob_[
"USD"];
1255 env(
fset(
gw_, asfRequireAuth));
1292 using namespace jtx;
1294 Env env{*
this, features};
1304 env(
fset(
gw_, asfRequireAuth));
1356 using namespace jtx;
1390 using namespace jtx;
1403 BEAST_EXPECT(st.
empty());
1407 BEAST_EXPECT(sa ==
XRP(100'000'000));
1420 using namespace jtx;
1423 auto const aud =
gw_[
"AUD"];
1438 BEAST_EXPECT(std::get<0>(result).empty());
1445 using namespace jtx;
1446 auto const charlie =
Account(
"charlie");
1451 AMM const ammCharlie(env, charlie,
XRP(10),
USD(11));
1453 BEAST_EXPECT(sa ==
XRP(1));
1455 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1457 auto const& pathElem = st[0][0];
1459 pathElem.isOffer() && pathElem.getIssuerID() ==
gw_.id() &&
1460 pathElem.getCurrency() ==
USD.currency);
1467 AMM const ammCharlie(env, charlie,
XRP(11),
USD(10));
1470 BEAST_EXPECT(sa ==
USD(1));
1472 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1474 auto const& pathElem = st[0][0];
1476 pathElem.isOffer() && pathElem.getIssuerID() ==
xrpAccount() &&
1485 testcase(
"Path Find: XRP -> XRP and XRP -> IOU");
1486 using namespace jtx;
1498 env.
fund(
XRP(1'000), a3, g1, g2, g3);
1502 env.
trust(g1[
"XYZ"](5'000), a1);
1503 env.
trust(g3[
"ABC"](5'000), a1);
1504 env.
trust(g2[
"XYZ"](5'000), a2);
1505 env.
trust(g3[
"ABC"](5'000), a2);
1506 env.
trust(a2[
"ABC"](1'000), a3);
1507 env.
trust(g1[
"XYZ"](100'000), m1);
1508 env.
trust(g2[
"XYZ"](100'000), m1);
1509 env.
trust(g3[
"ABC"](100'000), m1);
1512 env(
pay(g1, a1, g1[
"XYZ"](3'500)));
1513 env(
pay(g3, a1, g3[
"ABC"](1'200)));
1514 env(
pay(g1, m1, g1[
"XYZ"](25'000)));
1515 env(
pay(g2, m1, g2[
"XYZ"](25'000)));
1516 env(
pay(g3, m1, g3[
"ABC"](25'000)));
1519 AMM const ammM1G1G2(env, m1, g1[
"XYZ"](1'000), g2[
"XYZ"](1'000));
1520 AMM const ammM1XrpG3(env, m1,
XRP(10'000), g3[
"ABC"](1'000));
1526 auto const& sendAmt =
XRP(10);
1528 BEAST_EXPECT(
equal(da, sendAmt));
1529 BEAST_EXPECT(st.
empty());
1535 auto const& sendAmt =
XRP(200);
1538 BEAST_EXPECT(
equal(da, sendAmt));
1539 BEAST_EXPECT(st.
empty());
1543 auto const& sendAmt = g3[
"ABC"](10);
1545 BEAST_EXPECT(
equal(da, sendAmt));
1551 auto const& sendAmt = a2[
"ABC"](1);
1553 BEAST_EXPECT(
equal(da, sendAmt));
1559 auto const& sendAmt = a3[
"ABC"](1);
1561 BEAST_EXPECT(
equal(da, sendAmt));
1570 testcase(
"Path Find: non-XRP -> XRP");
1571 using namespace jtx;
1578 env.
fund(
XRP(1'000), a1, a2, g3);
1582 env.
trust(g3[
"ABC"](1'000), a1, a2);
1583 env.
trust(g3[
"ABC"](100'000), m1);
1586 env(
pay(g3, a1, g3[
"ABC"](1'000)));
1587 env(
pay(g3, a2, g3[
"ABC"](1'000)));
1588 env(
pay(g3, m1, g3[
"ABC"](1'200)));
1591 AMM const ammM1(env, m1, g3[
"ABC"](1'000),
XRP(10'010));
1596 auto const& sendAmt =
XRP(10);
1597 std::tie(st, sa, da) =
findPaths(env, a1, a2, sendAmt, std::nullopt, a2[
"ABC"].currency);
1598 BEAST_EXPECT(
equal(da, sendAmt));
1599 BEAST_EXPECT(
equal(sa, a1[
"ABC"](1)));
1606 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1607 using namespace jtx;
1620 env.
fund(
XRP(1'000), a1, a2, a3, g1, g2, g3, g4);
1622 env.
fund(
XRP(21'000), m1, m2);
1625 env.
trust(g1[
"HKD"](2'000), a1);
1626 env.
trust(g2[
"HKD"](2'000), a2);
1627 env.
trust(g1[
"HKD"](2'000), a3);
1628 env.
trust(g1[
"HKD"](100'000), m1);
1629 env.
trust(g2[
"HKD"](100'000), m1);
1630 env.
trust(g1[
"HKD"](100'000), m2);
1631 env.
trust(g2[
"HKD"](100'000), m2);
1634 env(
pay(g1, a1, g1[
"HKD"](1'000)));
1635 env(
pay(g2, a2, g2[
"HKD"](1'000)));
1636 env(
pay(g1, a3, g1[
"HKD"](1'000)));
1637 env(
pay(g1, m1, g1[
"HKD"](1'200)));
1638 env(
pay(g2, m1, g2[
"HKD"](5'000)));
1639 env(
pay(g1, m2, g1[
"HKD"](1'200)));
1640 env(
pay(g2, m2, g2[
"HKD"](5'000)));
1643 AMM const ammM1(env, m1, g1[
"HKD"](1'010), g2[
"HKD"](1'000));
1644 AMM const ammM2XrpG2(env, m2,
XRP(10'000), g2[
"HKD"](1'010));
1645 AMM const ammM2G1Xrp(env, m2, g1[
"HKD"](1'010),
XRP(10'000));
1653 auto const& sendAmt = g1[
"HKD"](10);
1655 findPaths(env, a1, g1, sendAmt, std::nullopt, g1[
"HKD"].currency);
1656 BEAST_EXPECT(st.
empty());
1657 BEAST_EXPECT(
equal(da, sendAmt));
1658 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1664 auto const& sendAmt = a1[
"HKD"](10);
1666 findPaths(env, a1, g1, sendAmt, std::nullopt, g1[
"HKD"].currency);
1667 BEAST_EXPECT(st.
empty());
1668 BEAST_EXPECT(
equal(da, sendAmt));
1669 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1675 auto const& sendAmt = a3[
"HKD"](10);
1677 findPaths(env, a1, a3, sendAmt, std::nullopt, g1[
"HKD"].currency);
1678 BEAST_EXPECT(
equal(da, sendAmt));
1679 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1686 auto const& sendAmt = g2[
"HKD"](10);
1688 findPaths(env, g1, g2, sendAmt, std::nullopt, g1[
"HKD"].currency);
1689 BEAST_EXPECT(
equal(da, sendAmt));
1690 BEAST_EXPECT(
equal(sa, g1[
"HKD"](10)));
1702 auto const& sendAmt = g2[
"HKD"](10);
1704 findPaths(env, a1, g2, sendAmt, std::nullopt, g1[
"HKD"].currency);
1705 BEAST_EXPECT(
equal(da, sendAmt));
1706 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1719 auto const& sendAmt = a2[
"HKD"](10);
1721 findPaths(env, a1, a2, sendAmt, std::nullopt, g1[
"HKD"].currency);
1722 BEAST_EXPECT(
equal(da, sendAmt));
1723 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1736 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1737 using namespace jtx;
1747 env.
fund(
XRP(1'000), a1, a2, a3, g1, g2);
1750 env.
trust(g1[
"HKD"](2'000), a1);
1751 env.
trust(g2[
"HKD"](2'000), a2);
1752 env.
trust(a2[
"HKD"](2'000), a3);
1753 env.
trust(g1[
"HKD"](100'000), m1);
1754 env.
trust(g2[
"HKD"](100'000), m1);
1757 env(
pay(g1, a1, g1[
"HKD"](1'000)));
1758 env(
pay(g2, a2, g2[
"HKD"](1'000)));
1759 env(
pay(g1, m1, g1[
"HKD"](5'000)));
1760 env(
pay(g2, m1, g2[
"HKD"](5'000)));
1763 AMM const ammM1(env, m1, g1[
"HKD"](1'010), g2[
"HKD"](1'000));
1767 auto const& sendAmt = a2[
"HKD"](10);
1770 std::tie(st, sa, da) =
findPaths(env, g1, a2, sendAmt, std::nullopt, g1[
"HKD"].currency);
1771 BEAST_EXPECT(
equal(da, sendAmt));
1772 BEAST_EXPECT(
equal(sa, g1[
"HKD"](10)));
1781 using namespace jtx;
1783 Env env(*
this, features);
1789 auto const ammXrpPool = env.
current()->fees().increment * 2;
1806 AMM const ammBob(env,
bob_, ammXrpPool,
USD(150));
1811 Txflags(tfNoRippleDirect | tfPartialPayment));
1814 BEAST_EXPECT(carolUSD >
USD(0) && carolUSD <
USD(50));
1822 using namespace jtx;
1826 Env env(*
this, features);
1838 BEAST_EXPECT(ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1842 Env env(*
this, features);
1855 BEAST_EXPECT(ammBobBtcXrp.expectBalances(
BTC(150),
XRP(100), ammBobBtcXrp.tokens()));
1860 Env env(*
this, features);
1872 BEAST_EXPECT(ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
1876 Env env(*
this, features);
1888 BEAST_EXPECT(ammBob.expectBalances(
USD(150),
XRP(100), ammBob.tokens()));
1892 Env env(*
this, features);
1941 Env env(*
this, features);
1974 auto const flowResult = [&] {
2011 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2013 if (flowResult.removableOffers.empty())
2016 for (
auto const& o : flowResult.removableOffers)
2040 Env env(*
this, features);
2060 Txflags(tfNoRippleDirect | tfPartialPayment));
2063 BEAST_EXPECT(ammBob.expectBalances(
USD(8.4),
XRPAmount{20}, ammBob.tokens()));
2071 using namespace jtx;
2075 Env env(*
this, features);
2086 Txflags(tfNoRippleDirect | tfPartialPayment));
2092 if (!features[fixAMMv1_1])
2095 BEAST_EXPECT(
amm.expectBalances(
2100 BEAST_EXPECT(
amm.expectBalances(
2111 Env env(*
this, features);
2131 Txflags(tfNoRippleDirect | tfPartialPayment));
2144 BEAST_EXPECT(
amm.expectBalances(
2152 Env env(*
this, features);
2165 AMM const amm2(env, ed,
EUR(1'000),
USD(1'000));
2170 Txflags(tfNoRippleDirect | tfPartialPayment));
2174 if (!features[fixAMMv1_1])
2208 Env env(*
this, features);
2219 BEAST_EXPECT(
amm.expectBalances(
USD(1'100),
EUR(1'000),
amm.tokens()));
2227 Env env(*
this, features);
2240 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2249 BEAST_EXPECT(
amm.expectBalances(
2250 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
USD(875),
amm.tokens()));
2258 Env env(*
this, features);
2271 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2274 if (!features[fixAMMv1_1])
2281 BEAST_EXPECT(
amm.expectBalances(
GBP(1'024),
USD(1'171.875),
amm.tokens()));
2291 BEAST_EXPECT(
amm.expectBalances(
2292 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2303 Env env(*
this, features);
2325 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2328 if (!features[fixAMMv1_1])
2341 STAmount{
GBP, UINT64_C(1'470'421052631579), -12}));
2348 STAmount{
EUR, UINT64_C(929'5789473684212), -13}}}));
2351 BEAST_EXPECT(
amm.expectBalances(
2352 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2353 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2369 STAmount{
GBP, UINT64_C(1'470'42105263158), -11}));
2376 STAmount{
EUR, UINT64_C(929'57894736842), -11}}}));
2379 BEAST_EXPECT(
amm.expectBalances(
2380 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2381 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2391 Env env(*
this, features);
2413 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2416 if (!features[fixAMMv1_1])
2426 BEAST_EXPECT(
amm.expectBalances(
2427 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2428 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2441 BEAST_EXPECT(
amm.expectBalances(
2442 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2443 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2452 STAmount{
EUR, UINT64_C(1'442'665816326531), -12}));
2459 STAmount{
USD, UINT64_C(1'340'267857142857), -12}}}));
2467 Env env(*
this, features);
2480 AMM const amm2(env, ed,
EUR(1'000),
USD(1'400));
2487 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2490 if (!features[fixAMMv1_1])
2499 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2500 STAmount{EUR, UINT64_C(920'78937795562), -11},
2505 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2506 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2518 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2519 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2524 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2525 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2535 Env env(*
this, features);
2550 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2553 if (!features[fixAMMv1_1])
2557 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
2558 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2563 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
2564 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2571 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
2572 STAmount{EUR, UINT64_C(902'4064171122975), -13},
2577 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
2578 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2595 using namespace jtx;
2608 Txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2610 BEAST_EXPECT(ammBob.expectBalances(
XRP(1'050),
USD(1'000), ammBob.tokens()));
2621 using namespace jtx;
2659 auto const jpy =
gw_[
"JPY"];
2667 AMM const ammAliceXrpJpy(env,
alice_,
XRP(100), jpy(100));
2682 using namespace jtx;
2683 Env env(*
this, features);
2684 auto const dan =
Account(
"dan");
2685 auto const ed =
Account(
"ed");
2696 AMM const ammEd(env, ed,
XRP(9),
USD(11));
2701 if (!features[fixAMMv1_1])
2729 testcase(
"Convert all of an asset using DeliverMin");
2731 using namespace jtx;
2734 Env env(*
this, features);
2768 Env env(*
this, features);
2782 Env env(*
this, features);
2803 auto const dan =
Account(
"dan");
2804 Env env(*
this, features);
2812 AMM const ammDan(env, dan,
XRP(1'000),
USD(1'100));
2813 if (!features[fixAMMv1_1])
2835 STAmount{USD, UINT64_C(999'99999909091), -11},
2846 using namespace jtx;
2852 Env env(*
this, features);
2872 env(
fset(becky, asfDepositAuth));
2888 using namespace jtx;
2909 auto failedIouPayments = [
this, &env]() {
2931 failedIouPayments();
2948 failedIouPayments();
2955 failedIouPayments();
2978 Env env(*
this, features);
2984 env.
fund(
XRP(1'000), g1, alice, bob);
2987 env.
trust(g1[
"USD"](100), bob);
2988 env.
trust(g1[
"USD"](205), alice);
2991 env(
pay(g1, bob, g1[
"USD"](10)));
2992 env(
pay(g1, alice, g1[
"USD"](205)));
2995 AMM const ammAlice(env, alice,
XRP(500), g1[
"USD"](105));
3001 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == g1.
human());
3002 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"100");
3003 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"10");
3010 BEAST_EXPECT(
lines[jss::lines][0u][jss::account] == g1.
human());
3011 BEAST_EXPECT(
lines[jss::lines][0u][jss::limit] ==
"205");
3013 BEAST_EXPECT(
lines[jss::lines][0u][jss::balance] ==
"100");
3018 env(
pay(alice, bob, g1[
"USD"](1)));
3021 env(
pay(bob, alice, g1[
"USD"](1)));
3026 env(
trust(g1, bob[
"USD"](0), tfSetFreeze));
3032 env(
offer(bob, g1[
"USD"](5),
XRP(25)));
3042 env(
pay(alice, bob, g1[
"USD"](1)));
3053 for (
auto const& it :
lines[jss::lines])
3055 if (it[jss::account] == bob.
human())
3061 if (!BEAST_EXPECT(bobLine))
3063 BEAST_EXPECT(bobLine[jss::freeze] ==
true);
3064 BEAST_EXPECT(bobLine[jss::balance] ==
"-16");
3071 for (
auto const& it :
lines[jss::lines])
3073 if (it[jss::account] == g1.
human())
3079 if (!BEAST_EXPECT(g1Line))
3081 BEAST_EXPECT(g1Line[jss::freeze_peer] ==
true);
3082 BEAST_EXPECT(g1Line[jss::balance] ==
"16");
3088 env(
trust(g1, bob[
"USD"](0), tfClearFreeze));
3093 auto ff = affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3095 ff[sfLowLimit.fieldName] ==
3097 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3098 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3109 Env env(*
this, features);
3119 env.
fund(
XRP(20'000), a2, a3, a4);
3122 env.
trust(g1[
"USD"](1'200), a1);
3123 env.
trust(g1[
"USD"](200), a2);
3124 env.
trust(g1[
"BTC"](100), a3);
3125 env.
trust(g1[
"BTC"](100), a4);
3128 env(
pay(g1, a1, g1[
"USD"](1'000)));
3129 env(
pay(g1, a2, g1[
"USD"](100)));
3130 env(
pay(g1, a3, g1[
"BTC"](100)));
3131 env(
pay(g1, a4, g1[
"BTC"](100)));
3134 AMM const ammG1(env, g1,
XRP(10'000), g1[
"USD"](100));
3144 "book_offers",
std::string(
"USD/") + g1.
human(),
"XRP")[jss::result][jss::offers];
3156 "book_offers",
"XRP",
std::string(
"USD/") + g1.
human())[jss::result][jss::offers];
3171 AMM ammA3(env, a3, g1[
"BTC"](1),
XRP(1));
3177 env(
pay(g1, a2, g1[
"USD"](1)));
3180 env(
pay(a2, g1, g1[
"USD"](1)));
3183 env(
pay(a2, a1, g1[
"USD"](1)));
3186 env(
pay(a1, a2, g1[
"USD"](1)));
3195 env(
fset(g1, asfGlobalFreeze));
3210 "book_offers",
"XRP",
std::string(
"USD/") + g1.
human())[jss::result][jss::offers];
3215 "book_offers",
std::string(
"USD/") + g1.
human(),
"XRP")[jss::result][jss::offers];
3223 env(
pay(g1, a2, g1[
"USD"](1)));
3226 env(
pay(a2, g1, g1[
"USD"](1)));
3236 testcase(
"Offers for Frozen Trust Lines");
3239 Env env(*
this, features);
3246 env.
fund(
XRP(2'000), g1, a3, a4);
3250 env.
trust(g1[
"USD"](1'000), a2);
3251 env.
trust(g1[
"USD"](2'000), a3);
3252 env.
trust(g1[
"USD"](2'001), a4);
3255 env(
pay(g1, a3, g1[
"USD"](2'000)));
3256 env(
pay(g1, a4, g1[
"USD"](2'001)));
3259 AMM const ammA3(env, a3,
XRP(1'000), g1[
"USD"](1'001));
3269 env(
offer(a4,
XRP(999), g1[
"USD"](999)));
3276 env(
trust(g1, a3am, tfSetFreeze));
3278 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3289 env(
trust(g1, a4[
"USD"](0), tfSetFreeze));
3293 env(
offer(a2, g1[
"USD"](999),
XRP(999)));
3305 testcase(
"Multisign AMM Transactions");
3307 using namespace jtx;
3308 Env env{*
this, features};
3313 fund(env,
gw_, {alice, becky, zelda},
XRP(20'000), {
USD(20'000)});
3317 env(
regkey(alice, alie));
3318 env(
fset(alice, asfDisableMaster),
Sig(alice));
3321 env(
signers(alice, 2, {{becky, 1}, {bogie, 1}}),
Sig(alie));
3325 Msig const ms{becky, bogie};
3342 ammAlice.
deposit(alice, 1'000'000);
3345 ammAlice.
withdraw(alice, 1'000'000);
3348 ammAlice.
vote({}, 1'000);
3351 env(ammAlice.
bid({.account = alice, .bidMin = 100}), ms).
close();
3362 using namespace jtx;
3366 Env env(*
this, features);
3384 using namespace jtx;
3388 Env env(*
this, features);
3389 auto const bobUsd =
bob_[
"USD"];
3390 auto const bobEur =
bob_[
"EUR"];
3397 AMM const ammBobXrpUsd(env,
bob_,
XRP(100), bobUsd(100));
3400 AMM const ammBobUsdEur(env,
bob_, bobUsd(100), bobEur(100));
3415 Txflags(tfNoRippleDirect | tfPartialPayment),
3420 Env env(*
this, features);
3434 Env env(*
this, features);
3453 using namespace jtx;
3455 auto const cny =
gw_[
"CNY"];
3458 Env env(*
this, features);
3479 Env env(*
this, features);
3493 AMM const ammBobEurCny(env,
bob_,
EUR(100), cny(100));
3519 using namespace jtx;
3532 using namespace jtx;
3540 using namespace jtx;
A generic endpoint for log messages.
TestcaseT testcase
Memberspace for declaring test cases.
Floating point representation of amounts with high dynamic range.
A currency issued by an account.
Sets the new scale and restores the old scale when it leaves scope.
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.
void pushBack(STPath const &e)
Discardable, editable view to a ledger.
virtual beast::Journal getJournal(std::string const &name)=0
virtual OpenLedger & getOpenLedger()=0
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Tests of AMM that use offers too.
void testRippleState(FeatureBitset features)
void testOfferFeesConsumeFunds(FeatureBitset features)
void testTransferRateOffer(FeatureBitset features)
void testCrossCurrencyBridged(FeatureBitset features)
void testOffersWhenFrozen(FeatureBitset features)
void testDirectToDirectPath(FeatureBitset features)
void testTransferRateNoOwnerFee(FeatureBitset features)
void testFillModes(FeatureBitset features)
void pathFindConsumeAll()
void testSellFlagExceedLimit(FeatureBitset features)
void testRmFundedOffer(FeatureBitset features)
void viaOffersViaGateway()
void testSelfIssueOffer(FeatureBitset features)
void testStepLimit(FeatureBitset features)
void testSellFlagBasic(FeatureBitset features)
void testTxMultisign(FeatureBitset features)
void testEnforceNoRipple(FeatureBitset features)
void testGlobalFreeze(FeatureBitset features)
void testLoop(FeatureBitset features)
void testBadPathAssert(FeatureBitset features)
void run() override
Runs the suite.
void testBridgedCross(FeatureBitset features)
void testCrossingLimits()
void testMissingAuth(FeatureBitset features)
void testCurrencyConversionEntire(FeatureBitset features)
void testCurrencyConversionInParts(FeatureBitset features)
void testToStrand(FeatureBitset features)
NumberMantissaScaleGuard const sg_
void testCrossCurrencyEndXRP(FeatureBitset features)
void testFalseDry(FeatureBitset features)
void testOfferCreateThenCross(FeatureBitset features)
void testOfferCrossWithXRP(FeatureBitset features)
void testBookStep(FeatureBitset features)
void testConvertAllOfAnAsset(FeatureBitset features)
void testGatewayCrossCurrency(FeatureBitset features)
void testSellWithFillOrKill(FeatureBitset features)
void testRequireAuth(FeatureBitset features)
void testPayment(FeatureBitset features)
void testRIPD1373(FeatureBitset features)
void testOfferCrossWithLimitOverride(FeatureBitset features)
void testCrossCurrencyStartXRP(FeatureBitset features)
TestPath & pushBack(Issue const &iss)
jtx::Account const alice_
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> const &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={testableAmendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
static FeatureBitset testableAmendments()
jtx::Account const carol_
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
static XRPAmount ammCrtFee(jtx::Env &env)
Convenience class to test AMM functionality.
bool expectTradingFee(std::uint16_t fee) const
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)
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
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)
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)
AccountID const & ammAccount() const
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< Asset, Asset > > const &assets=std::nullopt, std::optional< Ter > const &ter=std::nullopt)
json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Asset > const &asset1=std::nullopt, std::optional< Asset > const &asset2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::kApiInvalidVersion) const
Send amm_info RPC command.
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Immutable cryptographic account descriptor.
std::string const & human() const
Returns the human readable public key.
Sets the DeliverMin on a JTx.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
void require(Args const &... args)
Check a set of requirements.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
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.
@ Array
array value (ordered list)
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Keylet account(AccountID const &id) noexcept
AccountID root.
void nOffers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
std::vector< STAmount > fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
json::Value regkey(Account const &account, DisabledT)
Disable the regular key.
PrettyAmount xrpMinusFee(Env const &env, std::int64_t xrpAmount)
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
STPathElement allPathElements(AccountID const &a, Asset const &asset)
bool expectHolding(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
XrpT const XRP
Converts to XRP Issue or STAmount.
json::Value noop(Account const &account)
The null transaction.
OwnerCount< ltRIPPLE_STATE > lines
Match the number of trust lines in the account's owner directory.
bool same(STPathSet const &st1, Args const &... args)
XRPAmount txFee(Env const &env, std::uint16_t n)
std::tuple< STPathSet, STAmount, STAmount > findPaths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax, std::optional< PathAsset > const &srcAsset, std::optional< AccountID > const &srcIssuer, std::optional< uint256 > const &domain)
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
FeatureBitset testableAmendments()
STPathElement cpe(PathAsset const &pa)
STPathElement ipe(Asset const &asset)
json::Value ledgerEntryState(Env &env, Account const &acctA, Account const &acctB, std::string const ¤cy)
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
bool equal(STAmount const &sa1, STAmount const &sa2)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
json::Value ledgerEntryRoot(Env &env, Account const &acct)
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
OwnerCount< ltOFFER > offers
Match the number of offers in the account's owner directory.
STPath stpath(Args const &... args)
json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value getAccountLines(Env &env, AccountID const &acctId)
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
bool checkArraySize(json::Value const &val, unsigned int size)
json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
constexpr XRPAmount
Convert XRP to drops (integral types).
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2)
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
TAmounts< STAmount, STAmount > Amounts
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
bool toCurrency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
TER offerDelete(ApplyView &view, SLE::ref sle, beast::Journal j)
Delete an offer.
Currency const & xrpCurrency()
XRP currency.
std::string to_string(BaseUInt< Bits, Tag > const &a)
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.
TERSubset< CanCvtToTER > TER
AccountID const & xrpAccount()
Compute AccountID from public key.
Represents an XRP, IOU, or MPT quantity This customizes the string conversion and supports XRP conver...
STAmount const & value() const