rippled
Loading...
Searching...
No Matches
AMMExtended_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/AMM.h>
22#include <test/jtx/AMMTest.h>
23#include <test/jtx/PathSet.h>
24#include <test/jtx/amount.h>
25#include <test/jtx/sendmax.h>
26
27#include <xrpld/app/misc/AMMUtils.h>
28#include <xrpld/app/paths/AMMContext.h>
29#include <xrpld/app/paths/AMMOffer.h>
30#include <xrpld/app/paths/Flow.h>
31#include <xrpld/app/paths/detail/StrandFlow.h>
32#include <xrpld/ledger/PaymentSandbox.h>
33
34#include <xrpl/protocol/Feature.h>
35#include <xrpl/protocol/STParsedJSON.h>
36
37#include <utility>
38#include <vector>
39
40namespace ripple {
41namespace test {
42
47{
48private:
49 void
51 {
52 testcase("Incorrect Removal of Funded Offers");
53
54 // We need at least two paths. One at good quality and one at bad
55 // quality. The bad quality path needs two offer books in a row.
56 // Each offer book should have two offers at the same quality, the
57 // offers should be completely consumed, and the payment should
58 // require both offers to be satisfied. The first offer must
59 // be "taker gets" XRP. Ensure that the payment engine does not remove
60 // the first "taker gets" xrp offer, because the offer is still
61 // funded and not used for the payment.
62
63 using namespace jtx;
64 Env env{*this, features};
65
66 fund(
67 env,
68 gw,
69 {alice, bob, carol},
70 XRP(10'000),
71 {USD(200'000), BTC(2'000)});
72
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)));
78
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)));
83
84 // Good quality path
85 AMM ammCarol(env, carol, BTC(1'000), USD(100'100));
86
88
89 env(pay(alice, bob, USD(100)),
90 json(paths.json()),
91 sendmax(BTC(1'000)),
93
94 if (!features[fixAMMv1_1])
95 {
96 BEAST_EXPECT(ammCarol.expectBalances(
97 STAmount{BTC, UINT64_C(1'001'000000374812), -12},
98 USD(100'000),
99 ammCarol.tokens()));
100 }
101 else
102 {
103 BEAST_EXPECT(ammCarol.expectBalances(
104 STAmount{BTC, UINT64_C(1'001'000000374815), -12},
105 USD(100'000),
106 ammCarol.tokens()));
107 }
108
109 env.require(balance(bob, USD(200'100)));
110 BEAST_EXPECT(isOffer(env, carol, BTC(49), XRP(49)));
111 }
112
113 void
115 {
116 testcase("Enforce No Ripple");
117 using namespace jtx;
118
119 {
120 // No ripple with an implied account step after AMM
121 Env env{*this, features};
122
123 Account const dan("dan");
124 Account const gw1("gw1");
125 Account const gw2("gw2");
126 auto const USD1 = gw1["USD"];
127 auto const USD2 = gw2["USD"];
128
129 env.fund(XRP(20'000), alice, noripple(bob), carol, dan, gw1, gw2);
130 env.close();
131 env.trust(USD1(20'000), alice, carol, dan);
132 env(trust(bob, USD1(1'000), tfSetNoRipple));
133 env.trust(USD2(1'000), alice, carol, dan);
134 env(trust(bob, USD2(1'000), tfSetNoRipple));
135 env.close();
136
137 env(pay(gw1, dan, USD1(10'000)));
138 env(pay(gw1, bob, USD1(50)));
139 env(pay(gw2, bob, USD2(50)));
140 env.close();
141
142 AMM ammDan(env, dan, XRP(10'000), USD1(10'000));
143
144 env(pay(alice, carol, USD2(50)),
145 path(~USD1, bob),
146 sendmax(XRP(50)),
149 }
150
151 {
152 // Make sure payment works with default flags
153 Env env{*this, features};
154
155 Account const dan("dan");
156 Account const gw1("gw1");
157 Account const gw2("gw2");
158 auto const USD1 = gw1["USD"];
159 auto const USD2 = gw2["USD"];
160
161 env.fund(XRP(20'000), alice, bob, carol, gw1, gw2);
162 env.fund(XRP(20'000), dan);
163 env.close();
164 env.trust(USD1(20'000), alice, bob, carol, dan);
165 env.trust(USD2(1'000), alice, bob, carol, dan);
166 env.close();
167
168 env(pay(gw1, dan, USD1(10'050)));
169 env(pay(gw1, bob, USD1(50)));
170 env(pay(gw2, bob, USD2(50)));
171 env.close();
172
173 AMM ammDan(env, dan, XRP(10'000), USD1(10'050));
174
175 env(pay(alice, carol, USD2(50)),
176 path(~USD1, bob),
177 sendmax(XRP(50)),
179 BEAST_EXPECT(ammDan.expectBalances(
180 XRP(10'050), USD1(10'000), ammDan.tokens()));
181
182 BEAST_EXPECT(expectLedgerEntryRoot(
183 env, alice, XRP(20'000) - XRP(50) - txfee(env, 1)));
184 BEAST_EXPECT(expectLine(env, bob, USD1(100)));
185 BEAST_EXPECT(expectLine(env, bob, USD2(0)));
186 BEAST_EXPECT(expectLine(env, carol, USD2(50)));
187 }
188 }
189
190 void
192 {
193 testcase("Fill Modes");
194 using namespace jtx;
195
196 auto const startBalance = XRP(1'000'000);
197
198 // Fill or Kill - unless we fully cross, just charge a fee and don't
199 // place the offer on the books. But also clean up expired offers
200 // that are discovered along the way.
201 //
202 // fix1578 changes the return code. Verify expected behavior
203 // without and with fix1578.
204 for (auto const& tweakedFeatures :
205 {features - fix1578, features | fix1578})
206 {
207 testAMM(
208 [&](AMM& ammAlice, Env& env) {
209 // Order that can't be filled
210 TER const killedCode{
211 tweakedFeatures[fix1578] ? TER{tecKILLED}
212 : TER{tesSUCCESS}};
213 env(offer(carol, USD(100), XRP(100)),
215 ter(killedCode));
216 env.close();
217 BEAST_EXPECT(ammAlice.expectBalances(
218 XRP(10'100), USD(10'000), ammAlice.tokens()));
219 // fee = AMM
220 BEAST_EXPECT(expectLedgerEntryRoot(
221 env, carol, XRP(30'000) - (txfee(env, 1))));
222 BEAST_EXPECT(expectOffers(env, carol, 0));
223 BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
224
225 // Order that can be filled
226 env(offer(carol, XRP(100), USD(100)),
228 ter(tesSUCCESS));
229 BEAST_EXPECT(ammAlice.expectBalances(
230 XRP(10'000), USD(10'100), ammAlice.tokens()));
231 BEAST_EXPECT(expectLedgerEntryRoot(
232 env, carol, XRP(30'000) + XRP(100) - txfee(env, 2)));
233 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
234 BEAST_EXPECT(expectOffers(env, carol, 0));
235 },
236 {{XRP(10'100), USD(10'000)}},
237 0,
238 std::nullopt,
239 {tweakedFeatures});
240
241 // Immediate or Cancel - cross as much as possible
242 // and add nothing on the books.
243 testAMM(
244 [&](AMM& ammAlice, Env& env) {
245 env(offer(carol, XRP(200), USD(200)),
247 ter(tesSUCCESS));
248
249 // AMM generates a synthetic offer of 100USD/100XRP
250 // to match the CLOB offer quality.
251 BEAST_EXPECT(ammAlice.expectBalances(
252 XRP(10'000), USD(10'100), ammAlice.tokens()));
253 // +AMM - offer * fee
254 BEAST_EXPECT(expectLedgerEntryRoot(
255 env, carol, XRP(30'000) + XRP(100) - txfee(env, 1)));
256 // AMM
257 BEAST_EXPECT(expectLine(env, carol, USD(29'900)));
258 BEAST_EXPECT(expectOffers(env, carol, 0));
259 },
260 {{XRP(10'100), USD(10'000)}},
261 0,
262 std::nullopt,
263 {tweakedFeatures});
264
265 // tfPassive -- place the offer without crossing it.
266 testAMM(
267 [&](AMM& ammAlice, Env& env) {
268 // Carol creates a passive offer that could cross AMM.
269 // Carol's offer should stay in the ledger.
270 env(offer(carol, XRP(100), USD(100), tfPassive));
271 env.close();
272 BEAST_EXPECT(ammAlice.expectBalances(
273 XRP(10'100), STAmount{USD, 10'000}, ammAlice.tokens()));
274 BEAST_EXPECT(expectOffers(
275 env, carol, 1, {{{XRP(100), STAmount{USD, 100}}}}));
276 },
277 {{XRP(10'100), USD(10'000)}},
278 0,
279 std::nullopt,
280 {tweakedFeatures});
281
282 // tfPassive -- cross only offers of better quality.
283 testAMM(
284 [&](AMM& ammAlice, Env& env) {
285 env(offer(alice, USD(110), XRP(100)));
286 env.close();
287
288 // Carol creates a passive offer. That offer should cross
289 // AMM and leave Alice's offer untouched.
290 env(offer(carol, XRP(100), USD(100), tfPassive));
291 env.close();
292 BEAST_EXPECT(ammAlice.expectBalances(
293 XRP(10'900),
294 STAmount{USD, UINT64_C(9'082'56880733945), -11},
295 ammAlice.tokens()));
296 BEAST_EXPECT(expectOffers(env, carol, 0));
297 BEAST_EXPECT(expectOffers(env, alice, 1));
298 },
299 {{XRP(11'000), USD(9'000)}},
300 0,
301 std::nullopt,
302 {tweakedFeatures});
303 }
304 }
305
306 void
308 {
309 testcase("Offer Crossing with XRP, Normal order");
310
311 using namespace jtx;
312
313 Env env{*this, features};
314
315 fund(env, gw, {bob, alice}, XRP(300'000), {USD(100)}, Fund::All);
316
317 AMM ammAlice(env, alice, XRP(150'000), USD(50));
318
319 // Existing offer pays better than this wants.
320 // Partially consume existing offer.
321 // Pay 1 USD, get 3061224490 Drops.
322 auto const xrpTransferred = XRPAmount{3'061'224'490};
323 env(offer(bob, USD(1), XRP(4'000)));
324
325 BEAST_EXPECT(ammAlice.expectBalances(
326 XRP(150'000) + xrpTransferred,
327 USD(49),
328 IOUAmount{273'861'278752583, -8}));
329
330 BEAST_EXPECT(expectLine(env, bob, STAmount{USD, 101}));
331 BEAST_EXPECT(expectLedgerEntryRoot(
332 env, bob, XRP(300'000) - xrpTransferred - txfee(env, 1)));
333 BEAST_EXPECT(expectOffers(env, bob, 0));
334 }
335
336 void
338 {
339 testcase("Offer Crossing with Limit Override");
340
341 using namespace jtx;
342
343 Env env{*this, features};
344
345 env.fund(XRP(200'000), gw, alice, bob);
346 env.close();
347
348 env(trust(alice, USD(1'000)));
349
350 env(pay(gw, alice, alice["USD"](500)));
351
352 AMM ammAlice(env, alice, XRP(150'000), USD(51));
353 env(offer(bob, USD(1), XRP(3'000)));
354
355 BEAST_EXPECT(
356 ammAlice.expectBalances(XRP(153'000), USD(50), ammAlice.tokens()));
357
358 auto jrr = ledgerEntryState(env, bob, gw, "USD");
359 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-1");
360 jrr = ledgerEntryRoot(env, bob);
361 BEAST_EXPECT(
362 jrr[jss::node][sfBalance.fieldName] ==
363 to_string(
364 (XRP(200'000) - XRP(3'000) - env.current()->fees().base * 1)
365 .xrp()));
366 }
367
368 void
370 {
371 testcase("Currency Conversion: Entire Offer");
372
373 using namespace jtx;
374
375 Env env{*this, features};
376
377 fund(env, gw, {alice, bob}, XRP(10'000));
378 env.require(owners(bob, 0));
379
380 env(trust(alice, USD(100)));
381 env(trust(bob, USD(1'000)));
382 env(pay(gw, bob, USD(1'000)));
383
384 env.require(owners(alice, 1), owners(bob, 1));
385
386 env(pay(gw, alice, alice["USD"](100)));
387 AMM ammBob(env, bob, USD(200), XRP(1'500));
388
389 env(pay(alice, alice, XRP(500)), sendmax(USD(100)));
390
391 BEAST_EXPECT(
392 ammBob.expectBalances(USD(300), XRP(1'000), ammBob.tokens()));
393 BEAST_EXPECT(expectLine(env, alice, USD(0)));
394
395 auto jrr = ledgerEntryRoot(env, alice);
396 BEAST_EXPECT(
397 jrr[jss::node][sfBalance.fieldName] ==
398 to_string((XRP(10'000) + XRP(500) - env.current()->fees().base * 2)
399 .xrp()));
400 }
401
402 void
404 {
405 testcase("Currency Conversion: In Parts");
406
407 using namespace jtx;
408
409 testAMM(
410 [&](AMM& ammAlice, Env& env) {
411 // Alice converts USD to XRP which should fail
412 // due to PartialPayment.
413 env(pay(alice, alice, XRP(100)),
414 sendmax(USD(100)),
416
417 // Alice converts USD to XRP, should succeed because
418 // we permit partial payment
419 env(pay(alice, alice, XRP(100)),
420 sendmax(USD(100)),
422 env.close();
423 BEAST_EXPECT(ammAlice.expectBalances(
424 XRPAmount{9'900'990'100}, USD(10'100), ammAlice.tokens()));
425 // initial 30,000 - 10,000AMM - 100pay
426 BEAST_EXPECT(expectLine(env, alice, USD(19'900)));
427 // initial 30,000 - 10,0000AMM + 99.009900pay - fee*3
428 BEAST_EXPECT(expectLedgerEntryRoot(
429 env,
430 alice,
431 XRP(30'000) - XRP(10'000) + XRPAmount{99'009'900} -
432 ammCrtFee(env) - txfee(env, 2)));
433 },
434 {{XRP(10'000), USD(10'000)}},
435 0,
436 std::nullopt,
437 {features});
438 }
439
440 void
442 {
443 testcase("Cross Currency Payment: Start with XRP");
444
445 using namespace jtx;
446
447 testAMM(
448 [&](AMM& ammAlice, Env& env) {
449 env.fund(XRP(1'000), bob);
450 env.close();
451 env(trust(bob, USD(100)));
452 env.close();
453 env(pay(alice, bob, USD(100)), sendmax(XRP(100)));
454 BEAST_EXPECT(ammAlice.expectBalances(
455 XRP(10'100), USD(10'000), ammAlice.tokens()));
456 BEAST_EXPECT(expectLine(env, bob, USD(100)));
457 },
458 {{XRP(10'000), USD(10'100)}},
459 0,
460 std::nullopt,
461 {features});
462 }
463
464 void
466 {
467 testcase("Cross Currency Payment: End with XRP");
468
469 using namespace jtx;
470
471 testAMM(
472 [&](AMM& ammAlice, Env& env) {
473 env.fund(XRP(1'000), bob);
474 env.close();
475 env(trust(bob, USD(100)));
476 env.close();
477 env(pay(alice, bob, XRP(100)), sendmax(USD(100)));
478 BEAST_EXPECT(ammAlice.expectBalances(
479 XRP(10'000), USD(10'100), ammAlice.tokens()));
480 BEAST_EXPECT(expectLedgerEntryRoot(
481 env, bob, XRP(1'000) + XRP(100) - txfee(env, 1)));
482 },
483 {{XRP(10'100), USD(10'000)}},
484 0,
485 std::nullopt,
486 {features});
487 }
488
489 void
491 {
492 testcase("Cross Currency Payment: Bridged");
493
494 using namespace jtx;
495
496 Env env{*this, features};
497
498 auto const gw1 = Account{"gateway_1"};
499 auto const gw2 = Account{"gateway_2"};
500 auto const dan = Account{"dan"};
501 auto const USD1 = gw1["USD"];
502 auto const EUR1 = gw2["EUR"];
503
504 fund(env, gw1, {gw2, alice, bob, carol, dan}, XRP(60'000));
505 env(trust(alice, USD1(1'000)));
506 env.close();
507 env(trust(bob, EUR1(1'000)));
508 env.close();
509 env(trust(carol, USD1(10'000)));
510 env.close();
511 env(trust(dan, EUR1(1'000)));
512 env.close();
513
514 env(pay(gw1, alice, alice["USD"](500)));
515 env.close();
516 env(pay(gw1, carol, carol["USD"](6'000)));
517 env(pay(gw2, dan, dan["EUR"](400)));
518 env.close();
519
520 AMM ammCarol(env, carol, USD1(5'000), XRP(50'000));
521
522 env(offer(dan, XRP(500), EUR1(50)));
523 env.close();
524
526 jtp[0u][0u][jss::currency] = "XRP";
527 env(pay(alice, bob, EUR1(30)),
528 json(jss::Paths, jtp),
529 sendmax(USD1(333)));
530 env.close();
531 BEAST_EXPECT(ammCarol.expectBalances(
532 XRP(49'700),
533 STAmount{USD1, UINT64_C(5'030'181086519115), -12},
534 ammCarol.tokens()));
535 BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), EUR(20)}}}));
536 BEAST_EXPECT(expectLine(env, bob, STAmount{EUR1, 30}));
537 }
538
539 void
541 {
542 testcase("Offer Fees Consume Funds");
543
544 using namespace jtx;
545
546 Env env{*this, features};
547
548 auto const gw1 = Account{"gateway_1"};
549 auto const gw2 = Account{"gateway_2"};
550 auto const gw3 = Account{"gateway_3"};
551 auto const alice = Account{"alice"};
552 auto const bob = Account{"bob"};
553 auto const USD1 = gw1["USD"];
554 auto const USD2 = gw2["USD"];
555 auto const USD3 = gw3["USD"];
556
557 // Provide micro amounts to compensate for fees to make results round
558 // nice.
559 // reserve: Alice has 3 entries in the ledger, via trust lines
560 // fees:
561 // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) +
562 // 1 for payment == 4
563 auto const starting_xrp = XRP(100) +
564 env.current()->fees().accountReserve(3) +
565 env.current()->fees().base * 4;
566
567 env.fund(starting_xrp, gw1, gw2, gw3, alice);
568 env.fund(XRP(2'000), bob);
569 env.close();
570
571 env(trust(alice, USD1(1'000)));
572 env(trust(alice, USD2(1'000)));
573 env(trust(alice, USD3(1'000)));
574 env(trust(bob, USD1(1'200)));
575 env(trust(bob, USD2(1'100)));
576
577 env(pay(gw1, bob, bob["USD"](1'200)));
578
579 AMM ammBob(env, bob, XRP(1'000), USD1(1'200));
580 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
581 // Ask for more than available to prove reserve works.
582 env(offer(alice, USD1(200), XRP(200)));
583
584 // The pool gets only 100XRP for ~109.09USD, even though
585 // it can exchange more.
586 BEAST_EXPECT(ammBob.expectBalances(
587 XRP(1'100),
588 STAmount{USD1, UINT64_C(1'090'909090909091), -12},
589 ammBob.tokens()));
590
591 auto jrr = ledgerEntryState(env, alice, gw1, "USD");
592 BEAST_EXPECT(
593 jrr[jss::node][sfBalance.fieldName][jss::value] ==
594 "109.090909090909");
595 jrr = ledgerEntryRoot(env, alice);
596 BEAST_EXPECT(
597 jrr[jss::node][sfBalance.fieldName] == XRP(350).value().getText());
598 }
599
600 void
602 {
603 testcase("Offer Create, then Cross");
604
605 using namespace jtx;
606
607 Env env{*this, features};
608
609 fund(env, gw, {alice, bob}, XRP(200'000));
610
611 env(rate(gw, 1.005));
612
613 env(trust(alice, USD(1'000)));
614 env(trust(bob, USD(1'000)));
615
616 env(pay(gw, bob, USD(1)));
617 env(pay(gw, alice, USD(200)));
618
619 AMM ammAlice(env, alice, USD(150), XRP(150'100));
620 env(offer(bob, XRP(100), USD(0.1)));
621
622 BEAST_EXPECT(ammAlice.expectBalances(
623 USD(150.1), XRP(150'000), ammAlice.tokens()));
624
625 auto const jrr = ledgerEntryState(env, bob, gw, "USD");
626 // Bob pays 0.005 transfer fee. Note 10**-10 round-off.
627 BEAST_EXPECT(
628 jrr[jss::node][sfBalance.fieldName][jss::value] == "-0.8995000001");
629 }
630
631 void
633 {
634 testcase("Offer tfSell: Basic Sell");
635
636 using namespace jtx;
637
638 testAMM(
639 [&](AMM& ammAlice, Env& env) {
640 env(offer(carol, USD(100), XRP(100)), json(jss::Flags, tfSell));
641 env.close();
642 BEAST_EXPECT(ammAlice.expectBalances(
643 XRP(10'000), USD(9'999), ammAlice.tokens()));
644 BEAST_EXPECT(expectOffers(env, carol, 0));
645 BEAST_EXPECT(expectLine(env, carol, USD(30'101)));
646 BEAST_EXPECT(expectLedgerEntryRoot(
647 env, carol, XRP(30'000) - XRP(100) - txfee(env, 1)));
648 },
649 {{XRP(9'900), USD(10'100)}},
650 0,
651 std::nullopt,
652 {features});
653 }
654
655 void
657 {
658 testcase("Offer tfSell: 2x Sell Exceed Limit");
659
660 using namespace jtx;
661
662 Env env{*this, features};
663
664 auto const starting_xrp =
665 XRP(100) + reserve(env, 1) + env.current()->fees().base * 2;
666
667 env.fund(starting_xrp, gw, alice);
668 env.fund(XRP(2'000), bob);
669 env.close();
670
671 env(trust(alice, USD(150)));
672 env(trust(bob, USD(4'000)));
673
674 env(pay(gw, bob, bob["USD"](2'200)));
675
676 AMM ammBob(env, bob, XRP(1'000), USD(2'200));
677 // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available.
678 // Ask for more than available to prove reserve works.
679 // Taker pays 100 USD for 100 XRP.
680 // Selling XRP.
681 // Will sell all 100 XRP and get more USD than asked for.
682 env(offer(alice, USD(100), XRP(200)), json(jss::Flags, tfSell));
683 BEAST_EXPECT(
684 ammBob.expectBalances(XRP(1'100), USD(2'000), ammBob.tokens()));
685 BEAST_EXPECT(expectLine(env, alice, USD(200)));
686 BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(250)));
687 BEAST_EXPECT(expectOffers(env, alice, 0));
688 }
689
690 void
692 {
693 testcase("Client Issue: Gateway Cross Currency");
694
695 using namespace jtx;
696
697 Env env{*this, features};
698
699 auto const XTS = gw["XTS"];
700 auto const XXX = gw["XXX"];
701
702 auto const starting_xrp =
703 XRP(100.1) + reserve(env, 1) + env.current()->fees().base * 2;
704 fund(
705 env,
706 gw,
707 {alice, bob},
708 starting_xrp,
709 {XTS(100), XXX(100)},
710 Fund::All);
711
712 AMM ammAlice(env, alice, XTS(100), XXX(100));
713
714 Json::Value payment;
715 payment[jss::secret] = toBase58(generateSeed("bob"));
716 payment[jss::id] = env.seq(bob);
717 payment[jss::build_path] = true;
718 payment[jss::tx_json] = pay(bob, bob, bob["XXX"](1));
719 payment[jss::tx_json][jss::Sequence] =
720 env.current()
721 ->read(keylet::account(bob.id()))
722 ->getFieldU32(sfSequence);
723 payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
724 payment[jss::tx_json][jss::SendMax] =
725 bob["XTS"](1.5).value().getJson(JsonOptions::none);
726 payment[jss::tx_json][jss::Flags] = tfPartialPayment;
727 auto const jrr = env.rpc("json", "submit", to_string(payment));
728 BEAST_EXPECT(jrr[jss::result][jss::status] == "success");
729 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS");
730 if (!features[fixAMMv1_1])
731 {
732 BEAST_EXPECT(ammAlice.expectBalances(
733 STAmount(XTS, UINT64_C(101'010101010101), -12),
734 XXX(99),
735 ammAlice.tokens()));
736 BEAST_EXPECT(expectLine(
737 env, bob, STAmount{XTS, UINT64_C(98'989898989899), -12}));
738 }
739 else
740 {
741 BEAST_EXPECT(ammAlice.expectBalances(
742 STAmount(XTS, UINT64_C(101'0101010101011), -13),
743 XXX(99),
744 ammAlice.tokens()));
745 BEAST_EXPECT(expectLine(
746 env, bob, STAmount{XTS, UINT64_C(98'9898989898989), -13}));
747 }
748 BEAST_EXPECT(expectLine(env, bob, XXX(101)));
749 }
750
751 void
753 {
754 testcase("Bridged Crossing");
755
756 using namespace jtx;
757
758 {
759 Env env{*this, features};
760
761 fund(
762 env,
763 gw,
764 {alice, bob, carol},
765 {USD(15'000), EUR(15'000)},
766 Fund::All);
767
768 // The scenario:
769 // o USD/XRP AMM is created.
770 // o EUR/XRP AMM is created.
771 // o carol has EUR but wants USD.
772 // Note that carol's offer must come last. If carol's offer is
773 // placed before AMM is created, then autobridging will not occur.
774 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
775 AMM ammBob(env, bob, EUR(10'000), XRP(10'100));
776
777 // Carol makes an offer that consumes AMM liquidity and
778 // fully consumes Carol's offer.
779 env(offer(carol, USD(100), EUR(100)));
780 env.close();
781
782 BEAST_EXPECT(ammAlice.expectBalances(
783 XRP(10'100), USD(10'000), ammAlice.tokens()));
784 BEAST_EXPECT(ammBob.expectBalances(
785 XRP(10'000), EUR(10'100), ammBob.tokens()));
786 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
787 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
788 BEAST_EXPECT(expectOffers(env, carol, 0));
789 }
790
791 {
792 Env env{*this, features};
793
794 fund(
795 env,
796 gw,
797 {alice, bob, carol},
798 {USD(15'000), EUR(15'000)},
799 Fund::All);
800
801 // The scenario:
802 // o USD/XRP AMM is created.
803 // o EUR/XRP offer is created.
804 // o carol has EUR but wants USD.
805 // Note that carol's offer must come last. If carol's offer is
806 // placed before AMM and bob's offer are created, then autobridging
807 // will not occur.
808 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
809 env(offer(bob, EUR(100), XRP(100)));
810 env.close();
811
812 // Carol makes an offer that consumes AMM liquidity and
813 // fully consumes Carol's offer.
814 env(offer(carol, USD(100), EUR(100)));
815 env.close();
816
817 BEAST_EXPECT(ammAlice.expectBalances(
818 XRP(10'100), USD(10'000), ammAlice.tokens()));
819 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
820 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
821 BEAST_EXPECT(expectOffers(env, carol, 0));
822 BEAST_EXPECT(expectOffers(env, bob, 0));
823 }
824
825 {
826 Env env{*this, features};
827
828 fund(
829 env,
830 gw,
831 {alice, bob, carol},
832 {USD(15'000), EUR(15'000)},
833 Fund::All);
834
835 // The scenario:
836 // o USD/XRP offer is created.
837 // o EUR/XRP AMM is created.
838 // o carol has EUR but wants USD.
839 // Note that carol's offer must come last. If carol's offer is
840 // placed before AMM and alice's offer are created, then
841 // autobridging will not occur.
842 env(offer(alice, XRP(100), USD(100)));
843 env.close();
844 AMM ammBob(env, bob, EUR(10'000), XRP(10'100));
845
846 // Carol makes an offer that consumes AMM liquidity and
847 // fully consumes Carol's offer.
848 env(offer(carol, USD(100), EUR(100)));
849 env.close();
850
851 BEAST_EXPECT(ammBob.expectBalances(
852 XRP(10'000), EUR(10'100), ammBob.tokens()));
853 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
854 BEAST_EXPECT(expectLine(env, carol, EUR(14'900)));
855 BEAST_EXPECT(expectOffers(env, carol, 0));
856 BEAST_EXPECT(expectOffers(env, alice, 0));
857 }
858 }
859
860 void
862 {
863 // Test a number of different corner cases regarding offer crossing
864 // when both the tfSell flag and tfFillOrKill flags are set.
865 testcase("Combine tfSell with tfFillOrKill");
866
867 using namespace jtx;
868
869 // Code returned if an offer is killed.
870 TER const killedCode{
871 features[fix1578] ? TER{tecKILLED} : TER{tesSUCCESS}};
872
873 {
874 Env env{*this, features};
875 fund(env, gw, {alice, bob}, {USD(20'000)}, Fund::All);
876 AMM ammBob(env, bob, XRP(20'000), USD(200));
877 // alice submits a tfSell | tfFillOrKill offer that does not cross.
878 env(offer(alice, USD(2.1), XRP(210), tfSell | tfFillOrKill),
879 ter(killedCode));
880
881 BEAST_EXPECT(
882 ammBob.expectBalances(XRP(20'000), USD(200), ammBob.tokens()));
883 BEAST_EXPECT(expectOffers(env, bob, 0));
884 }
885 {
886 Env env{*this, features};
887 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
888 AMM ammBob(env, bob, XRP(20'000), USD(200));
889 // alice submits a tfSell | tfFillOrKill offer that crosses.
890 // Even though tfSell is present it doesn't matter this time.
891 env(offer(alice, USD(2), XRP(220), tfSell | tfFillOrKill));
892 env.close();
893 BEAST_EXPECT(ammBob.expectBalances(
894 XRP(20'220),
895 STAmount{USD, UINT64_C(197'8239366963403), -13},
896 ammBob.tokens()));
897 BEAST_EXPECT(expectLine(
898 env, alice, STAmount{USD, UINT64_C(1'002'17606330366), -11}));
899 BEAST_EXPECT(expectOffers(env, alice, 0));
900 }
901 {
902 // alice submits a tfSell | tfFillOrKill offer that crosses and
903 // returns more than was asked for (because of the tfSell flag).
904 Env env{*this, features};
905 fund(env, gw, {alice, bob}, {USD(1'000)}, Fund::All);
906 AMM ammBob(env, bob, XRP(20'000), USD(200));
907
908 env(offer(alice, USD(10), XRP(1'500), tfSell | tfFillOrKill));
909 env.close();
910
911 BEAST_EXPECT(ammBob.expectBalances(
912 XRP(21'500),
913 STAmount{USD, UINT64_C(186'046511627907), -12},
914 ammBob.tokens()));
915 BEAST_EXPECT(expectLine(
916 env, alice, STAmount{USD, UINT64_C(1'013'953488372093), -12}));
917 BEAST_EXPECT(expectOffers(env, alice, 0));
918 }
919 {
920 // alice submits a tfSell | tfFillOrKill offer that doesn't cross.
921 // This would have succeeded with a regular tfSell, but the
922 // fillOrKill prevents the transaction from crossing since not
923 // all of the offer is consumed because AMM generated offer,
924 // which matches alice's offer quality is ~ 10XRP/0.01996USD.
925 Env env{*this, features};
926 fund(env, gw, {alice, bob}, {USD(10'000)}, Fund::All);
927 AMM ammBob(env, bob, XRP(5000), USD(10));
928
929 env(offer(alice, USD(1), XRP(501), tfSell | tfFillOrKill),
930 ter(tecKILLED));
931 env.close();
932 BEAST_EXPECT(expectOffers(env, alice, 0));
933 BEAST_EXPECT(expectOffers(env, bob, 0));
934 }
935 }
936
937 void
939 {
940 testcase("Transfer Rate Offer");
941
942 using namespace jtx;
943
944 // AMM XRP/USD. Alice places USD/XRP offer.
945 testAMM(
946 [&](AMM& ammAlice, Env& env) {
947 env(rate(gw, 1.25));
948 env.close();
949
950 env(offer(carol, USD(100), XRP(100)));
951 env.close();
952
953 // AMM doesn't pay the transfer fee
954 BEAST_EXPECT(ammAlice.expectBalances(
955 XRP(10'100), USD(10'000), ammAlice.tokens()));
956 BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
957 BEAST_EXPECT(expectOffers(env, carol, 0));
958 },
959 {{XRP(10'000), USD(10'100)}},
960 0,
961 std::nullopt,
962 {features});
963
964 // Reverse the order, so the offer in the books is to sell XRP
965 // in return for USD.
966 testAMM(
967 [&](AMM& ammAlice, Env& env) {
968 env(rate(gw, 1.25));
969 env.close();
970
971 env(offer(carol, XRP(100), USD(100)));
972 env.close();
973
974 BEAST_EXPECT(ammAlice.expectBalances(
975 XRP(10'000), USD(10'100), ammAlice.tokens()));
976 // Carol pays 25% transfer fee
977 BEAST_EXPECT(expectLine(env, carol, USD(29'875)));
978 BEAST_EXPECT(expectOffers(env, carol, 0));
979 },
980 {{XRP(10'100), USD(10'000)}},
981 0,
982 std::nullopt,
983 {features});
984
985 {
986 // Bridged crossing.
987 Env env{*this, features};
988 fund(
989 env,
990 gw,
991 {alice, bob, carol},
992 {USD(15'000), EUR(15'000)},
993 Fund::All);
994 env(rate(gw, 1.25));
995
996 // The scenario:
997 // o USD/XRP AMM is created.
998 // o EUR/XRP Offer is created.
999 // o carol has EUR but wants USD.
1000 // Note that Carol's offer must come last. If Carol's offer is
1001 // placed before AMM is created, then autobridging will not occur.
1002 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
1003 env(offer(bob, EUR(100), XRP(100)));
1004 env.close();
1005
1006 // Carol makes an offer that consumes AMM liquidity and
1007 // fully consumes Bob's offer.
1008 env(offer(carol, USD(100), EUR(100)));
1009 env.close();
1010
1011 // AMM doesn't pay the transfer fee
1012 BEAST_EXPECT(ammAlice.expectBalances(
1013 XRP(10'100), USD(10'000), ammAlice.tokens()));
1014 BEAST_EXPECT(expectLine(env, carol, USD(15'100)));
1015 // Carol pays 25% transfer fee.
1016 BEAST_EXPECT(expectLine(env, carol, EUR(14'875)));
1017 BEAST_EXPECT(expectOffers(env, carol, 0));
1018 BEAST_EXPECT(expectOffers(env, bob, 0));
1019 }
1020
1021 {
1022 // Bridged crossing. The transfer fee is paid on the step not
1023 // involving AMM as src/dst.
1024 Env env{*this, features};
1025 fund(
1026 env,
1027 gw,
1028 {alice, bob, carol},
1029 {USD(15'000), EUR(15'000)},
1030 Fund::All);
1031 env(rate(gw, 1.25));
1032
1033 // The scenario:
1034 // o USD/XRP AMM is created.
1035 // o EUR/XRP Offer is created.
1036 // o carol has EUR but wants USD.
1037 // Note that Carol's offer must come last. If Carol's offer is
1038 // placed before AMM is created, then autobridging will not occur.
1039 AMM ammAlice(env, alice, XRP(10'000), USD(10'050));
1040 env(offer(bob, EUR(100), XRP(100)));
1041 env.close();
1042
1043 // Carol makes an offer that consumes AMM liquidity and
1044 // partially consumes Bob's offer.
1045 env(offer(carol, USD(50), EUR(50)));
1046 env.close();
1047 // This test verifies that the amount removed from an offer
1048 // accounts for the transfer fee that is removed from the
1049 // account but not from the remaining offer.
1050
1051 // AMM doesn't pay the transfer fee
1052 BEAST_EXPECT(ammAlice.expectBalances(
1053 XRP(10'050), USD(10'000), ammAlice.tokens()));
1054 BEAST_EXPECT(expectLine(env, carol, USD(15'050)));
1055 // Carol pays 25% transfer fee.
1056 BEAST_EXPECT(expectLine(env, carol, EUR(14'937.5)));
1057 BEAST_EXPECT(expectOffers(env, carol, 0));
1058 BEAST_EXPECT(
1059 expectOffers(env, bob, 1, {{Amounts{EUR(50), XRP(50)}}}));
1060 }
1061
1062 {
1063 // A trust line's QualityIn should not affect offer crossing.
1064 // Bridged crossing. The transfer fee is paid on the step not
1065 // involving AMM as src/dst.
1066 Env env{*this, features};
1067 fund(env, gw, {alice, carol, bob}, XRP(30'000));
1068 env(rate(gw, 1.25));
1069 env(trust(alice, USD(15'000)));
1070 env(trust(bob, EUR(15'000)));
1071 env(trust(carol, EUR(15'000)), qualityInPercent(80));
1072 env(trust(bob, USD(15'000)));
1073 env(trust(carol, USD(15'000)));
1074 env.close();
1075
1076 env(pay(gw, alice, USD(11'000)));
1077 env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000)));
1078 env.close();
1079 // 1000 / 0.8
1080 BEAST_EXPECT(expectLine(env, carol, EUR(1'250)));
1081 // The scenario:
1082 // o USD/XRP AMM is created.
1083 // o EUR/XRP Offer is created.
1084 // o carol has EUR but wants USD.
1085 // Note that Carol's offer must come last. If Carol's offer is
1086 // placed before AMM is created, then autobridging will not occur.
1087 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
1088 env(offer(bob, EUR(100), XRP(100)));
1089 env.close();
1090
1091 // Carol makes an offer that consumes AMM liquidity and
1092 // fully consumes Bob's offer.
1093 env(offer(carol, USD(100), EUR(100)));
1094 env.close();
1095
1096 // AMM doesn't pay the transfer fee
1097 BEAST_EXPECT(ammAlice.expectBalances(
1098 XRP(10'100), USD(10'000), ammAlice.tokens()));
1099 BEAST_EXPECT(expectLine(env, carol, USD(100)));
1100 // Carol pays 25% transfer fee: 1250 - 100(offer) - 25(transfer fee)
1101 BEAST_EXPECT(expectLine(env, carol, EUR(1'125)));
1102 BEAST_EXPECT(expectOffers(env, carol, 0));
1103 BEAST_EXPECT(expectOffers(env, bob, 0));
1104 }
1105
1106 {
1107 // A trust line's QualityOut should not affect offer crossing.
1108 // Bridged crossing. The transfer fee is paid on the step not
1109 // involving AMM as src/dst.
1110 Env env{*this, features};
1111 fund(env, gw, {alice, carol, bob}, XRP(30'000));
1112 env(rate(gw, 1.25));
1113 env(trust(alice, USD(15'000)));
1114 env(trust(bob, EUR(15'000)));
1115 env(trust(carol, EUR(15'000)), qualityOutPercent(120));
1116 env(trust(bob, USD(15'000)));
1117 env(trust(carol, USD(15'000)));
1118 env.close();
1119
1120 env(pay(gw, alice, USD(11'000)));
1121 env(pay(gw, carol, EUR(1'000)), sendmax(EUR(10'000)));
1122 env.close();
1123 BEAST_EXPECT(expectLine(env, carol, EUR(1'000)));
1124 // The scenario:
1125 // o USD/XRP AMM is created.
1126 // o EUR/XRP Offer is created.
1127 // o carol has EUR but wants USD.
1128 // Note that Carol's offer must come last. If Carol's offer is
1129 // placed before AMM is created, then autobridging will not occur.
1130 AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
1131 env(offer(bob, EUR(100), XRP(100)));
1132 env.close();
1133
1134 // Carol makes an offer that consumes AMM liquidity and
1135 // fully consumes Bob's offer.
1136 env(offer(carol, USD(100), EUR(100)));
1137 env.close();
1138
1139 // AMM pay doesn't transfer fee
1140 BEAST_EXPECT(ammAlice.expectBalances(
1141 XRP(10'100), USD(10'000), ammAlice.tokens()));
1142 BEAST_EXPECT(expectLine(env, carol, USD(100)));
1143 // Carol pays 25% transfer fee: 1000 - 100(offer) - 25(transfer fee)
1144 BEAST_EXPECT(expectLine(env, carol, EUR(875)));
1145 BEAST_EXPECT(expectOffers(env, carol, 0));
1146 BEAST_EXPECT(expectOffers(env, bob, 0));
1147 }
1148 }
1149
1150 void
1152 {
1153 // This test is not the same as corresponding testSelfIssueOffer()
1154 // in the Offer_test. It simply tests AMM with self issue and
1155 // offer crossing.
1156 using namespace jtx;
1157
1158 Env env{*this, features};
1159
1160 auto const USD_bob = bob["USD"];
1161 auto const f = env.current()->fees().base;
1162
1163 env.fund(XRP(30'000) + f, alice, bob);
1164 env.close();
1165 AMM ammBob(env, bob, XRP(10'000), USD_bob(10'100));
1166
1167 env(offer(alice, USD_bob(100), XRP(100)));
1168 env.close();
1169
1170 BEAST_EXPECT(ammBob.expectBalances(
1171 XRP(10'100), USD_bob(10'000), ammBob.tokens()));
1172 BEAST_EXPECT(expectOffers(env, alice, 0));
1173 BEAST_EXPECT(expectLine(env, alice, USD_bob(100)));
1174 }
1175
1176 void
1178 {
1179 // At one point in the past this invalid path caused assert. It
1180 // should not be possible for user-supplied data to cause assert.
1181 // Make sure assert is gone.
1182 testcase("Bad path assert");
1183
1184 using namespace jtx;
1185
1186 // The problem was identified when featureOwnerPaysFee was enabled,
1187 // so make sure that gets included.
1188 Env env{*this, features | featureOwnerPaysFee};
1189
1190 // The fee that's charged for transactions.
1191 auto const fee = env.current()->fees().base;
1192 {
1193 // A trust line's QualityOut should not affect offer crossing.
1194 auto const ann = Account("ann");
1195 auto const A_BUX = ann["BUX"];
1196 auto const bob = Account("bob");
1197 auto const cam = Account("cam");
1198 auto const dan = Account("dan");
1199 auto const D_BUX = dan["BUX"];
1200
1201 // Verify trust line QualityOut affects payments.
1202 env.fund(reserve(env, 4) + (fee * 4), ann, bob, cam, dan);
1203 env.close();
1204
1205 env(trust(bob, A_BUX(400)));
1206 env(trust(bob, D_BUX(200)), qualityOutPercent(120));
1207 env(trust(cam, D_BUX(100)));
1208 env.close();
1209 env(pay(dan, bob, D_BUX(100)));
1210 env.close();
1211 BEAST_EXPECT(expectLine(env, bob, D_BUX(100)));
1212
1213 env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200)));
1214 env.close();
1215
1216 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1217 BEAST_EXPECT(expectLine(env, ann, D_BUX(none)));
1218 BEAST_EXPECT(expectLine(env, bob, A_BUX(72)));
1219 BEAST_EXPECT(expectLine(env, bob, D_BUX(40)));
1220 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1221 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1222 BEAST_EXPECT(expectLine(env, dan, A_BUX(none)));
1223 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1224
1225 AMM ammBob(env, bob, A_BUX(30), D_BUX(30));
1226
1227 env(trust(ann, D_BUX(100)));
1228 env.close();
1229
1230 // This payment caused the assert.
1231 env(pay(ann, ann, D_BUX(30)),
1232 path(A_BUX, D_BUX),
1233 sendmax(A_BUX(30)),
1234 ter(temBAD_PATH));
1235 env.close();
1236
1237 BEAST_EXPECT(
1238 ammBob.expectBalances(A_BUX(30), D_BUX(30), ammBob.tokens()));
1239 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1240 BEAST_EXPECT(expectLine(env, ann, D_BUX(0)));
1241 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1242 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1243 BEAST_EXPECT(expectLine(env, dan, A_BUX(0)));
1244 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1245 }
1246 }
1247
1248 void
1250 {
1251 // The offer crossing code expects that a DirectStep is always
1252 // preceded by a BookStep. In one instance the default path
1253 // was not matching that assumption. Here we recreate that case
1254 // so we can prove the bug stays fixed.
1255 testcase("Direct to Direct path");
1256
1257 using namespace jtx;
1258
1259 Env env{*this, features};
1260
1261 auto const ann = Account("ann");
1262 auto const bob = Account("bob");
1263 auto const cam = Account("cam");
1264 auto const carol = Account("carol");
1265 auto const A_BUX = ann["BUX"];
1266 auto const B_BUX = bob["BUX"];
1267
1268 auto const fee = env.current()->fees().base;
1269 env.fund(XRP(1'000), carol);
1270 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
1271 env.close();
1272
1273 env(trust(ann, B_BUX(40)));
1274 env(trust(cam, A_BUX(40)));
1275 env(trust(bob, A_BUX(30)));
1276 env(trust(cam, B_BUX(40)));
1277 env(trust(carol, B_BUX(400)));
1278 env(trust(carol, A_BUX(400)));
1279 env.close();
1280
1281 env(pay(ann, cam, A_BUX(35)));
1282 env(pay(bob, cam, B_BUX(35)));
1283 env(pay(bob, carol, B_BUX(400)));
1284 env(pay(ann, carol, A_BUX(400)));
1285
1286 AMM ammCarol(env, carol, A_BUX(300), B_BUX(330));
1287
1288 // cam puts an offer on the books that her upcoming offer could cross.
1289 // But this offer should be deleted, not crossed, by her upcoming
1290 // offer.
1291 env(offer(cam, A_BUX(29), B_BUX(30), tfPassive));
1292 env.close();
1293 env.require(balance(cam, A_BUX(35)));
1294 env.require(balance(cam, B_BUX(35)));
1295 env.require(offers(cam, 1));
1296
1297 // This offer caused the assert.
1298 env(offer(cam, B_BUX(30), A_BUX(30)));
1299
1300 // AMM is consumed up to the first cam Offer quality
1301 if (!features[fixAMMv1_1])
1302 {
1303 BEAST_EXPECT(ammCarol.expectBalances(
1304 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1305 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1306 ammCarol.tokens()));
1307 BEAST_EXPECT(expectOffers(
1308 env,
1309 cam,
1310 1,
1311 {{Amounts{
1312 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1313 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1314 }
1315 else
1316 {
1317 BEAST_EXPECT(ammCarol.expectBalances(
1318 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1319 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1320 ammCarol.tokens()));
1321 BEAST_EXPECT(expectOffers(
1322 env,
1323 cam,
1324 1,
1325 {{Amounts{
1326 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1327 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1328 }
1329 }
1330
1331 void
1333 {
1334 testcase("lsfRequireAuth");
1335
1336 using namespace jtx;
1337
1338 Env env{*this, features};
1339
1340 auto const aliceUSD = alice["USD"];
1341 auto const bobUSD = bob["USD"];
1342
1343 env.fund(XRP(400'000), gw, alice, bob);
1344 env.close();
1345
1346 // GW requires authorization for holders of its IOUs
1347 env(fset(gw, asfRequireAuth));
1348 env.close();
1349
1350 // Properly set trust and have gw authorize bob and alice
1351 env(trust(gw, bobUSD(100)), txflags(tfSetfAuth));
1352 env(trust(bob, USD(100)));
1353 env(trust(gw, aliceUSD(100)), txflags(tfSetfAuth));
1354 env(trust(alice, USD(2'000)));
1355 env(pay(gw, alice, USD(1'000)));
1356 env.close();
1357 // Alice is able to create AMM since the GW has authorized her
1358 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
1359
1360 // Set up authorized trust line for AMM.
1361 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
1363 env.close();
1364
1365 env(pay(gw, bob, USD(50)));
1366 env.close();
1367
1368 BEAST_EXPECT(expectLine(env, bob, USD(50)));
1369
1370 // Bob's offer should cross Alice's AMM
1371 env(offer(bob, XRP(50), USD(50)));
1372 env.close();
1373
1374 BEAST_EXPECT(
1375 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
1376 BEAST_EXPECT(expectOffers(env, bob, 0));
1377 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1378 }
1379
1380 void
1382 {
1383 testcase("Missing Auth");
1384
1385 using namespace jtx;
1386
1387 Env env{*this, features};
1388
1389 env.fund(XRP(400'000), gw, alice, bob);
1390 env.close();
1391
1392 // Alice doesn't have the funds
1393 {
1394 AMM ammAlice(
1395 env, alice, USD(1'000), XRP(1'000), ter(tecUNFUNDED_AMM));
1396 }
1397
1398 env(fset(gw, asfRequireAuth));
1399 env.close();
1400
1401 env(trust(gw, bob["USD"](50)), txflags(tfSetfAuth));
1402 env.close();
1403 env(trust(bob, USD(50)));
1404 env.close();
1405
1406 env(pay(gw, bob, USD(50)));
1407 env.close();
1408 BEAST_EXPECT(expectLine(env, bob, USD(50)));
1409
1410 // Alice should not be able to create AMM without authorization.
1411 {
1412 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_LINE));
1413 }
1414
1415 // Set up a trust line for Alice, but don't authorize it. Alice
1416 // should still not be able to create AMM for USD/gw.
1417 env(trust(gw, alice["USD"](2'000)));
1418 env.close();
1419
1420 {
1421 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_AUTH));
1422 }
1423
1424 // Finally, set up an authorized trust line for Alice. Now Alice's
1425 // AMM create should succeed.
1426 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
1427 env(trust(alice, USD(2'000)));
1428 env(pay(gw, alice, USD(1'000)));
1429 env.close();
1430
1431 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
1432
1433 // Set up authorized trust line for AMM.
1434 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
1436 env.close();
1437
1438 // Now bob creates his offer again, which crosses with alice's AMM.
1439 env(offer(bob, XRP(50), USD(50)));
1440 env.close();
1441
1442 BEAST_EXPECT(
1443 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
1444 BEAST_EXPECT(expectOffers(env, bob, 0));
1445 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1446 }
1447
1448 void
1450 {
1451 using namespace jtx;
1454 testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3);
1468 testGatewayCrossCurrency(all - fixAMMv1_1 - fixAMMv1_3);
1476 testDirectToDirectPath(all - fixAMMv1_1 - fixAMMv1_3);
1479 }
1480
1481 void
1483 {
1484 testcase("path find consume all");
1485 using namespace jtx;
1486
1487 Env env = pathTestEnv();
1488 env.fund(XRP(100'000'250), alice);
1489 fund(env, gw, {carol, bob}, {USD(100)}, Fund::All);
1490 fund(env, gw, {alice}, {USD(100)}, Fund::IOUOnly);
1491 AMM ammCarol(env, carol, XRP(100), USD(100));
1492
1493 STPathSet st;
1494 STAmount sa;
1495 STAmount da;
1496 std::tie(st, sa, da) = find_paths(
1497 env,
1498 alice,
1499 bob,
1500 bob["AUD"](-1),
1501 std::optional<STAmount>(XRP(100'000'000)));
1502 BEAST_EXPECT(st.empty());
1503 std::tie(st, sa, da) = find_paths(
1504 env,
1505 alice,
1506 bob,
1507 bob["USD"](-1),
1508 std::optional<STAmount>(XRP(100'000'000)));
1509 // Alice sends all requested 100,000,000XRP
1510 BEAST_EXPECT(sa == XRP(100'000'000));
1511 // Bob gets ~99.99USD. This is the amount Bob
1512 // can get out of AMM for 100,000,000XRP.
1513 BEAST_EXPECT(equal(
1514 da, STAmount{bob["USD"].issue(), UINT64_C(99'9999000001), -10}));
1515 }
1516
1517 // carol holds gateway AUD, sells gateway AUD for XRP
1518 // bob will hold gateway AUD
1519 // alice pays bob gateway AUD using XRP
1520 void
1522 {
1523 testcase("via gateway");
1524 using namespace jtx;
1525
1526 Env env = pathTestEnv();
1527 auto const AUD = gw["AUD"];
1528 env.fund(XRP(10'000), alice, bob, carol, gw);
1529 env.close();
1530 env(rate(gw, 1.1));
1531 env.trust(AUD(2'000), bob, carol);
1532 env(pay(gw, carol, AUD(51)));
1533 env.close();
1534 AMM ammCarol(env, carol, XRP(40), AUD(51));
1535 env(pay(alice, bob, AUD(10)), sendmax(XRP(100)), paths(XRP));
1536 env.close();
1537 // AMM offer is 51.282052XRP/11AUD, 11AUD/1.1 = 10AUD to bob
1538 BEAST_EXPECT(
1539 ammCarol.expectBalances(XRP(51), AUD(40), ammCarol.tokens()));
1540 BEAST_EXPECT(expectLine(env, bob, AUD(10)));
1541
1542 auto const result =
1543 find_paths(env, alice, bob, Account(bob)["USD"](25));
1544 BEAST_EXPECT(std::get<0>(result).empty());
1545 }
1546
1547 void
1549 {
1550 testcase("Receive max");
1551 using namespace jtx;
1552 auto const charlie = Account("charlie");
1553 {
1554 // XRP -> IOU receive max
1555 Env env = pathTestEnv();
1556 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1557 AMM ammCharlie(env, charlie, XRP(10), USD(11));
1558 auto [st, sa, da] =
1559 find_paths(env, alice, bob, USD(-1), XRP(1).value());
1560 BEAST_EXPECT(sa == XRP(1));
1561 BEAST_EXPECT(equal(da, USD(1)));
1562 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1563 {
1564 auto const& pathElem = st[0][0];
1565 BEAST_EXPECT(
1566 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
1567 pathElem.getCurrency() == USD.currency);
1568 }
1569 }
1570 {
1571 // IOU -> XRP receive max
1572 Env env = pathTestEnv();
1573 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1574 AMM ammCharlie(env, charlie, XRP(11), USD(10));
1575 env.close();
1576 auto [st, sa, da] =
1577 find_paths(env, alice, bob, drops(-1), USD(1).value());
1578 BEAST_EXPECT(sa == USD(1));
1579 BEAST_EXPECT(equal(da, XRP(1)));
1580 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1581 {
1582 auto const& pathElem = st[0][0];
1583 BEAST_EXPECT(
1584 pathElem.isOffer() &&
1585 pathElem.getIssuerID() == xrpAccount() &&
1586 pathElem.getCurrency() == xrpCurrency());
1587 }
1588 }
1589 }
1590
1591 void
1593 {
1594 testcase("Path Find: XRP -> XRP and XRP -> IOU");
1595 using namespace jtx;
1596 Env env = pathTestEnv();
1597 Account A1{"A1"};
1598 Account A2{"A2"};
1599 Account A3{"A3"};
1600 Account G1{"G1"};
1601 Account G2{"G2"};
1602 Account G3{"G3"};
1603 Account M1{"M1"};
1604
1605 env.fund(XRP(100'000), A1);
1606 env.fund(XRP(10'000), A2);
1607 env.fund(XRP(1'000), A3, G1, G2, G3);
1608 env.fund(XRP(20'000), M1);
1609 env.close();
1610
1611 env.trust(G1["XYZ"](5'000), A1);
1612 env.trust(G3["ABC"](5'000), A1);
1613 env.trust(G2["XYZ"](5'000), A2);
1614 env.trust(G3["ABC"](5'000), A2);
1615 env.trust(A2["ABC"](1'000), A3);
1616 env.trust(G1["XYZ"](100'000), M1);
1617 env.trust(G2["XYZ"](100'000), M1);
1618 env.trust(G3["ABC"](100'000), M1);
1619 env.close();
1620
1621 env(pay(G1, A1, G1["XYZ"](3'500)));
1622 env(pay(G3, A1, G3["ABC"](1'200)));
1623 env(pay(G1, M1, G1["XYZ"](25'000)));
1624 env(pay(G2, M1, G2["XYZ"](25'000)));
1625 env(pay(G3, M1, G3["ABC"](25'000)));
1626 env.close();
1627
1628 AMM ammM1_G1_G2(env, M1, G1["XYZ"](1'000), G2["XYZ"](1'000));
1629 AMM ammM1_XRP_G3(env, M1, XRP(10'000), G3["ABC"](1'000));
1630
1631 STPathSet st;
1632 STAmount sa, da;
1633
1634 {
1635 auto const& send_amt = XRP(10);
1636 std::tie(st, sa, da) =
1637 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
1638 BEAST_EXPECT(equal(da, send_amt));
1639 BEAST_EXPECT(st.empty());
1640 }
1641
1642 {
1643 // no path should exist for this since dest account
1644 // does not exist.
1645 auto const& send_amt = XRP(200);
1646 std::tie(st, sa, da) = find_paths(
1647 env, A1, Account{"A0"}, send_amt, std::nullopt, xrpCurrency());
1648 BEAST_EXPECT(equal(da, send_amt));
1649 BEAST_EXPECT(st.empty());
1650 }
1651
1652 {
1653 auto const& send_amt = G3["ABC"](10);
1654 std::tie(st, sa, da) =
1655 find_paths(env, A2, G3, send_amt, std::nullopt, xrpCurrency());
1656 BEAST_EXPECT(equal(da, send_amt));
1657 BEAST_EXPECT(equal(sa, XRPAmount{101'010'102}));
1658 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]))));
1659 }
1660
1661 {
1662 auto const& send_amt = A2["ABC"](1);
1663 std::tie(st, sa, da) =
1664 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
1665 BEAST_EXPECT(equal(da, send_amt));
1666 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
1667 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3)));
1668 }
1669
1670 {
1671 auto const& send_amt = A3["ABC"](1);
1672 std::tie(st, sa, da) =
1673 find_paths(env, A1, A3, send_amt, std::nullopt, xrpCurrency());
1674 BEAST_EXPECT(equal(da, send_amt));
1675 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
1676 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3, A2)));
1677 }
1678 }
1679
1680 void
1682 {
1683 testcase("Path Find: non-XRP -> XRP");
1684 using namespace jtx;
1685 Env env = pathTestEnv();
1686 Account A1{"A1"};
1687 Account A2{"A2"};
1688 Account G3{"G3"};
1689 Account M1{"M1"};
1690
1691 env.fund(XRP(1'000), A1, A2, G3);
1692 env.fund(XRP(11'000), M1);
1693 env.close();
1694
1695 env.trust(G3["ABC"](1'000), A1, A2);
1696 env.trust(G3["ABC"](100'000), M1);
1697 env.close();
1698
1699 env(pay(G3, A1, G3["ABC"](1'000)));
1700 env(pay(G3, A2, G3["ABC"](1'000)));
1701 env(pay(G3, M1, G3["ABC"](1'200)));
1702 env.close();
1703
1704 AMM ammM1(env, M1, G3["ABC"](1'000), XRP(10'010));
1705
1706 STPathSet st;
1707 STAmount sa, da;
1708
1709 auto const& send_amt = XRP(10);
1710 std::tie(st, sa, da) =
1711 find_paths(env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
1712 BEAST_EXPECT(equal(da, send_amt));
1713 BEAST_EXPECT(equal(sa, A1["ABC"](1)));
1714 BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
1715 }
1716
1717 void
1719 {
1720 testcase("Path Find: non-XRP -> non-XRP, same currency");
1721 using namespace jtx;
1722 Env env = pathTestEnv();
1723 Account A1{"A1"};
1724 Account A2{"A2"};
1725 Account A3{"A3"};
1726 Account A4{"A4"};
1727 Account G1{"G1"};
1728 Account G2{"G2"};
1729 Account G3{"G3"};
1730 Account G4{"G4"};
1731 Account M1{"M1"};
1732 Account M2{"M2"};
1733
1734 env.fund(XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1735 env.fund(XRP(10'000), A4);
1736 env.fund(XRP(21'000), M1, M2);
1737 env.close();
1738
1739 env.trust(G1["HKD"](2'000), A1);
1740 env.trust(G2["HKD"](2'000), A2);
1741 env.trust(G1["HKD"](2'000), A3);
1742 env.trust(G1["HKD"](100'000), M1);
1743 env.trust(G2["HKD"](100'000), M1);
1744 env.trust(G1["HKD"](100'000), M2);
1745 env.trust(G2["HKD"](100'000), M2);
1746 env.close();
1747
1748 env(pay(G1, A1, G1["HKD"](1'000)));
1749 env(pay(G2, A2, G2["HKD"](1'000)));
1750 env(pay(G1, A3, G1["HKD"](1'000)));
1751 env(pay(G1, M1, G1["HKD"](1'200)));
1752 env(pay(G2, M1, G2["HKD"](5'000)));
1753 env(pay(G1, M2, G1["HKD"](1'200)));
1754 env(pay(G2, M2, G2["HKD"](5'000)));
1755 env.close();
1756
1757 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
1758 AMM ammM2XRP_G2(env, M2, XRP(10'000), G2["HKD"](1'010));
1759 AMM ammM2G1_XRP(env, M2, G1["HKD"](1'010), XRP(10'000));
1760
1761 STPathSet st;
1762 STAmount sa, da;
1763
1764 {
1765 // A) Borrow or repay --
1766 // Source -> Destination (repay source issuer)
1767 auto const& send_amt = G1["HKD"](10);
1768 std::tie(st, sa, da) = find_paths(
1769 env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
1770 BEAST_EXPECT(st.empty());
1771 BEAST_EXPECT(equal(da, send_amt));
1772 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1773 }
1774
1775 {
1776 // A2) Borrow or repay --
1777 // Source -> Destination (repay destination issuer)
1778 auto const& send_amt = A1["HKD"](10);
1779 std::tie(st, sa, da) = find_paths(
1780 env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
1781 BEAST_EXPECT(st.empty());
1782 BEAST_EXPECT(equal(da, send_amt));
1783 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1784 }
1785
1786 {
1787 // B) Common gateway --
1788 // Source -> AC -> Destination
1789 auto const& send_amt = A3["HKD"](10);
1790 std::tie(st, sa, da) = find_paths(
1791 env, A1, A3, send_amt, std::nullopt, G1["HKD"].currency);
1792 BEAST_EXPECT(equal(da, send_amt));
1793 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1794 BEAST_EXPECT(same(st, stpath(G1)));
1795 }
1796
1797 {
1798 // C) Gateway to gateway --
1799 // Source -> OB -> Destination
1800 auto const& send_amt = G2["HKD"](10);
1801 std::tie(st, sa, da) = find_paths(
1802 env, G1, G2, send_amt, std::nullopt, G1["HKD"].currency);
1803 BEAST_EXPECT(equal(da, send_amt));
1804 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1805 BEAST_EXPECT(same(
1806 st,
1807 stpath(IPE(G2["HKD"])),
1808 stpath(M1),
1809 stpath(M2),
1810 stpath(IPE(xrpIssue()), IPE(G2["HKD"]))));
1811 }
1812
1813 {
1814 // D) User to unlinked gateway via order book --
1815 // Source -> AC -> OB -> Destination
1816 auto const& send_amt = G2["HKD"](10);
1817 std::tie(st, sa, da) = find_paths(
1818 env, A1, G2, send_amt, std::nullopt, G1["HKD"].currency);
1819 BEAST_EXPECT(equal(da, send_amt));
1820 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1821 BEAST_EXPECT(same(
1822 st,
1823 stpath(G1, M1),
1824 stpath(G1, M2),
1825 stpath(G1, IPE(G2["HKD"])),
1826 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]))));
1827 }
1828
1829 {
1830 // I4) XRP bridge" --
1831 // Source -> AC -> OB to XRP -> OB from XRP -> AC ->
1832 // Destination
1833 auto const& send_amt = A2["HKD"](10);
1834 std::tie(st, sa, da) = find_paths(
1835 env, A1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1836 BEAST_EXPECT(equal(da, send_amt));
1837 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1838 BEAST_EXPECT(same(
1839 st,
1840 stpath(G1, M1, G2),
1841 stpath(G1, M2, G2),
1842 stpath(G1, IPE(G2["HKD"]), G2),
1843 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]), G2)));
1844 }
1845 }
1846
1847 void
1849 {
1850 testcase("Path Find: non-XRP -> non-XRP, same currency");
1851 using namespace jtx;
1852 Env env = pathTestEnv();
1853 Account A1{"A1"};
1854 Account A2{"A2"};
1855 Account A3{"A3"};
1856 Account G1{"G1"};
1857 Account G2{"G2"};
1858 Account M1{"M1"};
1859
1860 env.fund(XRP(11'000), M1);
1861 env.fund(XRP(1'000), A1, A2, A3, G1, G2);
1862 env.close();
1863
1864 env.trust(G1["HKD"](2'000), A1);
1865 env.trust(G2["HKD"](2'000), A2);
1866 env.trust(A2["HKD"](2'000), A3);
1867 env.trust(G1["HKD"](100'000), M1);
1868 env.trust(G2["HKD"](100'000), M1);
1869 env.close();
1870
1871 env(pay(G1, A1, G1["HKD"](1'000)));
1872 env(pay(G2, A2, G2["HKD"](1'000)));
1873 env(pay(G1, M1, G1["HKD"](5'000)));
1874 env(pay(G2, M1, G2["HKD"](5'000)));
1875 env.close();
1876
1877 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
1878
1879 // E) Gateway to user
1880 // Source -> OB -> AC -> Destination
1881 auto const& send_amt = A2["HKD"](10);
1882 STPathSet st;
1883 STAmount sa, da;
1884 std::tie(st, sa, da) =
1885 find_paths(env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1886 BEAST_EXPECT(equal(da, send_amt));
1887 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1888 BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
1889 }
1890
1891 void
1893 {
1894 testcase("falseDryChanges");
1895
1896 using namespace jtx;
1897
1898 Env env(*this, features);
1899
1900 env.fund(XRP(10'000), alice, gw);
1901 // This removes no ripple for carol,
1902 // different from the original test
1903 fund(env, gw, {carol}, XRP(10'000), {}, Fund::Acct);
1904 auto const AMMXRPPool = env.current()->fees().increment * 2;
1905 env.fund(reserve(env, 5) + ammCrtFee(env) + AMMXRPPool, bob);
1906 env.close();
1907 env.trust(USD(1'000), alice, bob, carol);
1908 env.trust(EUR(1'000), alice, bob, carol);
1909
1910 env(pay(gw, alice, EUR(50)));
1911 env(pay(gw, bob, USD(150)));
1912
1913 // Bob has _just_ slightly less than 50 xrp available
1914 // If his owner count changes, he will have more liquidity.
1915 // This is one error case to test (when Flow is used).
1916 // Computing the incoming xrp to the XRP/USD offer will require two
1917 // recursive calls to the EUR/XRP offer. The second call will return
1918 // tecPATH_DRY, but the entire path should not be marked as dry.
1919 // This is the second error case to test (when flowV1 is used).
1920 env(offer(bob, EUR(50), XRP(50)));
1921 AMM ammBob(env, bob, AMMXRPPool, USD(150));
1922
1923 env(pay(alice, carol, USD(1'000'000)),
1924 path(~XRP, ~USD),
1925 sendmax(EUR(500)),
1927
1928 auto const carolUSD = env.balance(carol, USD).value();
1929 BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50));
1930 }
1931
1932 void
1934 {
1935 testcase("Book Step");
1936
1937 using namespace jtx;
1938
1939 {
1940 // simple IOU/IOU offer
1941 Env env(*this, features);
1942
1943 fund(
1944 env,
1945 gw,
1946 {alice, bob, carol},
1947 XRP(10'000),
1948 {BTC(100), USD(150)},
1949 Fund::All);
1950
1951 AMM ammBob(env, bob, BTC(100), USD(150));
1952
1953 env(pay(alice, carol, USD(50)), path(~USD), sendmax(BTC(50)));
1954
1955 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
1956 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
1957 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1958 BEAST_EXPECT(expectLine(env, carol, USD(200)));
1959 BEAST_EXPECT(
1960 ammBob.expectBalances(BTC(150), USD(100), ammBob.tokens()));
1961 }
1962 {
1963 // simple IOU/XRP XRP/IOU offer
1964 Env env(*this, features);
1965
1966 fund(
1967 env,
1968 gw,
1969 {alice, carol, bob},
1970 XRP(10'000),
1971 {BTC(100), USD(150)},
1972 Fund::All);
1973
1974 AMM ammBobBTC_XRP(env, bob, BTC(100), XRP(150));
1975 AMM ammBobXRP_USD(env, bob, XRP(100), USD(150));
1976
1977 env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50)));
1978
1979 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
1980 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
1981 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1982 BEAST_EXPECT(expectLine(env, carol, USD(200)));
1983 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1984 BTC(150), XRP(100), ammBobBTC_XRP.tokens()));
1985 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
1986 XRP(150), USD(100), ammBobXRP_USD.tokens()));
1987 }
1988 {
1989 // simple XRP -> USD through offer and sendmax
1990 Env env(*this, features);
1991
1992 fund(
1993 env,
1994 gw,
1995 {alice, carol, bob},
1996 XRP(10'000),
1997 {USD(150)},
1998 Fund::All);
1999
2000 AMM ammBob(env, bob, XRP(100), USD(150));
2001
2002 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
2003
2004 BEAST_EXPECT(expectLedgerEntryRoot(
2005 env, alice, xrpMinusFee(env, 10'000 - 50)));
2006 BEAST_EXPECT(expectLedgerEntryRoot(
2007 env, bob, XRP(10'000) - XRP(100) - ammCrtFee(env)));
2008 BEAST_EXPECT(expectLine(env, bob, USD(0)));
2009 BEAST_EXPECT(expectLine(env, carol, USD(200)));
2010 BEAST_EXPECT(
2011 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
2012 }
2013 {
2014 // simple USD -> XRP through offer and sendmax
2015 Env env(*this, features);
2016
2017 fund(
2018 env,
2019 gw,
2020 {alice, carol, bob},
2021 XRP(10'000),
2022 {USD(100)},
2023 Fund::All);
2024
2025 AMM ammBob(env, bob, USD(100), XRP(150));
2026
2027 env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50)));
2028
2029 BEAST_EXPECT(expectLine(env, alice, USD(50)));
2030 BEAST_EXPECT(expectLedgerEntryRoot(
2031 env, bob, XRP(10'000) - XRP(150) - ammCrtFee(env)));
2032 BEAST_EXPECT(expectLine(env, bob, USD(0)));
2033 BEAST_EXPECT(expectLedgerEntryRoot(env, carol, XRP(10'000 + 50)));
2034 BEAST_EXPECT(
2035 ammBob.expectBalances(USD(150), XRP(100), ammBob.tokens()));
2036 }
2037 {
2038 // test unfunded offers are removed when payment succeeds
2039 Env env(*this, features);
2040
2041 env.fund(XRP(10'000), alice, carol, gw);
2042 env.fund(XRP(10'000), bob);
2043 env.close();
2044 env.trust(USD(1'000), alice, bob, carol);
2045 env.trust(BTC(1'000), alice, bob, carol);
2046 env.trust(EUR(1'000), alice, bob, carol);
2047 env.close();
2048
2049 env(pay(gw, alice, BTC(60)));
2050 env(pay(gw, bob, USD(200)));
2051 env(pay(gw, bob, EUR(150)));
2052 env.close();
2053
2054 env(offer(bob, BTC(50), USD(50)));
2055 env(offer(bob, BTC(40), EUR(50)));
2056 env.close();
2057 AMM ammBob(env, bob, EUR(100), USD(150));
2058
2059 // unfund offer
2060 env(pay(bob, gw, EUR(50)));
2061 BEAST_EXPECT(isOffer(env, bob, BTC(50), USD(50)));
2062 BEAST_EXPECT(isOffer(env, bob, BTC(40), EUR(50)));
2063
2064 env(pay(alice, carol, USD(50)),
2065 path(~USD),
2066 path(~EUR, ~USD),
2067 sendmax(BTC(60)));
2068
2069 env.require(balance(alice, BTC(10)));
2070 env.require(balance(bob, BTC(50)));
2071 env.require(balance(bob, USD(0)));
2072 env.require(balance(bob, EUR(0)));
2073 env.require(balance(carol, USD(50)));
2074 // used in the payment
2075 BEAST_EXPECT(!isOffer(env, bob, BTC(50), USD(50)));
2076 // found unfunded
2077 BEAST_EXPECT(!isOffer(env, bob, BTC(40), EUR(50)));
2078 // unchanged
2079 BEAST_EXPECT(
2080 ammBob.expectBalances(EUR(100), USD(150), ammBob.tokens()));
2081 }
2082 {
2083 // test unfunded offers are removed when the payment fails.
2084 // bob makes two offers: a funded 50 USD for 50 BTC and an
2085 // unfunded 50 EUR for 60 BTC. alice pays carol 61 USD with 61
2086 // BTC. alice only has 60 BTC, so the payment will fail. The
2087 // payment uses two paths: one through bob's funded offer and
2088 // one through his unfunded offer. When the payment fails `flow`
2089 // should return the unfunded offer. This test is intentionally
2090 // similar to the one that removes unfunded offers when the
2091 // payment succeeds.
2092 Env env(*this, features);
2093
2094 env.fund(XRP(10'000), bob, carol, gw);
2095 env.close();
2096 // Sets rippling on, this is different from
2097 // the original test
2098 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2099 env.trust(USD(1'000), alice, bob, carol);
2100 env.trust(BTC(1'000), alice, bob, carol);
2101 env.trust(EUR(1'000), alice, bob, carol);
2102 env.close();
2103
2104 env(pay(gw, alice, BTC(60)));
2105 env(pay(gw, bob, BTC(100)));
2106 env(pay(gw, bob, USD(100)));
2107 env(pay(gw, bob, EUR(50)));
2108 env(pay(gw, carol, EUR(1)));
2109 env.close();
2110
2111 // This is multiplath, which generates limited # of offers
2112 AMM ammBobBTC_USD(env, bob, BTC(50), USD(50));
2113 env(offer(bob, BTC(60), EUR(50)));
2114 env(offer(carol, BTC(1'000), EUR(1)));
2115 env(offer(bob, EUR(50), USD(50)));
2116
2117 // unfund offer
2118 env(pay(bob, gw, EUR(50)));
2119 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2120 BTC(50), USD(50), ammBobBTC_USD.tokens()));
2121 BEAST_EXPECT(isOffer(env, bob, BTC(60), EUR(50)));
2122 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
2123 BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50)));
2124
2125 auto flowJournal = env.app().logs().journal("Flow");
2126 auto const flowResult = [&] {
2127 STAmount deliver(USD(51));
2128 STAmount smax(BTC(61));
2129 PaymentSandbox sb(env.current().get(), tapNONE);
2131 auto IPE = [](Issue const& iss) {
2132 return STPathElement(
2134 xrpAccount(),
2135 iss.currency,
2136 iss.account);
2137 };
2138 {
2139 // BTC -> USD
2140 STPath p1({IPE(USD.issue())});
2141 paths.push_back(p1);
2142 // BTC -> EUR -> USD
2143 STPath p2({IPE(EUR.issue()), IPE(USD.issue())});
2144 paths.push_back(p2);
2145 }
2146
2147 return flow(
2148 sb,
2149 deliver,
2150 alice,
2151 carol,
2152 paths,
2153 false,
2154 false,
2155 true,
2157 std::nullopt,
2158 smax,
2159 std::nullopt,
2160 flowJournal);
2161 }();
2162
2163 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2164 env.app().openLedger().modify(
2165 [&](OpenView& view, beast::Journal j) {
2166 if (flowResult.removableOffers.empty())
2167 return false;
2168 Sandbox sb(&view, tapNONE);
2169 for (auto const& o : flowResult.removableOffers)
2170 if (auto ok = sb.peek(keylet::offer(o)))
2171 offerDelete(sb, ok, flowJournal);
2172 sb.apply(view);
2173 return true;
2174 });
2175
2176 // used in payment, but since payment failed should be untouched
2177 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2178 BTC(50), USD(50), ammBobBTC_USD.tokens()));
2179 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
2180 // found unfunded
2181 BEAST_EXPECT(!isOffer(env, bob, BTC(60), EUR(50)));
2182 }
2183 {
2184 // Do not produce more in the forward pass than the reverse pass
2185 // This test uses a path that whose reverse pass will compute a
2186 // 0.5 USD input required for a 1 EUR output. It sets a sendmax
2187 // of 0.4 USD, so the payment engine will need to do a forward
2188 // pass. Without limits, the 0.4 USD would produce 1000 EUR in
2189 // the forward pass. This test checks that the payment produces
2190 // 1 EUR, as expected.
2191
2192 Env env(*this, features);
2193 env.fund(XRP(10'000), bob, carol, gw);
2194 env.close();
2195 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2196 env.trust(USD(1'000), alice, bob, carol);
2197 env.trust(EUR(1'000), alice, bob, carol);
2198 env.close();
2199
2200 env(pay(gw, alice, USD(1'000)));
2201 env(pay(gw, bob, EUR(1'000)));
2202 env(pay(gw, bob, USD(1'000)));
2203 env.close();
2204
2205 // env(offer(bob, USD(1), drops(2)), txflags(tfPassive));
2206 AMM ammBob(env, bob, USD(8), XRPAmount{21});
2207 env(offer(bob, drops(1), EUR(1'000)), txflags(tfPassive));
2208
2209 env(pay(alice, carol, EUR(1)),
2210 path(~XRP, ~EUR),
2211 sendmax(USD(0.4)),
2213
2214 BEAST_EXPECT(expectLine(env, carol, EUR(1)));
2215 BEAST_EXPECT(ammBob.expectBalances(
2216 USD(8.4), XRPAmount{20}, ammBob.tokens()));
2217 }
2218 }
2219
2220 void
2222 {
2223 testcase("Transfer Rate");
2224
2225 using namespace jtx;
2226
2227 {
2228 // transfer fee on AMM
2229 Env env(*this, features);
2230
2231 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(1'000)});
2232 env(rate(gw, 1.25));
2233 env.close();
2234
2235 AMM ammBob(env, bob, XRP(100), USD(150));
2236 // no transfer fee on create
2237 BEAST_EXPECT(expectLine(env, bob, USD(1000 - 150)));
2238
2239 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
2240 env.close();
2241
2242 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 150)));
2243 BEAST_EXPECT(
2244 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
2245 BEAST_EXPECT(expectLedgerEntryRoot(
2246 env, alice, xrpMinusFee(env, 10'000 - 50)));
2247 BEAST_EXPECT(expectLine(env, carol, USD(1'050)));
2248 }
2249
2250 {
2251 // Transfer fee AMM and offer
2252 Env env(*this, features);
2253
2254 fund(
2255 env,
2256 gw,
2257 {alice, bob, carol},
2258 XRP(10'000),
2259 {USD(1'000), EUR(1'000)});
2260 env(rate(gw, 1.25));
2261 env.close();
2262
2263 AMM ammBob(env, bob, XRP(100), USD(140));
2264 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
2265
2266 env(offer(bob, USD(50), EUR(50)));
2267
2268 // alice buys 40EUR with 40XRP
2269 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
2270
2271 // 40XRP is swapped in for 40USD
2272 BEAST_EXPECT(
2273 ammBob.expectBalances(XRP(140), USD(100), ammBob.tokens()));
2274 // 40USD buys 40EUR via bob's offer. 40EUR delivered to carol
2275 // and bob pays 25% on 40EUR, 40EUR*0.25=10EUR
2276 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 40 - 40 * 0.25)));
2277 // bob gets 40USD back from the offer
2278 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 + 40)));
2279 BEAST_EXPECT(expectLedgerEntryRoot(
2280 env, alice, xrpMinusFee(env, 10'000 - 40)));
2281 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2282 BEAST_EXPECT(expectOffers(env, bob, 1, {{USD(10), EUR(10)}}));
2283 }
2284
2285 {
2286 // Transfer fee two consecutive AMM
2287 Env env(*this, features);
2288
2289 fund(
2290 env,
2291 gw,
2292 {alice, bob, carol},
2293 XRP(10'000),
2294 {USD(1'000), EUR(1'000)});
2295 env(rate(gw, 1.25));
2296 env.close();
2297
2298 AMM ammBobXRP_USD(env, bob, XRP(100), USD(140));
2299 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
2300
2301 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(140));
2302 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
2303 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
2304
2305 // alice buys 40EUR with 40XRP
2306 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
2307
2308 // 40XRP is swapped in for 40USD
2309 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
2310 XRP(140), USD(100), ammBobXRP_USD.tokens()));
2311 // 40USD is swapped in for 40EUR
2312 BEAST_EXPECT(ammBobUSD_EUR.expectBalances(
2313 USD(140), EUR(100), ammBobUSD_EUR.tokens()));
2314 // no other charges on bob
2315 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
2316 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
2317 BEAST_EXPECT(expectLedgerEntryRoot(
2318 env, alice, xrpMinusFee(env, 10'000 - 40)));
2319 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2320 }
2321
2322 {
2323 // Payment via AMM with limit quality, deliver less
2324 // than requested
2325 Env env(*this, features);
2326
2327 fund(
2328 env,
2329 gw,
2330 {alice, bob, carol},
2331 XRP(1'000),
2332 {USD(1'200), GBP(1'200)});
2333 env(rate(gw, 1.25));
2334 env.close();
2335
2336 AMM amm(env, bob, GBP(1'000), USD(1'100));
2337
2338 // requested quality limit is 90USD/110GBP = 0.8181
2339 // trade quality is 77.2727USD/94.4444GBP = 0.8181
2340 env(pay(alice, carol, USD(90)),
2341 path(~USD),
2342 sendmax(GBP(110)),
2344 env.close();
2345
2346 if (!features[fixAMMv1_1])
2347 {
2348 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
2349 // on 75.5555GBP
2350 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
2351 BEAST_EXPECT(expectLine(
2352 env,
2353 alice,
2354 STAmount{GBP, UINT64_C(1'105'555555555555), -12}));
2355 // 75.5555GBP is swapped in for 77.7272USD
2356 BEAST_EXPECT(amm.expectBalances(
2357 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
2358 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2359 amm.tokens()));
2360 }
2361 else
2362 {
2363 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
2364 // on 75.5555GBP
2365 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
2366 BEAST_EXPECT(expectLine(
2367 env,
2368 alice,
2369 STAmount{GBP, UINT64_C(1'105'555555555554), -12}));
2370 // 75.5555GBP is swapped in for 77.7272USD
2371 BEAST_EXPECT(amm.expectBalances(
2372 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
2373 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2374 amm.tokens()));
2375 }
2376 BEAST_EXPECT(expectLine(
2377 env, carol, STAmount{USD, UINT64_C(1'277'272727272728), -12}));
2378 }
2379
2380 {
2381 // AMM offer crossing
2382 Env env(*this, features);
2383
2384 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'200), EUR(1'200)});
2385 env(rate(gw, 1.25));
2386 env.close();
2387
2388 AMM amm(env, bob, USD(1'000), EUR(1'150));
2389
2390 env(offer(alice, EUR(100), USD(100)));
2391 env.close();
2392
2393 if (!features[fixAMMv1_1])
2394 {
2395 // 95.2380USD is swapped in for 100EUR
2396 BEAST_EXPECT(amm.expectBalances(
2397 STAmount{USD, UINT64_C(1'095'238095238095), -12},
2398 EUR(1'050),
2399 amm.tokens()));
2400 // alice pays 25% tr fee on 95.2380USD
2401 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
2402 BEAST_EXPECT(expectLine(
2403 env,
2404 alice,
2405 STAmount{USD, UINT64_C(1'080'952380952381), -12},
2406 EUR(1'300)));
2407 }
2408 else
2409 {
2410 // 95.2380USD is swapped in for 100EUR
2411 BEAST_EXPECT(amm.expectBalances(
2412 STAmount{USD, UINT64_C(1'095'238095238096), -12},
2413 EUR(1'050),
2414 amm.tokens()));
2415 // alice pays 25% tr fee on 95.2380USD
2416 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
2417 BEAST_EXPECT(expectLine(
2418 env,
2419 alice,
2420 STAmount{USD, UINT64_C(1'080'95238095238), -11},
2421 EUR(1'300)));
2422 }
2423 BEAST_EXPECT(expectOffers(env, alice, 0));
2424 }
2425
2426 {
2427 // First pass through a strand redeems, second pass issues,
2428 // through an offer limiting step is not an endpoint
2429 Env env(*this, features);
2430 auto const USDA = alice["USD"];
2431 auto const USDB = bob["USD"];
2432 Account const dan("dan");
2433
2434 env.fund(XRP(10'000), bob, carol, dan, gw);
2435 fund(env, {alice}, XRP(10'000));
2436 env(rate(gw, 1.25));
2437 env.trust(USD(2'000), alice, bob, carol, dan);
2438 env.trust(EUR(2'000), carol, dan);
2439 env.trust(USDA(1'000), bob);
2440 env.trust(USDB(1'000), gw);
2441 env(pay(gw, bob, USD(50)));
2442 env(pay(gw, dan, EUR(1'050)));
2443 env(pay(gw, dan, USD(1'000)));
2444 AMM ammDan(env, dan, USD(1'000), EUR(1'050));
2445
2446 if (!features[fixAMMv1_1])
2447 {
2448 // alice -> bob -> gw -> carol. $50 should have transfer fee;
2449 // $10, no fee
2450 env(pay(alice, carol, EUR(50)),
2451 path(bob, gw, ~EUR),
2452 sendmax(USDA(60)),
2454 BEAST_EXPECT(ammDan.expectBalances(
2455 USD(1'050), EUR(1'000), ammDan.tokens()));
2456 BEAST_EXPECT(expectLine(env, dan, USD(0)));
2457 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
2458 BEAST_EXPECT(expectLine(env, bob, USD(-10)));
2459 BEAST_EXPECT(expectLine(env, bob, USDA(60)));
2460 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
2461 }
2462 else
2463 {
2464 // alice -> bob -> gw -> carol. $50 should have transfer fee;
2465 // $10, no fee
2466 env(pay(alice, carol, EUR(50)),
2467 path(bob, gw, ~EUR),
2468 sendmax(USDA(60.1)),
2470 BEAST_EXPECT(ammDan.expectBalances(
2471 STAmount{USD, UINT64_C(1'050'000000000001), -12},
2472 EUR(1'000),
2473 ammDan.tokens()));
2474 BEAST_EXPECT(expectLine(env, dan, USD(0)));
2475 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
2476 BEAST_EXPECT(expectLine(
2477 env, bob, STAmount{USD, INT64_C(-10'000000000001), -12}));
2478 BEAST_EXPECT(expectLine(
2479 env, bob, STAmount{USDA, UINT64_C(60'000000000001), -12}));
2480 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
2481 }
2482 }
2483 }
2484
2485 void
2487 {
2488 testcase("No Owner Fee");
2489 using namespace jtx;
2490
2491 {
2492 // payment via AMM
2493 Env env(*this, features);
2494
2495 fund(
2496 env,
2497 gw,
2498 {alice, bob, carol},
2499 XRP(1'000),
2500 {USD(1'000), GBP(1'000)});
2501 env(rate(gw, 1.25));
2502 env.close();
2503
2504 AMM amm(env, bob, GBP(1'000), USD(1'000));
2505
2506 env(pay(alice, carol, USD(100)),
2507 path(~USD),
2508 sendmax(GBP(150)),
2510 env.close();
2511
2512 // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
2513 // 1,000 - 120*1.25 = 850GBP
2514 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2515 if (!features[fixAMMv1_1])
2516 {
2517 // 120GBP is swapped in for 107.1428USD
2518 BEAST_EXPECT(amm.expectBalances(
2519 GBP(1'120),
2520 STAmount{USD, UINT64_C(892'8571428571428), -13},
2521 amm.tokens()));
2522 }
2523 else
2524 {
2525 BEAST_EXPECT(amm.expectBalances(
2526 GBP(1'120),
2527 STAmount{USD, UINT64_C(892'8571428571429), -13},
2528 amm.tokens()));
2529 }
2530 // 25% of 85.7142USD is paid in tr fee
2531 // 85.7142*1.25 = 107.1428USD
2532 BEAST_EXPECT(expectLine(
2533 env, carol, STAmount(USD, UINT64_C(1'085'714285714286), -12)));
2534 }
2535
2536 {
2537 // Payment via offer and AMM
2538 Env env(*this, features);
2539 Account const ed("ed");
2540
2541 fund(
2542 env,
2543 gw,
2544 {alice, bob, carol, ed},
2545 XRP(1'000),
2546 {USD(1'000), EUR(1'000), GBP(1'000)});
2547 env(rate(gw, 1.25));
2548 env.close();
2549
2550 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
2551 env.close();
2552
2553 AMM amm(env, bob, EUR(1'000), USD(1'000));
2554
2555 env(pay(alice, carol, USD(100)),
2556 path(~EUR, ~USD),
2557 sendmax(GBP(150)),
2559 env.close();
2560
2561 // alice buys 120EUR with 120GBP via the offer
2562 // and pays 25% tr fee on 120GBP
2563 // 1,000 - 120*1.25 = 850GBP
2564 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2565 // consumed offer is 120GBP/120EUR
2566 // ed doesn't pay tr fee
2567 BEAST_EXPECT(expectLine(env, ed, EUR(880), GBP(1'120)));
2568 BEAST_EXPECT(
2569 expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}}));
2570 // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR
2571 // 96EUR is swapped in for 87.5912USD
2572 BEAST_EXPECT(amm.expectBalances(
2573 EUR(1'096),
2574 STAmount{USD, UINT64_C(912'4087591240876), -13},
2575 amm.tokens()));
2576 // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD
2577 BEAST_EXPECT(expectLine(
2578 env, carol, STAmount(USD, UINT64_C(1'070'07299270073), -11)));
2579 }
2580 {
2581 // Payment via AMM, AMM
2582 Env env(*this, features);
2583 Account const ed("ed");
2584
2585 fund(
2586 env,
2587 gw,
2588 {alice, bob, carol, ed},
2589 XRP(1'000),
2590 {USD(1'000), EUR(1'000), GBP(1'000)});
2591 env(rate(gw, 1.25));
2592 env.close();
2593
2594 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
2595 AMM amm2(env, ed, EUR(1'000), USD(1'000));
2596
2597 env(pay(alice, carol, USD(100)),
2598 path(~EUR, ~USD),
2599 sendmax(GBP(150)),
2601 env.close();
2602
2603 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2604 if (!features[fixAMMv1_1])
2605 {
2606 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
2607 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
2608 // 107.1428EUR
2609 BEAST_EXPECT(amm1.expectBalances(
2610 GBP(1'120),
2611 STAmount{EUR, UINT64_C(892'8571428571428), -13},
2612 amm1.tokens()));
2613 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
2614 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
2615 BEAST_EXPECT(amm2.expectBalances(
2616 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
2617 STAmount{USD, UINT64_C(921'0526315789471), -13},
2618 amm2.tokens()));
2619 }
2620 else
2621 {
2622 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
2623 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
2624 // 107.1428EUR
2625 BEAST_EXPECT(amm1.expectBalances(
2626 GBP(1'120),
2627 STAmount{EUR, UINT64_C(892'8571428571429), -13},
2628 amm1.tokens()));
2629 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
2630 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
2631 BEAST_EXPECT(amm2.expectBalances(
2632 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
2633 STAmount{USD, UINT64_C(921'052631578948), -12},
2634 amm2.tokens()));
2635 }
2636 // 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD
2637 BEAST_EXPECT(expectLine(
2638 env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12)));
2639 }
2640 {
2641 // AMM offer crossing
2642 Env env(*this, features);
2643
2644 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'100), EUR(1'100)});
2645 env(rate(gw, 1.25));
2646 env.close();
2647
2648 AMM amm(env, bob, USD(1'000), EUR(1'100));
2649 env(offer(alice, EUR(100), USD(100)));
2650 env.close();
2651
2652 // 100USD is swapped in for 100EUR
2653 BEAST_EXPECT(
2654 amm.expectBalances(USD(1'100), EUR(1'000), amm.tokens()));
2655 // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD
2656 BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200)));
2657 BEAST_EXPECT(expectOffers(env, alice, 0));
2658 }
2659
2660 {
2661 // Payment via AMM with limit quality
2662 Env env(*this, features);
2663
2664 fund(
2665 env,
2666 gw,
2667 {alice, bob, carol},
2668 XRP(1'000),
2669 {USD(1'000), GBP(1'000)});
2670 env(rate(gw, 1.25));
2671 env.close();
2672
2673 AMM amm(env, bob, GBP(1'000), USD(1'000));
2674
2675 // requested quality limit is 100USD/178.58GBP = 0.55997
2676 // trade quality is 100USD/178.5714 = 0.55999
2677 env(pay(alice, carol, USD(100)),
2678 path(~USD),
2679 sendmax(GBP(178.58)),
2681 env.close();
2682
2683 // alice buys 125USD with 142.8571GBP and pays 25% tr fee
2684 // on 142.8571GBP
2685 // 1,000 - 142.8571*1.25 = 821.4285GBP
2686 BEAST_EXPECT(expectLine(
2687 env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13)));
2688 // 142.8571GBP is swapped in for 125USD
2689 BEAST_EXPECT(amm.expectBalances(
2690 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2691 USD(875),
2692 amm.tokens()));
2693 // 25% on 100USD is paid in tr fee
2694 // 100*1.25 = 125USD
2695 BEAST_EXPECT(expectLine(env, carol, USD(1'100)));
2696 }
2697 {
2698 // Payment via AMM with limit quality, deliver less
2699 // than requested
2700 Env env(*this, features);
2701
2702 fund(
2703 env,
2704 gw,
2705 {alice, bob, carol},
2706 XRP(1'000),
2707 {USD(1'200), GBP(1'200)});
2708 env(rate(gw, 1.25));
2709 env.close();
2710
2711 AMM amm(env, bob, GBP(1'000), USD(1'200));
2712
2713 // requested quality limit is 90USD/120GBP = 0.75
2714 // trade quality is 22.5USD/30GBP = 0.75
2715 env(pay(alice, carol, USD(90)),
2716 path(~USD),
2717 sendmax(GBP(120)),
2719 env.close();
2720
2721 if (!features[fixAMMv1_1])
2722 {
2723 // alice buys 28.125USD with 24GBP and pays 25% tr fee
2724 // on 24GBP
2725 // 1,200 - 24*1.25 = 1,170GBP
2726 BEAST_EXPECT(expectLine(env, alice, GBP(1'170)));
2727 // 24GBP is swapped in for 28.125USD
2728 BEAST_EXPECT(amm.expectBalances(
2729 GBP(1'024), USD(1'171.875), amm.tokens()));
2730 }
2731 else
2732 {
2733 // alice buys 28.125USD with 24GBP and pays 25% tr fee
2734 // on 24GBP
2735 // 1,200 - 24*1.25 =~ 1,170GBP
2736 BEAST_EXPECT(expectLine(
2737 env,
2738 alice,
2739 STAmount{GBP, UINT64_C(1'169'999999999999), -12}));
2740 // 24GBP is swapped in for 28.125USD
2741 BEAST_EXPECT(amm.expectBalances(
2742 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2743 USD(1'171.875),
2744 amm.tokens()));
2745 }
2746 // 25% on 22.5USD is paid in tr fee
2747 // 22.5*1.25 = 28.125USD
2748 BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
2749 }
2750 {
2751 // Payment via offer and AMM with limit quality, deliver less
2752 // than requested
2753 Env env(*this, features);
2754 Account const ed("ed");
2755
2756 fund(
2757 env,
2758 gw,
2759 {alice, bob, carol, ed},
2760 XRP(1'000),
2761 {USD(1'400), EUR(1'400), GBP(1'400)});
2762 env(rate(gw, 1.25));
2763 env.close();
2764
2765 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
2766 env.close();
2767
2768 AMM amm(env, bob, EUR(1'000), USD(1'400));
2769
2770 // requested quality limit is 95USD/140GBP = 0.6785
2771 // trade quality is 59.7321USD/88.0262GBP = 0.6785
2772 env(pay(alice, carol, USD(95)),
2773 path(~EUR, ~USD),
2774 sendmax(GBP(140)),
2776 env.close();
2777
2778 if (!features[fixAMMv1_1])
2779 {
2780 // alice buys 70.4210EUR with 70.4210GBP via the offer
2781 // and pays 25% tr fee on 70.4210GBP
2782 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
2783 BEAST_EXPECT(expectLine(
2784 env,
2785 alice,
2786 STAmount{GBP, UINT64_C(1'311'973684210527), -12}));
2787 // ed doesn't pay tr fee, the balances reflect consumed offer
2788 // 70.4210GBP/70.4210EUR
2789 BEAST_EXPECT(expectLine(
2790 env,
2791 ed,
2792 STAmount{EUR, UINT64_C(1'329'578947368421), -12},
2793 STAmount{GBP, UINT64_C(1'470'421052631579), -12}));
2794 BEAST_EXPECT(expectOffers(
2795 env,
2796 ed,
2797 1,
2798 {Amounts{
2799 STAmount{GBP, UINT64_C(929'5789473684212), -13},
2800 STAmount{EUR, UINT64_C(929'5789473684212), -13}}}));
2801 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
2802 // 56.3368EUR is swapped in for 74.6651USD
2803 BEAST_EXPECT(amm.expectBalances(
2804 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2805 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2806 amm.tokens()));
2807 }
2808 else
2809 {
2810 // alice buys 70.4210EUR with 70.4210GBP via the offer
2811 // and pays 25% tr fee on 70.4210GBP
2812 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
2813 BEAST_EXPECT(expectLine(
2814 env,
2815 alice,
2816 STAmount{GBP, UINT64_C(1'311'973684210525), -12}));
2817 // ed doesn't pay tr fee, the balances reflect consumed offer
2818 // 70.4210GBP/70.4210EUR
2819 BEAST_EXPECT(expectLine(
2820 env,
2821 ed,
2822 STAmount{EUR, UINT64_C(1'329'57894736842), -11},
2823 STAmount{GBP, UINT64_C(1'470'42105263158), -11}));
2824 BEAST_EXPECT(expectOffers(
2825 env,
2826 ed,
2827 1,
2828 {Amounts{
2829 STAmount{GBP, UINT64_C(929'57894736842), -11},
2830 STAmount{EUR, UINT64_C(929'57894736842), -11}}}));
2831 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
2832 // 56.3368EUR is swapped in for 74.6651USD
2833 BEAST_EXPECT(amm.expectBalances(
2834 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2835 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2836 amm.tokens()));
2837 }
2838 // 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD
2839 BEAST_EXPECT(expectLine(
2840 env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12)));
2841 }
2842 {
2843 // Payment via AMM and offer with limit quality, deliver less
2844 // than requested
2845 Env env(*this, features);
2846 Account const ed("ed");
2847
2848 fund(
2849 env,
2850 gw,
2851 {alice, bob, carol, ed},
2852 XRP(1'000),
2853 {USD(1'400), EUR(1'400), GBP(1'400)});
2854 env(rate(gw, 1.25));
2855 env.close();
2856
2857 AMM amm(env, bob, GBP(1'000), EUR(1'000));
2858
2859 env(offer(ed, EUR(1'000), USD(1'400)), txflags(tfPassive));
2860 env.close();
2861
2862 // requested quality limit is 95USD/140GBP = 0.6785
2863 // trade quality is 47.7857USD/70.4210GBP = 0.6785
2864 env(pay(alice, carol, USD(95)),
2865 path(~EUR, ~USD),
2866 sendmax(GBP(140)),
2868 env.close();
2869
2870 if (!features[fixAMMv1_1])
2871 {
2872 // alice buys 53.3322EUR with 56.3368GBP via the amm
2873 // and pays 25% tr fee on 56.3368GBP
2874 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
2875 BEAST_EXPECT(expectLine(
2876 env,
2877 alice,
2878 STAmount{GBP, UINT64_C(1'329'578947368421), -12}));
2881 // 56.3368GBP is swapped in for 53.3322EUR
2882 BEAST_EXPECT(amm.expectBalances(
2883 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2884 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2885 amm.tokens()));
2886 }
2887 else
2888 {
2889 // alice buys 53.3322EUR with 56.3368GBP via the amm
2890 // and pays 25% tr fee on 56.3368GBP
2891 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
2892 BEAST_EXPECT(expectLine(
2893 env,
2894 alice,
2895 STAmount{GBP, UINT64_C(1'329'57894736842), -11}));
2898 // 56.3368GBP is swapped in for 53.3322EUR
2899 BEAST_EXPECT(amm.expectBalances(
2900 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2901 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2902 amm.tokens()));
2903 }
2904 // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
2905 // 42.6658EUR/59.7321USD
2906 BEAST_EXPECT(expectLine(
2907 env,
2908 ed,
2909 STAmount{USD, UINT64_C(1'340'267857142857), -12},
2910 STAmount{EUR, UINT64_C(1'442'665816326531), -12}));
2911 BEAST_EXPECT(expectOffers(
2912 env,
2913 ed,
2914 1,
2915 {Amounts{
2916 STAmount{EUR, UINT64_C(957'3341836734693), -13},
2917 STAmount{USD, UINT64_C(1'340'267857142857), -12}}}));
2918 // 25% on 47.7857USD is paid in tr fee 47.7857*1.25 = 59.7321USD
2919 BEAST_EXPECT(expectLine(
2920 env, carol, STAmount(USD, UINT64_C(1'447'785714285714), -12)));
2921 }
2922 {
2923 // Payment via AMM, AMM with limit quality, deliver less
2924 // than requested
2925 Env env(*this, features);
2926 Account const ed("ed");
2927
2928 fund(
2929 env,
2930 gw,
2931 {alice, bob, carol, ed},
2932 XRP(1'000),
2933 {USD(1'400), EUR(1'400), GBP(1'400)});
2934 env(rate(gw, 1.25));
2935 env.close();
2936
2937 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
2938 AMM amm2(env, ed, EUR(1'000), USD(1'400));
2939
2940 // requested quality limit is 90USD/145GBP = 0.6206
2941 // trade quality is 66.7432USD/107.5308GBP = 0.6206
2942 env(pay(alice, carol, USD(90)),
2943 path(~EUR, ~USD),
2944 sendmax(GBP(145)),
2946 env.close();
2947
2948 if (!features[fixAMMv1_1])
2949 {
2950 // alice buys 53.3322EUR with 107.5308GBP
2951 // 25% on 86.0246GBP is paid in tr fee
2952 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
2953 BEAST_EXPECT(expectLine(
2954 env,
2955 alice,
2956 STAmount{GBP, UINT64_C(1'292'469135802469), -12}));
2957 // 86.0246GBP is swapped in for 79.2106EUR
2958 BEAST_EXPECT(amm1.expectBalances(
2959 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2960 STAmount{EUR, UINT64_C(920'78937795562), -11},
2961 amm1.tokens()));
2962 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2963 // 63.3684EUR is swapped in for 83.4291USD
2964 BEAST_EXPECT(amm2.expectBalances(
2965 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2966 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2967 amm2.tokens()));
2968 }
2969 else
2970 {
2971 // alice buys 53.3322EUR with 107.5308GBP
2972 // 25% on 86.0246GBP is paid in tr fee
2973 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
2974 BEAST_EXPECT(expectLine(
2975 env,
2976 alice,
2977 STAmount{GBP, UINT64_C(1'292'469135802466), -12}));
2978 // 86.0246GBP is swapped in for 79.2106EUR
2979 BEAST_EXPECT(amm1.expectBalances(
2980 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2981 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2982 amm1.tokens()));
2983 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2984 // 63.3684EUR is swapped in for 83.4291USD
2985 BEAST_EXPECT(amm2.expectBalances(
2986 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2987 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2988 amm2.tokens()));
2989 }
2990 // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
2991 BEAST_EXPECT(expectLine(
2992 env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12)));
2993 }
2994 {
2995 // Payment by the issuer via AMM, AMM with limit quality,
2996 // deliver less than requested
2997 Env env(*this, features);
2998
2999 fund(
3000 env,
3001 gw,
3002 {alice, bob, carol},
3003 XRP(1'000),
3004 {USD(1'400), EUR(1'400), GBP(1'400)});
3005 env(rate(gw, 1.25));
3006 env.close();
3007
3008 AMM amm1(env, alice, GBP(1'000), EUR(1'000));
3009 AMM amm2(env, bob, EUR(1'000), USD(1'400));
3010
3011 // requested quality limit is 90USD/120GBP = 0.75
3012 // trade quality is 81.1111USD/108.1481GBP = 0.75
3013 env(pay(gw, carol, USD(90)),
3014 path(~EUR, ~USD),
3015 sendmax(GBP(120)),
3017 env.close();
3018
3019 if (!features[fixAMMv1_1])
3020 {
3021 // 108.1481GBP is swapped in for 97.5935EUR
3022 BEAST_EXPECT(amm1.expectBalances(
3023 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
3024 STAmount{EUR, UINT64_C(902'4064171122988), -13},
3025 amm1.tokens()));
3026 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
3027 // 78.0748EUR is swapped in for 101.3888USD
3028 BEAST_EXPECT(amm2.expectBalances(
3029 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
3030 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3031 amm2.tokens()));
3032 }
3033 else
3034 {
3035 // 108.1481GBP is swapped in for 97.5935EUR
3036 BEAST_EXPECT(amm1.expectBalances(
3037 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
3038 STAmount{EUR, UINT64_C(902'4064171122975), -13},
3039 amm1.tokens()));
3040 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
3041 // 78.0748EUR is swapped in for 101.3888USD
3042 BEAST_EXPECT(amm2.expectBalances(
3043 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
3044 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3045 amm2.tokens()));
3046 }
3047 // 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD
3048 BEAST_EXPECT(expectLine(
3049 env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12}));
3050 }
3051 }
3052
3053 void
3055 {
3056 // Single path with amm, offer, and limit quality. The quality limit
3057 // is such that the first offer should be taken but the second
3058 // should not. The total amount delivered should be the sum of the
3059 // two offers and sendMax should be more than the first offer.
3060 testcase("limitQuality");
3061 using namespace jtx;
3062
3063 {
3064 Env env(*this);
3065
3066 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
3067
3068 AMM ammBob(env, bob, XRP(1'000), USD(1'050));
3069 env(offer(bob, XRP(100), USD(50)));
3070
3071 env(pay(alice, carol, USD(100)),
3072 path(~USD),
3073 sendmax(XRP(100)),
3075
3076 BEAST_EXPECT(
3077 ammBob.expectBalances(XRP(1'050), USD(1'000), ammBob.tokens()));
3078 BEAST_EXPECT(expectLine(env, carol, USD(2'050)));
3079 BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}}));
3080 }
3081 }
3082
3083 void
3085 {
3086 testcase("Circular XRP");
3087
3088 using namespace jtx;
3089
3090 for (auto const withFix : {true, false})
3091 {
3092 auto const feats = withFix
3094 : supported_amendments() - FeatureBitset{fix1781};
3095
3096 // Payment path starting with XRP
3097 Env env(*this, feats);
3098 // Note, if alice doesn't have default ripple, then pay
3099 // fails with tecPATH_DRY.
3100 fund(
3101 env,
3102 gw,
3103 {alice, bob},
3104 XRP(10'000),
3105 {USD(200), EUR(200)},
3106 Fund::All);
3107
3108 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(101));
3109 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(101));
3110 env.close();
3111
3112 TER const expectedTer =
3113 withFix ? TER{temBAD_PATH_LOOP} : TER{tesSUCCESS};
3114 env(pay(alice, bob, EUR(1)),
3115 path(~USD, ~XRP, ~EUR),
3116 sendmax(XRP(1)),
3118 ter(expectedTer));
3119 }
3120 {
3121 // Payment path ending with XRP
3122 Env env(*this);
3123 // Note, if alice doesn't have default ripple, then pay fails
3124 // with tecPATH_DRY.
3125 fund(
3126 env,
3127 gw,
3128 {alice, bob},
3129 XRP(10'000),
3130 {USD(200), EUR(200)},
3131 Fund::All);
3132
3133 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
3134 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
3135 // EUR -> //XRP -> //USD ->XRP
3136 env(pay(alice, bob, XRP(1)),
3137 path(~XRP, ~USD, ~XRP),
3138 sendmax(EUR(1)),
3141 }
3142 {
3143 // Payment where loop is formed in the middle of the path, not
3144 // on an endpoint
3145 auto const JPY = gw["JPY"];
3146 Env env(*this);
3147 // Note, if alice doesn't have default ripple, then pay fails
3148 // with tecPATH_DRY.
3149 fund(
3150 env,
3151 gw,
3152 {alice, bob},
3153 XRP(10'000),
3154 {USD(200), EUR(200), JPY(200)},
3155 Fund::All);
3156
3157 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
3158 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
3159 AMM ammAliceXRP_JPY(env, alice, XRP(100), JPY(100));
3160
3161 env(pay(alice, bob, JPY(1)),
3162 path(~XRP, ~EUR, ~XRP, ~JPY),
3163 sendmax(USD(1)),
3166 }
3167 }
3168
3169 void
3171 {
3172 testcase("Step Limit");
3173
3174 using namespace jtx;
3175 Env env(*this, features);
3176 auto const dan = Account("dan");
3177 auto const ed = Account("ed");
3178
3179 fund(env, gw, {ed}, XRP(100'000'000), {USD(11)});
3180 env.fund(XRP(100'000'000), alice, bob, carol, dan);
3181 env.close();
3182 env.trust(USD(1), bob);
3183 env(pay(gw, bob, USD(1)));
3184 env.trust(USD(1), dan);
3185 env(pay(gw, dan, USD(1)));
3186 n_offers(env, 2'000, bob, XRP(1), USD(1));
3187 n_offers(env, 1, dan, XRP(1), USD(1));
3188 AMM ammEd(env, ed, XRP(9), USD(11));
3189
3190 // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
3191 // offer, removes 999 more as unfunded, then hits the step limit.
3192 env(offer(alice, USD(1'000), XRP(1'000)));
3193 if (!features[fixAMMv1_1])
3194 env.require(balance(
3195 alice, STAmount{USD, UINT64_C(2'050126257867561), -15}));
3196 else
3197 env.require(balance(
3198 alice, STAmount{USD, UINT64_C(2'050125257867587), -15}));
3199 env.require(owners(alice, 2));
3200 env.require(balance(bob, USD(0)));
3201 env.require(owners(bob, 1'001));
3202 env.require(balance(dan, USD(1)));
3203 env.require(owners(dan, 2));
3204
3205 // Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
3206 // 1000 offers as unfunded and hits the step limit.
3207 env(offer(carol, USD(1'000), XRP(1'000)));
3208 env.require(balance(carol, USD(none)));
3209 env.require(owners(carol, 1));
3210 env.require(balance(bob, USD(0)));
3211 env.require(owners(bob, 1));
3212 env.require(balance(dan, USD(1)));
3213 env.require(owners(dan, 2));
3214 }
3215
3216 void
3218 {
3219 testcase("Convert all of an asset using DeliverMin");
3220
3221 using namespace jtx;
3222
3223 {
3224 Env env(*this, features);
3225 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3226 env.trust(USD(100), alice, bob, carol);
3227 env(pay(alice, bob, USD(10)),
3228 delivermin(USD(10)),
3230 env(pay(alice, bob, USD(10)),
3231 delivermin(USD(-5)),
3234 env(pay(alice, bob, USD(10)),
3235 delivermin(XRP(5)),
3238 env(pay(alice, bob, USD(10)),
3239 delivermin(Account(carol)["USD"](5)),
3242 env(pay(alice, bob, USD(10)),
3243 delivermin(USD(15)),
3246 env(pay(gw, carol, USD(50)));
3247 AMM ammCarol(env, carol, XRP(10), USD(15));
3248 env(pay(alice, bob, USD(10)),
3249 paths(XRP),
3250 delivermin(USD(7)),
3252 sendmax(XRP(5)),
3254 env.require(balance(
3255 alice,
3256 drops(10'000'000'000 - env.current()->fees().base.drops())));
3257 env.require(balance(bob, XRP(10'000)));
3258 }
3259
3260 {
3261 Env env(*this, features);
3262 fund(env, gw, {alice, bob}, XRP(10'000));
3263 env.trust(USD(1'100), alice, bob);
3264 env(pay(gw, bob, USD(1'100)));
3265 AMM ammBob(env, bob, XRP(1'000), USD(1'100));
3266 env(pay(alice, alice, USD(10'000)),
3267 paths(XRP),
3268 delivermin(USD(100)),
3270 sendmax(XRP(100)));
3271 env.require(balance(alice, USD(100)));
3272 }
3273
3274 {
3275 Env env(*this, features);
3276 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3277 env.trust(USD(1'200), bob, carol);
3278 env(pay(gw, bob, USD(1'200)));
3279 AMM ammBob(env, bob, XRP(5'500), USD(1'200));
3280 env(pay(alice, carol, USD(10'000)),
3281 paths(XRP),
3282 delivermin(USD(200)),
3284 sendmax(XRP(1'000)),
3286 env(pay(alice, carol, USD(10'000)),
3287 paths(XRP),
3288 delivermin(USD(200)),
3290 sendmax(XRP(1'100)));
3291 BEAST_EXPECT(
3292 ammBob.expectBalances(XRP(6'600), USD(1'000), ammBob.tokens()));
3293 env.require(balance(carol, USD(200)));
3294 }
3295
3296 {
3297 auto const dan = Account("dan");
3298 Env env(*this, features);
3299 fund(env, gw, {alice, bob, carol, dan}, XRP(10'000));
3300 env.close();
3301 env.trust(USD(1'100), bob, carol, dan);
3302 env(pay(gw, bob, USD(100)));
3303 env(pay(gw, dan, USD(1'100)));
3304 env(offer(bob, XRP(100), USD(100)));
3305 env(offer(bob, XRP(1'000), USD(100)));
3306 AMM ammDan(env, dan, XRP(1'000), USD(1'100));
3307 if (!features[fixAMMv1_1])
3308 {
3309 env(pay(alice, carol, USD(10'000)),
3310 paths(XRP),
3311 delivermin(USD(200)),
3313 sendmax(XRP(200)));
3314 env.require(balance(bob, USD(0)));
3315 env.require(balance(carol, USD(200)));
3316 BEAST_EXPECT(ammDan.expectBalances(
3317 XRP(1'100), USD(1'000), ammDan.tokens()));
3318 }
3319 else
3320 {
3321 env(pay(alice, carol, USD(10'000)),
3322 paths(XRP),
3323 delivermin(USD(200)),
3325 sendmax(XRPAmount(200'000'001)));
3326 env.require(balance(bob, USD(0)));
3327 env.require(balance(
3328 carol, STAmount{USD, UINT64_C(200'00000090909), -11}));
3329 BEAST_EXPECT(ammDan.expectBalances(
3330 XRPAmount{1'100'000'001},
3331 STAmount{USD, UINT64_C(999'99999909091), -11},
3332 ammDan.tokens()));
3333 }
3334 }
3335 }
3336
3337 void
3339 {
3340 testcase("Payment");
3341
3342 using namespace jtx;
3343 Account const becky{"becky"};
3344
3345 bool const supportsPreauth = {features[featureDepositPreauth]};
3346
3347 // The initial implementation of DepositAuth had a bug where an
3348 // account with the DepositAuth flag set could not make a payment
3349 // to itself. That bug was fixed in the DepositPreauth amendment.
3350 Env env(*this, features);
3351 fund(env, gw, {alice, becky}, XRP(5'000));
3352 env.close();
3353
3354 env.trust(USD(1'000), alice);
3355 env.trust(USD(1'000), becky);
3356 env.close();
3357
3358 env(pay(gw, alice, USD(500)));
3359 env.close();
3360
3361 AMM ammAlice(env, alice, XRP(100), USD(140));
3362
3363 // becky pays herself USD (10) by consuming part of alice's offer.
3364 // Make sure the payment works if PaymentAuth is not involved.
3365 env(pay(becky, becky, USD(10)), path(~USD), sendmax(XRP(10)));
3366 env.close();
3367 BEAST_EXPECT(ammAlice.expectBalances(
3368 XRPAmount(107'692'308), USD(130), ammAlice.tokens()));
3369
3370 // becky decides to require authorization for deposits.
3371 env(fset(becky, asfDepositAuth));
3372 env.close();
3373
3374 // becky pays herself again. Whether it succeeds depends on
3375 // whether featureDepositPreauth is enabled.
3376 TER const expect{
3377 supportsPreauth ? TER{tesSUCCESS} : TER{tecNO_PERMISSION}};
3378
3379 env(pay(becky, becky, USD(10)),
3380 path(~USD),
3381 sendmax(XRP(10)),
3382 ter(expect));
3383
3384 env.close();
3385 }
3386
3387 void
3389 {
3390 // Exercise IOU payments and non-direct XRP payments to an account
3391 // that has the lsfDepositAuth flag set.
3392 testcase("Pay IOU");
3393
3394 using namespace jtx;
3395
3396 Env env(*this);
3397
3398 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3399 env.trust(USD(1'000), alice, bob, carol);
3400 env.close();
3401
3402 env(pay(gw, alice, USD(150)));
3403 env(pay(gw, carol, USD(150)));
3404 AMM ammCarol(env, carol, USD(100), XRPAmount(101));
3405
3406 // Make sure bob's trust line is all set up so he can receive USD.
3407 env(pay(alice, bob, USD(50)));
3408 env.close();
3409
3410 // bob sets the lsfDepositAuth flag.
3412 env.close();
3413
3414 // None of the following payments should succeed.
3415 auto failedIouPayments = [this, &env]() {
3417
3418 // Capture bob's balances before hand to confirm they don't
3419 // change.
3420 PrettyAmount const bobXrpBalance{env.balance(bob, XRP)};
3421 PrettyAmount const bobUsdBalance{env.balance(bob, USD)};
3422
3423 env(pay(alice, bob, USD(50)), ter(tecNO_PERMISSION));
3424 env.close();
3425
3426 // Note that even though alice is paying bob in XRP, the payment
3427 // is still not allowed since the payment passes through an
3428 // offer.
3429 env(pay(alice, bob, drops(1)),
3430 sendmax(USD(1)),
3432 env.close();
3433
3434 BEAST_EXPECT(bobXrpBalance == env.balance(bob, XRP));
3435 BEAST_EXPECT(bobUsdBalance == env.balance(bob, USD));
3436 };
3437
3438 // Test when bob has an XRP balance > base reserve.
3439 failedIouPayments();
3440
3441 // Set bob's XRP balance == base reserve. Also demonstrate that
3442 // bob can make payments while his lsfDepositAuth flag is set.
3443 env(pay(bob, alice, USD(25)));
3444 env.close();
3445
3446 {
3447 STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)};
3448 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
3449 env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee));
3450 env.close();
3451 }
3452
3453 // Test when bob's XRP balance == base reserve.
3454 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
3455 BEAST_EXPECT(env.balance(bob, USD) == USD(25));
3456 failedIouPayments();
3457
3458 // Test when bob has an XRP balance == 0.
3459 env(noop(bob), fee(reserve(env, 0)));
3460 env.close();
3461
3462 BEAST_EXPECT(env.balance(bob, XRP) == XRP(0));
3463 failedIouPayments();
3464
3465 // Give bob enough XRP for the fee to clear the lsfDepositAuth flag.
3466 env(pay(alice, bob, drops(env.current()->fees().base)));
3467
3468 // bob clears the lsfDepositAuth and the next payment succeeds.
3469 env(fclear(bob, asfDepositAuth));
3470 env.close();
3471
3472 env(pay(alice, bob, USD(50)));
3473 env.close();
3474
3475 env(pay(alice, bob, drops(1)), sendmax(USD(1)));
3476 env.close();
3477 BEAST_EXPECT(ammCarol.expectBalances(
3478 USD(101), XRPAmount(100), ammCarol.tokens()));
3479 }
3480
3481 void
3483 {
3484 testcase("RippleState Freeze");
3485
3486 using namespace test::jtx;
3487 Env env(*this, features);
3488
3489 Account const G1{"G1"};
3490 Account const alice{"alice"};
3491 Account const bob{"bob"};
3492
3493 env.fund(XRP(1'000), G1, alice, bob);
3494 env.close();
3495
3496 env.trust(G1["USD"](100), bob);
3497 env.trust(G1["USD"](205), alice);
3498 env.close();
3499
3500 env(pay(G1, bob, G1["USD"](10)));
3501 env(pay(G1, alice, G1["USD"](205)));
3502 env.close();
3503
3504 AMM ammAlice(env, alice, XRP(500), G1["USD"](105));
3505
3506 {
3507 auto lines = getAccountLines(env, bob);
3508 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3509 return;
3510 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3511 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100");
3512 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10");
3513 }
3514
3515 {
3516 auto lines = getAccountLines(env, alice, G1["USD"]);
3517 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3518 return;
3519 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3520 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205");
3521 // 105 transferred to AMM
3522 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100");
3523 }
3524
3525 // Account with line unfrozen (proving operations normally work)
3526 // test: can make Payment on that line
3527 env(pay(alice, bob, G1["USD"](1)));
3528
3529 // test: can receive Payment on that line
3530 env(pay(bob, alice, G1["USD"](1)));
3531 env.close();
3532
3533 // Is created via a TrustSet with SetFreeze flag
3534 // test: sets LowFreeze | HighFreeze flags
3535 env(trust(G1, bob["USD"](0), tfSetFreeze));
3536 env.close();
3537
3538 {
3539 // Account with line frozen by issuer
3540 // test: can buy more assets on that line
3541 env(offer(bob, G1["USD"](5), XRP(25)));
3542 env.close();
3543 BEAST_EXPECT(ammAlice.expectBalances(
3544 XRP(525), G1["USD"](100), ammAlice.tokens()));
3545 }
3546
3547 {
3548 // test: can not sell assets from that line
3549 env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER));
3550
3551 // test: can receive Payment on that line
3552 env(pay(alice, bob, G1["USD"](1)));
3553
3554 // test: can not make Payment from that line
3555 env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY));
3556 }
3557
3558 {
3559 // check G1 account lines
3560 // test: shows freeze
3561 auto lines = getAccountLines(env, G1);
3562 Json::Value bobLine;
3563 for (auto const& it : lines[jss::lines])
3564 {
3565 if (it[jss::account] == bob.human())
3566 {
3567 bobLine = it;
3568 break;
3569 }
3570 }
3571 if (!BEAST_EXPECT(bobLine))
3572 return;
3573 BEAST_EXPECT(bobLine[jss::freeze] == true);
3574 BEAST_EXPECT(bobLine[jss::balance] == "-16");
3575 }
3576
3577 {
3578 // test: shows freeze peer
3579 auto lines = getAccountLines(env, bob);
3580 Json::Value g1Line;
3581 for (auto const& it : lines[jss::lines])
3582 {
3583 if (it[jss::account] == G1.human())
3584 {
3585 g1Line = it;
3586 break;
3587 }
3588 }
3589 if (!BEAST_EXPECT(g1Line))
3590 return;
3591 BEAST_EXPECT(g1Line[jss::freeze_peer] == true);
3592 BEAST_EXPECT(g1Line[jss::balance] == "16");
3593 }
3594
3595 {
3596 // Is cleared via a TrustSet with ClearFreeze flag
3597 // test: sets LowFreeze | HighFreeze flags
3598 env(trust(G1, bob["USD"](0), tfClearFreeze));
3599 auto affected = env.meta()->getJson(
3600 JsonOptions::none)[sfAffectedNodes.fieldName];
3601 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3602 return;
3603 auto ff =
3604 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3605 BEAST_EXPECT(
3606 ff[sfLowLimit.fieldName] ==
3607 G1["USD"](0).value().getJson(JsonOptions::none));
3608 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3609 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3610 env.close();
3611 }
3612 }
3613
3614 void
3616 {
3617 testcase("Global Freeze");
3618
3619 using namespace test::jtx;
3620 Env env(*this, features);
3621
3622 Account G1{"G1"};
3623 Account A1{"A1"};
3624 Account A2{"A2"};
3625 Account A3{"A3"};
3626 Account A4{"A4"};
3627
3628 env.fund(XRP(12'000), G1);
3629 env.fund(XRP(1'000), A1);
3630 env.fund(XRP(20'000), A2, A3, A4);
3631 env.close();
3632
3633 env.trust(G1["USD"](1'200), A1);
3634 env.trust(G1["USD"](200), A2);
3635 env.trust(G1["BTC"](100), A3);
3636 env.trust(G1["BTC"](100), A4);
3637 env.close();
3638
3639 env(pay(G1, A1, G1["USD"](1'000)));
3640 env(pay(G1, A2, G1["USD"](100)));
3641 env(pay(G1, A3, G1["BTC"](100)));
3642 env(pay(G1, A4, G1["BTC"](100)));
3643 env.close();
3644
3645 AMM ammG1(env, G1, XRP(10'000), G1["USD"](100));
3646 env(offer(A1, XRP(10'000), G1["USD"](100)), txflags(tfPassive));
3647 env(offer(A2, G1["USD"](100), XRP(10'000)), txflags(tfPassive));
3648 env.close();
3649
3650 {
3651 // Account without GlobalFreeze (proving operations normally
3652 // work)
3653 // test: visible offers where taker_pays is unfrozen issuer
3654 auto offers = env.rpc(
3655 "book_offers",
3656 std::string("USD/") + G1.human(),
3657 "XRP")[jss::result][jss::offers];
3658 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3659 return;
3660 std::set<std::string> accounts;
3661 for (auto const& offer : offers)
3662 {
3663 accounts.insert(offer[jss::Account].asString());
3664 }
3665 BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts));
3666
3667 // test: visible offers where taker_gets is unfrozen issuer
3668 offers = env.rpc(
3669 "book_offers",
3670 "XRP",
3671 std::string("USD/") + G1.human())[jss::result][jss::offers];
3672 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3673 return;
3674 accounts.clear();
3675 for (auto const& offer : offers)
3676 {
3677 accounts.insert(offer[jss::Account].asString());
3678 }
3679 BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts));
3680 }
3681
3682 {
3683 // Offers/Payments
3684 // test: assets can be bought on the market
3685 // env(offer(A3, G1["BTC"](1), XRP(1)));
3686 AMM ammA3(env, A3, G1["BTC"](1), XRP(1));
3687
3688 // test: assets can be sold on the market
3689 // AMM is bidirectional
3690
3691 // test: direct issues can be sent
3692 env(pay(G1, A2, G1["USD"](1)));
3693
3694 // test: direct redemptions can be sent
3695 env(pay(A2, G1, G1["USD"](1)));
3696
3697 // test: via rippling can be sent
3698 env(pay(A2, A1, G1["USD"](1)));
3699
3700 // test: via rippling can be sent back
3701 env(pay(A1, A2, G1["USD"](1)));
3702 ammA3.withdrawAll(std::nullopt);
3703 }
3704
3705 {
3706 // Account with GlobalFreeze
3707 // set GlobalFreeze first
3708 // test: SetFlag GlobalFreeze will toggle back to freeze
3709 env.require(nflags(G1, asfGlobalFreeze));
3710 env(fset(G1, asfGlobalFreeze));
3711 env.require(flags(G1, asfGlobalFreeze));
3712 env.require(nflags(G1, asfNoFreeze));
3713
3714 // test: assets can't be bought on the market
3715 AMM ammA3(env, A3, G1["BTC"](1), XRP(1), ter(tecFROZEN));
3716
3717 // test: assets can't be sold on the market
3718 // AMM is bidirectional
3719 }
3720
3721 {
3722 // test: book_offers shows offers
3723 // (should these actually be filtered?)
3724 auto offers = env.rpc(
3725 "book_offers",
3726 "XRP",
3727 std::string("USD/") + G1.human())[jss::result][jss::offers];
3728 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3729 return;
3730
3731 offers = env.rpc(
3732 "book_offers",
3733 std::string("USD/") + G1.human(),
3734 "XRP")[jss::result][jss::offers];
3735 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3736 return;
3737 }
3738
3739 {
3740 // Payments
3741 // test: direct issues can be sent
3742 env(pay(G1, A2, G1["USD"](1)));
3743
3744 // test: direct redemptions can be sent
3745 env(pay(A2, G1, G1["USD"](1)));
3746
3747 // test: via rippling cant be sent
3748 env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY));
3749 }
3750 }
3751
3752 void
3754 {
3755 testcase("Offers for Frozen Trust Lines");
3756
3757 using namespace test::jtx;
3758 Env env(*this, features);
3759
3760 Account G1{"G1"};
3761 Account A2{"A2"};
3762 Account A3{"A3"};
3763 Account A4{"A4"};
3764
3765 env.fund(XRP(2'000), G1, A3, A4);
3766 env.fund(XRP(2'000), A2);
3767 env.close();
3768
3769 env.trust(G1["USD"](1'000), A2);
3770 env.trust(G1["USD"](2'000), A3);
3771 env.trust(G1["USD"](2'001), A4);
3772 env.close();
3773
3774 env(pay(G1, A3, G1["USD"](2'000)));
3775 env(pay(G1, A4, G1["USD"](2'001)));
3776 env.close();
3777
3778 AMM ammA3(env, A3, XRP(1'000), G1["USD"](1'001));
3779
3780 // removal after successful payment
3781 // test: make a payment with partially consuming offer
3782 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3783 env.close();
3784
3785 BEAST_EXPECT(
3786 ammA3.expectBalances(XRP(1'001), G1["USD"](1'000), ammA3.tokens()));
3787
3788 // test: someone else creates an offer providing liquidity
3789 env(offer(A4, XRP(999), G1["USD"](999)));
3790 env.close();
3791 // The offer consumes AMM offer
3792 BEAST_EXPECT(
3793 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
3794
3795 // test: AMM line is frozen
3796 auto const a3am =
3797 STAmount{Issue{to_currency("USD"), ammA3.ammAccount()}, 0};
3798 env(trust(G1, a3am, tfSetFreeze));
3799 auto const info = ammA3.ammRpcInfo();
3800 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3801 env.close();
3802
3803 // test: Can make a payment via the new offer
3804 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3805 env.close();
3806 // AMM is not consumed
3807 BEAST_EXPECT(
3808 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
3809
3810 // removal buy successful OfferCreate
3811 // test: freeze the new offer
3812 env(trust(G1, A4["USD"](0), tfSetFreeze));
3813 env.close();
3814
3815 // test: can no longer create a crossing offer
3816 env(offer(A2, G1["USD"](999), XRP(999)));
3817 env.close();
3818
3819 // test: offer was removed by offer_create
3820 auto offers = getAccountOffers(env, A4)[jss::offers];
3821 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
3822 return;
3823 }
3824
3825 void
3827 {
3828 testcase("Multisign AMM Transactions");
3829
3830 using namespace jtx;
3831 Env env{*this, features};
3832 Account const bogie{"bogie", KeyType::secp256k1};
3833 Account const alice{"alice", KeyType::secp256k1};
3834 Account const becky{"becky", KeyType::ed25519};
3835 Account const zelda{"zelda", KeyType::secp256k1};
3836 fund(env, gw, {alice, becky, zelda}, XRP(20'000), {USD(20'000)});
3837
3838 // alice uses a regular key with the master disabled.
3839 Account const alie{"alie", KeyType::secp256k1};
3840 env(regkey(alice, alie));
3842
3843 // Attach signers to alice.
3844 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
3845 env.close();
3846 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3847 env.require(owners(alice, signerListOwners + 0));
3848
3849 msig const ms{becky, bogie};
3850
3851 // Multisign all AMM transactions
3852 AMM ammAlice(
3853 env,
3854 alice,
3855 XRP(10'000),
3856 USD(10'000),
3857 false,
3858 0,
3859 ammCrtFee(env).drops(),
3860 std::nullopt,
3861 std::nullopt,
3862 ms,
3863 ter(tesSUCCESS));
3864 BEAST_EXPECT(ammAlice.expectBalances(
3865 XRP(10'000), USD(10'000), ammAlice.tokens()));
3866
3867 ammAlice.deposit(alice, 1'000'000);
3868 BEAST_EXPECT(ammAlice.expectBalances(
3869 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
3870
3871 ammAlice.withdraw(alice, 1'000'000);
3872 BEAST_EXPECT(ammAlice.expectBalances(
3873 XRP(10'000), USD(10'000), ammAlice.tokens()));
3874
3875 ammAlice.vote({}, 1'000);
3876 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3877
3878 env(ammAlice.bid({.account = alice, .bidMin = 100}), ms).close();
3879 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
3880 // 4000 tokens burnt
3881 BEAST_EXPECT(ammAlice.expectBalances(
3882 XRP(10'000), USD(10'000), IOUAmount{9'996'000, 0}));
3883 }
3884
3885 void
3887 {
3888 testcase("To Strand");
3889
3890 using namespace jtx;
3891
3892 // cannot have more than one offer with the same output issue
3893
3894 Env env(*this, features);
3895
3896 fund(
3897 env,
3898 gw,
3899 {alice, bob, carol},
3900 XRP(10'000),
3901 {USD(2'000), EUR(1'000)});
3902
3903 AMM bobXRP_USD(env, bob, XRP(1'000), USD(1'000));
3904 AMM bobUSD_EUR(env, bob, USD(1'000), EUR(1'000));
3905
3906 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
3907 env(pay(alice, carol, USD(100)),
3908 path(~USD, ~EUR, ~USD),
3909 sendmax(XRP(200)),
3912 }
3913
3914 void
3916 {
3917 using namespace jtx;
3918 testcase("RIPD1373");
3919
3920 {
3921 Env env(*this, features);
3922 auto const BobUSD = bob["USD"];
3923 auto const BobEUR = bob["EUR"];
3924 fund(env, gw, {alice, bob}, XRP(10'000));
3925 env.trust(USD(1'000), alice, bob);
3926 env.trust(EUR(1'000), alice, bob);
3927 env.close();
3928 fund(
3929 env,
3930 bob,
3931 {alice, gw},
3932 {BobUSD(100), BobEUR(100)},
3933 Fund::IOUOnly);
3934 env.close();
3935
3936 AMM ammBobXRP_USD(env, bob, XRP(100), BobUSD(100));
3937 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
3938
3939 AMM ammBobUSD_EUR(env, bob, BobUSD(100), BobEUR(100));
3940 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
3941
3942 Path const p = [&] {
3943 Path result;
3944 result.push_back(allpe(gw, BobUSD));
3945 result.push_back(cpe(EUR.currency));
3946 return result;
3947 }();
3948
3949 PathSet paths(p);
3950
3951 env(pay(alice, alice, EUR(1)),
3952 json(paths.json()),
3953 sendmax(XRP(10)),
3955 ter(temBAD_PATH));
3956 }
3957
3958 {
3959 Env env(*this, features);
3960
3961 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
3962
3963 AMM ammBob(env, bob, XRP(100), USD(100));
3964
3965 // payment path: XRP -> XRP/USD -> USD/XRP
3966 env(pay(alice, carol, XRP(100)),
3967 path(~USD, ~XRP),
3970 }
3971
3972 {
3973 Env env(*this, features);
3974
3975 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
3976
3977 AMM ammBob(env, bob, XRP(100), USD(100));
3978
3979 // payment path: XRP -> XRP/USD -> USD/XRP
3980 env(pay(alice, carol, XRP(100)),
3981 path(~USD, ~XRP),
3982 sendmax(XRP(200)),
3985 }
3986 }
3987
3988 void
3990 {
3991 testcase("test loop");
3992 using namespace jtx;
3993
3994 auto const CNY = gw["CNY"];
3995
3996 {
3997 Env env(*this, features);
3998
3999 env.fund(XRP(10'000), alice, bob, carol, gw);
4000 env.close();
4001 env.trust(USD(10'000), alice, bob, carol);
4002 env.close();
4003 env(pay(gw, bob, USD(100)));
4004 env(pay(gw, alice, USD(100)));
4005 env.close();
4006
4007 AMM ammBob(env, bob, XRP(100), USD(100));
4008
4009 // payment path: USD -> USD/XRP -> XRP/USD
4010 env(pay(alice, carol, USD(100)),
4011 sendmax(USD(100)),
4012 path(~XRP, ~USD),
4015 }
4016
4017 {
4018 Env env(*this, features);
4019
4020 env.fund(XRP(10'000), alice, bob, carol, gw);
4021 env.close();
4022 env.trust(USD(10'000), alice, bob, carol);
4023 env.trust(EUR(10'000), alice, bob, carol);
4024 env.trust(CNY(10'000), alice, bob, carol);
4025
4026 env(pay(gw, bob, USD(200)));
4027 env(pay(gw, bob, EUR(200)));
4028 env(pay(gw, bob, CNY(100)));
4029
4030 AMM ammBobXRP_USD(env, bob, XRP(100), USD(100));
4031 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(100));
4032 AMM ammBobEUR_CNY(env, bob, EUR(100), CNY(100));
4033
4034 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
4035 env(pay(alice, carol, CNY(100)),
4036 sendmax(XRP(100)),
4037 path(~USD, ~EUR, ~USD, ~CNY),
4040 }
4041 }
4042
4043 void
4045 {
4048 receive_max();
4049 path_find_01();
4050 path_find_02();
4051 path_find_05();
4052 path_find_06();
4053 }
4054
4055 void
4057 {
4058 using namespace jtx;
4060 FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
4061
4064 testBookStep(all | ownerPaysFee);
4065 testTransferRate(all | ownerPaysFee);
4066 testTransferRate((all - fixAMMv1_1 - fixAMMv1_3) | ownerPaysFee);
4068 testTransferRateNoOwnerFee(all - fixAMMv1_1 - fixAMMv1_3);
4071 }
4072
4073 void
4075 {
4076 using namespace jtx;
4079 testStepLimit(all - fixAMMv1_1 - fixAMMv1_3);
4080 }
4081
4082 void
4084 {
4085 using namespace jtx;
4088 test_convert_all_of_an_asset(all - fixAMMv1_1 - fixAMMv1_3);
4089 }
4090
4091 void
4093 {
4094 auto const supported{jtx::supported_amendments()};
4095 testPayment(supported - featureDepositPreauth);
4096 testPayment(supported);
4097 testPayIOU();
4098 }
4099
4100 void
4102 {
4103 using namespace test::jtx;
4104 auto const sa = supported_amendments();
4105 testRippleState(sa);
4106 testGlobalFreeze(sa);
4108 }
4109
4110 void
4112 {
4113 using namespace jtx;
4114 auto const all = supported_amendments();
4115
4117 all - featureMultiSignReserve - featureExpandedSignerList);
4118 testTxMultisign(all - featureExpandedSignerList);
4120 }
4121
4122 void
4124 {
4125 using namespace jtx;
4126 auto const all = supported_amendments();
4127
4130 testLoop(all);
4131 }
4132
4133 void
4134 run() override
4135 {
4136 testOffers();
4137 testPaths();
4138 testFlow();
4142 testFreeze();
4143 testMultisign();
4144 testPayStrand();
4145 }
4146};
4147
4148BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app, ripple, 1);
4149
4150} // namespace test
4151} // namespace ripple
Represents a JSON value.
Definition: json_value.h:150
A generic endpoint for log messages.
Definition: Journal.h:60
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
bool expect(Condition const &shouldBeTrue)
Evaluate a test condition.
Definition: suite.h:229
virtual OpenLedger & openLedger()=0
virtual Logs & logs()=0
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
A currency issued by an account.
Definition: Issue.h:36
beast::Journal journal(std::string const &name)
Definition: Log.cpp:160
bool modify(modify_type const &f)
Modify the open ledger.
Definition: OpenLedger.cpp:58
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:66
A wrapper which makes credits unavailable to balances.
bool empty() const
Definition: STPathSet.h:508
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
Path & push_back(Issue const &iss)
Definition: PathSet.h:134
jtx::Account const alice
Definition: AMMTest.h:77
jtx::Account const gw
Definition: AMMTest.h:75
jtx::Account const bob
Definition: AMMTest.h:78
jtx::Account const carol
Definition: AMMTest.h:76
void testAMM(std::function< void(jtx::AMM &, jtx::Env &)> &&cb, std::optional< std::pair< STAmount, STAmount > > const &pool=std::nullopt, std::uint16_t tfee=0, std::optional< jtx::ter > const &ter=std::nullopt, std::vector< FeatureBitset > const &features={supported_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:103
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:256
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:177
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:171
Convenience class to test AMM functionality.
Definition: AMM.h:124
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:166
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:642
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:280
IOUAmount tokens() const
Definition: AMM.h:343
AccountID const & ammAccount() const
Definition: AMM.h:331
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:317
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:542
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:237
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:416
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:669
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:279
Immutable cryptographic account descriptor.
Definition: Account.h:39
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:121
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:535
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:264
Application & app()
Definition: Env.h:261
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:447
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
A balance matches.
Definition: balance.h:39
Sets the DeliverMin on a JTx.
Definition: delivermin.h:33
Set the fee on a JTx.
Definition: fee.h:37
Match set account flags.
Definition: flags.h:125
Inject raw JSON.
Definition: jtx_json.h:33
Set a multisignature on a JTx.
Definition: multisign.h:67
Match clear account flags.
Definition: flags.h:142
Match the number of items in the account's owner directory.
Definition: owners.h:73
Add a path.
Definition: paths.h:58
Set Paths, SendMax on a JTx.
Definition: paths.h:35
Sets the QualityIn on a trust JTx.
Definition: quality.h:46
Sets the QualityOut on a trust JTx as a percentage.
Definition: quality.h:74
Check a set of conditions.
Definition: require.h:65
Sets the SendMax on a JTx.
Definition: sendmax.h:33
Set the regular signature on a JTx.
Definition: sig.h:35
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
Set the flags on a JTx.
Definition: txflags.h:31
T clear(T... args)
T end(T... args)
T find(T... args)
T insert(T... args)
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:274
bool checkArraySize(Json::Value const &val, unsigned int size)
Definition: TestHelpers.cpp:48
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition: flags.h:41
Json::Value ledgerEntryRoot(Env &env, Account const &acct)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:29
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:34
static none_t const none
Definition: tags.h:34
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition: owners.h:92
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
PrettyAmount xrpMinusFee(Env const &env, std::int64_t xrpAmount)
Definition: TestHelpers.cpp:99
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:32
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:29
bool expectLine(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Definition: TestHelpers.cpp:40
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
bool same(STPathSet const &st1, Args const &... args)
Definition: TestHelpers.h:165
Json::Value ledgerEntryState(Env &env, Account const &acct_a, Account const &acct_b, std::string const &currency)
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:37
void n_offers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:32
STPathElement IPE(Issue const &iss)
Definition: TestHelpers.cpp:81
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:29
STPathElement cpe(Currency const &c)
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
STPathElement allpe(AccountID const &a, Issue const &iss)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
XRPAmount txfee(Env const &env, std::uint16_t n)
Definition: TestHelpers.cpp:93
FeatureBitset supported_amendments()
Definition: Env.h:74
STPath stpath(Args const &... args)
Definition: TestHelpers.h:156
Json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
Definition: TestHelpers.cpp:32
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
Definition: PathSet.h:72
bool equal(std::unique_ptr< Step > const &s1, DirectStepInfo const &dsi)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
constexpr std::uint32_t asfGlobalFreeze
Definition: TxFlags.h:83
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:85
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
@ lsfHighFreeze
@ lsfLowFreeze
constexpr std::uint32_t asfNoFreeze
Definition: TxFlags.h:82
constexpr std::uint32_t tfFillOrKill
Definition: TxFlags.h:99
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition: StrandFlow.h:105
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:97
constexpr std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:98
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1458
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:80
@ no
Definition: Steps.h:45
constexpr std::uint32_t tfPartialPayment
Definition: TxFlags.h:107
constexpr std::uint32_t tfSetfAuth
Definition: TxFlags.h:114
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:119
constexpr std::uint32_t tfClearFreeze
Definition: TxFlags.h:118
@ tecUNFUNDED_OFFER
Definition: TER.h:284
@ tecFROZEN
Definition: TER.h:303
@ tecKILLED
Definition: TER.h:316
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecPATH_PARTIAL
Definition: TER.h:282
@ tecUNFUNDED_AMM
Definition: TER.h:328
@ tecNO_LINE
Definition: TER.h:301
@ tecPATH_DRY
Definition: TER.h:294
@ tecNO_AUTH
Definition: TER.h:300
constexpr std::uint32_t tfNoRippleDirect
Definition: TxFlags.h:106
@ tesSUCCESS
Definition: TER.h:244
constexpr std::uint32_t tfLimitQuality
Definition: TxFlags.h:108
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
@ tapNONE
Definition: ApplyView.h:32
constexpr std::uint32_t tfSell
Definition: TxFlags.h:100
constexpr std::uint32_t asfRequireAuth
Definition: TxFlags.h:78
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition: Seed.cpp:76
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:117
constexpr std::uint32_t tfSetNoRipple
Definition: TxFlags.h:115
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition: UintTypes.cpp:84
@ temBAD_AMOUNT
Definition: TER.h:89
@ temBAD_PATH_LOOP
Definition: TER.h:97
@ temBAD_PATH
Definition: TER.h:96
@ temBAD_SEND_XRP_PATHS
Definition: TER.h:103
@ temBAD_SEND_XRP_MAX
Definition: TER.h:100
Tests of AMM that use offers too.
void testGlobalFreeze(FeatureBitset features)
void testOfferCrossWithXRP(FeatureBitset features)
void testCurrencyConversionEntire(FeatureBitset features)
void testTransferRate(FeatureBitset features)
void testFalseDry(FeatureBitset features)
void testOfferCrossWithLimitOverride(FeatureBitset features)
void testTransferRateOffer(FeatureBitset features)
void testBookStep(FeatureBitset features)
void testBridgedCross(FeatureBitset features)
void test_convert_all_of_an_asset(FeatureBitset features)
void testGatewayCrossCurrency(FeatureBitset features)
void testRequireAuth(FeatureBitset features)
void testPayment(FeatureBitset features)
void testOfferFeesConsumeFunds(FeatureBitset features)
void testOffersWhenFrozen(FeatureBitset features)
void testSellFlagExceedLimit(FeatureBitset features)
void testCrossCurrencyBridged(FeatureBitset features)
void testBadPathAssert(FeatureBitset features)
void testLoop(FeatureBitset features)
void testOfferCreateThenCross(FeatureBitset features)
void testToStrand(FeatureBitset features)
void run() override
Runs the suite.
void testFillModes(FeatureBitset features)
void testMissingAuth(FeatureBitset features)
void testRIPD1373(FeatureBitset features)
void testCrossCurrencyEndXRP(FeatureBitset features)
void testCurrencyConversionInParts(FeatureBitset features)
void testTransferRateNoOwnerFee(FeatureBitset features)
void testRippleState(FeatureBitset features)
void testRmFundedOffer(FeatureBitset features)
void testSelfIssueOffer(FeatureBitset features)
void testDirectToDirectPath(FeatureBitset features)
void testStepLimit(FeatureBitset features)
void testEnforceNoRipple(FeatureBitset features)
void testCrossCurrencyStartXRP(FeatureBitset features)
void testSellWithFillOrKill(FeatureBitset features)
void testTxMultisign(FeatureBitset features)
void testSellFlagBasic(FeatureBitset features)
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...
STAmount const & value() const
T tie(T... args)