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/TestHelpers.h>
36 #include <test/jtx/amount.h>
37 #include <test/jtx/sendmax.h>
52 testcase(
"Incorrect Removal of Funded Offers");
64 Env env{*
this, features};
71 {USD(200'000),
BTC(2
'000)});
73 // Must be two offers at the same quality
74 // "taker gets" must be XRP
75 // (Different amounts so I can distinguish the offers)
76 env(offer(carol, BTC(49), XRP(49)));
77 env(offer(carol, BTC(51), XRP(51)));
79 // Offers for the poor quality path
80 // Must be two offers at the same quality
81 env(offer(carol, XRP(50), USD(50)));
82 env(offer(carol, XRP(50), USD(50)));
85 AMM ammCarol(env, carol, BTC(1'000),
USD(100
'100));
87 PathSet paths(Path(XRP, USD), Path(USD));
89 env(pay(alice, bob, USD(100)),
94 BEAST_EXPECT(ammCarol.expectBalances(
95 STAmount{BTC, UINT64_C(1
'001'000000374812), -12},
99 env.require(balance(bob, USD(200'100)));
106 testcase(
"Enforce No Ripple");
111 Env env{*
this, features};
116 auto const USD1 = gw1[
"USD"];
117 auto const USD2 = gw2[
"USD"];
119 env.fund(
XRP(20
'000), alice, noripple(bob), carol, dan, gw1, gw2);
121 env(trust(
bob, USD1(1
'000), tfSetNoRipple));
123 env(trust(
bob, USD2(1
'000), tfSetNoRipple));
125 env(pay(gw1, dan, USD1(10'000)));
126 env(pay(gw1,
bob, USD1(50)));
127 env(pay(gw2,
bob, USD2(50)));
129 AMM ammDan(env, dan,
XRP(10
'000), USD1(10'000));
140 Env env{*
this, features};
145 auto const USD1 = gw1[
"USD"];
146 auto const USD2 = gw2[
"USD"];
148 env.fund(
XRP(20
'000), alice, bob, carol, gw1, gw2);
149 env.fund(XRP(20'000), dan);
150 env.trust(USD1(20
'000), alice, bob, carol, dan);
153 env(pay(gw1, dan, USD1(10
'050)));
154 env(pay(gw1, bob, USD1(50)));
155 env(pay(gw2, bob, USD2(50)));
157 AMM ammDan(env, dan, XRP(10'000), USD1(10
'050));
159 env(pay(alice, carol, USD2(50)),
162 txflags(tfNoRippleDirect));
163 BEAST_EXPECT(ammDan.expectBalances(
164 XRP(10'050), USD1(10
'000), ammDan.tokens()));
166 BEAST_EXPECT(expectLedgerEntryRoot(
167 env, alice, XRP(20'000) -
XRP(50) -
txfee(env, 1)));
177 testcase(
"Fill Modes");
180 auto const startBalance =
XRP(1
'000'000);
188 for (
auto const& tweakedFeatures :
192 [&](
AMM& ammAlice,
Env& env) {
194 TER const killedCode{
202 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
205 env,
carol,
XRP(30
'000) - (txfee(env, 1))));
206 BEAST_EXPECT(expectOffers(env, carol, 0));
207 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
214 XRP(10
'000), USD(10'100), ammAlice.
tokens()));
216 env,
carol,
XRP(30
'000) + XRP(100) - txfee(env, 2)));
217 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
220 {{
XRP(10
'100), USD(10'000)}},
228 [&](
AMM& ammAlice,
Env& env) {
236 XRP(10
'000), USD(10'100), ammAlice.
tokens()));
239 env,
carol,
XRP(30
'000) + XRP(100) - txfee(env, 1)));
241 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
244 {{
XRP(10
'100), USD(10'000)}},
251 [&](
AMM& ammAlice,
Env& env) {
257 XRP(10
'100), STAmount{USD, 10'000}, ammAlice.
tokens()));
261 {{
XRP(10
'100), USD(10'000)}},
268 [&](
AMM& ammAlice,
Env& env) {
278 STAmount{USD, UINT64_C(9'082
'56880733945), -11},
280 BEAST_EXPECT(expectOffers(env, carol, 0));
281 BEAST_EXPECT(expectOffers(env, alice, 1));
283 {{XRP(11'000),
USD(9
'000)}},
291 testOfferCrossWithXRP(FeatureBitset features)
293 testcase("Offer Crossing with XRP, Normal order");
297 Env env{*this, features};
299 fund(env, gw, {bob, alice}, XRP(300'000), {
USD(100)}, Fund::All);
303 // Existing offer pays better than this wants.
304 // Partially consume existing offer.
305 // Pay 1 USD, get 3061224490 Drops.
306 auto const xrpTransferred = XRPAmount{3'061
'224'490};
307 env(offer(
bob,
USD(1), XRP(4
'000)));
309 BEAST_EXPECT(ammAlice.expectBalances(
310 XRP(150'000) + xrpTransferred,
314 BEAST_EXPECT(expectLine(env,
bob, STAmount{
USD, 101}));
316 env,
bob,
XRP(300
'000) - xrpTransferred - txfee(env, 1)));
317 BEAST_EXPECT(expectOffers(env, bob, 0));
321 testOfferCrossWithLimitOverride(FeatureBitset features)
323 testcase("Offer Crossing with Limit Override");
327 Env env{*this, features};
333 env(pay(gw, alice, alice["USD"](500)));
335 AMM ammAlice(env, alice, XRP(150'000),
USD(51));
339 ammAlice.expectBalances(XRP(153'000),
USD(50), ammAlice.
tokens()));
347 (
XRP(200
'000) - XRP(3'000) - env.
current()->fees().base * 1)
354 testcase(
"Currency Conversion: Entire Offer");
358 Env env{*
this, features};
361 env.require(owners(bob, 0));
363 env(trust(alice, USD(100)));
364 env(trust(bob, USD(1'000)));
367 env.require(owners(alice, 1), owners(bob, 1));
369 env(pay(gw, alice, alice["USD"](100)));
370 AMM ammBob(env, bob, USD(200), XRP(1'500));
375 ammBob.expectBalances(
USD(300),
XRP(1
'000), ammBob.tokens()));
376 BEAST_EXPECT(expectLine(env, alice, USD(0)));
378 auto jrr = ledgerEntryRoot(env, alice);
380 jrr[jss::node][sfBalance.fieldName] ==
381 to_string((XRP(10'000) +
XRP(500) - env.current()->fees().base * 2)
388 testcase(
"Currency Conversion: In Parts");
393 [&](
AMM& ammAlice,
Env& env) {
407 XRPAmount{9
'900'990
'100}, USD(10'100), ammAlice.tokens()));
409 BEAST_EXPECT(expectLine(env, alice, USD(19
'900)));
410 // initial 30,000 - 10,0000AMM + 99.009900pay - fee*3
411 BEAST_EXPECT(expectLedgerEntryRoot(
414 XRP(30'000) - XRP(10
'000) + XRPAmount{99'009
'900} -
415 ammCrtFee(env) - txfee(env, 2)));
417 {{XRP(10'000), USD(10
'000)}},
424 testCrossCurrencyStartXRP(FeatureBitset features)
426 testcase("Cross Currency Payment: Start with XRP");
431 [&](AMM& ammAlice, Env& env) {
432 env.fund(XRP(1'000), bob);
433 env(trust(bob, USD(100)));
435 env(pay(alice, bob, USD(100)), sendmax(XRP(100)));
436 BEAST_EXPECT(ammAlice.expectBalances(
437 XRP(10
'100), USD(10'000), ammAlice.tokens()));
438 BEAST_EXPECT(expectLine(env, bob, USD(100)));
440 {{XRP(10
'000), USD(10'100)}},
449 testcase(
"Cross Currency Payment: End with XRP");
454 [&](
AMM& ammAlice,
Env& env) {
456 env(trust(bob, USD(100)));
458 env(pay(alice, bob, XRP(100)), sendmax(USD(100)));
459 BEAST_EXPECT(ammAlice.expectBalances(
460 XRP(10'000),
USD(10
'100), ammAlice.tokens()));
461 BEAST_EXPECT(expectLedgerEntryRoot(
462 env, bob, XRP(1'000) +
XRP(100) -
txfee(env, 1)));
464 {{
XRP(10
'100), USD(10'000)}},
473 testcase(
"Cross Currency Payment: Bridged");
477 Env env{*
this, features};
479 auto const gw1 =
Account{
"gateway_1"};
480 auto const gw2 =
Account{
"gateway_2"};
481 auto const dan =
Account{
"dan"};
482 auto const USD1 = gw1[
"USD"];
483 auto const EUR1 = gw2[
"EUR"];
487 env(trust(alice, USD1(1'000)));
489 env(trust(
bob, EUR1(1
'000)));
491 env(trust(carol, USD1(10'000)));
493 env(trust(dan, EUR1(1
'000)));
496 env(pay(gw1, alice, alice["USD"](500)));
498 env(pay(gw1, carol, carol["USD"](6'000)));
499 env(pay(gw2, dan, dan[
"EUR"](400)));
502 AMM ammCarol(env,
carol, USD1(5
'000), XRP(50'000));
504 env(offer(dan,
XRP(500), EUR1(50)));
508 jtp[0u][0u][jss::currency] =
"XRP";
510 json(jss::Paths, jtp),
515 STAmount{USD1, UINT64_C(5'030
'181086519115), -12},
517 BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), EUR(20)}}}));
518 BEAST_EXPECT(expectLine(env, bob, STAmount{EUR1, 30}));
522 testOfferFeesConsumeFunds(FeatureBitset features)
524 testcase("Offer Fees Consume Funds");
528 Env env{*this, features};
530 auto const gw1 = Account{"gateway_1"};
531 auto const gw2 = Account{"gateway_2"};
532 auto const gw3 = Account{"gateway_3"};
533 auto const alice = Account{"alice"};
534 auto const bob = Account{"bob"};
535 auto const USD1 = gw1["USD"];
536 auto const USD2 = gw2["USD"];
537 auto const USD3 = gw3["USD"];
539 // Provide micro amounts to compensate for fees to make results round
541 // reserve: Alice has 3 entries in the ledger, via trust lines
543 // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) +
544 // 1 for payment == 4
545 auto const starting_xrp = XRP(100) +
546 env.current()->fees().accountReserve(3) +
547 env.current()->fees().base * 4;
549 env.fund(starting_xrp, gw1, gw2, gw3, alice);
550 env.fund(XRP(2'000),
bob);
552 env(trust(
alice, USD1(1
'000)));
553 env(trust(alice, USD2(1'000)));
554 env(trust(
alice, USD3(1
'000)));
555 env(trust(bob, USD1(1'200)));
556 env(trust(
bob, USD2(1
'100)));
558 env(pay(gw1, bob, bob["USD"](1'200)));
560 AMM ammBob(env,
bob,
XRP(1
'000), USD1(1'200));
563 env(offer(
alice, USD1(200),
XRP(200)));
567 BEAST_EXPECT(ammBob.expectBalances(
569 STAmount{USD1, UINT64_C(1'090
'909090909091), -12},
572 auto jrr = ledgerEntryState(env, alice, gw1, "USD");
574 jrr[jss::node][sfBalance.fieldName][jss::value] ==
576 jrr = ledgerEntryRoot(env, alice);
578 jrr[jss::node][sfBalance.fieldName] == XRP(350).value().getText());
582 testOfferCreateThenCross(FeatureBitset features)
584 testcase("Offer Create, then Cross");
588 Env env{*this, features};
590 fund(env, gw, {alice, bob}, XRP(200'000));
595 env(trust(bob, USD(1'000)));
601 env(offer(bob, XRP(100), USD(0.1)));
603 BEAST_EXPECT(ammAlice.expectBalances(
604 USD(150.1), XRP(150'000), ammAlice.
tokens()));
615 testcase(
"Offer tfSell: Basic Sell");
620 [&](
AMM& ammAlice,
Env& env) {
624 XRP(10
'000), USD(9'999), ammAlice.
tokens()));
627 BEAST_EXPECT(expectLedgerEntryRoot(
628 env, carol, XRP(30'000) -
XRP(100) -
txfee(env, 1)));
630 {{
XRP(9
'900), USD(10'100)}},
639 testcase(
"Offer tfSell: 2x Sell Exceed Limit");
643 Env env{*
this, features};
645 auto const starting_xrp =
651 env(trust(alice, USD(150)));
652 env(trust(bob, USD(4'000)));
654 env(pay(
gw,
bob,
bob[
"USD"](2
'200)));
656 AMM ammBob(env, bob, XRP(1'000),
USD(2
'200));
657 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
658 // Ask for more than available to prove reserve works.
659 // Taker pays 100 USD for 100 XRP.
661 // Will sell all 100 XRP and get more USD than asked for.
662 env(offer(alice, USD(100), XRP(200)), json(jss::Flags, tfSell));
664 ammBob.expectBalances(XRP(1'100),
USD(2
'000), ammBob.tokens()));
665 BEAST_EXPECT(expectLine(env, alice, USD(200)));
666 BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(250)));
667 BEAST_EXPECT(expectOffers(env, alice, 0));
671 testGatewayCrossCurrency(FeatureBitset features)
673 testcase("Client Issue: Gateway Cross Currency");
677 Env env{*this, features};
679 auto const XTS = gw["XTS"];
680 auto const XXX = gw["XXX"];
682 auto const starting_xrp =
683 XRP(100.1) + reserve(env, 1) + env.current()->fees().base * 2;
689 {XTS(100), XXX(100)},
692 AMM ammAlice(env, alice, XTS(100), XXX(100));
695 payment[jss::secret] = toBase58(generateSeed("bob"));
696 payment[jss::id] = env.seq(bob);
697 payment[jss::build_path] = true;
698 payment[jss::tx_json] = pay(bob, bob, bob["XXX"](1));
699 payment[jss::tx_json][jss::Sequence] =
701 ->read(keylet::account(bob.id()))
702 ->getFieldU32(sfSequence);
703 payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
704 payment[jss::tx_json][jss::SendMax] =
705 bob["XTS"](1.5).value().getJson(JsonOptions::none);
706 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
707 auto const jrr = env.rpc("json", "submit", to_string(payment));
708 BEAST_EXPECT(jrr[jss::result][jss::status] == "success");
709 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
710 BEAST_EXPECT(ammAlice.expectBalances(
711 STAmount(XTS, UINT64_C(101'010101010101), -12),
715 env,
bob,
STAmount{XTS, UINT64_C(98
'989898989899), -12}));
716 BEAST_EXPECT(expectLine(env, bob, XXX(101)));
720 testBridgedCross(FeatureBitset features)
722 testcase("Bridged Crossing");
727 Env env{*this, features};
733 {USD(15'000),
EUR(15
'000)},
737 // o USD/XRP AMM is created.
738 // o EUR/XRP AMM is created.
739 // o carol has EUR but wants USD.
740 // Note that carol's offer must come last. If
carol's offer is
741 // placed before AMM is created, then autobridging will not occur.
742 AMM ammAlice(env, alice, XRP(10'000),
USD(10
'100));
743 AMM ammBob(env, bob, EUR(10'000),
XRP(10
'100));
745 // Carol makes an offer that consumes AMM liquidity and
746 // fully consumes Carol's offer.
751 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
752 BEAST_EXPECT(ammBob.expectBalances(
753 XRP(10
'000), EUR(10'100), ammBob.tokens()));
755 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
760 Env env{*
this, features};
766 {
USD(15
'000), EUR(15'000)},
776 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
786 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
788 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
794 Env env{*
this, features};
800 {
USD(15
'000), EUR(15'000)},
812 AMM ammBob(env,
bob,
EUR(10
'000), XRP(10'100));
819 BEAST_EXPECT(ammBob.expectBalances(
820 XRP(10
'000), EUR(10'100), ammBob.tokens()));
822 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
833 testcase(
"Combine tfSell with tfFillOrKill");
838 TER const killedCode{
842 Env env{*
this, features};
844 AMM ammBob(env, bob, XRP(20'000),
USD(200));
850 ammBob.expectBalances(
XRP(20
'000), USD(200), ammBob.tokens()));
851 BEAST_EXPECT(expectOffers(env, bob, 0));
854 Env env{*this, features};
855 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
856 AMM ammBob(env,
bob,
XRP(20
'000), USD(200));
857 // alice submits a tfSell | tfFillOrKill offer that crosses.
858 // Even though tfSell is present it doesn't matter
this time.
861 BEAST_EXPECT(ammBob.expectBalances(
863 STAmount{USD, UINT64_C(197'8239366963403), -13},
872 Env env{*
this, features};
874 AMM ammBob(env, bob, XRP(20'000),
USD(200));
876 env(offer(
alice,
USD(10), XRP(1
'500), tfSell | tfFillOrKill));
879 BEAST_EXPECT(ammBob.expectBalances(
881 STAmount{
USD, UINT64_C(186
'046511627907), -12},
883 BEAST_EXPECT(expectLine(
884 env, alice, STAmount{USD, UINT64_C(1'013
'953488372093), -12}));
885 BEAST_EXPECT(expectOffers(env, alice, 0));
888 // alice submits a tfSell | tfFillOrKill offer that doesn't cross.
893 Env env{*this, features};
895 AMM ammBob(env, bob, XRP(5000), USD(10));
897 env(offer(alice, USD(1), XRP(501), tfSell | tfFillOrKill),
900 BEAST_EXPECT(expectOffers(env, alice, 0));
901 BEAST_EXPECT(expectOffers(env, bob, 0));
906 testTransferRateOffer(FeatureBitset features)
908 testcase("Transfer Rate Offer");
912 // AMM XRP/USD. Alice places USD/XRP offer.
914 [&](AMM& ammAlice, Env& env) {
918 env(offer(carol, USD(100), XRP(100)));
921 // AMM doesn't pay the transfer
fee
923 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
925 BEAST_EXPECT(expectOffers(env, carol, 0));
927 {{XRP(10'000),
USD(10
'100)}},
932 // Reverse the order, so the offer in the books is to sell XRP
933 // in return for USD.
935 [&](AMM& ammAlice, Env& env) {
939 env(offer(carol, XRP(100), USD(100)));
942 BEAST_EXPECT(ammAlice.expectBalances(
943 XRP(10'000),
USD(10
'100), ammAlice.tokens()));
944 // Carol pays 25% transfer fee
945 BEAST_EXPECT(expectLine(env, carol, USD(29'875)));
948 {{
XRP(10
'100), USD(10'000)}},
955 Env env{*
this, features};
960 {
USD(15
'000), EUR(15'000)},
970 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
981 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
983 // Carol pays 25% transfer fee.
984 BEAST_EXPECT(expectLine(env, carol, EUR(14'875)));
992 Env env{*
this, features};
997 {
USD(15
'000), EUR(15'000)},
1007 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'050));
1021 XRP(10
'050), USD(10'000), ammAlice.
tokens()));
1023 // Carol pays 25% transfer fee.
1024 BEAST_EXPECT(expectLine(env, carol, EUR(14'937.5)));
1034 Env env{*
this, features};
1036 env(rate(gw, 1.25));
1037 env(trust(alice, USD(15'000)));
1039 env(trust(carol, EUR(15'000)), qualityInPercent(80));
1041 env(trust(carol, USD(15'000)));
1045 env(pay(gw, carol, EUR(1'000)), sendmax(
EUR(10
'000)));
1048 BEAST_EXPECT(expectLine(env, carol, EUR(1'250)));
1055 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
1066 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
1070 BEAST_EXPECT(expectOffers(env, carol, 0));
1071 BEAST_EXPECT(expectOffers(env, bob, 0));
1075 // A trust line's QualityOut should not affect offer crossing.
1078 Env env{*this, features};
1080 env(rate(gw, 1.25));
1081 env(trust(alice, USD(15'000)));
1083 env(trust(carol, EUR(15'000)), qualityOutPercent(120));
1085 env(trust(carol, USD(15'000)));
1089 env(pay(gw, carol, EUR(1'000)), sendmax(
EUR(10
'000)));
1091 BEAST_EXPECT(expectLine(env, carol, EUR(1'000)));
1098 AMM ammAlice(env,
alice,
XRP(10
'000), USD(10'100));
1109 XRP(10
'100), USD(10'000), ammAlice.
tokens()));
1124 using namespace jtx;
1126 Env env{*
this, features};
1128 auto const USD_bob =
bob[
"USD"];
1129 auto const f = env.
current()->fees().base;
1131 env.
fund(
XRP(30
'000) + f, alice, bob);
1133 AMM ammBob(env, bob, XRP(10'000), USD_bob(10
'100));
1135 env(offer(alice, USD_bob(100), XRP(100)));
1138 BEAST_EXPECT(ammBob.expectBalances(
1139 XRP(10'100), USD_bob(10
'000), ammBob.tokens()));
1140 BEAST_EXPECT(expectOffers(env, alice, 0));
1141 BEAST_EXPECT(expectLine(env, alice, USD_bob(100)));
1145 testBadPathAssert(FeatureBitset features)
1147 // At one point in the past this invalid path caused assert. It
1148 // should not be possible for user-supplied data to cause assert.
1149 // Make sure assert is gone.
1150 testcase("Bad path assert");
1152 using namespace jtx;
1154 // The problem was identified when featureOwnerPaysFee was enabled,
1155 // so make sure that gets included.
1156 Env env{*this, features | featureOwnerPaysFee};
1158 // The fee that's charged
for transactions.
1162 auto const ann = Account(
"ann");
1163 auto const A_BUX = ann[
"BUX"];
1164 auto const bob = Account(
"bob");
1165 auto const cam = Account(
"cam");
1166 auto const dan = Account(
"dan");
1167 auto const D_BUX = dan[
"BUX"];
1170 env.fund(reserve(env, 4) + (fee * 4), ann, bob, cam, dan);
1173 env(trust(bob, A_BUX(400)));
1174 env(trust(bob, D_BUX(200)), qualityOutPercent(120));
1175 env(trust(cam, D_BUX(100)));
1177 env(pay(dan, bob, D_BUX(100)));
1179 BEAST_EXPECT(expectLine(env, bob, D_BUX(100)));
1181 env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200)));
1184 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1185 BEAST_EXPECT(expectLine(env, ann, D_BUX(none)));
1186 BEAST_EXPECT(expectLine(env, bob, A_BUX(72)));
1187 BEAST_EXPECT(expectLine(env, bob, D_BUX(40)));
1188 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1189 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1190 BEAST_EXPECT(expectLine(env, dan, A_BUX(none)));
1191 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1193 AMM ammBob(env, bob, A_BUX(30), D_BUX(30));
1195 env(trust(ann, D_BUX(100)));
1199 env(pay(ann, ann, D_BUX(30)),
1206 ammBob.expectBalances(A_BUX(30), D_BUX(30), ammBob.tokens()));
1207 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1208 BEAST_EXPECT(expectLine(env, ann, D_BUX(0)));
1209 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1210 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1211 BEAST_EXPECT(expectLine(env, dan, A_BUX(0)));
1212 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1223 testcase(
"Direct to Direct path");
1225 using namespace jtx;
1227 Env env{*
this, features};
1229 auto const ann =
Account(
"ann");
1231 auto const cam =
Account(
"cam");
1233 auto const A_BUX = ann[
"BUX"];
1234 auto const B_BUX =
bob[
"BUX"];
1238 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
1241 env(trust(ann, B_BUX(40)));
1242 env(trust(cam, A_BUX(40)));
1243 env(trust(bob, A_BUX(30)));
1244 env(trust(cam, B_BUX(40)));
1245 env(trust(carol, B_BUX(400)));
1246 env(trust(carol, A_BUX(400)));
1249 env(pay(ann, cam, A_BUX(35)));
1250 env(pay(bob, cam, B_BUX(35)));
1251 env(pay(bob, carol, B_BUX(400)));
1252 env(pay(ann, carol, A_BUX(400)));
1254 AMM ammCarol(env, carol, A_BUX(300), B_BUX(330));
1256 // cam puts an offer on the books that her upcoming offer could cross.
1257 // But this offer should be deleted, not crossed, by her upcoming
1259 env(offer(cam, A_BUX(29), B_BUX(30), tfPassive));
1261 env.require(balance(cam, A_BUX(35)));
1262 env.require(balance(cam, B_BUX(35)));
1263 env.require(offers(cam, 1));
1265 // This offer caused the assert.
1266 env(offer(cam, B_BUX(30), A_BUX(30)));
1268 // AMM is consumed up to the first cam Offer quality
1269 BEAST_EXPECT(ammCarol.expectBalances(
1270 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1271 STAmount{B_BUX, UINT64_C(320
'0215509984417), -13},
1272 ammCarol.tokens()));
1273 BEAST_EXPECT(expectOffers(
1278 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1279 STAmount{A_BUX, UINT64_C(20
'0215509984417), -13}}}}));
1283 testRequireAuth(FeatureBitset features)
1285 testcase("lsfRequireAuth");
1287 using namespace jtx;
1289 Env env{*this, features};
1291 auto const aliceUSD = alice["USD"];
1292 auto const bobUSD = bob["USD"];
1303 env(trust(
bob,
USD(100)));
1306 env(pay(gw, alice, USD(1'000)));
1319 BEAST_EXPECT(expectLine(env,
bob,
USD(50)));
1322 env(offer(
bob, XRP(50),
USD(50)));
1327 BEAST_EXPECT(expectOffers(env,
bob, 0));
1328 BEAST_EXPECT(expectLine(env,
bob,
USD(0)));
1334 testcase(
"Missing Auth");
1336 using namespace jtx;
1338 Env env{*
this, features};
1340 env.
fund(
XRP(400
'000), gw, alice, bob);
1343 // Alice doesn't have the funds
1354 env(trust(
bob,
USD(50)));
1368 env(trust(
gw,
alice[
"USD"](2
'000)));
1372 AMM ammAlice(env, alice, USD(1'000),
XRP(1
'000), ter(tecNO_AUTH));
1375 // Finally, set up an authorized trust line for Alice. Now Alice's
1379 env(pay(gw, alice, USD(1'000)));
1402 using namespace jtx;
1444 testcase(
"path find consume all");
1445 using namespace jtx;
1462 BEAST_EXPECT(st.
empty());
1470 BEAST_EXPECT(sa ==
XRP(100
'000'000));
1474 da,
STAmount{
bob[
"USD"].issue(), UINT64_C(99
'9999000001), -10}));
1477 // carol holds gateway AUD, sells gateway AUD for XRP
1478 // bob will hold gateway AUD
1479 // alice pays bob gateway AUD using XRP
1481 via_offers_via_gateway()
1483 testcase("via gateway");
1484 using namespace jtx;
1486 Env env = pathTestEnv();
1487 auto const AUD = gw["AUD"];
1490 env.
trust(AUD(2
'000), bob, carol);
1491 env(pay(gw, carol, AUD(51)));
1493 AMM ammCarol(env, carol, XRP(40), AUD(51));
1494 env(pay(alice, bob, AUD(10)), sendmax(XRP(100)), paths(XRP));
1496 // AMM offer is 51.282052XRP/11AUD, 11AUD/1.1 = 10AUD to bob
1498 ammCarol.expectBalances(XRP(51), AUD(40), ammCarol.tokens()));
1499 BEAST_EXPECT(expectLine(env, bob, AUD(10)));
1502 find_paths(env, alice, bob, Account(bob)["USD"](25));
1503 BEAST_EXPECT(std::get<0>(result).empty());
1509 testcase("Receive max");
1510 using namespace jtx;
1511 auto const charlie = Account("charlie");
1513 // XRP -> IOU receive max
1514 Env env = pathTestEnv();
1515 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1516 AMM ammCharlie(env, charlie, XRP(10), USD(11));
1518 find_paths(env, alice, bob, USD(-1), XRP(1).value());
1519 BEAST_EXPECT(sa == XRP(1));
1520 BEAST_EXPECT(equal(da, USD(1)));
1521 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1523 auto const& pathElem = st[0][0];
1525 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
1526 pathElem.getCurrency() == USD.currency);
1530 // IOU -> XRP receive max
1531 Env env = pathTestEnv();
1532 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1533 AMM ammCharlie(env, charlie, XRP(11), USD(10));
1536 find_paths(env, alice, bob, drops(-1), USD(1).value());
1537 BEAST_EXPECT(sa == USD(1));
1538 BEAST_EXPECT(equal(da, XRP(1)));
1539 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1541 auto const& pathElem = st[0][0];
1543 pathElem.isOffer() &&
1544 pathElem.getIssuerID() == xrpAccount() &&
1545 pathElem.getCurrency() == xrpCurrency());
1553 testcase("Path Find: XRP -> XRP and XRP -> IOU");
1554 using namespace jtx;
1555 Env env = pathTestEnv();
1564 env.fund(XRP(100'000), A1);
1566 env.fund(XRP(1'000), A3, G1, G2, G3);
1570 env.trust(G1["XYZ"](5'000), A1);
1571 env.
trust(G3[
"ABC"](5
'000), A1);
1572 env.trust(G2["XYZ"](5'000), A2);
1573 env.
trust(G3[
"ABC"](5
'000), A2);
1574 env.trust(A2["ABC"](1'000), A3);
1575 env.
trust(G1[
"XYZ"](100
'000), M1);
1576 env.trust(G2["XYZ"](100'000), M1);
1577 env.
trust(G3[
"ABC"](100
'000), M1);
1580 env(pay(G1, A1, G1["XYZ"](3'500)));
1581 env(pay(G3, A1, G3[
"ABC"](1
'200)));
1582 env(pay(G1, M1, G1["XYZ"](25'000)));
1583 env(pay(G2, M1, G2[
"XYZ"](25
'000)));
1584 env(pay(G3, M1, G3["ABC"](25'000)));
1587 AMM ammM1_G1_G2(env, M1, G1[
"XYZ"](1
'000), G2["XYZ"](1'000));
1588 AMM ammM1_XRP_G3(env, M1,
XRP(10
'000), G3["ABC"](1'000));
1594 auto const& send_amt =
XRP(10);
1597 BEAST_EXPECT(
equal(da, send_amt));
1598 BEAST_EXPECT(st.
empty());
1604 auto const& send_amt =
XRP(200);
1607 BEAST_EXPECT(
equal(da, send_amt));
1608 BEAST_EXPECT(st.
empty());
1612 auto const& send_amt = G3[
"ABC"](10);
1615 BEAST_EXPECT(
equal(da, send_amt));
1616 BEAST_EXPECT(
equal(sa, XRPAmount{101
'010'102}));
1621 auto const& send_amt = A2[
"ABC"](1);
1624 BEAST_EXPECT(
equal(da, send_amt));
1625 BEAST_EXPECT(
equal(sa, XRPAmount{10
'010'011}));
1630 auto const& send_amt = A3[
"ABC"](1);
1633 BEAST_EXPECT(
equal(da, send_amt));
1634 BEAST_EXPECT(
equal(sa, XRPAmount{10
'010'011}));
1642 testcase(
"Path Find: non-XRP -> XRP");
1643 using namespace jtx;
1650 env.
fund(
XRP(1
'000), A1, A2, G3);
1651 env.fund(XRP(11'000), M1);
1654 env.
trust(G3[
"ABC"](1
'000), A1, A2);
1655 env.trust(G3["ABC"](100'000), M1);
1658 env(pay(G3, A1, G3[
"ABC"](1
'000)));
1659 env(pay(G3, A2, G3["ABC"](1'000)));
1660 env(pay(G3, M1, G3[
"ABC"](1
'200)));
1663 AMM ammM1(env, M1, G3["ABC"](1'000),
XRP(10
'010));
1668 auto const& send_amt = XRP(10);
1669 std::tie(st, sa, da) =
1670 find_paths(env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
1671 BEAST_EXPECT(equal(da, send_amt));
1672 BEAST_EXPECT(equal(sa, A1["ABC"](1)));
1673 BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
1679 testcase("Path Find: non-XRP -> non-XRP, same currency");
1680 using namespace jtx;
1681 Env env = pathTestEnv();
1693 env.fund(XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1695 env.fund(XRP(21'000), M1, M2);
1698 env.
trust(G1[
"HKD"](2
'000), A1);
1699 env.trust(G2["HKD"](2'000), A2);
1700 env.
trust(G1[
"HKD"](2
'000), A3);
1701 env.trust(G1["HKD"](100'000), M1);
1702 env.
trust(G2[
"HKD"](100
'000), M1);
1703 env.trust(G1["HKD"](100'000), M2);
1704 env.
trust(G2[
"HKD"](100
'000), M2);
1707 env(pay(G1, A1, G1["HKD"](1'000)));
1708 env(pay(G2, A2, G2[
"HKD"](1
'000)));
1709 env(pay(G1, A3, G1["HKD"](1'000)));
1710 env(pay(G1, M1, G1[
"HKD"](1
'200)));
1711 env(pay(G2, M1, G2["HKD"](5'000)));
1712 env(pay(G1, M2, G1[
"HKD"](1
'200)));
1713 env(pay(G2, M2, G2["HKD"](5'000)));
1716 AMM ammM1(env, M1, G1[
"HKD"](1
'010), G2["HKD"](1'000));
1717 AMM ammM2XRP_G2(env, M2,
XRP(10
'000), G2["HKD"](1'010));
1718 AMM ammM2G1_XRP(env, M2, G1[
"HKD"](1
'010), XRP(10'000));
1726 auto const& send_amt = G1[
"HKD"](10);
1728 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1729 BEAST_EXPECT(st.
empty());
1730 BEAST_EXPECT(
equal(da, send_amt));
1731 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1737 auto const& send_amt = A1[
"HKD"](10);
1739 env, A1, G1, send_amt, std::nullopt, G1[
"HKD"].currency);
1740 BEAST_EXPECT(st.
empty());
1741 BEAST_EXPECT(
equal(da, send_amt));
1742 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1748 auto const& send_amt = A3[
"HKD"](10);
1750 env, A1, A3, send_amt, std::nullopt, G1[
"HKD"].currency);
1751 BEAST_EXPECT(
equal(da, send_amt));
1752 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1753 BEAST_EXPECT(same(st, stpath(G1)));
1759 auto const& send_amt = G2[
"HKD"](10);
1761 env, G1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1762 BEAST_EXPECT(
equal(da, send_amt));
1763 BEAST_EXPECT(
equal(sa, G1[
"HKD"](10)));
1766 stpath(IPE(G2[
"HKD"])),
1769 stpath(IPE(
xrpIssue()), IPE(G2[
"HKD"]))));
1775 auto const& send_amt = G2[
"HKD"](10);
1777 env, A1, G2, send_amt, std::nullopt, G1[
"HKD"].currency);
1778 BEAST_EXPECT(
equal(da, send_amt));
1779 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1792 auto const& send_amt = A2[
"HKD"](10);
1794 env, A1, A2, send_amt, std::nullopt, G1[
"HKD"].currency);
1795 BEAST_EXPECT(
equal(da, send_amt));
1796 BEAST_EXPECT(
equal(sa, A1[
"HKD"](10)));
1809 testcase(
"Path Find: non-XRP -> non-XRP, same currency");
1810 using namespace jtx;
1820 env.fund(XRP(1'000), A1, A2, A3, G1, G2);
1823 env.
trust(G1[
"HKD"](2
'000), A1);
1824 env.trust(G2["HKD"](2'000), A2);
1825 env.
trust(A2[
"HKD"](2
'000), A3);
1826 env.trust(G1["HKD"](100'000), M1);
1827 env.
trust(G2[
"HKD"](100
'000), M1);
1830 env(pay(G1, A1, G1["HKD"](1'000)));
1831 env(pay(G2, A2, G2[
"HKD"](1
'000)));
1832 env(pay(G1, M1, G1["HKD"](5'000)));
1833 env(pay(G2, M1, G2[
"HKD"](5
'000)));
1836 AMM ammM1(env, M1, G1["HKD"](1'010), G2[
"HKD"](1
'000));
1838 // E) Gateway to user
1839 // Source -> OB -> AC -> Destination
1840 auto const& send_amt = A2["HKD"](10);
1843 std::tie(st, sa, da) =
1844 find_paths(env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1845 BEAST_EXPECT(equal(da, send_amt));
1846 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1847 BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
1851 testFalseDry(FeatureBitset features)
1853 testcase("falseDryChanges");
1855 using namespace jtx;
1857 Env env(*this, features);
1859 env.fund(XRP(10'000),
alice,
gw);
1863 auto const AMMXRPPool = env.current()->fees().increment * 2;
1864 env.fund(reserve(env, 5) + ammCrtFee(env) + AMMXRPPool, bob);
1866 env.trust(
EUR(1
'000), alice, bob, carol);
1868 env(pay(gw, alice, EUR(50)));
1869 env(pay(gw, bob, USD(150)));
1871 // Bob has _just_ slightly less than 50 xrp available
1872 // If his owner count changes, he will have more liquidity.
1873 // This is one error case to test (when Flow is used).
1874 // Computing the incoming xrp to the XRP/USD offer will require two
1875 // recursive calls to the EUR/XRP offer. The second call will return
1876 // tecPATH_DRY, but the entire path should not be marked as dry.
1877 // This is the second error case to test (when flowV1 is used).
1878 env(offer(bob, EUR(50), XRP(50)));
1879 AMM ammBob(env, bob, AMMXRPPool, USD(150));
1881 env(pay(alice, carol, USD(1'000
'000)),
1884 txflags(tfNoRippleDirect | tfPartialPayment));
1886 auto const carolUSD = env.balance(carol, USD).value();
1887 BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50));
1891 testBookStep(FeatureBitset features)
1893 testcase("Book Step");
1895 using namespace jtx;
1898 // simple IOU/IOU offer
1899 Env env(*this, features);
1904 {alice, bob, carol},
1918 ammBob.expectBalances(
BTC(150),
USD(100), ammBob.tokens()));
1922 Env env(*
this, features);
1929 {BTC(100), USD(150)},
1932 AMM ammBobBTC_XRP(env, bob, BTC(100), XRP(150));
1933 AMM ammBobXRP_USD(env, bob, XRP(100), USD(150));
1935 env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50)));
1937 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
1938 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
1939 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1940 BEAST_EXPECT(expectLine(env, carol, USD(200)));
1941 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1942 BTC(150), XRP(100), ammBobBTC_XRP.tokens()));
1943 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
1944 XRP(150), USD(100), ammBobXRP_USD.tokens()));
1947 // simple XRP -> USD through offer and sendmax
1948 Env env(*this, features);
1953 {alice, carol, bob},
1964 BEAST_EXPECT(expectLedgerEntryRoot(
1969 ammBob.expectBalances(
XRP(150),
USD(100), ammBob.tokens()));
1973 Env env(*
this, features);
1983 AMM ammBob(env, bob, USD(100), XRP(150));
1985 env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50)));
1987 BEAST_EXPECT(expectLine(env, alice, USD(50)));
1988 BEAST_EXPECT(expectLedgerEntryRoot(
1993 ammBob.expectBalances(USD(150), XRP(100), ammBob.tokens()));
1996 // test unfunded offers are removed when payment succeeds
1997 Env env(*this, features);
2000 env.fund(
XRP(10
'000), bob);
2002 env.trust(
BTC(1
'000), alice, bob, carol);
2023 env.require(balance(
alice,
BTC(10)));
2024 env.require(balance(
bob,
BTC(50)));
2025 env.require(balance(
bob,
USD(0)));
2026 env.require(balance(
bob,
EUR(0)));
2027 env.require(balance(
carol,
USD(50)));
2034 ammBob.expectBalances(
EUR(100),
USD(150), ammBob.tokens()));
2046 Env env(*
this, features);
2048 env.fund(
XRP(10
'000), bob, carol, gw);
2049 // Sets rippling on, this is different from
2050 // the original test
2051 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2052 env.trust(
USD(1
'000), alice, bob, carol);
2054 env.trust(
EUR(1
'000), alice, bob, carol);
2056 env(pay(gw, alice, BTC(60)));
2057 env(pay(gw, bob, BTC(100)));
2058 env(pay(gw, bob, USD(100)));
2059 env(pay(gw, bob, EUR(50)));
2060 env(pay(gw, carol, EUR(1)));
2062 // This is multiplath, which generates limited # of offers
2063 AMM ammBobBTC_USD(env, bob, BTC(50), USD(50));
2064 env(offer(bob, BTC(60), EUR(50)));
2065 env(offer(carol, BTC(1'000),
EUR(1)));
2070 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2071 BTC(50),
USD(50), ammBobBTC_USD.tokens()));
2074 BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50)));
2076 auto flowJournal = env.app().logs().journal("Flow");
2077 auto const flowResult = [&] {
2078 STAmount deliver(USD(51));
2079 STAmount smax(BTC(61));
2080 PaymentSandbox sb(env.current().get(), tapNONE);
2082 auto IPE = [](Issue const& iss) {
2083 return STPathElement(
2084 STPathElement::typeCurrency | STPathElement::typeIssuer,
2091 STPath p1({IPE(USD.issue())});
2092 paths.push_back(p1);
2093 // BTC -> EUR -> USD
2094 STPath p2({IPE(EUR.issue()), IPE(USD.issue())});
2095 paths.push_back(p2);
2113 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2114 env.app().openLedger().modify(
2115 [&](OpenView& view, beast::Journal j) {
2116 if (flowResult.removableOffers.empty())
2118 Sandbox sb(&view, tapNONE);
2119 for (auto const& o : flowResult.removableOffers)
2120 if (auto ok = sb.peek(keylet::offer(o)))
2121 offerDelete(sb, ok, flowJournal);
2126 // used in payment, but since payment failed should be untouched
2127 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2128 BTC(50), USD(50), ammBobBTC_USD.tokens()));
2129 BEAST_EXPECT(isOffer(env, carol, BTC(1'000),
EUR(1)));
2142 Env env(*
this, features);
2143 env.fund(
XRP(10
'000), bob, carol, gw);
2144 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2145 env.trust(
USD(1
'000), alice, bob, carol);
2149 env(pay(gw, bob, EUR(1'000)));
2152 // env(offer(bob, USD(1), drops(2)), txflags(tfPassive));
2153 AMM ammBob(env, bob, USD(8), XRPAmount{21});
2154 env(offer(bob, drops(1), EUR(1'000)), txflags(
tfPassive));
2162 BEAST_EXPECT(ammBob.expectBalances(
2163 USD(8.4), XRPAmount{20}, ammBob.tokens()));
2170 testcase(
"Transfer Rate");
2172 using namespace jtx;
2176 Env env(*
this, features);
2191 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
2192 BEAST_EXPECT(expectLedgerEntryRoot(
2193 env, alice, xrpMinusFee(env, 10'000 - 50)));
2198 // Transfer fee AMM and offer
2199 Env env(*this, features);
2204 {alice, bob, carol},
2206 {
USD(1
'000), EUR(1'000)});
2213 env(offer(bob, USD(50), EUR(50)));
2215 // alice buys 40EUR with 40XRP
2216 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
2218 // 40XRP is swapped in for 40USD
2220 ammBob.expectBalances(XRP(140), USD(100), ammBob.tokens()));
2221 // 40USD buys 40EUR via bob's offer. 40
EUR delivered to
carol
2224 // bob gets 40USD back from the offer
2225 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 + 40)));
2228 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2234 Env env(*
this, features);
2241 {USD(1'000),
EUR(1
'000)});
2242 env(rate(gw, 1.25));
2245 AMM ammBobXRP_USD(env, bob, XRP(100), USD(140));
2246 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
2249 BEAST_EXPECT(expectLine(env,
bob,
EUR(1
'000 - 140)));
2250 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
2256 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
2257 XRP(140),
USD(100), ammBobXRP_USD.tokens()));
2259 BEAST_EXPECT(ammBobUSD_EUR.expectBalances(
2260 USD(140),
EUR(100), ammBobUSD_EUR.tokens()));
2262 BEAST_EXPECT(expectLine(env,
bob,
USD(1
'000 - 140 - 100)));
2263 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
2264 BEAST_EXPECT(expectLedgerEntryRoot(
2265 env,
alice, xrpMinusFee(env, 10
'000 - 40)));
2266 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2272 Env env(*
this, features);
2279 {USD(1'200),
GBP(1
'200)});
2280 env(rate(gw, 1.25));
2283 AMM amm(env, bob, GBP(1'000),
USD(1
'100));
2285 // requested quality limit is 90USD/110GBP = 0.8181
2286 // trade quality is 77.2727USD/94.4444GBP = 0.8181
2287 env(pay(alice, carol, USD(90)),
2290 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2293 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
2295 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
2296 BEAST_EXPECT(expectLine(
2297 env, alice, STAmount{GBP, UINT64_C(1'105
'555555555555), -12}));
2298 // 75.5555GBP is swapped in for 77.7272USD
2299 BEAST_EXPECT(amm.expectBalances(
2300 STAmount{GBP, UINT64_C(1'075
'555555555556), -12},
2301 STAmount{USD, UINT64_C(1'022
'727272727272), -12},
2303 BEAST_EXPECT(expectLine(
2304 env, carol, STAmount{USD, UINT64_C(1'277
'272727272728), -12}));
2308 // AMM offer crossing
2309 Env env(*this, features);
2311 fund(env, gw, {alice, bob}, XRP(1'000), {
USD(1
'200), EUR(1'200)});
2321 BEAST_EXPECT(
amm.expectBalances(
2322 STAmount{USD, UINT64_C(1
'095'238095238095), -12},
2325 // alice pays 25% tr fee on 95.2380USD
2326 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
2327 BEAST_EXPECT(expectLine(
2330 STAmount{USD, UINT64_C(1'080
'952380952381), -12},
2338 Env env(*
this, features);
2339 auto const USDA =
alice[
"USD"];
2340 auto const USDB =
bob[
"USD"];
2341 Account
const dan(
"dan");
2343 env.fund(
XRP(10
'000), bob, carol, dan, gw);
2344 fund(env, {alice}, XRP(10'000));
2346 env.trust(
USD(2
'000), alice, bob, carol, dan);
2347 env.trust(EUR(2'000),
carol, dan);
2348 env.trust(USDA(1
'000), bob);
2349 env.trust(USDB(1'000),
gw);
2352 env(pay(gw, dan, USD(1'000)));
2353 AMM ammDan(env, dan,
USD(1
'000), EUR(1'050));
2363 ammDan.expectBalances(
USD(1
'050), EUR(1'000), ammDan.tokens()));
2375 testcase(
"No Owner Fee");
2376 using namespace jtx;
2380 Env env(*
this, features);
2387 {USD(1'000),
GBP(1
'000)});
2388 env(rate(gw, 1.25));
2391 AMM amm(env, bob, GBP(1'000),
USD(1
'000));
2393 env(pay(alice, carol, USD(100)),
2396 txflags(tfNoRippleDirect | tfPartialPayment));
2399 // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
2400 // 1,000 - 120*1.25 = 850GBP
2401 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2402 // 120GBP is swapped in for 107.1428USD
2403 BEAST_EXPECT(amm.expectBalances(
2407 // 25% of 85.7142USD is paid in tr fee
2408 // 85.7142*1.25 = 107.1428USD
2409 BEAST_EXPECT(expectLine(
2410 env, carol, STAmount(USD, UINT64_C(1'085
'714285714286), -12)));
2414 // Payment via offer and AMM
2415 Env env(*this, features);
2416 Account const ed("ed");
2421 {alice, bob, carol, ed},
2423 {
USD(1
'000), EUR(1'000),
GBP(1
'000)});
2424 env(rate(gw, 1.25));
2427 env(offer(ed, GBP(1'000),
EUR(1
'000)), txflags(tfPassive));
2430 AMM amm(env, bob, EUR(1'000),
USD(1
'000));
2432 env(pay(alice, carol, USD(100)),
2435 txflags(tfNoRippleDirect | tfPartialPayment));
2438 // alice buys 120EUR with 120GBP via the offer
2439 // and pays 25% tr fee on 120GBP
2440 // 1,000 - 120*1.25 = 850GBP
2441 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2442 // consumed offer is 120GBP/120EUR
2443 // ed doesn't pay tr
fee
2446 expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}}));
2447 // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR
2448 // 96EUR is swapped in for 87.5912USD
2449 BEAST_EXPECT(amm.expectBalances(
2453 // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD
2454 BEAST_EXPECT(expectLine(
2455 env, carol, STAmount(USD, UINT64_C(1'070
'07299270073), -11)));
2458 // Payment via AMM, AMM
2459 Env env(*this, features);
2460 Account const ed("ed");
2465 {alice, bob, carol, ed},
2467 {
USD(1
'000), EUR(1'000),
GBP(1
'000)});
2468 env(rate(gw, 1.25));
2471 AMM amm1(env, bob, GBP(1'000),
EUR(1
'000));
2472 AMM amm2(env, ed, EUR(1'000),
USD(1
'000));
2474 env(pay(alice, carol, USD(100)),
2477 txflags(tfNoRippleDirect | tfPartialPayment));
2480 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on 120GBP
2481 // 1,000 - 120*1.25 = 850GBP
2482 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2483 // 120GBP is swapped in for 107.1428EUR
2484 BEAST_EXPECT(amm1.expectBalances(
2488 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 = 107.1428EUR
2489 // 85.7142EUR is swapped in for 78.9473USD
2490 BEAST_EXPECT(amm2.expectBalances(
2491 STAmount(EUR, UINT64_C(1'085
'714285714286), -12),
2492 STAmount{USD, UINT64_C(921'0526315789471), -13},
2500 Env env(*
this, features);
2503 env(rate(gw, 1.25));
2506 AMM amm(env, bob, USD(1'000),
EUR(1
'100));
2507 env(offer(alice, EUR(100), USD(100)));
2510 // 100USD is swapped in for 100EUR
2512 amm.expectBalances(USD(1'100),
EUR(1
'000), amm.tokens()));
2513 // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD
2514 BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200)));
2520 Env env(*
this, features);
2527 {USD(1'000),
GBP(1
'000)});
2528 env(rate(gw, 1.25));
2531 AMM amm(env, bob, GBP(1'000),
USD(1
'000));
2533 // requested quality limit is 100USD/178.58GBP = 0.55997
2534 // trade quality is 100USD/178.5714 = 0.55999
2535 env(pay(alice, carol, USD(100)),
2537 sendmax(GBP(178.58)),
2538 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2541 // alice buys 125USD with 142.8571GBP and pays 25% tr fee
2543 // 1,000 - 142.8571*1.25 = 821.4285GBP
2544 BEAST_EXPECT(expectLine(
2545 env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13)));
2547 BEAST_EXPECT(amm.expectBalances(
2548 STAmount{GBP, UINT64_C(1
'142'857142857143), -12},
2556 // Payment via AMM with limit quality, deliver less
2558 Env env(*this, features);
2563 {alice, bob, carol},
2565 {
USD(1
'200), GBP(1'200)});
2569 AMM amm(env,
bob,
GBP(1
'000), USD(1'200));
2583 // 24GBP is swapped in for 28.125USD
2585 amm.expectBalances(GBP(1'024),
USD(1
'171.875), amm.tokens()));
2586 // 25% on 22.5USD is paid in tr fee
2587 // 22.5*1.25 = 28.125USD
2588 BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
2593 Env env(*
this, features);
2601 {USD(1'400),
EUR(1
'400), GBP(1'400)});
2608 AMM amm(env,
bob,
EUR(1
'000), USD(1'400));
2629 STAmount{
GBP, UINT64_C(1
'470'421052631579), -12}));
2636 STAmount{EUR, UINT64_C(929'5789473684212), -13}}}));
2639 BEAST_EXPECT(amm.expectBalances(
2640 STAmount{EUR, UINT64_C(1
'056'336842105263), -12},
2641 STAmount{USD, UINT64_C(1
'325'334821428571), -12},
2650 Env env(*
this, features);
2658 {USD(1'400),
EUR(1
'400), GBP(1'400)});
2662 AMM amm(env,
bob,
GBP(1
'000), EUR(1'000));
2682 BEAST_EXPECT(amm.expectBalances(
2683 STAmount{GBP, UINT64_C(1
'056'336842105263), -12},
2684 STAmount{EUR, UINT64_C(946
'6677295918366), -13},
2686 // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
2687 // 42.6658EUR/59.7321USD
2688 BEAST_EXPECT(expectLine(
2691 STAmount{USD, UINT64_C(1'340
'267857142857), -12},
2692 STAmount{EUR, UINT64_C(1'442
'665816326531), -12}));
2693 BEAST_EXPECT(expectOffers(
2698 STAmount{EUR, UINT64_C(957'3341836734693), -13},
2699 STAmount{USD, UINT64_C(1
'340'267857142857), -12}}}));
2707 Env env(*
this, features);
2715 {USD(1'400),
EUR(1
'400), GBP(1'400)});
2719 AMM amm1(env,
bob,
GBP(1
'000), EUR(1'000));
2720 AMM amm2(env, ed,
EUR(1
'000), USD(1'400));
2737 STAmount{GBP, UINT64_C(1
'086'024691358025), -12},
2738 STAmount{EUR, UINT64_C(920
'78937795562), -11},
2740 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2741 // 63.3684EUR is swapped in for 83.4291USD
2742 BEAST_EXPECT(amm2.expectBalances(
2743 STAmount{EUR, UINT64_C(1'063
'368497635504), -12},
2744 STAmount{USD, UINT64_C(1'316
'570881226053), -12},
2746 // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
2747 BEAST_EXPECT(expectLine(
2748 env, carol, STAmount(USD, UINT64_C(1'466
'743295019157), -12)));
2751 // Payment by the issuer via AMM, AMM with limit quality,
2752 // deliver less than requested
2753 Env env(*this, features);
2758 {alice, bob, carol},
2760 {USD(1
'400), EUR(1'400), GBP(1
'400)});
2761 env(rate(gw, 1.25));
2764 AMM amm1(env, alice, GBP(1'000), EUR(1
'000));
2765 AMM amm2(env, bob, EUR(1'000), USD(1
'400));
2767 // requested quality limit is 90USD/120GBP = 0.75
2768 // trade quality is 81.1111USD/108.1481GBP = 0.75
2769 env(pay(gw, carol, USD(90)),
2772 txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2775 // 108.1481GBP is swapped in for 97.5935EUR
2776 BEAST_EXPECT(amm1.expectBalances(
2777 STAmount{GBP, UINT64_C(1'108
'148148148149), -12},
2778 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2783 STAmount{EUR, UINT64_C(1
'078'074866310161), -12},
2784 STAmount{USD, UINT64_C(1
'298'611111111111), -12},
2799 testcase(
"limitQuality");
2800 using namespace jtx;
2807 AMM ammBob(env,
bob,
XRP(1
'000), USD(1'050));
2816 ammBob.expectBalances(
XRP(1
'050), USD(1'000), ammBob.tokens()));
2818 BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}}));
2825 testcase("Circular XRP");
2827 using namespace jtx;
2829 for (auto const withFix : {true, false})
2831 auto const feats = withFix
2832 ? supported_amendments()
2833 : supported_amendments() - FeatureBitset{fix1781};
2835 // Payment path starting with XRP
2836 Env env(*this, feats);
2837 // Note, if alice doesn't have
default ripple, then pay
2844 {USD(200), EUR(200)},
2847 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(101));
2848 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(101));
2851 TER const expectedTer =
2852 withFix ? TER{temBAD_PATH_LOOP} : TER{tesSUCCESS};
2853 env(pay(alice, bob, EUR(1)),
2854 path(~USD, ~XRP, ~EUR),
2856 txflags(tfNoRippleDirect),
2860 // Payment path ending with XRP
2862 // Note, if alice doesn't have
default ripple, then pay fails
2869 {USD(200), EUR(200)},
2872 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
2873 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
2874 // EUR -> //XRP -> //USD ->XRP
2875 env(pay(alice, bob, XRP(1)),
2876 path(~XRP, ~USD, ~XRP),
2878 txflags(tfNoRippleDirect),
2879 ter(temBAD_PATH_LOOP));
2882 // Payment where loop is formed in the middle of the path, not
2884 auto const JPY = gw["JPY"];
2886 // Note, if alice doesn't have
default ripple, then pay fails
2893 {USD(200), EUR(200), JPY(200)},
2896 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
2897 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
2898 AMM ammAliceXRP_JPY(env, alice, XRP(100), JPY(100));
2900 env(pay(alice, bob, JPY(1)),
2901 path(~XRP, ~EUR, ~XRP, ~JPY),
2903 txflags(tfNoRippleDirect),
2904 ter(temBAD_PATH_LOOP));
2909 testStepLimit(FeatureBitset features)
2911 testcase("Step Limit");
2913 using namespace jtx;
2914 Env env(*this, features);
2915 auto const dan = Account("dan");
2916 auto const ed = Account("ed");
2918 fund(env, gw, {ed}, XRP(100'000
'000), {USD(11)});
2919 env.fund(XRP(100'000
'000), alice, bob, carol, dan);
2920 env.trust(USD(1), bob);
2921 env(pay(gw, bob, USD(1)));
2922 env.trust(USD(1), dan);
2923 env(pay(gw, dan, USD(1)));
2924 n_offers(env, 2'000,
bob,
XRP(1),
USD(1));
2930 env(offer(
alice,
USD(1
'000), XRP(1'000)));
2933 env.require(owners(alice, 2));
2934 env.require(balance(bob, USD(0)));
2935 env.require(owners(bob, 1'001));
2937 env.require(
owners(dan, 2));
2941 env(offer(
carol,
USD(1
'000), XRP(1'000)));
2947 env.require(
owners(dan, 2));
2953 testcase(
"Convert all of an asset using DeliverMin");
2955 using namespace jtx;
2958 Env env(*
this, features);
2960 env.trust(USD(100), alice, bob, carol);
2961 env(pay(alice, bob, USD(10)),
2962 delivermin(USD(10)),
2963 ter(temBAD_AMOUNT));
2964 env(pay(alice, bob, USD(10)),
2965 delivermin(USD(-5)),
2966 txflags(tfPartialPayment),
2967 ter(temBAD_AMOUNT));
2968 env(pay(alice, bob, USD(10)),
2970 txflags(tfPartialPayment),
2971 ter(temBAD_AMOUNT));
2972 env(pay(alice, bob, USD(10)),
2973 delivermin(Account(carol)["USD"](5)),
2974 txflags(tfPartialPayment),
2975 ter(temBAD_AMOUNT));
2976 env(pay(alice, bob, USD(10)),
2977 delivermin(USD(15)),
2978 txflags(tfPartialPayment),
2979 ter(temBAD_AMOUNT));
2980 env(pay(gw, carol, USD(50)));
2981 AMM ammCarol(env, carol, XRP(10), USD(15));
2982 env(pay(alice, bob, USD(10)),
2985 txflags(tfPartialPayment),
2987 ter(tecPATH_PARTIAL));
2988 env.require(balance(alice, XRP(9'999.99999)));
2993 Env env(*this, features);
2994 fund(env, gw, {alice, bob}, XRP(10'000));
2996 env(pay(gw, bob, USD(1'100)));
2997 AMM ammBob(env,
bob,
XRP(1
'000), USD(1'100));
3000 delivermin(USD(100)),
3001 txflags(tfPartialPayment),
3003 env.require(balance(alice, USD(100)));
3007 Env env(*this, features);
3008 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3010 env(pay(gw, bob, USD(1'200)));
3011 AMM ammBob(env,
bob,
XRP(5
'500), USD(1'200));
3014 delivermin(USD(200)),
3015 txflags(tfPartialPayment),
3016 sendmax(XRP(1'000)),
3020 delivermin(USD(200)),
3021 txflags(tfPartialPayment),
3022 sendmax(XRP(1'100)));
3029 auto const dan =
Account(
"dan");
3030 Env env(*
this, features);
3032 env.trust(USD(1'100),
bob,
carol, dan);
3034 env(pay(
gw, dan,
USD(1
'100)));
3035 env(offer(bob, XRP(100), USD(100)));
3036 env(offer(bob, XRP(1'000),
USD(100)));
3037 AMM ammDan(env, dan,
XRP(1
'000), USD(1'100));
3040 delivermin(USD(200)),
3041 txflags(tfPartialPayment),
3043 env.require(balance(bob, USD(0)));
3044 env.require(balance(carol, USD(200)));
3046 ammDan.expectBalances(XRP(1'100),
USD(1
'000), ammDan.tokens()));
3051 testPayment(FeatureBitset features)
3053 testcase("Payment");
3055 using namespace jtx;
3056 Account const becky{"becky"};
3058 bool const supportsPreauth = {features[featureDepositPreauth]};
3060 // The initial implementation of DepositAuth had a bug where an
3061 // account with the DepositAuth flag set could not make a payment
3062 // to itself. That bug was fixed in the DepositPreauth amendment.
3063 Env env(*this, features);
3064 fund(env, gw, {alice, becky}, XRP(5'000));
3067 env.trust(
USD(1
'000), alice);
3068 env.trust(USD(1'000), becky);
3092 env(pay(becky, becky,
USD(10)),
3105 testcase(
"Pay IOU");
3107 using namespace jtx;
3128 auto failedIouPayments = [
this, &env]() {
3152 failedIouPayments();
3162 env(pay(
bob,
alice, bobPaysXRP),
fee(bobPaysFee));
3169 failedIouPayments();
3176 failedIouPayments();
3197 testcase(
"RippleState Freeze");
3199 using namespace test::jtx;
3200 Env env(*
this, features);
3206 env.
fund(
XRP(1
'000), G1, alice, bob);
3209 env.trust(G1["USD"](100), bob);
3210 env.trust(G1["USD"](205), alice);
3213 env(pay(G1, bob, G1["USD"](10)));
3214 env(pay(G1, alice, G1["USD"](205)));
3217 AMM ammAlice(env, alice, XRP(500), G1["USD"](105));
3220 auto lines = getAccountLines(env, bob);
3221 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3223 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3224 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100");
3225 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10");
3229 auto lines = getAccountLines(env, alice, G1["USD"]);
3230 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3232 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3233 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205");
3234 // 105 transferred to AMM
3235 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100");
3239 // Account with line unfrozen (proving operations normally work)
3240 // test: can make Payment on that line
3241 env(pay(alice, bob, G1["USD"](1)));
3243 // test: can receive Payment on that line
3244 env(pay(bob, alice, G1["USD"](1)));
3249 // Is created via a TrustSet with SetFreeze flag
3250 // test: sets LowFreeze | HighFreeze flags
3251 env(trust(G1, bob["USD"](0), tfSetFreeze));
3252 auto affected = env.meta()->getJson(
3253 JsonOptions::none)[sfAffectedNodes.fieldName];
3254 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3257 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3259 ff[sfLowLimit.fieldName] ==
3260 G1["USD"](0).value().getJson(JsonOptions::none));
3261 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze);
3262 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3267 // Account with line frozen by issuer
3268 // test: can buy more assets on that line
3269 env(offer(bob, G1["USD"](5), XRP(25)));
3270 auto affected = env.meta()->getJson(
3271 JsonOptions::none)[sfAffectedNodes.fieldName];
3272 if (!BEAST_EXPECT(checkArraySize(affected, 4u)))
3275 affected[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3277 ff[sfHighLimit.fieldName] ==
3278 bob["USD"](100).value().getJson(JsonOptions::none));
3279 auto amt = STAmount{Issue{to_currency("USD"), noAccount()}, -15}
3281 .getJson(JsonOptions::none);
3282 BEAST_EXPECT(ff[sfBalance.fieldName] == amt);
3284 BEAST_EXPECT(ammAlice.expectBalances(
3285 XRP(525), G1["USD"](100), ammAlice.tokens()));
3289 // test: can not sell assets from that line
3290 env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER));
3292 // test: can receive Payment on that line
3293 env(pay(alice, bob, G1["USD"](1)));
3295 // test: can not make Payment from that line
3296 env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY));
3300 // check G1 account lines
3301 // test: shows freeze
3302 auto lines = getAccountLines(env, G1);
3303 Json::Value bobLine;
3304 for (auto const& it : lines[jss::lines])
3306 if (it[jss::account] == bob.human())
3312 if (!BEAST_EXPECT(bobLine))
3314 BEAST_EXPECT(bobLine[jss::freeze] == true);
3315 BEAST_EXPECT(bobLine[jss::balance] == "-16");
3319 // test: shows freeze peer
3320 auto lines = getAccountLines(env, bob);
3322 for (auto const& it : lines[jss::lines])
3324 if (it[jss::account] == G1.human())
3330 if (!BEAST_EXPECT(g1Line))
3332 BEAST_EXPECT(g1Line[jss::freeze_peer] == true);
3333 BEAST_EXPECT(g1Line[jss::balance] == "16");
3337 // Is cleared via a TrustSet with ClearFreeze flag
3338 // test: sets LowFreeze | HighFreeze flags
3339 env(trust(G1, bob["USD"](0), tfClearFreeze));
3340 auto affected = env.meta()->getJson(
3341 JsonOptions::none)[sfAffectedNodes.fieldName];
3342 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3345 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3347 ff[sfLowLimit.fieldName] ==
3348 G1["USD"](0).value().getJson(JsonOptions::none));
3349 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3350 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3356 testGlobalFreeze(FeatureBitset features)
3358 testcase("Global Freeze");
3360 using namespace test::jtx;
3361 Env env(*this, features);
3369 env.fund(XRP(12'000), G1);
3371 env.fund(XRP(20'000), A2, A3, A4);
3374 env.
trust(G1[
"USD"](1
'200), A1);
3375 env.trust(G1["USD"](200), A2);
3376 env.trust(G1["BTC"](100), A3);
3377 env.trust(G1["BTC"](100), A4);
3380 env(pay(G1, A1, G1["USD"](1'000)));
3381 env(pay(G1, A2, G1[
"USD"](100)));
3382 env(pay(G1, A3, G1[
"BTC"](100)));
3383 env(pay(G1, A4, G1[
"BTC"](100)));
3386 AMM ammG1(env, G1,
XRP(10
'000), G1["USD"](100));
3388 env(offer(A2, G1[
"USD"](100),
XRP(10
'000)), txflags(tfPassive));
3392 // Account without GlobalFreeze (proving operations normally
3394 // test: visible offers where taker_pays is unfrozen issuer
3395 auto offers = env.rpc(
3397 std::string("USD/") + G1.human(),
3398 "XRP")[jss::result][jss::offers];
3399 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3401 std::set<std::string> accounts;
3402 for (auto const& offer : offers)
3404 accounts.insert(offer[jss::Account].asString());
3406 BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts));
3408 // test: visible offers where taker_gets is unfrozen issuer
3412 std::string("USD/") + G1.human())[jss::result][jss::offers];
3413 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3416 for (auto const& offer : offers)
3418 accounts.insert(offer[jss::Account].asString());
3420 BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts));
3425 // test: assets can be bought on the market
3426 // env(offer(A3, G1["BTC"](1), XRP(1)));
3427 AMM ammA3(env, A3, G1["BTC"](1), XRP(1));
3429 // test: assets can be sold on the market
3430 // AMM is bidirectional
3432 // test: direct issues can be sent
3433 env(pay(G1, A2, G1["USD"](1)));
3435 // test: direct redemptions can be sent
3436 env(pay(A2, G1, G1["USD"](1)));
3438 // test: via rippling can be sent
3439 env(pay(A2, A1, G1["USD"](1)));
3441 // test: via rippling can be sent back
3442 env(pay(A1, A2, G1["USD"](1)));
3443 ammA3.withdrawAll(std::nullopt);
3447 // Account with GlobalFreeze
3448 // set GlobalFreeze first
3449 // test: SetFlag GlobalFreeze will toggle back to freeze
3450 env.require(nflags(G1, asfGlobalFreeze));
3451 env(fset(G1, asfGlobalFreeze));
3452 env.require(flags(G1, asfGlobalFreeze));
3453 env.require(nflags(G1, asfNoFreeze));
3455 // test: assets can't be bought on the market
3468 std::string(
"USD/") + G1.human())[jss::result][jss::offers];
3475 "XRP")[jss::result][jss::offers];
3483 env(pay(G1, A2, G1[
"USD"](1)));
3486 env(pay(A2, G1, G1[
"USD"](1)));
3496 testcase(
"Offers for Frozen Trust Lines");
3498 using namespace test::jtx;
3499 Env env(*
this, features);
3506 env.
fund(
XRP(2
'000), G1, A3, A4);
3507 env.fund(XRP(2'000), A2);
3510 env.
trust(G1[
"USD"](1
'000), A2);
3511 env.trust(G1["USD"](2'000), A3);
3512 env.
trust(G1[
"USD"](2
'001), A4);
3515 env(pay(G1, A3, G1["USD"](2'000)));
3516 env(pay(G1, A4, G1[
"USD"](2
'001)));
3519 AMM ammA3(env, A3, XRP(1'000), G1[
"USD"](1
'001));
3521 // removal after successful payment
3522 // test: make a payment with partially consuming offer
3523 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3527 ammA3.expectBalances(XRP(1'001), G1[
"USD"](1
'000), ammA3.tokens()));
3529 // test: someone else creates an offer providing liquidity
3530 env(offer(A4, XRP(999), G1["USD"](999)));
3532 // The offer consumes AMM offer
3534 ammA3.expectBalances(XRP(1'000), G1[
"USD"](1
'001), ammA3.tokens()));
3536 // test: AMM line is frozen
3538 STAmount{Issue{to_currency("USD"), ammA3.ammAccount()}, 0};
3539 env(trust(G1, a3am, tfSetFreeze));
3540 auto const info = ammA3.ammRpcInfo();
3541 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3543 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
3544 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3547 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3549 ff[sfHighLimit.fieldName] ==
3550 G1["USD"](0).value().getJson(JsonOptions::none));
3551 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3552 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfHighFreeze);
3555 // test: Can make a payment via the new offer
3556 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3558 // AMM is not consumed
3560 ammA3.expectBalances(XRP(1'000), G1[
"USD"](1
'001), ammA3.tokens()));
3562 // removal buy successful OfferCreate
3563 // test: freeze the new offer
3564 env(trust(G1, A4["USD"](0), tfSetFreeze));
3566 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
3567 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3569 ff = affected[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3571 ff[sfLowLimit.fieldName] ==
3572 G1["USD"](0).value().getJson(JsonOptions::none));
3573 BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze);
3574 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3577 // test: can no longer create a crossing offer
3578 env(offer(A2, G1["USD"](999), XRP(999)));
3580 env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName];
3581 if (!BEAST_EXPECT(checkArraySize(affected, 8u)))
3583 auto created = affected[0u][sfCreatedNode.fieldName];
3585 created[sfNewFields.fieldName][jss::Account] == A2.human());
3588 // test: offer was removed by offer_create
3589 auto offers = getAccountOffers(env, A4)[jss::offers];
3590 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
3595 testTxMultisign(FeatureBitset features)
3597 testcase("Multisign AMM Transactions");
3599 using namespace jtx;
3600 Env env{*this, features};
3601 Account const bogie{"bogie", KeyType::secp256k1};
3602 Account const alice{"alice", KeyType::secp256k1};
3603 Account const becky{"becky", KeyType::ed25519};
3604 Account const zelda{"zelda", KeyType::secp256k1};
3605 fund(env, gw, {alice, becky, zelda}, XRP(20'000), {
USD(20
'000)});
3607 // alice uses a regular key with the master disabled.
3608 Account const alie{"alie", KeyType::secp256k1};
3609 env(regkey(alice, alie));
3610 env(fset(alice, asfDisableMaster), sig(alice));
3612 // Attach signers to alice.
3613 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
3615 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3616 env.require(owners(alice, signerListOwners + 0));
3618 // Multisign all AMM transactions
3626 ammCrtFee(env).drops(),
3631 BEAST_EXPECT(ammAlice.expectBalances(
3632 XRP(10'000),
USD(10
'000), ammAlice.tokens()));
3634 ammAlice.deposit(alice, 1'000
'000);
3635 BEAST_EXPECT(ammAlice.expectBalances(
3636 XRP(11'000),
USD(11
'000), IOUAmount{11'000
'000, 0}));
3638 ammAlice.withdraw(alice, 1'000
'000);
3639 BEAST_EXPECT(ammAlice.expectBalances(
3640 XRP(10'000),
USD(10
'000), ammAlice.tokens()));
3642 ammAlice.vote({}, 1'000);
3645 ammAlice.bid(alice, 100);
3646 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
3655 testcase(
"To Strand");
3657 using namespace jtx;
3661 Env env(*
this, features);
3668 {USD(2'000),
EUR(1
'000)});
3670 AMM bobXRP_USD(env, bob, XRP(1'000),
USD(1
'000));
3671 AMM bobUSD_EUR(env, bob, USD(1'000),
EUR(1
'000));
3673 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
3674 env(pay(alice, carol, USD(100)),
3675 path(~USD, ~EUR, ~USD),
3677 txflags(tfNoRippleDirect),
3678 ter(temBAD_PATH_LOOP));
3682 testRIPD1373(FeatureBitset features)
3684 using namespace jtx;
3685 testcase("RIPD1373");
3688 Env env(*this, features);
3689 auto const BobUSD = bob["USD"];
3690 auto const BobEUR = bob["EUR"];
3691 fund(env, gw, {alice, bob}, XRP(10'000));
3698 {BobUSD(100), BobEUR(100)},
3701 AMM ammBobXRP_USD(env,
bob, XRP(100), BobUSD(100));
3704 AMM ammBobUSD_EUR(env,
bob, BobUSD(100), BobEUR(100));
3707 Path
const p = [&] {
3709 result.push_back(allpe(
gw, BobUSD));
3724 Env env(*
this, features);
3728 AMM ammBob(env, bob, XRP(100), USD(100));
3730 // payment path: XRP -> XRP/USD -> USD/XRP
3731 env(pay(alice, carol, XRP(100)),
3733 txflags(tfNoRippleDirect),
3734 ter(temBAD_SEND_XRP_PATHS));
3738 Env env(*this, features);
3740 fund(env, gw, {alice, bob, carol}, XRP(10'000), {
USD(100)});
3756 testcase(
"test loop");
3757 using namespace jtx;
3759 auto const CNY =
gw[
"CNY"];
3762 Env env(*
this, features);
3764 env.
fund(
XRP(10
'000), alice, bob, carol, gw);
3781 Env env(*
this, features);
3783 env.
fund(
XRP(10
'000), alice, bob, carol, gw);
3785 env.
trust(
EUR(10
'000), alice, bob, carol);
3790 env(pay(
gw,
bob, CNY(100)));
3794 AMM ammBobEUR_CNY(env,
bob,
EUR(100), CNY(100));
3820 using namespace jtx;
3836 using namespace jtx;
3844 using namespace jtx;
3861 using namespace test::jtx;
3871 using namespace jtx;
3883 using namespace jtx;