19 #include <ripple/app/misc/AMMHelpers.h>
20 #include <ripple/app/misc/AMMUtils.h>
21 #include <ripple/app/paths/AMMContext.h>
22 #include <ripple/app/paths/AMMOffer.h>
23 #include <ripple/app/paths/Flow.h>
24 #include <ripple/app/paths/impl/StrandFlow.h>
25 #include <ripple/ledger/PaymentSandbox.h>
26 #include <ripple/protocol/AMMCore.h>
27 #include <ripple/protocol/STParsedJSON.h>
28 #include <ripple/resource/Fees.h>
29 #include <ripple/rpc/RPCHandler.h>
30 #include <ripple/rpc/impl/RPCHelpers.h>
32 #include <test/jtx/AMM.h>
33 #include <test/jtx/AMMTest.h>
34 #include <test/jtx/PathSet.h>
35 #include <test/jtx/amount.h>
36 #include <test/jtx/sendmax.h>
51 testcase(
"Incorrect Removal of Funded Offers");
63 Env env{*
this, features};
70 {USD(200'000),
BTC(2
'000)});
72 // Must be two offers at the same quality
73 // "taker gets" must be XRP
74 // (Different amounts so I can distinguish the offers)
75 env(offer(carol, BTC(49), XRP(49)));
76 env(offer(carol, BTC(51), XRP(51)));
78 // Offers for the poor quality path
79 // Must be two offers at the same quality
80 env(offer(carol, XRP(50), USD(50)));
81 env(offer(carol, XRP(50), USD(50)));
84 AMM ammCarol(env, carol, BTC(1'000),
USD(100
'100));
86 PathSet paths(Path(XRP, USD), Path(USD));
88 env(pay(alice, bob, USD(100)),
93 BEAST_EXPECT(ammCarol.expectBalances(
94 STAmount{BTC, UINT64_C(1
'001'000000374812), -12},
98 env.require(balance(bob, USD(200'100)));
105 testcase(
"Enforce No Ripple");
110 Env env{*
this, features};
115 auto const USD1 = gw1[
"USD"];
116 auto const USD2 = gw2[
"USD"];
118 env.fund(
XRP(20
'000), alice, noripple(bob), carol, dan, gw1, gw2);
120 env(trust(
bob, USD1(1
'000), tfSetNoRipple));
122 env(trust(
bob, USD2(1
'000), tfSetNoRipple));
124 env(pay(gw1, dan, USD1(10'000)));
125 env(pay(gw1,
bob, USD1(50)));
126 env(pay(gw2,
bob, USD2(50)));
128 AMM ammDan(env, dan,
XRP(10
'000), USD1(10'000));
139 Env env{*
this, features};
144 auto const USD1 = gw1[
"USD"];
145 auto const USD2 = gw2[
"USD"];
147 env.fund(
XRP(20
'000), alice, bob, carol, gw1, gw2);
148 env.fund(XRP(20'000), dan);
149 env.trust(USD1(20
'000), alice, bob, carol, dan);
152 env(pay(gw1, dan, USD1(10
'050)));
153 env(pay(gw1, bob, USD1(50)));
154 env(pay(gw2, bob, USD2(50)));
156 AMM ammDan(env, dan, XRP(10'000), USD1(10
'050));
158 env(pay(alice, carol, USD2(50)),
161 txflags(tfNoRippleDirect));
162 BEAST_EXPECT(ammDan.expectBalances(
163 XRP(10'050), USD1(10
'000), ammDan.tokens()));
165 BEAST_EXPECT(expectLedgerEntryRoot(
166 env, alice, XRP(20'000) -
XRP(50) -
txfee(env, 1)));
176 testcase(
"Fill Modes");
179 auto const startBalance =
XRP(1
'000'000);
187 for (
auto const& tweakedFeatures :
191 [&](
AMM& ammAlice,
Env& env) {
193 TER const killedCode{
201 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
204 env,
carol,
XRP(30
'000) - (txfee(env, 1))));
205 BEAST_EXPECT(expectOffers(env, carol, 0));
206 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
213 XRP(10
'000), USD(10'100), ammAlice.
tokens()));
215 env,
carol,
XRP(30
'000) + XRP(100) - txfee(env, 2)));
216 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
219 {{
XRP(10
'100), USD(10'000)}},
227 [&](
AMM& ammAlice,
Env& env) {
235 XRP(10
'000), USD(10'100), ammAlice.
tokens()));
238 env,
carol,
XRP(30
'000) + XRP(100) - txfee(env, 1)));
240 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
243 {{
XRP(10
'100), USD(10'000)}},
250 [&](
AMM& ammAlice,
Env& env) {
256 XRP(10
'100), STAmount{USD, 10'000}, ammAlice.
tokens()));
260 {{
XRP(10
'100), USD(10'000)}},
267 [&](
AMM& ammAlice,
Env& env) {
277 STAmount{USD, UINT64_C(9'082
'56880733945), -11},
279 BEAST_EXPECT(expectOffers(env, carol, 0));
280 BEAST_EXPECT(expectOffers(env, alice, 1));
282 {{XRP(11'000),
USD(9
'000)}},
290 testOfferCrossWithXRP(FeatureBitset features)
292 testcase("Offer Crossing with XRP, Normal order");
296 Env env{*this, features};
298 fund(env, gw, {bob, alice}, XRP(300'000), {
USD(100)}, Fund::All);
302 // Existing offer pays better than this wants.
303 // Partially consume existing offer.
304 // Pay 1 USD, get 3061224490 Drops.
305 auto const xrpTransferred = XRPAmount{3'061
'224'490};
306 env(offer(
bob,
USD(1), XRP(4
'000)));
308 BEAST_EXPECT(ammAlice.expectBalances(
309 XRP(150'000) + xrpTransferred,
313 BEAST_EXPECT(expectLine(env,
bob, STAmount{
USD, 101}));
315 env,
bob,
XRP(300
'000) - xrpTransferred - txfee(env, 1)));
316 BEAST_EXPECT(expectOffers(env, bob, 0));
320 testOfferCrossWithLimitOverride(FeatureBitset features)
322 testcase("Offer Crossing with Limit Override");
326 Env env{*this, features};
332 env(pay(gw, alice, alice["USD"](500)));
334 AMM ammAlice(env, alice, XRP(150'000),
USD(51));
338 ammAlice.expectBalances(XRP(153'000),
USD(50), ammAlice.
tokens()));
346 (
XRP(200
'000) - XRP(3'000) - env.
current()->fees().base * 1)
353 testcase(
"Currency Conversion: Entire Offer");
357 Env env{*
this, features};
360 env.require(owners(bob, 0));
362 env(trust(alice, USD(100)));
363 env(trust(bob, USD(1'000)));
366 env.require(owners(alice, 1), owners(bob, 1));
368 env(pay(gw, alice, alice["USD"](100)));
369 AMM ammBob(env, bob, USD(200), XRP(1'500));
374 ammBob.expectBalances(
USD(300),
XRP(1
'000), ammBob.tokens()));
375 BEAST_EXPECT(expectLine(env, alice, USD(0)));
377 auto jrr = ledgerEntryRoot(env, alice);
379 jrr[jss::node][sfBalance.fieldName] ==
380 to_string((XRP(10'000) +
XRP(500) - env.current()->fees().base * 2)
387 testcase(
"Currency Conversion: In Parts");
392 [&](
AMM& ammAlice,
Env& env) {
406 XRPAmount{9
'900'990
'100}, USD(10'100), ammAlice.tokens()));
408 BEAST_EXPECT(expectLine(env, alice, USD(19
'900)));
409 // initial 30,000 - 10,0000AMM + 99.009900pay - fee*3
410 BEAST_EXPECT(expectLedgerEntryRoot(
413 XRP(30'000) - XRP(10
'000) + XRPAmount{99'009
'900} -
414 ammCrtFee(env) - txfee(env, 2)));
416 {{XRP(10'000), USD(10
'000)}},
423 testCrossCurrencyStartXRP(FeatureBitset features)
425 testcase("Cross Currency Payment: Start with XRP");
430 [&](AMM& ammAlice, Env& env) {
431 env.fund(XRP(1'000), bob);
432 env(trust(bob, USD(100)));
434 env(pay(alice, bob, USD(100)), sendmax(XRP(100)));
435 BEAST_EXPECT(ammAlice.expectBalances(
436 XRP(10
'100), USD(10'000), ammAlice.tokens()));
437 BEAST_EXPECT(expectLine(env, bob, USD(100)));
439 {{XRP(10
'000), USD(10'100)}},
448 testcase(
"Cross Currency Payment: End with XRP");
453 [&](
AMM& ammAlice,
Env& env) {
455 env(trust(bob, USD(100)));
457 env(pay(alice, bob, XRP(100)), sendmax(USD(100)));
458 BEAST_EXPECT(ammAlice.expectBalances(
459 XRP(10'000),
USD(10
'100), ammAlice.tokens()));
460 BEAST_EXPECT(expectLedgerEntryRoot(
461 env, bob, XRP(1'000) +
XRP(100) -
txfee(env, 1)));
463 {{
XRP(10
'100), USD(10'000)}},
472 testcase(
"Cross Currency Payment: Bridged");
476 Env env{*
this, features};
478 auto const gw1 =
Account{
"gateway_1"};
479 auto const gw2 =
Account{
"gateway_2"};
480 auto const dan =
Account{
"dan"};
481 auto const USD1 = gw1[
"USD"];
482 auto const EUR1 = gw2[
"EUR"];
486 env(trust(alice, USD1(1'000)));
488 env(trust(
bob, EUR1(1
'000)));
490 env(trust(carol, USD1(10'000)));
492 env(trust(dan, EUR1(1
'000)));
495 env(pay(gw1, alice, alice["USD"](500)));
497 env(pay(gw1, carol, carol["USD"](6'000)));
498 env(pay(gw2, dan, dan[
"EUR"](400)));
501 AMM ammCarol(env,
carol, USD1(5
'000), XRP(50'000));
503 env(offer(dan,
XRP(500), EUR1(50)));
507 jtp[0u][0u][jss::currency] =
"XRP";
509 json(jss::Paths, jtp),
514 STAmount{USD1, UINT64_C(5'030
'181086519115), -12},
516 BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), EUR(20)}}}));
517 BEAST_EXPECT(expectLine(env, bob, STAmount{EUR1, 30}));
521 testOfferFeesConsumeFunds(FeatureBitset features)
523 testcase("Offer Fees Consume Funds");
527 Env env{*this, features};
529 auto const gw1 = Account{"gateway_1"};
530 auto const gw2 = Account{"gateway_2"};
531 auto const gw3 = Account{"gateway_3"};
532 auto const alice = Account{"alice"};
533 auto const bob = Account{"bob"};
534 auto const USD1 = gw1["USD"];
535 auto const USD2 = gw2["USD"];
536 auto const USD3 = gw3["USD"];
538 // Provide micro amounts to compensate for fees to make results round
540 // reserve: Alice has 3 entries in the ledger, via trust lines
542 // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) +
543 // 1 for payment == 4
544 auto const starting_xrp = XRP(100) +
545 env.current()->fees().accountReserve(3) +
546 env.current()->fees().base * 4;
548 env.fund(starting_xrp, gw1, gw2, gw3, alice);
549 env.fund(XRP(2'000),
bob);
551 env(trust(
alice, USD1(1
'000)));
552 env(trust(alice, USD2(1'000)));
553 env(trust(
alice, USD3(1
'000)));
554 env(trust(bob, USD1(1'200)));
555 env(trust(
bob, USD2(1
'100)));
557 env(pay(gw1, bob, bob["USD"](1'200)));
559 AMM ammBob(env,
bob,
XRP(1
'000), USD1(1'200));
562 env(offer(
alice, USD1(200),
XRP(200)));
566 BEAST_EXPECT(ammBob.expectBalances(
568 STAmount{USD1, UINT64_C(1'090
'909090909091), -12},
571 auto jrr = ledgerEntryState(env, alice, gw1, "USD");
573 jrr[jss::node][sfBalance.fieldName][jss::value] ==
575 jrr = ledgerEntryRoot(env, alice);
577 jrr[jss::node][sfBalance.fieldName] == XRP(350).value().getText());
581 testOfferCreateThenCross(FeatureBitset features)
583 testcase("Offer Create, then Cross");
587 Env env{*this, features};
589 fund(env, gw, {alice, bob}, XRP(200'000));
594 env(trust(bob, USD(1'000)));
600 env(offer(bob, XRP(100), USD(0.1)));
602 BEAST_EXPECT(ammAlice.expectBalances(
603 USD(150.1), XRP(150'000), ammAlice.
tokens()));
614 testcase(
"Offer tfSell: Basic Sell");
619 [&](
AMM& ammAlice,
Env& env) {
623 XRP(10
'000), USD(9'999), ammAlice.
tokens()));
626 BEAST_EXPECT(expectLedgerEntryRoot(
627 env, carol, XRP(30'000) -
XRP(100) -
txfee(env, 1)));
629 {{
XRP(9
'900), USD(10'100)}},
638 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
642 Env env{*
this, features};
644 auto const starting_xrp =
650 env(trust(alice, USD(150)));
651 env(trust(bob, USD(4'000)));
653 env(pay(
gw,
bob,
bob[
"USD"](2
'200)));
655 AMM ammBob(env, bob, XRP(1'000),
USD(2
'200));
656 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
657 // Ask for more than available to prove reserve works.
658 // Taker pays 100 USD for 100 XRP.
660 // Will sell all 100 XRP and get more USD than asked for.
661 env(offer(alice, USD(100), XRP(200)), json(jss::Flags, tfSell));
663 ammBob.expectBalances(XRP(1'100),
USD(2
'000), ammBob.tokens()));
664 BEAST_EXPECT(expectLine(env, alice, USD(200)));
665 BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(250)));
666 BEAST_EXPECT(expectOffers(env, alice, 0));
670 testGatewayCrossCurrency(FeatureBitset features)
672 testcase("Client Issue: Gateway Cross Currency");
676 Env env{*this, features};
678 auto const XTS = gw["XTS"];
679 auto const XXX = gw["XXX"];
681 auto const starting_xrp =
682 XRP(100.1) + reserve(env, 1) + env.current()->fees().base * 2;
688 {XTS(100), XXX(100)},
691 AMM ammAlice(env, alice, XTS(100), XXX(100));
694 payment[jss::secret] = toBase58(generateSeed("bob"));
695 payment[jss::id] = env.seq(bob);
696 payment[jss::build_path] = true;
697 payment[jss::tx_json] = pay(bob, bob, bob["XXX"](1));
698 payment[jss::tx_json][jss::Sequence] =
700 ->read(keylet::account(bob.id()))
701 ->getFieldU32(sfSequence);
702 payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
703 payment[jss::tx_json][jss::SendMax] =
704 bob["XTS"](1.5).value().getJson(JsonOptions::none);
705 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
706 auto const jrr = env.rpc("json", "submit", to_string(payment));
707 BEAST_EXPECT(jrr[jss::result][jss::status] == "success");
708 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
709 BEAST_EXPECT(ammAlice.expectBalances(
710 STAmount(XTS, UINT64_C(101'010101010101), -12),
714 env,
bob,
STAmount{XTS, UINT64_C(98
'989898989899), -12}));
715 BEAST_EXPECT(expectLine(env, bob, XXX(101)));
719 testBridgedCross(FeatureBitset features)
721 testcase("Bridged Crossing");
726 Env env{*this, features};
732 {USD(15'000),
EUR(15
'000)},
736 // o USD/XRP AMM is created.
737 // o EUR/XRP AMM is created.
738 // o carol has EUR but wants USD.
739 // Note that carol's offer must come last. If
carol's offer is
740 // placed before AMM is created, then autobridging will not occur.
741 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'100));
742 AMM ammBob(env, bob, EUR(10'000),
XRP(10
'100));
744 // Carol makes an offer that consumes AMM liquidity and
745 // fully consumes Carol's offer.
750 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
751 BEAST_EXPECT(ammBob.expectBalances(
752 XRP(10
'000), EUR(10'100), ammBob.tokens()));
754 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
759 Env env{*
this, features};
765 {
USD(15
'000), EUR(15'000)},
775 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
785 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
787 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
793 Env env{*
this, features};
799 {
USD(15
'000), EUR(15'000)},
811 AMM ammBob(env,
bob,
EUR(10
'000), XRP(10'100));
818 BEAST_EXPECT(ammBob.expectBalances(
819 XRP(10
'000), EUR(10'100), ammBob.tokens()));
821 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
832 testcase(
"Combine tfSell with tfFillOrKill");
837 TER const killedCode{
841 Env env{*
this, features};
843 AMM ammBob(env, bob, XRP(20'000),
USD(200));
849 ammBob.expectBalances(
XRP(20
'000), USD(200), ammBob.tokens()));
850 BEAST_EXPECT(expectOffers(env, bob, 0));
853 Env env{*this, features};
854 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
855 AMM ammBob(env,
bob,
XRP(20
'000), USD(200));
856 // alice submits a tfSell | tfFillOrKill offer that crosses.
857 // Even though tfSell is present it doesn't matter
this time.
860 BEAST_EXPECT(ammBob.expectBalances(
862 STAmount{USD, UINT64_C(197'8239366963403), -13},
871 Env env{*
this, features};
873 AMM ammBob(env, bob, XRP(20'000),
USD(200));
875 env(offer(
alice,
USD(10), XRP(1
'500), tfSell | tfFillOrKill));
878 BEAST_EXPECT(ammBob.expectBalances(
880 STAmount{
USD, UINT64_C(186
'046511627907), -12},
882 BEAST_EXPECT(expectLine(
883 env, alice, STAmount{USD, UINT64_C(1'013
'953488372093), -12}));
884 BEAST_EXPECT(expectOffers(env, alice, 0));
887 // alice submits a tfSell | tfFillOrKill offer that doesn't cross.
892 Env env{*this, features};
894 AMM ammBob(env, bob, XRP(5000), USD(10));
896 env(offer(alice, USD(1), XRP(501), tfSell | tfFillOrKill),
899 BEAST_EXPECT(expectOffers(env, alice, 0));
900 BEAST_EXPECT(expectOffers(env, bob, 0));
905 testTransferRateOffer(FeatureBitset features)
907 testcase("Transfer Rate Offer");
911 // AMM XRP/USD. Alice places USD/XRP offer.
913 [&](AMM& ammAlice, Env& env) {
917 env(offer(carol, USD(100), XRP(100)));
920 // AMM doesn't pay the transfer
fee
922 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
924 BEAST_EXPECT(expectOffers(env, carol, 0));
926 {{XRP(10'000),
USD(10
'100)}},
931 // Reverse the order, so the offer in the books is to sell XRP
932 // in return for USD.
934 [&](AMM& ammAlice, Env& env) {
938 env(offer(carol, XRP(100), USD(100)));
941 BEAST_EXPECT(ammAlice.expectBalances(
942 XRP(10'000),
USD(10
'100), ammAlice.tokens()));
943 // Carol pays 25% transfer fee
944 BEAST_EXPECT(expectLine(env, carol, USD(29'875)));
947 {{
XRP(10
'100), USD(10'000)}},
954 Env env{*
this, features};
959 {
USD(15
'000), EUR(15'000)},
969 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
980 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
982 // Carol pays 25% transfer fee.
983 BEAST_EXPECT(expectLine(env, carol, EUR(14'875)));
991 Env env{*
this, features};
996 {
USD(15
'000), EUR(15'000)},
1006 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'050));
1020 XRP(10
'050), USD(10'000), ammAlice.
tokens()));
1022 // Carol pays 25% transfer fee.
1023 BEAST_EXPECT(expectLine(env, carol, EUR(14'937.5)));
1033 Env env{*
this, features};
1035 env(rate(gw, 1.25));
1036 env(trust(alice, USD(15'000)));
1038 env(trust(carol, EUR(15'000)), qualityInPercent(80));
1040 env(trust(carol, USD(15'000)));
1044 env(pay(gw, carol, EUR(1'000)), sendmax(
EUR(10
'000)));
1047 BEAST_EXPECT(expectLine(env, carol, EUR(1'250)));
1054 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
1065 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
1069 BEAST_EXPECT(expectOffers(env, carol, 0));
1070 BEAST_EXPECT(expectOffers(env, bob, 0));
1074 // A trust line's QualityOut should not affect offer crossing.
1077 Env env{*this, features};
1079 env(rate(gw, 1.25));
1080 env(trust(alice, USD(15'000)));
1082 env(trust(carol, EUR(15'000)), qualityOutPercent(120));
1084 env(trust(carol, USD(15'000)));
1088 env(pay(gw, carol, EUR(1'000)), sendmax(
EUR(10
'000)));
1090 BEAST_EXPECT(expectLine(env, carol, EUR(1'000)));
1097 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
1108 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
1123 using namespace jtx;
1125 Env env{*
this, features};
1127 auto const USD_bob =
bob[
"USD"];
1128 auto const f = env.
current()->fees().base;
1130 env.
fund(
XRP(30
'000) + f, alice, bob);
1132 AMM ammBob(env, bob, XRP(10'000), USD_bob(10
'100));
1134 env(offer(alice, USD_bob(100), XRP(100)));
1137 BEAST_EXPECT(ammBob.expectBalances(
1138 XRP(10'100), USD_bob(10
'000), ammBob.tokens()));
1139 BEAST_EXPECT(expectOffers(env, alice, 0));
1140 BEAST_EXPECT(expectLine(env, alice, USD_bob(100)));
1144 testBadPathAssert(FeatureBitset features)
1146 // At one point in the past this invalid path caused assert. It
1147 // should not be possible for user-supplied data to cause assert.
1148 // Make sure assert is gone.
1149 testcase("Bad path assert");
1151 using namespace jtx;
1153 // The problem was identified when featureOwnerPaysFee was enabled,
1154 // so make sure that gets included.
1155 Env env{*this, features | featureOwnerPaysFee};
1157 // The fee that's charged
for transactions.
1161 auto const ann = Account(
"ann");
1162 auto const A_BUX = ann[
"BUX"];
1163 auto const bob = Account(
"bob");
1164 auto const cam = Account(
"cam");
1165 auto const dan = Account(
"dan");
1166 auto const D_BUX = dan[
"BUX"];
1169 env.fund(reserve(env, 4) + (fee * 4), ann, bob, cam, dan);
1172 env(trust(bob, A_BUX(400)));
1173 env(trust(bob, D_BUX(200)), qualityOutPercent(120));
1174 env(trust(cam, D_BUX(100)));
1176 env(pay(dan, bob, D_BUX(100)));
1178 BEAST_EXPECT(expectLine(env, bob, D_BUX(100)));
1180 env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200)));
1183 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1184 BEAST_EXPECT(expectLine(env, ann, D_BUX(none)));
1185 BEAST_EXPECT(expectLine(env, bob, A_BUX(72)));
1186 BEAST_EXPECT(expectLine(env, bob, D_BUX(40)));
1187 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1188 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1189 BEAST_EXPECT(expectLine(env, dan, A_BUX(none)));
1190 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1192 AMM ammBob(env, bob, A_BUX(30), D_BUX(30));
1194 env(trust(ann, D_BUX(100)));
1198 env(pay(ann, ann, D_BUX(30)),
1205 ammBob.expectBalances(A_BUX(30), D_BUX(30), ammBob.tokens()));
1206 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1207 BEAST_EXPECT(expectLine(env, ann, D_BUX(0)));
1208 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1209 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1210 BEAST_EXPECT(expectLine(env, dan, A_BUX(0)));
1211 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1222 testcase(
"Direct to Direct path");
1224 using namespace jtx;
1226 Env env{*
this, features};
1228 auto const ann =
Account(
"ann");
1230 auto const cam =
Account(
"cam");
1232 auto const A_BUX = ann[
"BUX"];
1233 auto const B_BUX =
bob[
"BUX"];
1237 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
1240 env(trust(ann, B_BUX(40)));
1241 env(trust(cam, A_BUX(40)));
1242 env(trust(bob, A_BUX(30)));
1243 env(trust(cam, B_BUX(40)));
1244 env(trust(carol, B_BUX(400)));
1245 env(trust(carol, A_BUX(400)));
1248 env(pay(ann, cam, A_BUX(35)));
1249 env(pay(bob, cam, B_BUX(35)));
1250 env(pay(bob, carol, B_BUX(400)));
1251 env(pay(ann, carol, A_BUX(400)));
1253 AMM ammCarol(env, carol, A_BUX(300), B_BUX(330));
1255 // cam puts an offer on the books that her upcoming offer could cross.
1256 // But this offer should be deleted, not crossed, by her upcoming
1258 env(offer(cam, A_BUX(29), B_BUX(30), tfPassive));
1260 env.require(balance(cam, A_BUX(35)));
1261 env.require(balance(cam, B_BUX(35)));
1262 env.require(offers(cam, 1));
1264 // This offer caused the assert.
1265 env(offer(cam, B_BUX(30), A_BUX(30)));
1267 // AMM is consumed up to the first cam Offer quality
1268 BEAST_EXPECT(ammCarol.expectBalances(
1269 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1270 STAmount{B_BUX, UINT64_C(320
'0215509984417), -13},
1271 ammCarol.tokens()));
1272 BEAST_EXPECT(expectOffers(
1277 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1278 STAmount{A_BUX, UINT64_C(20
'0215509984417), -13}}}}));
1282 testRequireAuth(FeatureBitset features)
1284 testcase("lsfRequireAuth");
1286 using namespace jtx;
1288 Env env{*this, features};
1290 auto const aliceUSD = alice["USD"];
1291 auto const bobUSD = bob["USD"];
1302 env(trust(
bob,
USD(100)));
1305 env(pay(gw, alice, USD(1'000)));
1318 BEAST_EXPECT(expectLine(env,
bob,
USD(50)));
1321 env(offer(
bob, XRP(50),
USD(50)));
1326 BEAST_EXPECT(expectOffers(env,
bob, 0));
1327 BEAST_EXPECT(expectLine(env,
bob,
USD(0)));
1333 testcase(
"Missing Auth");
1335 using namespace jtx;
1337 Env env{*
this, features};
1339 env.
fund(
XRP(400
'000), gw, alice, bob);
1342 // Alice doesn't have the funds
1353 env(trust(
bob,
USD(50)));
1367 env(trust(
gw,
alice[
"USD"](2
'000)));
1371 AMM ammAlice(env, alice, USD(1'000),
XRP(1
'000), ter(tecNO_AUTH));
1374 // Finally, set up an authorized trust line for Alice. Now Alice's
1378 env(pay(gw, alice, USD(1'000)));
1401 using namespace jtx;
1443 testcase(
"path find consume all");
1444 using namespace jtx;
1461 BEAST_EXPECT(st.
empty());
1469 BEAST_EXPECT(sa ==
XRP(100
'000'000));
1473 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99
'9999000001), -10}));
1476 // carol holds gateway AUD, sells gateway AUD for XRP
1477 // bob will hold gateway AUD
1478 // alice pays bob gateway AUD using XRP
1480 via_offers_via_gateway()
1482 testcase("via gateway");
1483 using namespace jtx;
1485 Env env = pathTestEnv();
1486 auto const AUD = gw["AUD"];
1489 env.
trust(AUD(2
'000), bob, carol);
1490 env(pay(gw, carol, AUD(51)));
1492 AMM ammCarol(env, carol, XRP(40), AUD(51));
1493 env(pay(alice, bob, AUD(10)), sendmax(XRP(100)), paths(XRP));
1495 // AMM offer is 51.282052XRP/11AUD, 11AUD/1.1 = 10AUD to bob
1497 ammCarol.expectBalances(XRP(51), AUD(40), ammCarol.tokens()));
1498 BEAST_EXPECT(expectLine(env, bob, AUD(10)));
1501 find_paths(env, alice, bob, Account(bob)["USD"](25));
1502 BEAST_EXPECT(std::get<0>(result).empty());
1508 testcase("Receive max");
1509 using namespace jtx;
1510 auto const charlie = Account("charlie");
1512 // XRP -> IOU receive max
1513 Env env = pathTestEnv();
1514 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1515 AMM ammCharlie(env, charlie, XRP(10), USD(11));
1517 find_paths(env, alice, bob, USD(-1), XRP(1).value());
1518 BEAST_EXPECT(sa == XRP(1));
1519 BEAST_EXPECT(equal(da, USD(1)));
1520 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1522 auto const& pathElem = st[0][0];
1524 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
1525 pathElem.getCurrency() == USD.currency);
1529 // IOU -> XRP receive max
1530 Env env = pathTestEnv();
1531 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1532 AMM ammCharlie(env, charlie, XRP(11), USD(10));
1535 find_paths(env, alice, bob, drops(-1), USD(1).value());
1536 BEAST_EXPECT(sa == USD(1));
1537 BEAST_EXPECT(equal(da, XRP(1)));
1538 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1540 auto const& pathElem = st[0][0];
1542 pathElem.isOffer() &&
1543 pathElem.getIssuerID() == xrpAccount() &&
1544 pathElem.getCurrency() == xrpCurrency());
1552 testcase("Path Find: XRP -> XRP and XRP -> IOU");
1553 using namespace jtx;
1554 Env env = pathTestEnv();
1563 env.fund(XRP(100'000), A1);
1565 env.fund(XRP(1'000), A3, G1, G2, G3);
1569 env.trust(G1["XYZ"](5'000), A1);
1570 env.
trust(G3[
"ABC"](5
'000), A1);
1571 env.trust(G2["XYZ"](5'000), A2);
1572 env.
trust(G3[
"ABC"](5
'000), A2);
1573 env.trust(A2["ABC"](1'000), A3);
1574 env.
trust(G1[
"XYZ"](100
'000), M1);
1575 env.trust(G2["XYZ"](100'000), M1);
1576 env.
trust(G3[
"ABC"](100
'000), M1);
1579 env(pay(G1, A1, G1["XYZ"](3'500)));
1580 env(pay(G3, A1, G3[
"ABC"](1
'200)));
1581 env(pay(G1, M1, G1["XYZ"](25'000)));
1582 env(pay(G2, M1, G2[
"XYZ"](25
'000)));
1583 env(pay(G3, M1, G3["ABC"](25'000)));
1586 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1
'000), G2["XYZ"](1'000));
1587 AMM ammM1_XRP_G3(env, M1,
XRP(10
'000), G3["ABC"](1'000));
1593 auto const& send_amt =
XRP(10);
1596 BEAST_EXPECT(
equal(da, send_amt));
1597 BEAST_EXPECT(st.
empty());
1603 auto const& send_amt =
XRP(200);
1606 BEAST_EXPECT(
equal(da, send_amt));
1607 BEAST_EXPECT(st.
empty());
1611 auto const& send_amt = G3[
"ABC"](10);
1614 BEAST_EXPECT(
equal(da, send_amt));
1615 BEAST_EXPECT(
equal(sa, XRPAmount{101
'010'102}));
1620 auto const& send_amt = A2[
"ABC"](1);
1623 BEAST_EXPECT(
equal(da, send_amt));
1624 BEAST_EXPECT(
equal(sa, XRPAmount{10
'010'011}));
1629 auto const& send_amt = A3[
"ABC"](1);
1632 BEAST_EXPECT(
equal(da, send_amt));
1633 BEAST_EXPECT(
equal(sa, XRPAmount{10
'010'011}));
1641 testcase(
"Path Find: non-XRP -> XRP");
1642 using namespace jtx;
1649 env.
fund(
XRP(1
'000), A1, A2, G3);
1650 env.fund(XRP(11'000), M1);
1653 env.
trust(G3[
"ABC"](1
'000), A1, A2);
1654 env.trust(G3["ABC"](100'000), M1);
1657 env(pay(G3, A1, G3[
"ABC"](1
'000)));
1658 env(pay(G3, A2, G3["ABC"](1'000)));
1659 env(pay(G3, M1, G3[
"ABC"](1
'200)));
1662 AMM ammM1(env, M1, G3["ABC"](1'000),
XRP(10
'010));
1667 auto const& send_amt = XRP(10);
1668 std::tie(st, sa, da) =
1669 find_paths(env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
1670 BEAST_EXPECT(equal(da, send_amt));
1671 BEAST_EXPECT(equal(sa, A1["ABC"](1)));
1672 BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
1678 testcase("Path Find: non-XRP -> non-XRP, same currency");
1679 using namespace jtx;
1680 Env env = pathTestEnv();
1692 env.fund(XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1694 env.fund(XRP(21'000), M1, M2);
1697 env.
trust(G1[
"HKD"](2
'000), A1);
1698 env.trust(G2["HKD"](2'000), A2);
1699 env.
trust(G1[
"HKD"](2
'000), A3);
1700 env.trust(G1["HKD"](100'000), M1);
1701 env.
trust(G2[
"HKD"](100
'000), M1);
1702 env.trust(G1["HKD"](100'000), M2);
1703 env.
trust(G2[
"HKD"](100
'000), M2);
1706 env(pay(G1, A1, G1["HKD"](1'000)));
1707 env(pay(G2, A2, G2[
"HKD"](1
'000)));
1708 env(pay(G1, A3, G1["HKD"](1'000)));
1709 env(pay(G1, M1, G1[
"HKD"](1
'200)));
1710 env(pay(G2, M1, G2["HKD"](5'000)));
1711 env(pay(G1, M2, G1[
"HKD"](1
'200)));
1712 env(pay(G2, M2, G2["HKD"](5'000)));
1715 AMM ammM1(env, M1, G1[
"HKD"](1
'010), G2["HKD"](1'000));
1716 AMM ammM2XRP_G2(env, M2,
XRP(10
'000), G2["HKD"](1'010));
1717 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1
'010), XRP(10'000));
1725 auto const& send_amt = G1[
"HKD"](10);
1727 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1728 BEAST_EXPECT(st.
empty());
1729 BEAST_EXPECT(
equal(da, send_amt));
1730 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1736 auto const& send_amt = A1[
"HKD"](10);
1738 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1739 BEAST_EXPECT(st.
empty());
1740 BEAST_EXPECT(
equal(da, send_amt));
1741 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1747 auto const& send_amt = A3[
"HKD"](10);
1749 env, A1, A3, send_amt, std::nullopt, G1[
"HKD"].currency);
1750 BEAST_EXPECT(
equal(da, send_amt));
1751 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1752 BEAST_EXPECT(same(st, stpath(G1)));
1758 auto const& send_amt = G2[
"HKD"](10);
1760 env, G1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1761 BEAST_EXPECT(
equal(da, send_amt));
1762 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1765 stpath(IPE(G2[
"HKD"])),
1768 stpath(IPE(
xrpIssue()), IPE(G2[
"HKD"]))));
1774 auto const& send_amt = G2[
"HKD"](10);
1776 env, A1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1777 BEAST_EXPECT(
equal(da, send_amt));
1778 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1791 auto const& send_amt = A2[
"HKD"](10);
1793 env, A1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1794 BEAST_EXPECT(
equal(da, send_amt));
1795 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1808 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1809 using namespace jtx;
1819 env.fund(XRP(1'000), A1, A2, A3, G1, G2);
1822 env.
trust(G1[
"HKD"](2
'000), A1);
1823 env.trust(G2["HKD"](2'000), A2);
1824 env.
trust(A2[
"HKD"](2
'000), A3);
1825 env.trust(G1["HKD"](100'000), M1);
1826 env.
trust(G2[
"HKD"](100
'000), M1);
1829 env(pay(G1, A1, G1["HKD"](1'000)));
1830 env(pay(G2, A2, G2[
"HKD"](1
'000)));
1831 env(pay(G1, M1, G1["HKD"](5'000)));
1832 env(pay(G2, M1, G2[
"HKD"](5
'000)));
1835 AMM ammM1(env, M1, G1["HKD"](1'010), G2[
"HKD"](1
'000));
1837 // E) Gateway to user
1838 // Source -> OB -> AC -> Destination
1839 auto const& send_amt = A2["HKD"](10);
1842 std::tie(st, sa, da) =
1843 find_paths(env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1844 BEAST_EXPECT(equal(da, send_amt));
1845 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1846 BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
1850 testFalseDry(FeatureBitset features)
1852 testcase("falseDryChanges");
1854 using namespace jtx;
1856 Env env(*this, features);
1858 env.fund(XRP(10'000),
alice,
gw);
1862 auto const AMMXRPPool = env.current()->fees().increment * 2;
1863 env.fund(reserve(env, 5) + ammCrtFee(env) + AMMXRPPool, bob);
1865 env.trust(
EUR(1
'000), alice, bob, carol);
1867 env(pay(gw, alice, EUR(50)));
1868 env(pay(gw, bob, USD(150)));
1870 // Bob has _just_ slightly less than 50 xrp available
1871 // If his owner count changes, he will have more liquidity.
1872 // This is one error case to test (when Flow is used).
1873 // Computing the incoming xrp to the XRP/USD offer will require two
1874 // recursive calls to the EUR/XRP offer. The second call will return
1875 // tecPATH_DRY, but the entire path should not be marked as dry.
1876 // This is the second error case to test (when flowV1 is used).
1877 env(offer(bob, EUR(50), XRP(50)));
1878 AMM ammBob(env, bob, AMMXRPPool, USD(150));
1880 env(pay(alice, carol, USD(1'000
'000)),
1883 txflags(tfNoRippleDirect | tfPartialPayment));
1885 auto const carolUSD = env.balance(carol, USD).value();
1886 BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50));
1890 testBookStep(FeatureBitset features)
1892 testcase("Book Step");
1894 using namespace jtx;
1897 // simple IOU/IOU offer
1898 Env env(*this, features);
1903 {alice, bob, carol},
1917 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1921 Env env(*
this, features);
1928 {BTC(100), USD(150)},
1931 AMM ammBobBTC_XRP(env, bob, BTC(100), XRP(150));
1932 AMM ammBobXRP_USD(env, bob, XRP(100), USD(150));
1934 env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50)));
1936 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
1937 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
1938 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1939 BEAST_EXPECT(expectLine(env, carol, USD(200)));
1940 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1941 BTC(150), XRP(100), ammBobBTC_XRP.tokens()));
1942 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
1943 XRP(150), USD(100), ammBobXRP_USD.tokens()));
1946 // simple XRP -> USD through offer and sendmax
1947 Env env(*this, features);
1952 {alice, carol, bob},
1963 BEAST_EXPECT(expectLedgerEntryRoot(
1968 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
1972 Env env(*
this, features);
1982 AMM ammBob(env, bob, USD(100), XRP(150));
1984 env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50)));
1986 BEAST_EXPECT(expectLine(env, alice, USD(50)));
1987 BEAST_EXPECT(expectLedgerEntryRoot(
1992 ammBob.expectBalances(USD(150), XRP(100), ammBob.tokens()));
1995 // test unfunded offers are removed when payment succeeds
1996 Env env(*this, features);
1999 env.fund(
XRP(10
'000), bob);
2001 env.trust(
BTC(1
'000), alice, bob, carol);
2022 env.require(balance(
alice,
BTC(10)));
2023 env.require(balance(
bob,
BTC(50)));
2024 env.require(balance(
bob,
USD(0)));
2025 env.require(balance(
bob,
EUR(0)));
2026 env.require(balance(
carol,
USD(50)));
2033 ammBob.expectBalances(
EUR(100),
USD(150), ammBob.tokens()));
2045 Env env(*
this, features);
2047 env.fund(
XRP(10
'000), bob, carol, gw);
2048 // Sets rippling on, this is different from
2049 // the original test
2050 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2051 env.trust(
USD(1
'000), alice, bob, carol);
2053 env.trust(
EUR(1
'000), alice, bob, carol);
2055 env(pay(gw, alice, BTC(60)));
2056 env(pay(gw, bob, BTC(100)));
2057 env(pay(gw, bob, USD(100)));
2058 env(pay(gw, bob, EUR(50)));
2059 env(pay(gw, carol, EUR(1)));
2061 // This is multiplath, which generates limited # of offers
2062 AMM ammBobBTC_USD(env, bob, BTC(50), USD(50));
2063 env(offer(bob, BTC(60), EUR(50)));
2064 env(offer(carol, BTC(1'000),
EUR(1)));
2069 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2070 BTC(50),
USD(50), ammBobBTC_USD.tokens()));
2073 BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50)));
2075 auto flowJournal = env.app().logs().journal("Flow");
2076 auto const flowResult = [&] {
2077 STAmount deliver(USD(51));
2078 STAmount smax(BTC(61));
2079 PaymentSandbox sb(env.current().get(), tapNONE);
2081 auto IPE = [](Issue const& iss) {
2082 return STPathElement(
2083 STPathElement::typeCurrency | STPathElement::typeIssuer,
2090 STPath p1({IPE(USD.issue())});
2091 paths.push_back(p1);
2092 // BTC -> EUR -> USD
2093 STPath p2({IPE(EUR.issue()), IPE(USD.issue())});
2094 paths.push_back(p2);
2112 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2113 env.app().openLedger().modify(
2114 [&](OpenView& view, beast::Journal j) {
2115 if (flowResult.removableOffers.empty())
2117 Sandbox sb(&view, tapNONE);
2118 for (auto const& o : flowResult.removableOffers)
2119 if (auto ok = sb.peek(keylet::offer(o)))
2120 offerDelete(sb, ok, flowJournal);
2125 // used in payment, but since payment failed should be untouched
2126 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2127 BTC(50), USD(50), ammBobBTC_USD.tokens()));
2128 BEAST_EXPECT(isOffer(env, carol, BTC(1'000),
EUR(1)));
2141 Env env(*
this, features);
2142 env.fund(
XRP(10
'000), bob, carol, gw);
2143 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2144 env.trust(
USD(1
'000), alice, bob, carol);
2148 env(pay(gw, bob, EUR(1'000)));
2151 // env(offer(bob, USD(1), drops(2)), txflags(tfPassive));
2152 AMM ammBob(env, bob, USD(8), XRPAmount{21});
2153 env(offer(bob, drops(1), EUR(1'000)), txflags(
tfPassive));
2161 BEAST_EXPECT(ammBob.expectBalances(
2162 USD(8.4), XRPAmount{20}, ammBob.tokens()));
2169 testcase(
"Transfer Rate");
2171 using namespace jtx;
2175 Env env(*
this, features);
2190 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
2191 BEAST_EXPECT(expectLedgerEntryRoot(
2192 env, alice, xrpMinusFee(env, 10'000 - 50)));
2197 // Transfer fee AMM and offer
2198 Env env(*this, features);
2203 {alice, bob, carol},
2205 {
USD(1
'000), EUR(1'000)});
2212 env(offer(bob, USD(50), EUR(50)));
2214 // alice buys 40EUR with 40XRP
2215 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
2217 // 40XRP is swapped in for 40USD
2219 ammBob.expectBalances(XRP(140), USD(100), ammBob.tokens()));
2220 // 40USD buys 40EUR via bob's offer. 40
EUR delivered to
carol
2223 // bob gets 40USD back from the offer
2224 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 + 40)));
2227 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2233 Env env(*
this, features);
2240 {USD(1'000),
EUR(1
'000)});
2241 env(rate(gw, 1.25));
2244 AMM ammBobXRP_USD(env, bob, XRP(100), USD(140));
2245 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
2248 BEAST_EXPECT(expectLine(env,
bob,
EUR(1
'000 - 140)));
2249 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
2255 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
2256 XRP(140),
USD(100), ammBobXRP_USD.tokens()));
2258 BEAST_EXPECT(ammBobUSD_EUR.expectBalances(
2259 USD(140),
EUR(100), ammBobUSD_EUR.tokens()));
2261 BEAST_EXPECT(expectLine(env,
bob,
USD(1
'000 - 140 - 100)));
2262 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
2263 BEAST_EXPECT(expectLedgerEntryRoot(
2264 env,
alice, xrpMinusFee(env, 10
'000 - 40)));
2265 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2271 Env env(*
this, features);
2278 {USD(1'200),
GBP(1
'200)});
2279 env(rate(gw, 1.25));
2282 AMM amm(env, bob, GBP(1'000),
USD(1
'100));
2284 // requested quality limit is 90USD/110GBP = 0.8181
2285 // trade quality is 77.2727USD/94.4444GBP = 0.8181
2286 env(pay(alice, carol, USD(90)),
2289 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2292 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
2294 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
2295 BEAST_EXPECT(expectLine(
2296 env, alice, STAmount{GBP, UINT64_C(1'105
'555555555555), -12}));
2297 // 75.5555GBP is swapped in for 77.7272USD
2298 BEAST_EXPECT(amm.expectBalances(
2299 STAmount{GBP, UINT64_C(1'075
'555555555556), -12},
2300 STAmount{USD, UINT64_C(1'022
'727272727272), -12},
2302 BEAST_EXPECT(expectLine(
2303 env, carol, STAmount{USD, UINT64_C(1'277
'272727272728), -12}));
2307 // AMM offer crossing
2308 Env env(*this, features);
2310 fund(env, gw, {alice, bob}, XRP(1'000), {
USD(1
'200), EUR(1'200)});
2320 BEAST_EXPECT(
amm.expectBalances(
2321 STAmount{USD, UINT64_C(1
'095'238095238095), -12},
2324 // alice pays 25% tr fee on 95.2380USD
2325 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
2326 BEAST_EXPECT(expectLine(
2329 STAmount{USD, UINT64_C(1'080
'952380952381), -12},
2337 Env env(*
this, features);
2338 auto const USDA =
alice[
"USD"];
2339 auto const USDB =
bob[
"USD"];
2340 Account
const dan(
"dan");
2342 env.fund(
XRP(10
'000), bob, carol, dan, gw);
2343 fund(env, {alice}, XRP(10'000));
2345 env.trust(
USD(2
'000), alice, bob, carol, dan);
2346 env.trust(EUR(2'000),
carol, dan);
2347 env.trust(USDA(1
'000), bob);
2348 env.trust(USDB(1'000),
gw);
2351 env(pay(gw, dan, USD(1'000)));
2352 AMM ammDan(env, dan,
USD(1
'000), EUR(1'050));
2362 ammDan.expectBalances(
USD(1
'050), EUR(1'000), ammDan.tokens()));
2374 testcase(
"No Owner Fee");
2375 using namespace jtx;
2379 Env env(*
this, features);
2386 {USD(1'000),
GBP(1
'000)});
2387 env(rate(gw, 1.25));
2390 AMM amm(env, bob, GBP(1'000),
USD(1
'000));
2392 env(pay(alice, carol, USD(100)),
2395 txflags(tfNoRippleDirect | tfPartialPayment));
2398 // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
2399 // 1,000 - 120*1.25 = 850GBP
2400 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2401 // 120GBP is swapped in for 107.1428USD
2402 BEAST_EXPECT(amm.expectBalances(
2406 // 25% of 85.7142USD is paid in tr fee
2407 // 85.7142*1.25 = 107.1428USD
2408 BEAST_EXPECT(expectLine(
2409 env, carol, STAmount(USD, UINT64_C(1'085
'714285714286), -12)));
2413 // Payment via offer and AMM
2414 Env env(*this, features);
2415 Account const ed("ed");
2420 {alice, bob, carol, ed},
2422 {
USD(1
'000), EUR(1'000),
GBP(1
'000)});
2423 env(rate(gw, 1.25));
2426 env(offer(ed, GBP(1'000),
EUR(1
'000)), txflags(tfPassive));
2429 AMM amm(env, bob, EUR(1'000),
USD(1
'000));
2431 env(pay(alice, carol, USD(100)),
2434 txflags(tfNoRippleDirect | tfPartialPayment));
2437 // alice buys 120EUR with 120GBP via the offer
2438 // and pays 25% tr fee on 120GBP
2439 // 1,000 - 120*1.25 = 850GBP
2440 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2441 // consumed offer is 120GBP/120EUR
2442 // ed doesn't pay tr
fee
2445 expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}}));
2446 // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR
2447 // 96EUR is swapped in for 87.5912USD
2448 BEAST_EXPECT(amm.expectBalances(
2452 // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD
2453 BEAST_EXPECT(expectLine(
2454 env, carol, STAmount(USD, UINT64_C(1'070
'07299270073), -11)));
2457 // Payment via AMM, AMM
2458 Env env(*this, features);
2459 Account const ed("ed");
2464 {alice, bob, carol, ed},
2466 {
USD(1
'000), EUR(1'000),
GBP(1
'000)});
2467 env(rate(gw, 1.25));
2470 AMM amm1(env, bob, GBP(1'000),
EUR(1
'000));
2471 AMM amm2(env, ed, EUR(1'000),
USD(1
'000));
2473 env(pay(alice, carol, USD(100)),
2476 txflags(tfNoRippleDirect | tfPartialPayment));
2479 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on 120GBP
2480 // 1,000 - 120*1.25 = 850GBP
2481 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2482 // 120GBP is swapped in for 107.1428EUR
2483 BEAST_EXPECT(amm1.expectBalances(
2487 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 = 107.1428EUR
2488 // 85.7142EUR is swapped in for 78.9473USD
2489 BEAST_EXPECT(amm2.expectBalances(
2490 STAmount(EUR, UINT64_C(1'085
'714285714286), -12),
2491 STAmount{USD, UINT64_C(921'0526315789471), -13},
2499 Env env(*
this, features);
2502 env(rate(gw, 1.25));
2505 AMM amm(env, bob, USD(1'000),
EUR(1
'100));
2506 env(offer(alice, EUR(100), USD(100)));
2509 // 100USD is swapped in for 100EUR
2511 amm.expectBalances(USD(1'100),
EUR(1
'000), amm.tokens()));
2512 // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD
2513 BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200)));
2519 Env env(*
this, features);
2526 {USD(1'000),
GBP(1
'000)});
2527 env(rate(gw, 1.25));
2530 AMM amm(env, bob, GBP(1'000),
USD(1
'000));
2532 // requested quality limit is 100USD/178.58GBP = 0.55997
2533 // trade quality is 100USD/178.5714 = 0.55999
2534 env(pay(alice, carol, USD(100)),
2536 sendmax(GBP(178.58)),
2537 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2540 // alice buys 125USD with 142.8571GBP and pays 25% tr fee
2542 // 1,000 - 142.8571*1.25 = 821.4285GBP
2543 BEAST_EXPECT(expectLine(
2544 env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13)));
2546 BEAST_EXPECT(amm.expectBalances(
2547 STAmount{GBP, UINT64_C(1
'142'857142857143), -12},
2555 // Payment via AMM with limit quality, deliver less
2557 Env env(*this, features);
2562 {alice, bob, carol},
2564 {
USD(1
'200), GBP(1'200)});
2568 AMM amm(env,
bob,
GBP(1
'000), USD(1'200));
2582 // 24GBP is swapped in for 28.125USD
2584 amm.expectBalances(GBP(1'024),
USD(1
'171.875), amm.tokens()));
2585 // 25% on 22.5USD is paid in tr fee
2586 // 22.5*1.25 = 28.125USD
2587 BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
2592 Env env(*
this, features);
2600 {USD(1'400),
EUR(1
'400), GBP(1'400)});
2607 AMM amm(env,
bob,
EUR(1
'000), USD(1'400));
2628 STAmount{
GBP, UINT64_C(1
'470'421052631579), -12}));
2635 STAmount{EUR, UINT64_C(929'5789473684212), -13}}}));
2638 BEAST_EXPECT(amm.expectBalances(
2639 STAmount{EUR, UINT64_C(1
'056'336842105263), -12},
2640 STAmount{USD, UINT64_C(1
'325'334821428571), -12},
2649 Env env(*
this, features);
2657 {USD(1'400),
EUR(1
'400), GBP(1'400)});
2661 AMM amm(env,
bob,
GBP(1
'000), EUR(1'000));
2681 BEAST_EXPECT(amm.expectBalances(
2682 STAmount{GBP, UINT64_C(1
'056'336842105263), -12},
2683 STAmount{EUR, UINT64_C(946
'6677295918366), -13},
2685 // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
2686 // 42.6658EUR/59.7321USD
2687 BEAST_EXPECT(expectLine(
2690 STAmount{USD, UINT64_C(1'340
'267857142857), -12},
2691 STAmount{EUR, UINT64_C(1'442
'665816326531), -12}));
2692 BEAST_EXPECT(expectOffers(
2697 STAmount{EUR, UINT64_C(957'3341836734693), -13},
2698 STAmount{USD, UINT64_C(1
'340'267857142857), -12}}}));
2706 Env env(*
this, features);
2714 {USD(1'400),
EUR(1
'400), GBP(1'400)});
2718 AMM amm1(env,
bob,
GBP(1
'000), EUR(1'000));
2719 AMM amm2(env, ed,
EUR(1
'000), USD(1'400));
2736 STAmount{GBP, UINT64_C(1
'086'024691358025), -12},
2737 STAmount{EUR, UINT64_C(920
'78937795562), -11},
2739 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2740 // 63.3684EUR is swapped in for 83.4291USD
2741 BEAST_EXPECT(amm2.expectBalances(
2742 STAmount{EUR, UINT64_C(1'063
'368497635504), -12},
2743 STAmount{USD, UINT64_C(1'316
'570881226053), -12},
2745 // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
2746 BEAST_EXPECT(expectLine(
2747 env, carol, STAmount(USD, UINT64_C(1'466
'743295019157), -12)));
2750 // Payment by the issuer via AMM, AMM with limit quality,
2751 // deliver less than requested
2752 Env env(*this, features);
2757 {alice, bob, carol},
2759 {USD(1
'400), EUR(1'400), GBP(1
'400)});
2760 env(rate(gw, 1.25));
2763 AMM amm1(env, alice, GBP(1'000), EUR(1
'000));
2764 AMM amm2(env, bob, EUR(1'000), USD(1
'400));
2766 // requested quality limit is 90USD/120GBP = 0.75
2767 // trade quality is 81.1111USD/108.1481GBP = 0.75
2768 env(pay(gw, carol, USD(90)),
2771 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2774 // 108.1481GBP is swapped in for 97.5935EUR
2775 BEAST_EXPECT(amm1.expectBalances(
2776 STAmount{GBP, UINT64_C(1'108
'148148148149), -12},
2777 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2782 STAmount{EUR, UINT64_C(1
'078'074866310161), -12},
2783 STAmount{USD, UINT64_C(1
'298'611111111111), -12},
2798 testcase(
"limitQuality");
2799 using namespace jtx;
2806 AMM ammBob(env,
bob,
XRP(1
'000), USD(1'050));
2815 ammBob.expectBalances(
XRP(1
'050), USD(1'000), ammBob.tokens()));
2817 BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}}));
2824 testcase("Circular XRP");
2826 using namespace jtx;
2828 for (auto const withFix : {true, false})
2830 auto const feats = withFix
2831 ? supported_amendments()
2832 : supported_amendments() - FeatureBitset{fix1781};
2834 // Payment path starting with XRP
2835 Env env(*this, feats);
2836 // Note, if alice doesn't have
default ripple, then pay
2843 {USD(200), EUR(200)},
2846 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(101));
2847 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(101));
2850 TER const expectedTer =
2851 withFix ? TER{temBAD_PATH_LOOP} : TER{tesSUCCESS};
2852 env(pay(alice, bob, EUR(1)),
2853 path(~USD, ~XRP, ~EUR),
2855 txflags(tfNoRippleDirect),
2859 // Payment path ending with XRP
2861 // Note, if alice doesn't have
default ripple, then pay fails
2868 {USD(200), EUR(200)},
2871 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
2872 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
2873 // EUR -> //XRP -> //USD ->XRP
2874 env(pay(alice, bob, XRP(1)),
2875 path(~XRP, ~USD, ~XRP),
2877 txflags(tfNoRippleDirect),
2878 ter(temBAD_PATH_LOOP));
2881 // Payment where loop is formed in the middle of the path, not
2883 auto const JPY = gw["JPY"];
2885 // Note, if alice doesn't have
default ripple, then pay fails
2892 {USD(200), EUR(200), JPY(200)},
2895 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
2896 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
2897 AMM ammAliceXRP_JPY(env, alice, XRP(100), JPY(100));
2899 env(pay(alice, bob, JPY(1)),
2900 path(~XRP, ~EUR, ~XRP, ~JPY),
2902 txflags(tfNoRippleDirect),
2903 ter(temBAD_PATH_LOOP));
2908 testStepLimit(FeatureBitset features)
2910 testcase("Step Limit");
2912 using namespace jtx;
2913 Env env(*this, features);
2914 auto const dan = Account("dan");
2915 auto const ed = Account("ed");
2917 fund(env, gw, {ed}, XRP(100'000
'000), {USD(11)});
2918 env.fund(XRP(100'000
'000), alice, bob, carol, dan);
2919 env.trust(USD(1), bob);
2920 env(pay(gw, bob, USD(1)));
2921 env.trust(USD(1), dan);
2922 env(pay(gw, dan, USD(1)));
2923 n_offers(env, 2'000,
bob,
XRP(1),
USD(1));
2929 env(offer(
alice,
USD(1
'000), XRP(1'000)));
2932 env.require(owners(alice, 2));
2933 env.require(balance(bob, USD(0)));
2934 env.require(owners(bob, 1'001));
2936 env.require(
owners(dan, 2));
2940 env(offer(
carol,
USD(1
'000), XRP(1'000)));
2946 env.require(
owners(dan, 2));
2952 testcase(
"Convert all of an asset using DeliverMin");
2954 using namespace jtx;
2957 Env env(*
this, features);
2959 env.trust(USD(100), alice, bob, carol);
2960 env(pay(alice, bob, USD(10)),
2961 delivermin(USD(10)),
2962 ter(temBAD_AMOUNT));
2963 env(pay(alice, bob, USD(10)),
2964 delivermin(USD(-5)),
2965 txflags(tfPartialPayment),
2966 ter(temBAD_AMOUNT));
2967 env(pay(alice, bob, USD(10)),
2969 txflags(tfPartialPayment),
2970 ter(temBAD_AMOUNT));
2971 env(pay(alice, bob, USD(10)),
2972 delivermin(Account(carol)["USD"](5)),
2973 txflags(tfPartialPayment),
2974 ter(temBAD_AMOUNT));
2975 env(pay(alice, bob, USD(10)),
2976 delivermin(USD(15)),
2977 txflags(tfPartialPayment),
2978 ter(temBAD_AMOUNT));
2979 env(pay(gw, carol, USD(50)));
2980 AMM ammCarol(env, carol, XRP(10), USD(15));
2981 env(pay(alice, bob, USD(10)),
2984 txflags(tfPartialPayment),
2986 ter(tecPATH_PARTIAL));
2987 env.require(balance(alice, XRP(9'999.99999)));
2992 Env env(*this, features);
2993 fund(env, gw, {alice, bob}, XRP(10'000));
2995 env(pay(gw, bob, USD(1'100)));
2996 AMM ammBob(env,
bob,
XRP(1
'000), USD(1'100));
2999 delivermin(USD(100)),
3000 txflags(tfPartialPayment),
3002 env.require(balance(alice, USD(100)));
3006 Env env(*this, features);
3007 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3009 env(pay(gw, bob, USD(1'200)));
3010 AMM ammBob(env,
bob,
XRP(5
'500), USD(1'200));
3013 delivermin(USD(200)),
3014 txflags(tfPartialPayment),
3015 sendmax(XRP(1'000)),
3019 delivermin(USD(200)),
3020 txflags(tfPartialPayment),
3021 sendmax(XRP(1'100)));
3028 auto const dan =
Account(
"dan");
3029 Env env(*
this, features);
3031 env.trust(USD(1'100),
bob,
carol, dan);
3033 env(pay(
gw, dan,
USD(1
'100)));
3034 env(offer(bob, XRP(100), USD(100)));
3035 env(offer(bob, XRP(1'000),
USD(100)));
3036 AMM ammDan(env, dan,
XRP(1
'000), USD(1'100));
3039 delivermin(USD(200)),
3040 txflags(tfPartialPayment),
3042 env.require(balance(bob, USD(0)));
3043 env.require(balance(carol, USD(200)));
3045 ammDan.expectBalances(XRP(1'100),
USD(1
'000), ammDan.tokens()));
3050 testPayment(FeatureBitset features)
3052 testcase("Payment");
3054 using namespace jtx;
3055 Account const becky{"becky"};
3057 bool const supportsPreauth = {features[featureDepositPreauth]};
3059 // The initial implementation of DepositAuth had a bug where an
3060 // account with the DepositAuth flag set could not make a payment
3061 // to itself. That bug was fixed in the DepositPreauth amendment.
3062 Env env(*this, features);
3063 fund(env, gw, {alice, becky}, XRP(5'000));
3066 env.trust(
USD(1
'000), alice);
3067 env.trust(USD(1'000), becky);
3091 env(pay(becky, becky,
USD(10)),
3104 testcase(
"Pay IOU");
3106 using namespace jtx;
3127 auto failedIouPayments = [
this, &env]() {
3151 failedIouPayments();
3161 env(pay(
bob,
alice, bobPaysXRP),
fee(bobPaysFee));
3168 failedIouPayments();
3175 failedIouPayments();
3196 testcase(
"RippleState Freeze");
3198 using namespace test::jtx;
3199 Env env(*
this, features);
3205 env.
fund(
XRP(1
'000), G1, alice, bob);
3208 env.trust(G1["USD"](100), bob);
3209 env.trust(G1["USD"](205), alice);
3212 env(pay(G1, bob, G1["USD"](10)));
3213 env(pay(G1, alice, G1["USD"](205)));
3216 AMM ammAlice(env, alice, XRP(500), G1["USD"](105));
3219 auto lines = getAccountLines(env, bob);
3220 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3222 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3223 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100");
3224 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10");
3228 auto lines = getAccountLines(env, alice, G1["USD"]);
3229 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3231 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3232 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205");
3233 // 105 transferred to AMM
3234 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100");
3238 // Account with line unfrozen (proving operations normally work)
3239 // test: can make Payment on that line
3240 env(pay(alice, bob, G1["USD"](1)));
3242 // test: can receive Payment on that line
3243 env(pay(bob, alice, G1["USD"](1)));
3248 // Is created via a TrustSet with SetFreeze flag
3249 // test: sets LowFreeze | HighFreeze flags
3250 env(trust(G1, bob["USD"](0), tfSetFreeze));
3251 auto affected = env.meta()->getJson(
3252 JsonOptions::none)[sfAffectedNodes.fieldName];
3253 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3256 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3258 ff[sfLowLimit.fieldName] ==
3259 G1["USD"](0).value().getJson(JsonOptions::none));
3260 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze);
3261 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3266 // Account with line frozen by issuer
3267 // test: can buy more assets on that line
3268 env(offer(bob, G1["USD"](5), XRP(25)));
3269 auto affected = env.meta()->getJson(
3270 JsonOptions::none)[sfAffectedNodes.fieldName];
3271 if (!BEAST_EXPECT(checkArraySize(affected, 4u)))
3274 affected[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3276 ff[sfHighLimit.fieldName] ==
3277 bob["USD"](100).value().getJson(JsonOptions::none));
3278 auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15}
3280 .getJson(JsonOptions::none);
3281 BEAST_EXPECT(ff[sfBalance.fieldName] == amt);
3283 BEAST_EXPECT(ammAlice.expectBalances(
3284 XRP(525), G1["USD"](100), ammAlice.tokens()));
3288 // test: can not sell assets from that line
3289 env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER));
3291 // test: can receive Payment on that line
3292 env(pay(alice, bob, G1["USD"](1)));
3294 // test: can not make Payment from that line
3295 env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY));
3299 // check G1 account lines
3300 // test: shows freeze
3301 auto lines = getAccountLines(env, G1);
3302 Json::Value bobLine;
3303 for (auto const& it : lines[jss::lines])
3305 if (it[jss::account] == bob.human())
3311 if (!BEAST_EXPECT(bobLine))
3313 BEAST_EXPECT(bobLine[jss::freeze] == true);
3314 BEAST_EXPECT(bobLine[jss::balance] == "-16");
3318 // test: shows freeze peer
3319 auto lines = getAccountLines(env, bob);
3321 for (auto const& it : lines[jss::lines])
3323 if (it[jss::account] == G1.human())
3329 if (!BEAST_EXPECT(g1Line))
3331 BEAST_EXPECT(g1Line[jss::freeze_peer] == true);
3332 BEAST_EXPECT(g1Line[jss::balance] == "16");
3336 // Is cleared via a TrustSet with ClearFreeze flag
3337 // test: sets LowFreeze | HighFreeze flags
3338 env(trust(G1, bob["USD"](0), tfClearFreeze));
3339 auto affected = env.meta()->getJson(
3340 JsonOptions::none)[sfAffectedNodes.fieldName];
3341 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3344 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3346 ff[sfLowLimit.fieldName] ==
3347 G1["USD"](0).value().getJson(JsonOptions::none));
3348 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3349 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3355 testGlobalFreeze(FeatureBitset features)
3357 testcase("Global Freeze");
3359 using namespace test::jtx;
3360 Env env(*this, features);
3368 env.fund(XRP(12'000), G1);
3370 env.fund(XRP(20'000), A2, A3, A4);
3373 env.
trust(G1[
"USD"](1
'200), A1);
3374 env.trust(G1["USD"](200), A2);
3375 env.trust(G1["BTC"](100), A3);
3376 env.trust(G1["BTC"](100), A4);
3379 env(pay(G1, A1, G1["USD"](1'000)));
3380 env(pay(G1, A2, G1[
"USD"](100)));
3381 env(pay(G1, A3, G1[
"BTC"](100)));
3382 env(pay(G1, A4, G1[
"BTC"](100)));
3385 AMM ammG1(env, G1,
XRP(10
'000), G1["USD"](100));
3387 env(offer(A2, G1[
"USD"](100),
XRP(10
'000)), txflags(tfPassive));
3391 // Account without GlobalFreeze (proving operations normally
3393 // test: visible offers where taker_pays is unfrozen issuer
3394 auto offers = env.rpc(
3396 std::string("USD/") + G1.human(),
3397 "XRP")[jss::result][jss::offers];
3398 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3400 std::set<std::string> accounts;
3401 for (auto const& offer : offers)
3403 accounts.insert(offer[jss::Account].asString());
3405 BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts));
3407 // test: visible offers where taker_gets is unfrozen issuer
3411 std::string("USD/") + G1.human())[jss::result][jss::offers];
3412 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3415 for (auto const& offer : offers)
3417 accounts.insert(offer[jss::Account].asString());
3419 BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts));
3424 // test: assets can be bought on the market
3425 // env(offer(A3, G1["BTC"](1), XRP(1)));
3426 AMM ammA3(env, A3, G1["BTC"](1), XRP(1));
3428 // test: assets can be sold on the market
3429 // AMM is bidirectional
3431 // test: direct issues can be sent
3432 env(pay(G1, A2, G1["USD"](1)));
3434 // test: direct redemptions can be sent
3435 env(pay(A2, G1, G1["USD"](1)));
3437 // test: via rippling can be sent
3438 env(pay(A2, A1, G1["USD"](1)));
3440 // test: via rippling can be sent back
3441 env(pay(A1, A2, G1["USD"](1)));
3442 ammA3.withdrawAll(std::nullopt);
3446 // Account with GlobalFreeze
3447 // set GlobalFreeze first
3448 // test: SetFlag GlobalFreeze will toggle back to freeze
3449 env.require(nflags(G1, asfGlobalFreeze));
3450 env(fset(G1, asfGlobalFreeze));
3451 env.require(flags(G1, asfGlobalFreeze));
3452 env.require(nflags(G1, asfNoFreeze));
3454 // test: assets can't be bought on the market
3467 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3474 "XRP")[jss::result][jss::offers];
3482 env(pay(G1, A2, G1[
"USD"](1)));
3485 env(pay(A2, G1, G1[
"USD"](1)));
3495 testcase(
"Offers for Frozen Trust Lines");
3497 using namespace test::jtx;
3498 Env env(*
this, features);
3505 env.
fund(
XRP(2
'000), G1, A3, A4);
3506 env.fund(XRP(2'000), A2);
3509 env.
trust(G1[
"USD"](1
'000), A2);
3510 env.trust(G1["USD"](2'000), A3);
3511 env.
trust(G1[
"USD"](2
'001), A4);
3514 env(pay(G1, A3, G1["USD"](2'000)));
3515 env(pay(G1, A4, G1[
"USD"](2
'001)));
3518 AMM ammA3(env, A3, XRP(1'000), G1[
"USD"](1
'001));
3520 // removal after successful payment
3521 // test: make a payment with partially consuming offer
3522 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3526 ammA3.expectBalances(XRP(1'001), G1[
"USD"](1
'000), ammA3.tokens()));
3528 // test: someone else creates an offer providing liquidity
3529 env(offer(A4, XRP(999), G1["USD"](999)));
3531 // The offer consumes AMM offer
3533 ammA3.expectBalances(XRP(1'000), G1[
"USD"](1
'001), ammA3.tokens()));
3535 // test: AMM line is frozen
3537 STAmount{Issue{to_currency("USD"), ammA3.ammAccount()}, 0};
3538 env(trust(G1, a3am, tfSetFreeze));
3539 auto const info = ammA3.ammRpcInfo();
3540 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3542 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
3543 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3546 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3548 ff[sfHighLimit.fieldName] ==
3549 G1["USD"](0).value().getJson(JsonOptions::none));
3550 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3551 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfHighFreeze);
3554 // test: Can make a payment via the new offer
3555 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3557 // AMM is not consumed
3559 ammA3.expectBalances(XRP(1'000), G1[
"USD"](1
'001), ammA3.tokens()));
3561 // removal buy successful OfferCreate
3562 // test: freeze the new offer
3563 env(trust(G1, A4["USD"](0), tfSetFreeze));
3565 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
3566 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3568 ff = affected[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3570 ff[sfLowLimit.fieldName] ==
3571 G1["USD"](0).value().getJson(JsonOptions::none));
3572 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze);
3573 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3576 // test: can no longer create a crossing offer
3577 env(offer(A2, G1["USD"](999), XRP(999)));
3579 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
3580 if (!BEAST_EXPECT(checkArraySize(affected, 8u)))
3582 auto created = affected[0u][sfCreatedNode.fieldName];
3584 created[sfNewFields.fieldName][jss::Account] == A2.human());
3587 // test: offer was removed by offer_create
3588 auto offers = getAccountOffers(env, A4)[jss::offers];
3589 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
3594 testTxMultisign(FeatureBitset features)
3596 testcase("Multisign AMM Transactions");
3598 using namespace jtx;
3599 Env env{*this, features};
3600 Account const bogie{"bogie", KeyType::secp256k1};
3601 Account const alice{"alice", KeyType::secp256k1};
3602 Account const becky{"becky", KeyType::ed25519};
3603 Account const zelda{"zelda", KeyType::secp256k1};
3604 fund(env, gw, {alice, becky, zelda}, XRP(20'000), {
USD(20
'000)});
3606 // alice uses a regular key with the master disabled.
3607 Account const alie{"alie", KeyType::secp256k1};
3608 env(regkey(alice, alie));
3609 env(fset(alice, asfDisableMaster), sig(alice));
3611 // Attach signers to alice.
3612 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
3614 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3615 env.require(owners(alice, signerListOwners + 0));
3617 // Multisign all AMM transactions
3625 ammCrtFee(env).drops(),
3630 BEAST_EXPECT(ammAlice.expectBalances(
3631 XRP(10'000),
USD(10
'000), ammAlice.tokens()));
3633 ammAlice.deposit(alice, 1'000
'000);
3634 BEAST_EXPECT(ammAlice.expectBalances(
3635 XRP(11'000),
USD(11
'000), IOUAmount{11'000
'000, 0}));
3637 ammAlice.withdraw(alice, 1'000
'000);
3638 BEAST_EXPECT(ammAlice.expectBalances(
3639 XRP(10'000),
USD(10
'000), ammAlice.tokens()));
3641 ammAlice.vote({}, 1'000);
3644 ammAlice.bid(alice, 100);
3645 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
3654 testcase(
"To Strand");
3656 using namespace jtx;
3660 Env env(*
this, features);
3667 {USD(2'000),
EUR(1
'000)});
3669 AMM bobXRP_USD(env, bob, XRP(1'000),
USD(1
'000));
3670 AMM bobUSD_EUR(env, bob, USD(1'000),
EUR(1
'000));
3672 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
3673 env(pay(alice, carol, USD(100)),
3674 path(~USD, ~EUR, ~USD),
3676 txflags(tfNoRippleDirect),
3677 ter(temBAD_PATH_LOOP));
3681 testRIPD1373(FeatureBitset features)
3683 using namespace jtx;
3684 testcase("RIPD1373");
3687 Env env(*this, features);
3688 auto const BobUSD = bob["USD"];
3689 auto const BobEUR = bob["EUR"];
3690 fund(env, gw, {alice, bob}, XRP(10'000));
3697 {BobUSD(100), BobEUR(100)},
3700 AMM ammBobXRP_USD(env,
bob, XRP(100), BobUSD(100));
3703 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3706 Path
const p = [&] {
3708 result.push_back(allpe(
gw, BobUSD));
3723 Env env(*
this, features);
3727 AMM ammBob(env, bob, XRP(100), USD(100));
3729 // payment path: XRP -> XRP/USD -> USD/XRP
3730 env(pay(alice, carol, XRP(100)),
3732 txflags(tfNoRippleDirect),
3733 ter(temBAD_SEND_XRP_PATHS));
3737 Env env(*this, features);
3739 fund(env, gw, {alice, bob, carol}, XRP(10'000), {
USD(100)});
3755 testcase(
"test loop");
3756 using namespace jtx;
3758 auto const CNY =
gw[
"CNY"];
3761 Env env(*
this, features);
3763 env.
fund(
XRP(10
'000), alice, bob, carol, gw);
3780 Env env(*
this, features);
3782 env.
fund(
XRP(10
'000), alice, bob, carol, gw);
3784 env.
trust(
EUR(10
'000), alice, bob, carol);
3789 env(pay(
gw,
bob, CNY(100)));
3793 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
3819 using namespace jtx;
3835 using namespace jtx;
3843 using namespace jtx;
3860 using namespace test::jtx;
3870 using namespace jtx;
3882 using namespace jtx;