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);
1468 testGatewayCrossCurrency(all - fixAMMv1_1);
1476 testDirectToDirectPath(all - fixAMMv1_1);
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 flowJournal);
2160 }();
2161
2162 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2163 env.app().openLedger().modify(
2164 [&](OpenView& view, beast::Journal j) {
2165 if (flowResult.removableOffers.empty())
2166 return false;
2167 Sandbox sb(&view, tapNONE);
2168 for (auto const& o : flowResult.removableOffers)
2169 if (auto ok = sb.peek(keylet::offer(o)))
2170 offerDelete(sb, ok, flowJournal);
2171 sb.apply(view);
2172 return true;
2173 });
2174
2175 // used in payment, but since payment failed should be untouched
2176 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2177 BTC(50), USD(50), ammBobBTC_USD.tokens()));
2178 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
2179 // found unfunded
2180 BEAST_EXPECT(!isOffer(env, bob, BTC(60), EUR(50)));
2181 }
2182 {
2183 // Do not produce more in the forward pass than the reverse pass
2184 // This test uses a path that whose reverse pass will compute a
2185 // 0.5 USD input required for a 1 EUR output. It sets a sendmax
2186 // of 0.4 USD, so the payment engine will need to do a forward
2187 // pass. Without limits, the 0.4 USD would produce 1000 EUR in
2188 // the forward pass. This test checks that the payment produces
2189 // 1 EUR, as expected.
2190
2191 Env env(*this, features);
2192 env.fund(XRP(10'000), bob, carol, gw);
2193 env.close();
2194 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2195 env.trust(USD(1'000), alice, bob, carol);
2196 env.trust(EUR(1'000), alice, bob, carol);
2197 env.close();
2198
2199 env(pay(gw, alice, USD(1'000)));
2200 env(pay(gw, bob, EUR(1'000)));
2201 env(pay(gw, bob, USD(1'000)));
2202 env.close();
2203
2204 // env(offer(bob, USD(1), drops(2)), txflags(tfPassive));
2205 AMM ammBob(env, bob, USD(8), XRPAmount{21});
2206 env(offer(bob, drops(1), EUR(1'000)), txflags(tfPassive));
2207
2208 env(pay(alice, carol, EUR(1)),
2209 path(~XRP, ~EUR),
2210 sendmax(USD(0.4)),
2212
2213 BEAST_EXPECT(expectLine(env, carol, EUR(1)));
2214 BEAST_EXPECT(ammBob.expectBalances(
2215 USD(8.4), XRPAmount{20}, ammBob.tokens()));
2216 }
2217 }
2218
2219 void
2221 {
2222 testcase("Transfer Rate");
2223
2224 using namespace jtx;
2225
2226 {
2227 // transfer fee on AMM
2228 Env env(*this, features);
2229
2230 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(1'000)});
2231 env(rate(gw, 1.25));
2232 env.close();
2233
2234 AMM ammBob(env, bob, XRP(100), USD(150));
2235 // no transfer fee on create
2236 BEAST_EXPECT(expectLine(env, bob, USD(1000 - 150)));
2237
2238 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
2239 env.close();
2240
2241 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 150)));
2242 BEAST_EXPECT(
2243 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
2244 BEAST_EXPECT(expectLedgerEntryRoot(
2245 env, alice, xrpMinusFee(env, 10'000 - 50)));
2246 BEAST_EXPECT(expectLine(env, carol, USD(1'050)));
2247 }
2248
2249 {
2250 // Transfer fee AMM and offer
2251 Env env(*this, features);
2252
2253 fund(
2254 env,
2255 gw,
2256 {alice, bob, carol},
2257 XRP(10'000),
2258 {USD(1'000), EUR(1'000)});
2259 env(rate(gw, 1.25));
2260 env.close();
2261
2262 AMM ammBob(env, bob, XRP(100), USD(140));
2263 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
2264
2265 env(offer(bob, USD(50), EUR(50)));
2266
2267 // alice buys 40EUR with 40XRP
2268 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
2269
2270 // 40XRP is swapped in for 40USD
2271 BEAST_EXPECT(
2272 ammBob.expectBalances(XRP(140), USD(100), ammBob.tokens()));
2273 // 40USD buys 40EUR via bob's offer. 40EUR delivered to carol
2274 // and bob pays 25% on 40EUR, 40EUR*0.25=10EUR
2275 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 40 - 40 * 0.25)));
2276 // bob gets 40USD back from the offer
2277 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 + 40)));
2278 BEAST_EXPECT(expectLedgerEntryRoot(
2279 env, alice, xrpMinusFee(env, 10'000 - 40)));
2280 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2281 BEAST_EXPECT(expectOffers(env, bob, 1, {{USD(10), EUR(10)}}));
2282 }
2283
2284 {
2285 // Transfer fee two consecutive AMM
2286 Env env(*this, features);
2287
2288 fund(
2289 env,
2290 gw,
2291 {alice, bob, carol},
2292 XRP(10'000),
2293 {USD(1'000), EUR(1'000)});
2294 env(rate(gw, 1.25));
2295 env.close();
2296
2297 AMM ammBobXRP_USD(env, bob, XRP(100), USD(140));
2298 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140)));
2299
2300 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(140));
2301 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
2302 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
2303
2304 // alice buys 40EUR with 40XRP
2305 env(pay(alice, carol, EUR(40)), path(~USD, ~EUR), sendmax(XRP(40)));
2306
2307 // 40XRP is swapped in for 40USD
2308 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
2309 XRP(140), USD(100), ammBobXRP_USD.tokens()));
2310 // 40USD is swapped in for 40EUR
2311 BEAST_EXPECT(ammBobUSD_EUR.expectBalances(
2312 USD(140), EUR(100), ammBobUSD_EUR.tokens()));
2313 // no other charges on bob
2314 BEAST_EXPECT(expectLine(env, bob, USD(1'000 - 140 - 100)));
2315 BEAST_EXPECT(expectLine(env, bob, EUR(1'000 - 140)));
2316 BEAST_EXPECT(expectLedgerEntryRoot(
2317 env, alice, xrpMinusFee(env, 10'000 - 40)));
2318 BEAST_EXPECT(expectLine(env, carol, EUR(1'040)));
2319 }
2320
2321 {
2322 // Payment via AMM with limit quality, deliver less
2323 // than requested
2324 Env env(*this, features);
2325
2326 fund(
2327 env,
2328 gw,
2329 {alice, bob, carol},
2330 XRP(1'000),
2331 {USD(1'200), GBP(1'200)});
2332 env(rate(gw, 1.25));
2333 env.close();
2334
2335 AMM amm(env, bob, GBP(1'000), USD(1'100));
2336
2337 // requested quality limit is 90USD/110GBP = 0.8181
2338 // trade quality is 77.2727USD/94.4444GBP = 0.8181
2339 env(pay(alice, carol, USD(90)),
2340 path(~USD),
2341 sendmax(GBP(110)),
2343 env.close();
2344
2345 if (!features[fixAMMv1_1])
2346 {
2347 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
2348 // on 75.5555GBP
2349 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
2350 BEAST_EXPECT(expectLine(
2351 env,
2352 alice,
2353 STAmount{GBP, UINT64_C(1'105'555555555555), -12}));
2354 // 75.5555GBP is swapped in for 77.7272USD
2355 BEAST_EXPECT(amm.expectBalances(
2356 STAmount{GBP, UINT64_C(1'075'555555555556), -12},
2357 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2358 amm.tokens()));
2359 }
2360 else
2361 {
2362 // alice buys 77.2727USD with 75.5555GBP and pays 25% tr fee
2363 // on 75.5555GBP
2364 // 1,200 - 75.55555*1.25 = 1200 - 94.4444 = 1105.55555GBP
2365 BEAST_EXPECT(expectLine(
2366 env,
2367 alice,
2368 STAmount{GBP, UINT64_C(1'105'555555555554), -12}));
2369 // 75.5555GBP is swapped in for 77.7272USD
2370 BEAST_EXPECT(amm.expectBalances(
2371 STAmount{GBP, UINT64_C(1'075'555555555557), -12},
2372 STAmount{USD, UINT64_C(1'022'727272727272), -12},
2373 amm.tokens()));
2374 }
2375 BEAST_EXPECT(expectLine(
2376 env, carol, STAmount{USD, UINT64_C(1'277'272727272728), -12}));
2377 }
2378
2379 {
2380 // AMM offer crossing
2381 Env env(*this, features);
2382
2383 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'200), EUR(1'200)});
2384 env(rate(gw, 1.25));
2385 env.close();
2386
2387 AMM amm(env, bob, USD(1'000), EUR(1'150));
2388
2389 env(offer(alice, EUR(100), USD(100)));
2390 env.close();
2391
2392 if (!features[fixAMMv1_1])
2393 {
2394 // 95.2380USD is swapped in for 100EUR
2395 BEAST_EXPECT(amm.expectBalances(
2396 STAmount{USD, UINT64_C(1'095'238095238095), -12},
2397 EUR(1'050),
2398 amm.tokens()));
2399 // alice pays 25% tr fee on 95.2380USD
2400 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
2401 BEAST_EXPECT(expectLine(
2402 env,
2403 alice,
2404 STAmount{USD, UINT64_C(1'080'952380952381), -12},
2405 EUR(1'300)));
2406 }
2407 else
2408 {
2409 // 95.2380USD is swapped in for 100EUR
2410 BEAST_EXPECT(amm.expectBalances(
2411 STAmount{USD, UINT64_C(1'095'238095238096), -12},
2412 EUR(1'050),
2413 amm.tokens()));
2414 // alice pays 25% tr fee on 95.2380USD
2415 // 1200-95.2380*1.25 = 1200 - 119.0477 = 1080.9523USD
2416 BEAST_EXPECT(expectLine(
2417 env,
2418 alice,
2419 STAmount{USD, UINT64_C(1'080'95238095238), -11},
2420 EUR(1'300)));
2421 }
2422 BEAST_EXPECT(expectOffers(env, alice, 0));
2423 }
2424
2425 {
2426 // First pass through a strand redeems, second pass issues,
2427 // through an offer limiting step is not an endpoint
2428 Env env(*this, features);
2429 auto const USDA = alice["USD"];
2430 auto const USDB = bob["USD"];
2431 Account const dan("dan");
2432
2433 env.fund(XRP(10'000), bob, carol, dan, gw);
2434 fund(env, {alice}, XRP(10'000));
2435 env(rate(gw, 1.25));
2436 env.trust(USD(2'000), alice, bob, carol, dan);
2437 env.trust(EUR(2'000), carol, dan);
2438 env.trust(USDA(1'000), bob);
2439 env.trust(USDB(1'000), gw);
2440 env(pay(gw, bob, USD(50)));
2441 env(pay(gw, dan, EUR(1'050)));
2442 env(pay(gw, dan, USD(1'000)));
2443 AMM ammDan(env, dan, USD(1'000), EUR(1'050));
2444
2445 if (!features[fixAMMv1_1])
2446 {
2447 // alice -> bob -> gw -> carol. $50 should have transfer fee;
2448 // $10, no fee
2449 env(pay(alice, carol, EUR(50)),
2450 path(bob, gw, ~EUR),
2451 sendmax(USDA(60)),
2453 BEAST_EXPECT(ammDan.expectBalances(
2454 USD(1'050), EUR(1'000), ammDan.tokens()));
2455 BEAST_EXPECT(expectLine(env, dan, USD(0)));
2456 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
2457 BEAST_EXPECT(expectLine(env, bob, USD(-10)));
2458 BEAST_EXPECT(expectLine(env, bob, USDA(60)));
2459 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
2460 }
2461 else
2462 {
2463 // alice -> bob -> gw -> carol. $50 should have transfer fee;
2464 // $10, no fee
2465 env(pay(alice, carol, EUR(50)),
2466 path(bob, gw, ~EUR),
2467 sendmax(USDA(60.1)),
2469 BEAST_EXPECT(ammDan.expectBalances(
2470 STAmount{USD, UINT64_C(1'050'000000000001), -12},
2471 EUR(1'000),
2472 ammDan.tokens()));
2473 BEAST_EXPECT(expectLine(env, dan, USD(0)));
2474 BEAST_EXPECT(expectLine(env, dan, EUR(0)));
2475 BEAST_EXPECT(expectLine(
2476 env, bob, STAmount{USD, INT64_C(-10'000000000001), -12}));
2477 BEAST_EXPECT(expectLine(
2478 env, bob, STAmount{USDA, UINT64_C(60'000000000001), -12}));
2479 BEAST_EXPECT(expectLine(env, carol, EUR(50)));
2480 }
2481 }
2482 }
2483
2484 void
2486 {
2487 testcase("No Owner Fee");
2488 using namespace jtx;
2489
2490 {
2491 // payment via AMM
2492 Env env(*this, features);
2493
2494 fund(
2495 env,
2496 gw,
2497 {alice, bob, carol},
2498 XRP(1'000),
2499 {USD(1'000), GBP(1'000)});
2500 env(rate(gw, 1.25));
2501 env.close();
2502
2503 AMM amm(env, bob, GBP(1'000), USD(1'000));
2504
2505 env(pay(alice, carol, USD(100)),
2506 path(~USD),
2507 sendmax(GBP(150)),
2509 env.close();
2510
2511 // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
2512 // 1,000 - 120*1.25 = 850GBP
2513 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2514 if (!features[fixAMMv1_1])
2515 {
2516 // 120GBP is swapped in for 107.1428USD
2517 BEAST_EXPECT(amm.expectBalances(
2518 GBP(1'120),
2519 STAmount{USD, UINT64_C(892'8571428571428), -13},
2520 amm.tokens()));
2521 }
2522 else
2523 {
2524 BEAST_EXPECT(amm.expectBalances(
2525 GBP(1'120),
2526 STAmount{USD, UINT64_C(892'8571428571429), -13},
2527 amm.tokens()));
2528 }
2529 // 25% of 85.7142USD is paid in tr fee
2530 // 85.7142*1.25 = 107.1428USD
2531 BEAST_EXPECT(expectLine(
2532 env, carol, STAmount(USD, UINT64_C(1'085'714285714286), -12)));
2533 }
2534
2535 {
2536 // Payment via offer and AMM
2537 Env env(*this, features);
2538 Account const ed("ed");
2539
2540 fund(
2541 env,
2542 gw,
2543 {alice, bob, carol, ed},
2544 XRP(1'000),
2545 {USD(1'000), EUR(1'000), GBP(1'000)});
2546 env(rate(gw, 1.25));
2547 env.close();
2548
2549 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
2550 env.close();
2551
2552 AMM amm(env, bob, EUR(1'000), USD(1'000));
2553
2554 env(pay(alice, carol, USD(100)),
2555 path(~EUR, ~USD),
2556 sendmax(GBP(150)),
2558 env.close();
2559
2560 // alice buys 120EUR with 120GBP via the offer
2561 // and pays 25% tr fee on 120GBP
2562 // 1,000 - 120*1.25 = 850GBP
2563 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2564 // consumed offer is 120GBP/120EUR
2565 // ed doesn't pay tr fee
2566 BEAST_EXPECT(expectLine(env, ed, EUR(880), GBP(1'120)));
2567 BEAST_EXPECT(
2568 expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}}));
2569 // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR
2570 // 96EUR is swapped in for 87.5912USD
2571 BEAST_EXPECT(amm.expectBalances(
2572 EUR(1'096),
2573 STAmount{USD, UINT64_C(912'4087591240876), -13},
2574 amm.tokens()));
2575 // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD
2576 BEAST_EXPECT(expectLine(
2577 env, carol, STAmount(USD, UINT64_C(1'070'07299270073), -11)));
2578 }
2579 {
2580 // Payment via AMM, AMM
2581 Env env(*this, features);
2582 Account const ed("ed");
2583
2584 fund(
2585 env,
2586 gw,
2587 {alice, bob, carol, ed},
2588 XRP(1'000),
2589 {USD(1'000), EUR(1'000), GBP(1'000)});
2590 env(rate(gw, 1.25));
2591 env.close();
2592
2593 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
2594 AMM amm2(env, ed, EUR(1'000), USD(1'000));
2595
2596 env(pay(alice, carol, USD(100)),
2597 path(~EUR, ~USD),
2598 sendmax(GBP(150)),
2600 env.close();
2601
2602 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2603 if (!features[fixAMMv1_1])
2604 {
2605 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
2606 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
2607 // 107.1428EUR
2608 BEAST_EXPECT(amm1.expectBalances(
2609 GBP(1'120),
2610 STAmount{EUR, UINT64_C(892'8571428571428), -13},
2611 amm1.tokens()));
2612 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
2613 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
2614 BEAST_EXPECT(amm2.expectBalances(
2615 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
2616 STAmount{USD, UINT64_C(921'0526315789471), -13},
2617 amm2.tokens()));
2618 }
2619 else
2620 {
2621 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
2622 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
2623 // 107.1428EUR
2624 BEAST_EXPECT(amm1.expectBalances(
2625 GBP(1'120),
2626 STAmount{EUR, UINT64_C(892'8571428571429), -13},
2627 amm1.tokens()));
2628 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
2629 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
2630 BEAST_EXPECT(amm2.expectBalances(
2631 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
2632 STAmount{USD, UINT64_C(921'052631578948), -12},
2633 amm2.tokens()));
2634 }
2635 // 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD
2636 BEAST_EXPECT(expectLine(
2637 env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12)));
2638 }
2639 {
2640 // AMM offer crossing
2641 Env env(*this, features);
2642
2643 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'100), EUR(1'100)});
2644 env(rate(gw, 1.25));
2645 env.close();
2646
2647 AMM amm(env, bob, USD(1'000), EUR(1'100));
2648 env(offer(alice, EUR(100), USD(100)));
2649 env.close();
2650
2651 // 100USD is swapped in for 100EUR
2652 BEAST_EXPECT(
2653 amm.expectBalances(USD(1'100), EUR(1'000), amm.tokens()));
2654 // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD
2655 BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200)));
2656 BEAST_EXPECT(expectOffers(env, alice, 0));
2657 }
2658
2659 {
2660 // Payment via AMM with limit quality
2661 Env env(*this, features);
2662
2663 fund(
2664 env,
2665 gw,
2666 {alice, bob, carol},
2667 XRP(1'000),
2668 {USD(1'000), GBP(1'000)});
2669 env(rate(gw, 1.25));
2670 env.close();
2671
2672 AMM amm(env, bob, GBP(1'000), USD(1'000));
2673
2674 // requested quality limit is 100USD/178.58GBP = 0.55997
2675 // trade quality is 100USD/178.5714 = 0.55999
2676 env(pay(alice, carol, USD(100)),
2677 path(~USD),
2678 sendmax(GBP(178.58)),
2680 env.close();
2681
2682 // alice buys 125USD with 142.8571GBP and pays 25% tr fee
2683 // on 142.8571GBP
2684 // 1,000 - 142.8571*1.25 = 821.4285GBP
2685 BEAST_EXPECT(expectLine(
2686 env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13)));
2687 // 142.8571GBP is swapped in for 125USD
2688 BEAST_EXPECT(amm.expectBalances(
2689 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2690 USD(875),
2691 amm.tokens()));
2692 // 25% on 100USD is paid in tr fee
2693 // 100*1.25 = 125USD
2694 BEAST_EXPECT(expectLine(env, carol, USD(1'100)));
2695 }
2696 {
2697 // Payment via AMM with limit quality, deliver less
2698 // than requested
2699 Env env(*this, features);
2700
2701 fund(
2702 env,
2703 gw,
2704 {alice, bob, carol},
2705 XRP(1'000),
2706 {USD(1'200), GBP(1'200)});
2707 env(rate(gw, 1.25));
2708 env.close();
2709
2710 AMM amm(env, bob, GBP(1'000), USD(1'200));
2711
2712 // requested quality limit is 90USD/120GBP = 0.75
2713 // trade quality is 22.5USD/30GBP = 0.75
2714 env(pay(alice, carol, USD(90)),
2715 path(~USD),
2716 sendmax(GBP(120)),
2718 env.close();
2719
2720 if (!features[fixAMMv1_1])
2721 {
2722 // alice buys 28.125USD with 24GBP and pays 25% tr fee
2723 // on 24GBP
2724 // 1,200 - 24*1.25 = 1,170GBP
2725 BEAST_EXPECT(expectLine(env, alice, GBP(1'170)));
2726 // 24GBP is swapped in for 28.125USD
2727 BEAST_EXPECT(amm.expectBalances(
2728 GBP(1'024), USD(1'171.875), amm.tokens()));
2729 }
2730 else
2731 {
2732 // alice buys 28.125USD with 24GBP and pays 25% tr fee
2733 // on 24GBP
2734 // 1,200 - 24*1.25 =~ 1,170GBP
2735 BEAST_EXPECT(expectLine(
2736 env,
2737 alice,
2738 STAmount{GBP, UINT64_C(1'169'999999999999), -12}));
2739 // 24GBP is swapped in for 28.125USD
2740 BEAST_EXPECT(amm.expectBalances(
2741 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2742 USD(1'171.875),
2743 amm.tokens()));
2744 }
2745 // 25% on 22.5USD is paid in tr fee
2746 // 22.5*1.25 = 28.125USD
2747 BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
2748 }
2749 {
2750 // Payment via offer and AMM with limit quality, deliver less
2751 // than requested
2752 Env env(*this, features);
2753 Account const ed("ed");
2754
2755 fund(
2756 env,
2757 gw,
2758 {alice, bob, carol, ed},
2759 XRP(1'000),
2760 {USD(1'400), EUR(1'400), GBP(1'400)});
2761 env(rate(gw, 1.25));
2762 env.close();
2763
2764 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
2765 env.close();
2766
2767 AMM amm(env, bob, EUR(1'000), USD(1'400));
2768
2769 // requested quality limit is 95USD/140GBP = 0.6785
2770 // trade quality is 59.7321USD/88.0262GBP = 0.6785
2771 env(pay(alice, carol, USD(95)),
2772 path(~EUR, ~USD),
2773 sendmax(GBP(140)),
2775 env.close();
2776
2777 if (!features[fixAMMv1_1])
2778 {
2779 // alice buys 70.4210EUR with 70.4210GBP via the offer
2780 // and pays 25% tr fee on 70.4210GBP
2781 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
2782 BEAST_EXPECT(expectLine(
2783 env,
2784 alice,
2785 STAmount{GBP, UINT64_C(1'311'973684210527), -12}));
2786 // ed doesn't pay tr fee, the balances reflect consumed offer
2787 // 70.4210GBP/70.4210EUR
2788 BEAST_EXPECT(expectLine(
2789 env,
2790 ed,
2791 STAmount{EUR, UINT64_C(1'329'578947368421), -12},
2792 STAmount{GBP, UINT64_C(1'470'421052631579), -12}));
2793 BEAST_EXPECT(expectOffers(
2794 env,
2795 ed,
2796 1,
2797 {Amounts{
2798 STAmount{GBP, UINT64_C(929'5789473684212), -13},
2799 STAmount{EUR, UINT64_C(929'5789473684212), -13}}}));
2800 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
2801 // 56.3368EUR is swapped in for 74.6651USD
2802 BEAST_EXPECT(amm.expectBalances(
2803 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2804 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2805 amm.tokens()));
2806 }
2807 else
2808 {
2809 // alice buys 70.4210EUR with 70.4210GBP via the offer
2810 // and pays 25% tr fee on 70.4210GBP
2811 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
2812 BEAST_EXPECT(expectLine(
2813 env,
2814 alice,
2815 STAmount{GBP, UINT64_C(1'311'973684210525), -12}));
2816 // ed doesn't pay tr fee, the balances reflect consumed offer
2817 // 70.4210GBP/70.4210EUR
2818 BEAST_EXPECT(expectLine(
2819 env,
2820 ed,
2821 STAmount{EUR, UINT64_C(1'329'57894736842), -11},
2822 STAmount{GBP, UINT64_C(1'470'42105263158), -11}));
2823 BEAST_EXPECT(expectOffers(
2824 env,
2825 ed,
2826 1,
2827 {Amounts{
2828 STAmount{GBP, UINT64_C(929'57894736842), -11},
2829 STAmount{EUR, UINT64_C(929'57894736842), -11}}}));
2830 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
2831 // 56.3368EUR is swapped in for 74.6651USD
2832 BEAST_EXPECT(amm.expectBalances(
2833 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2834 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2835 amm.tokens()));
2836 }
2837 // 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD
2838 BEAST_EXPECT(expectLine(
2839 env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12)));
2840 }
2841 {
2842 // Payment via AMM and offer with limit quality, deliver less
2843 // than requested
2844 Env env(*this, features);
2845 Account const ed("ed");
2846
2847 fund(
2848 env,
2849 gw,
2850 {alice, bob, carol, ed},
2851 XRP(1'000),
2852 {USD(1'400), EUR(1'400), GBP(1'400)});
2853 env(rate(gw, 1.25));
2854 env.close();
2855
2856 AMM amm(env, bob, GBP(1'000), EUR(1'000));
2857
2858 env(offer(ed, EUR(1'000), USD(1'400)), txflags(tfPassive));
2859 env.close();
2860
2861 // requested quality limit is 95USD/140GBP = 0.6785
2862 // trade quality is 47.7857USD/70.4210GBP = 0.6785
2863 env(pay(alice, carol, USD(95)),
2864 path(~EUR, ~USD),
2865 sendmax(GBP(140)),
2867 env.close();
2868
2869 if (!features[fixAMMv1_1])
2870 {
2871 // alice buys 53.3322EUR with 56.3368GBP via the amm
2872 // and pays 25% tr fee on 56.3368GBP
2873 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
2874 BEAST_EXPECT(expectLine(
2875 env,
2876 alice,
2877 STAmount{GBP, UINT64_C(1'329'578947368421), -12}));
2880 // 56.3368GBP is swapped in for 53.3322EUR
2881 BEAST_EXPECT(amm.expectBalances(
2882 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2883 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2884 amm.tokens()));
2885 }
2886 else
2887 {
2888 // alice buys 53.3322EUR with 56.3368GBP via the amm
2889 // and pays 25% tr fee on 56.3368GBP
2890 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
2891 BEAST_EXPECT(expectLine(
2892 env,
2893 alice,
2894 STAmount{GBP, UINT64_C(1'329'57894736842), -11}));
2897 // 56.3368GBP is swapped in for 53.3322EUR
2898 BEAST_EXPECT(amm.expectBalances(
2899 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2900 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2901 amm.tokens()));
2902 }
2903 // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
2904 // 42.6658EUR/59.7321USD
2905 BEAST_EXPECT(expectLine(
2906 env,
2907 ed,
2908 STAmount{USD, UINT64_C(1'340'267857142857), -12},
2909 STAmount{EUR, UINT64_C(1'442'665816326531), -12}));
2910 BEAST_EXPECT(expectOffers(
2911 env,
2912 ed,
2913 1,
2914 {Amounts{
2915 STAmount{EUR, UINT64_C(957'3341836734693), -13},
2916 STAmount{USD, UINT64_C(1'340'267857142857), -12}}}));
2917 // 25% on 47.7857USD is paid in tr fee 47.7857*1.25 = 59.7321USD
2918 BEAST_EXPECT(expectLine(
2919 env, carol, STAmount(USD, UINT64_C(1'447'785714285714), -12)));
2920 }
2921 {
2922 // Payment via AMM, AMM with limit quality, deliver less
2923 // than requested
2924 Env env(*this, features);
2925 Account const ed("ed");
2926
2927 fund(
2928 env,
2929 gw,
2930 {alice, bob, carol, ed},
2931 XRP(1'000),
2932 {USD(1'400), EUR(1'400), GBP(1'400)});
2933 env(rate(gw, 1.25));
2934 env.close();
2935
2936 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
2937 AMM amm2(env, ed, EUR(1'000), USD(1'400));
2938
2939 // requested quality limit is 90USD/145GBP = 0.6206
2940 // trade quality is 66.7432USD/107.5308GBP = 0.6206
2941 env(pay(alice, carol, USD(90)),
2942 path(~EUR, ~USD),
2943 sendmax(GBP(145)),
2945 env.close();
2946
2947 if (!features[fixAMMv1_1])
2948 {
2949 // alice buys 53.3322EUR with 107.5308GBP
2950 // 25% on 86.0246GBP is paid in tr fee
2951 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
2952 BEAST_EXPECT(expectLine(
2953 env,
2954 alice,
2955 STAmount{GBP, UINT64_C(1'292'469135802469), -12}));
2956 // 86.0246GBP is swapped in for 79.2106EUR
2957 BEAST_EXPECT(amm1.expectBalances(
2958 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2959 STAmount{EUR, UINT64_C(920'78937795562), -11},
2960 amm1.tokens()));
2961 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2962 // 63.3684EUR is swapped in for 83.4291USD
2963 BEAST_EXPECT(amm2.expectBalances(
2964 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2965 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2966 amm2.tokens()));
2967 }
2968 else
2969 {
2970 // alice buys 53.3322EUR with 107.5308GBP
2971 // 25% on 86.0246GBP is paid in tr fee
2972 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
2973 BEAST_EXPECT(expectLine(
2974 env,
2975 alice,
2976 STAmount{GBP, UINT64_C(1'292'469135802466), -12}));
2977 // 86.0246GBP is swapped in for 79.2106EUR
2978 BEAST_EXPECT(amm1.expectBalances(
2979 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2980 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2981 amm1.tokens()));
2982 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2983 // 63.3684EUR is swapped in for 83.4291USD
2984 BEAST_EXPECT(amm2.expectBalances(
2985 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2986 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2987 amm2.tokens()));
2988 }
2989 // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
2990 BEAST_EXPECT(expectLine(
2991 env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12)));
2992 }
2993 {
2994 // Payment by the issuer via AMM, AMM with limit quality,
2995 // deliver less than requested
2996 Env env(*this, features);
2997
2998 fund(
2999 env,
3000 gw,
3001 {alice, bob, carol},
3002 XRP(1'000),
3003 {USD(1'400), EUR(1'400), GBP(1'400)});
3004 env(rate(gw, 1.25));
3005 env.close();
3006
3007 AMM amm1(env, alice, GBP(1'000), EUR(1'000));
3008 AMM amm2(env, bob, EUR(1'000), USD(1'400));
3009
3010 // requested quality limit is 90USD/120GBP = 0.75
3011 // trade quality is 81.1111USD/108.1481GBP = 0.75
3012 env(pay(gw, carol, USD(90)),
3013 path(~EUR, ~USD),
3014 sendmax(GBP(120)),
3016 env.close();
3017
3018 if (!features[fixAMMv1_1])
3019 {
3020 // 108.1481GBP is swapped in for 97.5935EUR
3021 BEAST_EXPECT(amm1.expectBalances(
3022 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
3023 STAmount{EUR, UINT64_C(902'4064171122988), -13},
3024 amm1.tokens()));
3025 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
3026 // 78.0748EUR is swapped in for 101.3888USD
3027 BEAST_EXPECT(amm2.expectBalances(
3028 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
3029 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3030 amm2.tokens()));
3031 }
3032 else
3033 {
3034 // 108.1481GBP is swapped in for 97.5935EUR
3035 BEAST_EXPECT(amm1.expectBalances(
3036 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
3037 STAmount{EUR, UINT64_C(902'4064171122975), -13},
3038 amm1.tokens()));
3039 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
3040 // 78.0748EUR is swapped in for 101.3888USD
3041 BEAST_EXPECT(amm2.expectBalances(
3042 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
3043 STAmount{USD, UINT64_C(1'298'611111111111), -12},
3044 amm2.tokens()));
3045 }
3046 // 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD
3047 BEAST_EXPECT(expectLine(
3048 env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12}));
3049 }
3050 }
3051
3052 void
3054 {
3055 // Single path with amm, offer, and limit quality. The quality limit
3056 // is such that the first offer should be taken but the second
3057 // should not. The total amount delivered should be the sum of the
3058 // two offers and sendMax should be more than the first offer.
3059 testcase("limitQuality");
3060 using namespace jtx;
3061
3062 {
3063 Env env(*this);
3064
3065 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
3066
3067 AMM ammBob(env, bob, XRP(1'000), USD(1'050));
3068 env(offer(bob, XRP(100), USD(50)));
3069
3070 env(pay(alice, carol, USD(100)),
3071 path(~USD),
3072 sendmax(XRP(100)),
3074
3075 BEAST_EXPECT(
3076 ammBob.expectBalances(XRP(1'050), USD(1'000), ammBob.tokens()));
3077 BEAST_EXPECT(expectLine(env, carol, USD(2'050)));
3078 BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}}));
3079 }
3080 }
3081
3082 void
3084 {
3085 testcase("Circular XRP");
3086
3087 using namespace jtx;
3088
3089 for (auto const withFix : {true, false})
3090 {
3091 auto const feats = withFix
3093 : supported_amendments() - FeatureBitset{fix1781};
3094
3095 // Payment path starting with XRP
3096 Env env(*this, feats);
3097 // Note, if alice doesn't have default ripple, then pay
3098 // fails with tecPATH_DRY.
3099 fund(
3100 env,
3101 gw,
3102 {alice, bob},
3103 XRP(10'000),
3104 {USD(200), EUR(200)},
3105 Fund::All);
3106
3107 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(101));
3108 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(101));
3109 env.close();
3110
3111 TER const expectedTer =
3112 withFix ? TER{temBAD_PATH_LOOP} : TER{tesSUCCESS};
3113 env(pay(alice, bob, EUR(1)),
3114 path(~USD, ~XRP, ~EUR),
3115 sendmax(XRP(1)),
3117 ter(expectedTer));
3118 }
3119 {
3120 // Payment path ending with XRP
3121 Env env(*this);
3122 // Note, if alice doesn't have default ripple, then pay fails
3123 // with tecPATH_DRY.
3124 fund(
3125 env,
3126 gw,
3127 {alice, bob},
3128 XRP(10'000),
3129 {USD(200), EUR(200)},
3130 Fund::All);
3131
3132 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
3133 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
3134 // EUR -> //XRP -> //USD ->XRP
3135 env(pay(alice, bob, XRP(1)),
3136 path(~XRP, ~USD, ~XRP),
3137 sendmax(EUR(1)),
3140 }
3141 {
3142 // Payment where loop is formed in the middle of the path, not
3143 // on an endpoint
3144 auto const JPY = gw["JPY"];
3145 Env env(*this);
3146 // Note, if alice doesn't have default ripple, then pay fails
3147 // with tecPATH_DRY.
3148 fund(
3149 env,
3150 gw,
3151 {alice, bob},
3152 XRP(10'000),
3153 {USD(200), EUR(200), JPY(200)},
3154 Fund::All);
3155
3156 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
3157 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
3158 AMM ammAliceXRP_JPY(env, alice, XRP(100), JPY(100));
3159
3160 env(pay(alice, bob, JPY(1)),
3161 path(~XRP, ~EUR, ~XRP, ~JPY),
3162 sendmax(USD(1)),
3165 }
3166 }
3167
3168 void
3170 {
3171 testcase("Step Limit");
3172
3173 using namespace jtx;
3174 Env env(*this, features);
3175 auto const dan = Account("dan");
3176 auto const ed = Account("ed");
3177
3178 fund(env, gw, {ed}, XRP(100'000'000), {USD(11)});
3179 env.fund(XRP(100'000'000), alice, bob, carol, dan);
3180 env.close();
3181 env.trust(USD(1), bob);
3182 env(pay(gw, bob, USD(1)));
3183 env.trust(USD(1), dan);
3184 env(pay(gw, dan, USD(1)));
3185 n_offers(env, 2'000, bob, XRP(1), USD(1));
3186 n_offers(env, 1, dan, XRP(1), USD(1));
3187 AMM ammEd(env, ed, XRP(9), USD(11));
3188
3189 // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
3190 // offer, removes 999 more as unfunded, then hits the step limit.
3191 env(offer(alice, USD(1'000), XRP(1'000)));
3192 if (!features[fixAMMv1_1])
3193 env.require(balance(
3194 alice, STAmount{USD, UINT64_C(2'050126257867561), -15}));
3195 else
3196 env.require(balance(
3197 alice, STAmount{USD, UINT64_C(2'050125257867587), -15}));
3198 env.require(owners(alice, 2));
3199 env.require(balance(bob, USD(0)));
3200 env.require(owners(bob, 1'001));
3201 env.require(balance(dan, USD(1)));
3202 env.require(owners(dan, 2));
3203
3204 // Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
3205 // 1000 offers as unfunded and hits the step limit.
3206 env(offer(carol, USD(1'000), XRP(1'000)));
3207 env.require(balance(carol, USD(none)));
3208 env.require(owners(carol, 1));
3209 env.require(balance(bob, USD(0)));
3210 env.require(owners(bob, 1));
3211 env.require(balance(dan, USD(1)));
3212 env.require(owners(dan, 2));
3213 }
3214
3215 void
3217 {
3218 testcase("Convert all of an asset using DeliverMin");
3219
3220 using namespace jtx;
3221
3222 {
3223 Env env(*this, features);
3224 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3225 env.trust(USD(100), alice, bob, carol);
3226 env(pay(alice, bob, USD(10)),
3227 delivermin(USD(10)),
3229 env(pay(alice, bob, USD(10)),
3230 delivermin(USD(-5)),
3233 env(pay(alice, bob, USD(10)),
3234 delivermin(XRP(5)),
3237 env(pay(alice, bob, USD(10)),
3238 delivermin(Account(carol)["USD"](5)),
3241 env(pay(alice, bob, USD(10)),
3242 delivermin(USD(15)),
3245 env(pay(gw, carol, USD(50)));
3246 AMM ammCarol(env, carol, XRP(10), USD(15));
3247 env(pay(alice, bob, USD(10)),
3248 paths(XRP),
3249 delivermin(USD(7)),
3251 sendmax(XRP(5)),
3253 env.require(balance(
3254 alice,
3255 drops(10'000'000'000 - env.current()->fees().base.drops())));
3256 env.require(balance(bob, XRP(10'000)));
3257 }
3258
3259 {
3260 Env env(*this, features);
3261 fund(env, gw, {alice, bob}, XRP(10'000));
3262 env.trust(USD(1'100), alice, bob);
3263 env(pay(gw, bob, USD(1'100)));
3264 AMM ammBob(env, bob, XRP(1'000), USD(1'100));
3265 env(pay(alice, alice, USD(10'000)),
3266 paths(XRP),
3267 delivermin(USD(100)),
3269 sendmax(XRP(100)));
3270 env.require(balance(alice, USD(100)));
3271 }
3272
3273 {
3274 Env env(*this, features);
3275 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3276 env.trust(USD(1'200), bob, carol);
3277 env(pay(gw, bob, USD(1'200)));
3278 AMM ammBob(env, bob, XRP(5'500), USD(1'200));
3279 env(pay(alice, carol, USD(10'000)),
3280 paths(XRP),
3281 delivermin(USD(200)),
3283 sendmax(XRP(1'000)),
3285 env(pay(alice, carol, USD(10'000)),
3286 paths(XRP),
3287 delivermin(USD(200)),
3289 sendmax(XRP(1'100)));
3290 BEAST_EXPECT(
3291 ammBob.expectBalances(XRP(6'600), USD(1'000), ammBob.tokens()));
3292 env.require(balance(carol, USD(200)));
3293 }
3294
3295 {
3296 auto const dan = Account("dan");
3297 Env env(*this, features);
3298 fund(env, gw, {alice, bob, carol, dan}, XRP(10'000));
3299 env.close();
3300 env.trust(USD(1'100), bob, carol, dan);
3301 env(pay(gw, bob, USD(100)));
3302 env(pay(gw, dan, USD(1'100)));
3303 env(offer(bob, XRP(100), USD(100)));
3304 env(offer(bob, XRP(1'000), USD(100)));
3305 AMM ammDan(env, dan, XRP(1'000), USD(1'100));
3306 if (!features[fixAMMv1_1])
3307 {
3308 env(pay(alice, carol, USD(10'000)),
3309 paths(XRP),
3310 delivermin(USD(200)),
3312 sendmax(XRP(200)));
3313 env.require(balance(bob, USD(0)));
3314 env.require(balance(carol, USD(200)));
3315 BEAST_EXPECT(ammDan.expectBalances(
3316 XRP(1'100), USD(1'000), ammDan.tokens()));
3317 }
3318 else
3319 {
3320 env(pay(alice, carol, USD(10'000)),
3321 paths(XRP),
3322 delivermin(USD(200)),
3324 sendmax(XRPAmount(200'000'001)));
3325 env.require(balance(bob, USD(0)));
3326 env.require(balance(
3327 carol, STAmount{USD, UINT64_C(200'00000090909), -11}));
3328 BEAST_EXPECT(ammDan.expectBalances(
3329 XRPAmount{1'100'000'001},
3330 STAmount{USD, UINT64_C(999'99999909091), -11},
3331 ammDan.tokens()));
3332 }
3333 }
3334 }
3335
3336 void
3338 {
3339 testcase("Payment");
3340
3341 using namespace jtx;
3342 Account const becky{"becky"};
3343
3344 bool const supportsPreauth = {features[featureDepositPreauth]};
3345
3346 // The initial implementation of DepositAuth had a bug where an
3347 // account with the DepositAuth flag set could not make a payment
3348 // to itself. That bug was fixed in the DepositPreauth amendment.
3349 Env env(*this, features);
3350 fund(env, gw, {alice, becky}, XRP(5'000));
3351 env.close();
3352
3353 env.trust(USD(1'000), alice);
3354 env.trust(USD(1'000), becky);
3355 env.close();
3356
3357 env(pay(gw, alice, USD(500)));
3358 env.close();
3359
3360 AMM ammAlice(env, alice, XRP(100), USD(140));
3361
3362 // becky pays herself USD (10) by consuming part of alice's offer.
3363 // Make sure the payment works if PaymentAuth is not involved.
3364 env(pay(becky, becky, USD(10)), path(~USD), sendmax(XRP(10)));
3365 env.close();
3366 BEAST_EXPECT(ammAlice.expectBalances(
3367 XRPAmount(107'692'308), USD(130), ammAlice.tokens()));
3368
3369 // becky decides to require authorization for deposits.
3370 env(fset(becky, asfDepositAuth));
3371 env.close();
3372
3373 // becky pays herself again. Whether it succeeds depends on
3374 // whether featureDepositPreauth is enabled.
3375 TER const expect{
3376 supportsPreauth ? TER{tesSUCCESS} : TER{tecNO_PERMISSION}};
3377
3378 env(pay(becky, becky, USD(10)),
3379 path(~USD),
3380 sendmax(XRP(10)),
3381 ter(expect));
3382
3383 env.close();
3384 }
3385
3386 void
3388 {
3389 // Exercise IOU payments and non-direct XRP payments to an account
3390 // that has the lsfDepositAuth flag set.
3391 testcase("Pay IOU");
3392
3393 using namespace jtx;
3394
3395 Env env(*this);
3396
3397 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3398 env.trust(USD(1'000), alice, bob, carol);
3399 env.close();
3400
3401 env(pay(gw, alice, USD(150)));
3402 env(pay(gw, carol, USD(150)));
3403 AMM ammCarol(env, carol, USD(100), XRPAmount(101));
3404
3405 // Make sure bob's trust line is all set up so he can receive USD.
3406 env(pay(alice, bob, USD(50)));
3407 env.close();
3408
3409 // bob sets the lsfDepositAuth flag.
3411 env.close();
3412
3413 // None of the following payments should succeed.
3414 auto failedIouPayments = [this, &env]() {
3416
3417 // Capture bob's balances before hand to confirm they don't
3418 // change.
3419 PrettyAmount const bobXrpBalance{env.balance(bob, XRP)};
3420 PrettyAmount const bobUsdBalance{env.balance(bob, USD)};
3421
3422 env(pay(alice, bob, USD(50)), ter(tecNO_PERMISSION));
3423 env.close();
3424
3425 // Note that even though alice is paying bob in XRP, the payment
3426 // is still not allowed since the payment passes through an
3427 // offer.
3428 env(pay(alice, bob, drops(1)),
3429 sendmax(USD(1)),
3431 env.close();
3432
3433 BEAST_EXPECT(bobXrpBalance == env.balance(bob, XRP));
3434 BEAST_EXPECT(bobUsdBalance == env.balance(bob, USD));
3435 };
3436
3437 // Test when bob has an XRP balance > base reserve.
3438 failedIouPayments();
3439
3440 // Set bob's XRP balance == base reserve. Also demonstrate that
3441 // bob can make payments while his lsfDepositAuth flag is set.
3442 env(pay(bob, alice, USD(25)));
3443 env.close();
3444
3445 {
3446 STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)};
3447 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
3448 env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee));
3449 env.close();
3450 }
3451
3452 // Test when bob's XRP balance == base reserve.
3453 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
3454 BEAST_EXPECT(env.balance(bob, USD) == USD(25));
3455 failedIouPayments();
3456
3457 // Test when bob has an XRP balance == 0.
3458 env(noop(bob), fee(reserve(env, 0)));
3459 env.close();
3460
3461 BEAST_EXPECT(env.balance(bob, XRP) == XRP(0));
3462 failedIouPayments();
3463
3464 // Give bob enough XRP for the fee to clear the lsfDepositAuth flag.
3465 env(pay(alice, bob, drops(env.current()->fees().base)));
3466
3467 // bob clears the lsfDepositAuth and the next payment succeeds.
3468 env(fclear(bob, asfDepositAuth));
3469 env.close();
3470
3471 env(pay(alice, bob, USD(50)));
3472 env.close();
3473
3474 env(pay(alice, bob, drops(1)), sendmax(USD(1)));
3475 env.close();
3476 BEAST_EXPECT(ammCarol.expectBalances(
3477 USD(101), XRPAmount(100), ammCarol.tokens()));
3478 }
3479
3480 void
3482 {
3483 testcase("RippleState Freeze");
3484
3485 using namespace test::jtx;
3486 Env env(*this, features);
3487
3488 Account const G1{"G1"};
3489 Account const alice{"alice"};
3490 Account const bob{"bob"};
3491
3492 env.fund(XRP(1'000), G1, alice, bob);
3493 env.close();
3494
3495 env.trust(G1["USD"](100), bob);
3496 env.trust(G1["USD"](205), alice);
3497 env.close();
3498
3499 env(pay(G1, bob, G1["USD"](10)));
3500 env(pay(G1, alice, G1["USD"](205)));
3501 env.close();
3502
3503 AMM ammAlice(env, alice, XRP(500), G1["USD"](105));
3504
3505 {
3506 auto lines = getAccountLines(env, bob);
3507 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3508 return;
3509 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3510 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100");
3511 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10");
3512 }
3513
3514 {
3515 auto lines = getAccountLines(env, alice, G1["USD"]);
3516 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3517 return;
3518 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3519 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205");
3520 // 105 transferred to AMM
3521 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100");
3522 }
3523
3524 // Account with line unfrozen (proving operations normally work)
3525 // test: can make Payment on that line
3526 env(pay(alice, bob, G1["USD"](1)));
3527
3528 // test: can receive Payment on that line
3529 env(pay(bob, alice, G1["USD"](1)));
3530 env.close();
3531
3532 // Is created via a TrustSet with SetFreeze flag
3533 // test: sets LowFreeze | HighFreeze flags
3534 env(trust(G1, bob["USD"](0), tfSetFreeze));
3535 env.close();
3536
3537 {
3538 // Account with line frozen by issuer
3539 // test: can buy more assets on that line
3540 env(offer(bob, G1["USD"](5), XRP(25)));
3541 env.close();
3542 BEAST_EXPECT(ammAlice.expectBalances(
3543 XRP(525), G1["USD"](100), ammAlice.tokens()));
3544 }
3545
3546 {
3547 // test: can not sell assets from that line
3548 env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER));
3549
3550 // test: can receive Payment on that line
3551 env(pay(alice, bob, G1["USD"](1)));
3552
3553 // test: can not make Payment from that line
3554 env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY));
3555 }
3556
3557 {
3558 // check G1 account lines
3559 // test: shows freeze
3560 auto lines = getAccountLines(env, G1);
3561 Json::Value bobLine;
3562 for (auto const& it : lines[jss::lines])
3563 {
3564 if (it[jss::account] == bob.human())
3565 {
3566 bobLine = it;
3567 break;
3568 }
3569 }
3570 if (!BEAST_EXPECT(bobLine))
3571 return;
3572 BEAST_EXPECT(bobLine[jss::freeze] == true);
3573 BEAST_EXPECT(bobLine[jss::balance] == "-16");
3574 }
3575
3576 {
3577 // test: shows freeze peer
3578 auto lines = getAccountLines(env, bob);
3579 Json::Value g1Line;
3580 for (auto const& it : lines[jss::lines])
3581 {
3582 if (it[jss::account] == G1.human())
3583 {
3584 g1Line = it;
3585 break;
3586 }
3587 }
3588 if (!BEAST_EXPECT(g1Line))
3589 return;
3590 BEAST_EXPECT(g1Line[jss::freeze_peer] == true);
3591 BEAST_EXPECT(g1Line[jss::balance] == "16");
3592 }
3593
3594 {
3595 // Is cleared via a TrustSet with ClearFreeze flag
3596 // test: sets LowFreeze | HighFreeze flags
3597 env(trust(G1, bob["USD"](0), tfClearFreeze));
3598 auto affected = env.meta()->getJson(
3599 JsonOptions::none)[sfAffectedNodes.fieldName];
3600 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3601 return;
3602 auto ff =
3603 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3604 BEAST_EXPECT(
3605 ff[sfLowLimit.fieldName] ==
3606 G1["USD"](0).value().getJson(JsonOptions::none));
3607 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3608 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3609 env.close();
3610 }
3611 }
3612
3613 void
3615 {
3616 testcase("Global Freeze");
3617
3618 using namespace test::jtx;
3619 Env env(*this, features);
3620
3621 Account G1{"G1"};
3622 Account A1{"A1"};
3623 Account A2{"A2"};
3624 Account A3{"A3"};
3625 Account A4{"A4"};
3626
3627 env.fund(XRP(12'000), G1);
3628 env.fund(XRP(1'000), A1);
3629 env.fund(XRP(20'000), A2, A3, A4);
3630 env.close();
3631
3632 env.trust(G1["USD"](1'200), A1);
3633 env.trust(G1["USD"](200), A2);
3634 env.trust(G1["BTC"](100), A3);
3635 env.trust(G1["BTC"](100), A4);
3636 env.close();
3637
3638 env(pay(G1, A1, G1["USD"](1'000)));
3639 env(pay(G1, A2, G1["USD"](100)));
3640 env(pay(G1, A3, G1["BTC"](100)));
3641 env(pay(G1, A4, G1["BTC"](100)));
3642 env.close();
3643
3644 AMM ammG1(env, G1, XRP(10'000), G1["USD"](100));
3645 env(offer(A1, XRP(10'000), G1["USD"](100)), txflags(tfPassive));
3646 env(offer(A2, G1["USD"](100), XRP(10'000)), txflags(tfPassive));
3647 env.close();
3648
3649 {
3650 // Account without GlobalFreeze (proving operations normally
3651 // work)
3652 // test: visible offers where taker_pays is unfrozen issuer
3653 auto offers = env.rpc(
3654 "book_offers",
3655 std::string("USD/") + G1.human(),
3656 "XRP")[jss::result][jss::offers];
3657 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3658 return;
3659 std::set<std::string> accounts;
3660 for (auto const& offer : offers)
3661 {
3662 accounts.insert(offer[jss::Account].asString());
3663 }
3664 BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts));
3665
3666 // test: visible offers where taker_gets is unfrozen issuer
3667 offers = env.rpc(
3668 "book_offers",
3669 "XRP",
3670 std::string("USD/") + G1.human())[jss::result][jss::offers];
3671 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3672 return;
3673 accounts.clear();
3674 for (auto const& offer : offers)
3675 {
3676 accounts.insert(offer[jss::Account].asString());
3677 }
3678 BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts));
3679 }
3680
3681 {
3682 // Offers/Payments
3683 // test: assets can be bought on the market
3684 // env(offer(A3, G1["BTC"](1), XRP(1)));
3685 AMM ammA3(env, A3, G1["BTC"](1), XRP(1));
3686
3687 // test: assets can be sold on the market
3688 // AMM is bidirectional
3689
3690 // test: direct issues can be sent
3691 env(pay(G1, A2, G1["USD"](1)));
3692
3693 // test: direct redemptions can be sent
3694 env(pay(A2, G1, G1["USD"](1)));
3695
3696 // test: via rippling can be sent
3697 env(pay(A2, A1, G1["USD"](1)));
3698
3699 // test: via rippling can be sent back
3700 env(pay(A1, A2, G1["USD"](1)));
3701 ammA3.withdrawAll(std::nullopt);
3702 }
3703
3704 {
3705 // Account with GlobalFreeze
3706 // set GlobalFreeze first
3707 // test: SetFlag GlobalFreeze will toggle back to freeze
3708 env.require(nflags(G1, asfGlobalFreeze));
3709 env(fset(G1, asfGlobalFreeze));
3710 env.require(flags(G1, asfGlobalFreeze));
3711 env.require(nflags(G1, asfNoFreeze));
3712
3713 // test: assets can't be bought on the market
3714 AMM ammA3(env, A3, G1["BTC"](1), XRP(1), ter(tecFROZEN));
3715
3716 // test: assets can't be sold on the market
3717 // AMM is bidirectional
3718 }
3719
3720 {
3721 // test: book_offers shows offers
3722 // (should these actually be filtered?)
3723 auto offers = env.rpc(
3724 "book_offers",
3725 "XRP",
3726 std::string("USD/") + G1.human())[jss::result][jss::offers];
3727 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3728 return;
3729
3730 offers = env.rpc(
3731 "book_offers",
3732 std::string("USD/") + G1.human(),
3733 "XRP")[jss::result][jss::offers];
3734 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3735 return;
3736 }
3737
3738 {
3739 // Payments
3740 // test: direct issues can be sent
3741 env(pay(G1, A2, G1["USD"](1)));
3742
3743 // test: direct redemptions can be sent
3744 env(pay(A2, G1, G1["USD"](1)));
3745
3746 // test: via rippling cant be sent
3747 env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY));
3748 }
3749 }
3750
3751 void
3753 {
3754 testcase("Offers for Frozen Trust Lines");
3755
3756 using namespace test::jtx;
3757 Env env(*this, features);
3758
3759 Account G1{"G1"};
3760 Account A2{"A2"};
3761 Account A3{"A3"};
3762 Account A4{"A4"};
3763
3764 env.fund(XRP(2'000), G1, A3, A4);
3765 env.fund(XRP(2'000), A2);
3766 env.close();
3767
3768 env.trust(G1["USD"](1'000), A2);
3769 env.trust(G1["USD"](2'000), A3);
3770 env.trust(G1["USD"](2'001), A4);
3771 env.close();
3772
3773 env(pay(G1, A3, G1["USD"](2'000)));
3774 env(pay(G1, A4, G1["USD"](2'001)));
3775 env.close();
3776
3777 AMM ammA3(env, A3, XRP(1'000), G1["USD"](1'001));
3778
3779 // removal after successful payment
3780 // test: make a payment with partially consuming offer
3781 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3782 env.close();
3783
3784 BEAST_EXPECT(
3785 ammA3.expectBalances(XRP(1'001), G1["USD"](1'000), ammA3.tokens()));
3786
3787 // test: someone else creates an offer providing liquidity
3788 env(offer(A4, XRP(999), G1["USD"](999)));
3789 env.close();
3790 // The offer consumes AMM offer
3791 BEAST_EXPECT(
3792 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
3793
3794 // test: AMM line is frozen
3795 auto const a3am =
3796 STAmount{Issue{to_currency("USD"), ammA3.ammAccount()}, 0};
3797 env(trust(G1, a3am, tfSetFreeze));
3798 auto const info = ammA3.ammRpcInfo();
3799 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3800 env.close();
3801
3802 // test: Can make a payment via the new offer
3803 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3804 env.close();
3805 // AMM is not consumed
3806 BEAST_EXPECT(
3807 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
3808
3809 // removal buy successful OfferCreate
3810 // test: freeze the new offer
3811 env(trust(G1, A4["USD"](0), tfSetFreeze));
3812 env.close();
3813
3814 // test: can no longer create a crossing offer
3815 env(offer(A2, G1["USD"](999), XRP(999)));
3816 env.close();
3817
3818 // test: offer was removed by offer_create
3819 auto offers = getAccountOffers(env, A4)[jss::offers];
3820 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
3821 return;
3822 }
3823
3824 void
3826 {
3827 testcase("Multisign AMM Transactions");
3828
3829 using namespace jtx;
3830 Env env{*this, features};
3831 Account const bogie{"bogie", KeyType::secp256k1};
3832 Account const alice{"alice", KeyType::secp256k1};
3833 Account const becky{"becky", KeyType::ed25519};
3834 Account const zelda{"zelda", KeyType::secp256k1};
3835 fund(env, gw, {alice, becky, zelda}, XRP(20'000), {USD(20'000)});
3836
3837 // alice uses a regular key with the master disabled.
3838 Account const alie{"alie", KeyType::secp256k1};
3839 env(regkey(alice, alie));
3841
3842 // Attach signers to alice.
3843 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
3844 env.close();
3845 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3846 env.require(owners(alice, signerListOwners + 0));
3847
3848 msig const ms{becky, bogie};
3849
3850 // Multisign all AMM transactions
3851 AMM ammAlice(
3852 env,
3853 alice,
3854 XRP(10'000),
3855 USD(10'000),
3856 false,
3857 0,
3858 ammCrtFee(env).drops(),
3859 std::nullopt,
3860 std::nullopt,
3861 ms,
3862 ter(tesSUCCESS));
3863 BEAST_EXPECT(ammAlice.expectBalances(
3864 XRP(10'000), USD(10'000), ammAlice.tokens()));
3865
3866 ammAlice.deposit(alice, 1'000'000);
3867 BEAST_EXPECT(ammAlice.expectBalances(
3868 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
3869
3870 ammAlice.withdraw(alice, 1'000'000);
3871 BEAST_EXPECT(ammAlice.expectBalances(
3872 XRP(10'000), USD(10'000), ammAlice.tokens()));
3873
3874 ammAlice.vote({}, 1'000);
3875 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3876
3877 env(ammAlice.bid({.account = alice, .bidMin = 100}), ms).close();
3878 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
3879 // 4000 tokens burnt
3880 BEAST_EXPECT(ammAlice.expectBalances(
3881 XRP(10'000), USD(10'000), IOUAmount{9'996'000, 0}));
3882 }
3883
3884 void
3886 {
3887 testcase("To Strand");
3888
3889 using namespace jtx;
3890
3891 // cannot have more than one offer with the same output issue
3892
3893 Env env(*this, features);
3894
3895 fund(
3896 env,
3897 gw,
3898 {alice, bob, carol},
3899 XRP(10'000),
3900 {USD(2'000), EUR(1'000)});
3901
3902 AMM bobXRP_USD(env, bob, XRP(1'000), USD(1'000));
3903 AMM bobUSD_EUR(env, bob, USD(1'000), EUR(1'000));
3904
3905 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
3906 env(pay(alice, carol, USD(100)),
3907 path(~USD, ~EUR, ~USD),
3908 sendmax(XRP(200)),
3911 }
3912
3913 void
3915 {
3916 using namespace jtx;
3917 testcase("RIPD1373");
3918
3919 {
3920 Env env(*this, features);
3921 auto const BobUSD = bob["USD"];
3922 auto const BobEUR = bob["EUR"];
3923 fund(env, gw, {alice, bob}, XRP(10'000));
3924 env.trust(USD(1'000), alice, bob);
3925 env.trust(EUR(1'000), alice, bob);
3926 env.close();
3927 fund(
3928 env,
3929 bob,
3930 {alice, gw},
3931 {BobUSD(100), BobEUR(100)},
3932 Fund::IOUOnly);
3933 env.close();
3934
3935 AMM ammBobXRP_USD(env, bob, XRP(100), BobUSD(100));
3936 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
3937
3938 AMM ammBobUSD_EUR(env, bob, BobUSD(100), BobEUR(100));
3939 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
3940
3941 Path const p = [&] {
3942 Path result;
3943 result.push_back(allpe(gw, BobUSD));
3944 result.push_back(cpe(EUR.currency));
3945 return result;
3946 }();
3947
3948 PathSet paths(p);
3949
3950 env(pay(alice, alice, EUR(1)),
3951 json(paths.json()),
3952 sendmax(XRP(10)),
3954 ter(temBAD_PATH));
3955 }
3956
3957 {
3958 Env env(*this, features);
3959
3960 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
3961
3962 AMM ammBob(env, bob, XRP(100), USD(100));
3963
3964 // payment path: XRP -> XRP/USD -> USD/XRP
3965 env(pay(alice, carol, XRP(100)),
3966 path(~USD, ~XRP),
3969 }
3970
3971 {
3972 Env env(*this, features);
3973
3974 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
3975
3976 AMM ammBob(env, bob, XRP(100), USD(100));
3977
3978 // payment path: XRP -> XRP/USD -> USD/XRP
3979 env(pay(alice, carol, XRP(100)),
3980 path(~USD, ~XRP),
3981 sendmax(XRP(200)),
3984 }
3985 }
3986
3987 void
3989 {
3990 testcase("test loop");
3991 using namespace jtx;
3992
3993 auto const CNY = gw["CNY"];
3994
3995 {
3996 Env env(*this, features);
3997
3998 env.fund(XRP(10'000), alice, bob, carol, gw);
3999 env.close();
4000 env.trust(USD(10'000), alice, bob, carol);
4001 env.close();
4002 env(pay(gw, bob, USD(100)));
4003 env(pay(gw, alice, USD(100)));
4004 env.close();
4005
4006 AMM ammBob(env, bob, XRP(100), USD(100));
4007
4008 // payment path: USD -> USD/XRP -> XRP/USD
4009 env(pay(alice, carol, USD(100)),
4010 sendmax(USD(100)),
4011 path(~XRP, ~USD),
4014 }
4015
4016 {
4017 Env env(*this, features);
4018
4019 env.fund(XRP(10'000), alice, bob, carol, gw);
4020 env.close();
4021 env.trust(USD(10'000), alice, bob, carol);
4022 env.trust(EUR(10'000), alice, bob, carol);
4023 env.trust(CNY(10'000), alice, bob, carol);
4024
4025 env(pay(gw, bob, USD(200)));
4026 env(pay(gw, bob, EUR(200)));
4027 env(pay(gw, bob, CNY(100)));
4028
4029 AMM ammBobXRP_USD(env, bob, XRP(100), USD(100));
4030 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(100));
4031 AMM ammBobEUR_CNY(env, bob, EUR(100), CNY(100));
4032
4033 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
4034 env(pay(alice, carol, CNY(100)),
4035 sendmax(XRP(100)),
4036 path(~USD, ~EUR, ~USD, ~CNY),
4039 }
4040 }
4041
4042 void
4044 {
4047 receive_max();
4048 path_find_01();
4049 path_find_02();
4050 path_find_05();
4051 path_find_06();
4052 }
4053
4054 void
4056 {
4057 using namespace jtx;
4059 FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
4060
4063 testBookStep(all | ownerPaysFee);
4064 testTransferRate(all | ownerPaysFee);
4065 testTransferRate((all - fixAMMv1_1) | ownerPaysFee);
4067 testTransferRateNoOwnerFee(all - fixAMMv1_1);
4070 }
4071
4072 void
4074 {
4075 using namespace jtx;
4078 testStepLimit(all - fixAMMv1_1);
4079 }
4080
4081 void
4083 {
4084 using namespace jtx;
4087 test_convert_all_of_an_asset(all - fixAMMv1_1);
4088 }
4089
4090 void
4092 {
4093 auto const supported{jtx::supported_amendments()};
4094 testPayment(supported - featureDepositPreauth);
4095 testPayment(supported);
4096 testPayIOU();
4097 }
4098
4099 void
4101 {
4102 using namespace test::jtx;
4103 auto const sa = supported_amendments();
4104 testRippleState(sa);
4105 testGlobalFreeze(sa);
4107 }
4108
4109 void
4111 {
4112 using namespace jtx;
4113 auto const all = supported_amendments();
4114
4116 all - featureMultiSignReserve - featureExpandedSignerList);
4117 testTxMultisign(all - featureExpandedSignerList);
4119 }
4120
4121 void
4123 {
4124 using namespace jtx;
4125 auto const all = supported_amendments();
4126
4129 testLoop(all);
4130 }
4131
4132 void
4133 run() override
4134 {
4135 testOffers();
4136 testPaths();
4137 testFlow();
4141 testFreeze();
4142 testMultisign();
4143 testPayStrand();
4144 }
4145};
4146
4147BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app, ripple, 1);
4148
4149} // namespace test
4150} // 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:56
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:57
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:68
jtx::Account const gw
Definition: AMMTest.h:66
jtx::Account const bob
Definition: AMMTest.h:69
jtx::Account const carol
Definition: AMMTest.h:67
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:102
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:239
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:160
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:154
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:161
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:637
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:275
IOUAmount tokens() const
Definition: AMM.h:337
AccountID const & ammAccount() const
Definition: AMM.h:325
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:312
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:537
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:232
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:411
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:664
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:273
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:770
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:66
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:177
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:267
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:156
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:36
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:147
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:82
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:84
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
@ lsfHighFreeze
@ lsfLowFreeze
constexpr std::uint32_t asfNoFreeze
Definition: TxFlags.h:81
constexpr std::uint32_t tfFillOrKill
Definition: TxFlags.h:98
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:96
constexpr std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:97
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1459
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:79
@ no
Definition: Steps.h:44
constexpr std::uint32_t tfPartialPayment
Definition: TxFlags.h:105
constexpr std::uint32_t tfSetfAuth
Definition: TxFlags.h:112
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:119
constexpr std::uint32_t tfClearFreeze
Definition: TxFlags.h:116
@ 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:104
@ tesSUCCESS
Definition: TER.h:244
constexpr std::uint32_t tfLimitQuality
Definition: TxFlags.h:106
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:99
constexpr std::uint32_t asfRequireAuth
Definition: TxFlags.h:77
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition: Seed.cpp:76
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:115
constexpr std::uint32_t tfSetNoRipple
Definition: TxFlags.h:113
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)