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 Env env{*this, features};
1187
1188 // The fee that's charged for transactions.
1189 auto const fee = env.current()->fees().base;
1190 {
1191 // A trust line's QualityOut should not affect offer crossing.
1192 auto const ann = Account("ann");
1193 auto const A_BUX = ann["BUX"];
1194 auto const bob = Account("bob");
1195 auto const cam = Account("cam");
1196 auto const dan = Account("dan");
1197 auto const D_BUX = dan["BUX"];
1198
1199 // Verify trust line QualityOut affects payments.
1200 env.fund(reserve(env, 4) + (fee * 4), ann, bob, cam, dan);
1201 env.close();
1202
1203 env(trust(bob, A_BUX(400)));
1204 env(trust(bob, D_BUX(200)), qualityOutPercent(120));
1205 env(trust(cam, D_BUX(100)));
1206 env.close();
1207 env(pay(dan, bob, D_BUX(100)));
1208 env.close();
1209 BEAST_EXPECT(expectLine(env, bob, D_BUX(100)));
1210
1211 env(pay(ann, cam, D_BUX(60)), path(bob, dan), sendmax(A_BUX(200)));
1212 env.close();
1213
1214 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1215 BEAST_EXPECT(expectLine(env, ann, D_BUX(none)));
1216 BEAST_EXPECT(expectLine(env, bob, A_BUX(72)));
1217 BEAST_EXPECT(expectLine(env, bob, D_BUX(40)));
1218 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1219 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1220 BEAST_EXPECT(expectLine(env, dan, A_BUX(none)));
1221 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1222
1223 AMM ammBob(env, bob, A_BUX(30), D_BUX(30));
1224
1225 env(trust(ann, D_BUX(100)));
1226 env.close();
1227
1228 // This payment caused the assert.
1229 env(pay(ann, ann, D_BUX(30)),
1230 path(A_BUX, D_BUX),
1231 sendmax(A_BUX(30)),
1232 ter(temBAD_PATH));
1233 env.close();
1234
1235 BEAST_EXPECT(
1236 ammBob.expectBalances(A_BUX(30), D_BUX(30), ammBob.tokens()));
1237 BEAST_EXPECT(expectLine(env, ann, A_BUX(none)));
1238 BEAST_EXPECT(expectLine(env, ann, D_BUX(0)));
1239 BEAST_EXPECT(expectLine(env, cam, A_BUX(none)));
1240 BEAST_EXPECT(expectLine(env, cam, D_BUX(60)));
1241 BEAST_EXPECT(expectLine(env, dan, A_BUX(0)));
1242 BEAST_EXPECT(expectLine(env, dan, D_BUX(none)));
1243 }
1244 }
1245
1246 void
1248 {
1249 // The offer crossing code expects that a DirectStep is always
1250 // preceded by a BookStep. In one instance the default path
1251 // was not matching that assumption. Here we recreate that case
1252 // so we can prove the bug stays fixed.
1253 testcase("Direct to Direct path");
1254
1255 using namespace jtx;
1256
1257 Env env{*this, features};
1258
1259 auto const ann = Account("ann");
1260 auto const bob = Account("bob");
1261 auto const cam = Account("cam");
1262 auto const carol = Account("carol");
1263 auto const A_BUX = ann["BUX"];
1264 auto const B_BUX = bob["BUX"];
1265
1266 auto const fee = env.current()->fees().base;
1267 env.fund(XRP(1'000), carol);
1268 env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam);
1269 env.close();
1270
1271 env(trust(ann, B_BUX(40)));
1272 env(trust(cam, A_BUX(40)));
1273 env(trust(bob, A_BUX(30)));
1274 env(trust(cam, B_BUX(40)));
1275 env(trust(carol, B_BUX(400)));
1276 env(trust(carol, A_BUX(400)));
1277 env.close();
1278
1279 env(pay(ann, cam, A_BUX(35)));
1280 env(pay(bob, cam, B_BUX(35)));
1281 env(pay(bob, carol, B_BUX(400)));
1282 env(pay(ann, carol, A_BUX(400)));
1283
1284 AMM ammCarol(env, carol, A_BUX(300), B_BUX(330));
1285
1286 // cam puts an offer on the books that her upcoming offer could cross.
1287 // But this offer should be deleted, not crossed, by her upcoming
1288 // offer.
1289 env(offer(cam, A_BUX(29), B_BUX(30), tfPassive));
1290 env.close();
1291 env.require(balance(cam, A_BUX(35)));
1292 env.require(balance(cam, B_BUX(35)));
1293 env.require(offers(cam, 1));
1294
1295 // This offer caused the assert.
1296 env(offer(cam, B_BUX(30), A_BUX(30)));
1297
1298 // AMM is consumed up to the first cam Offer quality
1299 if (!features[fixAMMv1_1])
1300 {
1301 BEAST_EXPECT(ammCarol.expectBalances(
1302 STAmount{A_BUX, UINT64_C(309'3541659651605), -13},
1303 STAmount{B_BUX, UINT64_C(320'0215509984417), -13},
1304 ammCarol.tokens()));
1305 BEAST_EXPECT(expectOffers(
1306 env,
1307 cam,
1308 1,
1309 {{Amounts{
1310 STAmount{B_BUX, UINT64_C(20'0215509984417), -13},
1311 STAmount{A_BUX, UINT64_C(20'0215509984417), -13}}}}));
1312 }
1313 else
1314 {
1315 BEAST_EXPECT(ammCarol.expectBalances(
1316 STAmount{A_BUX, UINT64_C(309'3541659651604), -13},
1317 STAmount{B_BUX, UINT64_C(320'0215509984419), -13},
1318 ammCarol.tokens()));
1319 BEAST_EXPECT(expectOffers(
1320 env,
1321 cam,
1322 1,
1323 {{Amounts{
1324 STAmount{B_BUX, UINT64_C(20'0215509984419), -13},
1325 STAmount{A_BUX, UINT64_C(20'0215509984419), -13}}}}));
1326 }
1327 }
1328
1329 void
1331 {
1332 testcase("lsfRequireAuth");
1333
1334 using namespace jtx;
1335
1336 Env env{*this, features};
1337
1338 auto const aliceUSD = alice["USD"];
1339 auto const bobUSD = bob["USD"];
1340
1341 env.fund(XRP(400'000), gw, alice, bob);
1342 env.close();
1343
1344 // GW requires authorization for holders of its IOUs
1345 env(fset(gw, asfRequireAuth));
1346 env.close();
1347
1348 // Properly set trust and have gw authorize bob and alice
1349 env(trust(gw, bobUSD(100)), txflags(tfSetfAuth));
1350 env(trust(bob, USD(100)));
1351 env(trust(gw, aliceUSD(100)), txflags(tfSetfAuth));
1352 env(trust(alice, USD(2'000)));
1353 env(pay(gw, alice, USD(1'000)));
1354 env.close();
1355 // Alice is able to create AMM since the GW has authorized her
1356 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
1357
1358 // Set up authorized trust line for AMM.
1359 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
1361 env.close();
1362
1363 env(pay(gw, bob, USD(50)));
1364 env.close();
1365
1366 BEAST_EXPECT(expectLine(env, bob, USD(50)));
1367
1368 // Bob's offer should cross Alice's AMM
1369 env(offer(bob, XRP(50), USD(50)));
1370 env.close();
1371
1372 BEAST_EXPECT(
1373 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
1374 BEAST_EXPECT(expectOffers(env, bob, 0));
1375 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1376 }
1377
1378 void
1380 {
1381 testcase("Missing Auth");
1382
1383 using namespace jtx;
1384
1385 Env env{*this, features};
1386
1387 env.fund(XRP(400'000), gw, alice, bob);
1388 env.close();
1389
1390 // Alice doesn't have the funds
1391 {
1392 AMM ammAlice(
1393 env, alice, USD(1'000), XRP(1'000), ter(tecUNFUNDED_AMM));
1394 }
1395
1396 env(fset(gw, asfRequireAuth));
1397 env.close();
1398
1399 env(trust(gw, bob["USD"](50)), txflags(tfSetfAuth));
1400 env.close();
1401 env(trust(bob, USD(50)));
1402 env.close();
1403
1404 env(pay(gw, bob, USD(50)));
1405 env.close();
1406 BEAST_EXPECT(expectLine(env, bob, USD(50)));
1407
1408 // Alice should not be able to create AMM without authorization.
1409 {
1410 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_LINE));
1411 }
1412
1413 // Set up a trust line for Alice, but don't authorize it. Alice
1414 // should still not be able to create AMM for USD/gw.
1415 env(trust(gw, alice["USD"](2'000)));
1416 env.close();
1417
1418 {
1419 AMM ammAlice(env, alice, USD(1'000), XRP(1'000), ter(tecNO_AUTH));
1420 }
1421
1422 // Finally, set up an authorized trust line for Alice. Now Alice's
1423 // AMM create should succeed.
1424 env(trust(gw, alice["USD"](100)), txflags(tfSetfAuth));
1425 env(trust(alice, USD(2'000)));
1426 env(pay(gw, alice, USD(1'000)));
1427 env.close();
1428
1429 AMM ammAlice(env, alice, USD(1'000), XRP(1'050));
1430
1431 // Set up authorized trust line for AMM.
1432 env(trust(gw, STAmount{Issue{USD.currency, ammAlice.ammAccount()}, 10}),
1434 env.close();
1435
1436 // Now bob creates his offer again, which crosses with alice's AMM.
1437 env(offer(bob, XRP(50), USD(50)));
1438 env.close();
1439
1440 BEAST_EXPECT(
1441 ammAlice.expectBalances(USD(1'050), XRP(1'000), ammAlice.tokens()));
1442 BEAST_EXPECT(expectOffers(env, bob, 0));
1443 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1444 }
1445
1446 void
1448 {
1449 using namespace jtx;
1452 testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3);
1466 testGatewayCrossCurrency(all - fixAMMv1_1 - fixAMMv1_3);
1474 testDirectToDirectPath(all - fixAMMv1_1 - fixAMMv1_3);
1477 }
1478
1479 void
1481 {
1482 testcase("path find consume all");
1483 using namespace jtx;
1484
1485 Env env = pathTestEnv();
1486 env.fund(XRP(100'000'250), alice);
1487 fund(env, gw, {carol, bob}, {USD(100)}, Fund::All);
1488 fund(env, gw, {alice}, {USD(100)}, Fund::IOUOnly);
1489 AMM ammCarol(env, carol, XRP(100), USD(100));
1490
1491 STPathSet st;
1492 STAmount sa;
1493 STAmount da;
1494 std::tie(st, sa, da) = find_paths(
1495 env,
1496 alice,
1497 bob,
1498 bob["AUD"](-1),
1499 std::optional<STAmount>(XRP(100'000'000)));
1500 BEAST_EXPECT(st.empty());
1501 std::tie(st, sa, da) = find_paths(
1502 env,
1503 alice,
1504 bob,
1505 bob["USD"](-1),
1506 std::optional<STAmount>(XRP(100'000'000)));
1507 // Alice sends all requested 100,000,000XRP
1508 BEAST_EXPECT(sa == XRP(100'000'000));
1509 // Bob gets ~99.99USD. This is the amount Bob
1510 // can get out of AMM for 100,000,000XRP.
1511 BEAST_EXPECT(equal(
1512 da, STAmount{bob["USD"].issue(), UINT64_C(99'9999000001), -10}));
1513 }
1514
1515 // carol holds gateway AUD, sells gateway AUD for XRP
1516 // bob will hold gateway AUD
1517 // alice pays bob gateway AUD using XRP
1518 void
1520 {
1521 testcase("via gateway");
1522 using namespace jtx;
1523
1524 Env env = pathTestEnv();
1525 auto const AUD = gw["AUD"];
1526 env.fund(XRP(10'000), alice, bob, carol, gw);
1527 env.close();
1528 env(rate(gw, 1.1));
1529 env.trust(AUD(2'000), bob, carol);
1530 env(pay(gw, carol, AUD(51)));
1531 env.close();
1532 AMM ammCarol(env, carol, XRP(40), AUD(51));
1533 env(pay(alice, bob, AUD(10)), sendmax(XRP(100)), paths(XRP));
1534 env.close();
1535 // AMM offer is 51.282052XRP/11AUD, 11AUD/1.1 = 10AUD to bob
1536 BEAST_EXPECT(
1537 ammCarol.expectBalances(XRP(51), AUD(40), ammCarol.tokens()));
1538 BEAST_EXPECT(expectLine(env, bob, AUD(10)));
1539
1540 auto const result =
1541 find_paths(env, alice, bob, Account(bob)["USD"](25));
1542 BEAST_EXPECT(std::get<0>(result).empty());
1543 }
1544
1545 void
1547 {
1548 testcase("Receive max");
1549 using namespace jtx;
1550 auto const charlie = Account("charlie");
1551 {
1552 // XRP -> IOU receive max
1553 Env env = pathTestEnv();
1554 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1555 AMM ammCharlie(env, charlie, XRP(10), USD(11));
1556 auto [st, sa, da] =
1557 find_paths(env, alice, bob, USD(-1), XRP(1).value());
1558 BEAST_EXPECT(sa == XRP(1));
1559 BEAST_EXPECT(equal(da, USD(1)));
1560 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1561 {
1562 auto const& pathElem = st[0][0];
1563 BEAST_EXPECT(
1564 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
1565 pathElem.getCurrency() == USD.currency);
1566 }
1567 }
1568 {
1569 // IOU -> XRP receive max
1570 Env env = pathTestEnv();
1571 fund(env, gw, {alice, bob, charlie}, {USD(11)}, Fund::All);
1572 AMM ammCharlie(env, charlie, XRP(11), USD(10));
1573 env.close();
1574 auto [st, sa, da] =
1575 find_paths(env, alice, bob, drops(-1), USD(1).value());
1576 BEAST_EXPECT(sa == USD(1));
1577 BEAST_EXPECT(equal(da, XRP(1)));
1578 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1579 {
1580 auto const& pathElem = st[0][0];
1581 BEAST_EXPECT(
1582 pathElem.isOffer() &&
1583 pathElem.getIssuerID() == xrpAccount() &&
1584 pathElem.getCurrency() == xrpCurrency());
1585 }
1586 }
1587 }
1588
1589 void
1591 {
1592 testcase("Path Find: XRP -> XRP and XRP -> IOU");
1593 using namespace jtx;
1594 Env env = pathTestEnv();
1595 Account A1{"A1"};
1596 Account A2{"A2"};
1597 Account A3{"A3"};
1598 Account G1{"G1"};
1599 Account G2{"G2"};
1600 Account G3{"G3"};
1601 Account M1{"M1"};
1602
1603 env.fund(XRP(100'000), A1);
1604 env.fund(XRP(10'000), A2);
1605 env.fund(XRP(1'000), A3, G1, G2, G3);
1606 env.fund(XRP(20'000), M1);
1607 env.close();
1608
1609 env.trust(G1["XYZ"](5'000), A1);
1610 env.trust(G3["ABC"](5'000), A1);
1611 env.trust(G2["XYZ"](5'000), A2);
1612 env.trust(G3["ABC"](5'000), A2);
1613 env.trust(A2["ABC"](1'000), A3);
1614 env.trust(G1["XYZ"](100'000), M1);
1615 env.trust(G2["XYZ"](100'000), M1);
1616 env.trust(G3["ABC"](100'000), M1);
1617 env.close();
1618
1619 env(pay(G1, A1, G1["XYZ"](3'500)));
1620 env(pay(G3, A1, G3["ABC"](1'200)));
1621 env(pay(G1, M1, G1["XYZ"](25'000)));
1622 env(pay(G2, M1, G2["XYZ"](25'000)));
1623 env(pay(G3, M1, G3["ABC"](25'000)));
1624 env.close();
1625
1626 AMM ammM1_G1_G2(env, M1, G1["XYZ"](1'000), G2["XYZ"](1'000));
1627 AMM ammM1_XRP_G3(env, M1, XRP(10'000), G3["ABC"](1'000));
1628
1629 STPathSet st;
1630 STAmount sa, da;
1631
1632 {
1633 auto const& send_amt = XRP(10);
1634 std::tie(st, sa, da) =
1635 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
1636 BEAST_EXPECT(equal(da, send_amt));
1637 BEAST_EXPECT(st.empty());
1638 }
1639
1640 {
1641 // no path should exist for this since dest account
1642 // does not exist.
1643 auto const& send_amt = XRP(200);
1644 std::tie(st, sa, da) = find_paths(
1645 env, A1, Account{"A0"}, send_amt, std::nullopt, xrpCurrency());
1646 BEAST_EXPECT(equal(da, send_amt));
1647 BEAST_EXPECT(st.empty());
1648 }
1649
1650 {
1651 auto const& send_amt = G3["ABC"](10);
1652 std::tie(st, sa, da) =
1653 find_paths(env, A2, G3, send_amt, std::nullopt, xrpCurrency());
1654 BEAST_EXPECT(equal(da, send_amt));
1655 BEAST_EXPECT(equal(sa, XRPAmount{101'010'102}));
1656 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]))));
1657 }
1658
1659 {
1660 auto const& send_amt = A2["ABC"](1);
1661 std::tie(st, sa, da) =
1662 find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
1663 BEAST_EXPECT(equal(da, send_amt));
1664 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
1665 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3)));
1666 }
1667
1668 {
1669 auto const& send_amt = A3["ABC"](1);
1670 std::tie(st, sa, da) =
1671 find_paths(env, A1, A3, send_amt, std::nullopt, xrpCurrency());
1672 BEAST_EXPECT(equal(da, send_amt));
1673 BEAST_EXPECT(equal(sa, XRPAmount{10'010'011}));
1674 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3, A2)));
1675 }
1676 }
1677
1678 void
1680 {
1681 testcase("Path Find: non-XRP -> XRP");
1682 using namespace jtx;
1683 Env env = pathTestEnv();
1684 Account A1{"A1"};
1685 Account A2{"A2"};
1686 Account G3{"G3"};
1687 Account M1{"M1"};
1688
1689 env.fund(XRP(1'000), A1, A2, G3);
1690 env.fund(XRP(11'000), M1);
1691 env.close();
1692
1693 env.trust(G3["ABC"](1'000), A1, A2);
1694 env.trust(G3["ABC"](100'000), M1);
1695 env.close();
1696
1697 env(pay(G3, A1, G3["ABC"](1'000)));
1698 env(pay(G3, A2, G3["ABC"](1'000)));
1699 env(pay(G3, M1, G3["ABC"](1'200)));
1700 env.close();
1701
1702 AMM ammM1(env, M1, G3["ABC"](1'000), XRP(10'010));
1703
1704 STPathSet st;
1705 STAmount sa, da;
1706
1707 auto const& send_amt = XRP(10);
1708 std::tie(st, sa, da) =
1709 find_paths(env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
1710 BEAST_EXPECT(equal(da, send_amt));
1711 BEAST_EXPECT(equal(sa, A1["ABC"](1)));
1712 BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
1713 }
1714
1715 void
1717 {
1718 testcase("Path Find: non-XRP -> non-XRP, same currency");
1719 using namespace jtx;
1720 Env env = pathTestEnv();
1721 Account A1{"A1"};
1722 Account A2{"A2"};
1723 Account A3{"A3"};
1724 Account A4{"A4"};
1725 Account G1{"G1"};
1726 Account G2{"G2"};
1727 Account G3{"G3"};
1728 Account G4{"G4"};
1729 Account M1{"M1"};
1730 Account M2{"M2"};
1731
1732 env.fund(XRP(1'000), A1, A2, A3, G1, G2, G3, G4);
1733 env.fund(XRP(10'000), A4);
1734 env.fund(XRP(21'000), M1, M2);
1735 env.close();
1736
1737 env.trust(G1["HKD"](2'000), A1);
1738 env.trust(G2["HKD"](2'000), A2);
1739 env.trust(G1["HKD"](2'000), A3);
1740 env.trust(G1["HKD"](100'000), M1);
1741 env.trust(G2["HKD"](100'000), M1);
1742 env.trust(G1["HKD"](100'000), M2);
1743 env.trust(G2["HKD"](100'000), M2);
1744 env.close();
1745
1746 env(pay(G1, A1, G1["HKD"](1'000)));
1747 env(pay(G2, A2, G2["HKD"](1'000)));
1748 env(pay(G1, A3, G1["HKD"](1'000)));
1749 env(pay(G1, M1, G1["HKD"](1'200)));
1750 env(pay(G2, M1, G2["HKD"](5'000)));
1751 env(pay(G1, M2, G1["HKD"](1'200)));
1752 env(pay(G2, M2, G2["HKD"](5'000)));
1753 env.close();
1754
1755 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
1756 AMM ammM2XRP_G2(env, M2, XRP(10'000), G2["HKD"](1'010));
1757 AMM ammM2G1_XRP(env, M2, G1["HKD"](1'010), XRP(10'000));
1758
1759 STPathSet st;
1760 STAmount sa, da;
1761
1762 {
1763 // A) Borrow or repay --
1764 // Source -> Destination (repay source issuer)
1765 auto const& send_amt = G1["HKD"](10);
1766 std::tie(st, sa, da) = find_paths(
1767 env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
1768 BEAST_EXPECT(st.empty());
1769 BEAST_EXPECT(equal(da, send_amt));
1770 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1771 }
1772
1773 {
1774 // A2) Borrow or repay --
1775 // Source -> Destination (repay destination issuer)
1776 auto const& send_amt = A1["HKD"](10);
1777 std::tie(st, sa, da) = find_paths(
1778 env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
1779 BEAST_EXPECT(st.empty());
1780 BEAST_EXPECT(equal(da, send_amt));
1781 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1782 }
1783
1784 {
1785 // B) Common gateway --
1786 // Source -> AC -> Destination
1787 auto const& send_amt = A3["HKD"](10);
1788 std::tie(st, sa, da) = find_paths(
1789 env, A1, A3, send_amt, std::nullopt, G1["HKD"].currency);
1790 BEAST_EXPECT(equal(da, send_amt));
1791 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1792 BEAST_EXPECT(same(st, stpath(G1)));
1793 }
1794
1795 {
1796 // C) Gateway to gateway --
1797 // Source -> OB -> Destination
1798 auto const& send_amt = G2["HKD"](10);
1799 std::tie(st, sa, da) = find_paths(
1800 env, G1, G2, send_amt, std::nullopt, G1["HKD"].currency);
1801 BEAST_EXPECT(equal(da, send_amt));
1802 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1803 BEAST_EXPECT(same(
1804 st,
1805 stpath(IPE(G2["HKD"])),
1806 stpath(M1),
1807 stpath(M2),
1808 stpath(IPE(xrpIssue()), IPE(G2["HKD"]))));
1809 }
1810
1811 {
1812 // D) User to unlinked gateway via order book --
1813 // Source -> AC -> OB -> Destination
1814 auto const& send_amt = G2["HKD"](10);
1815 std::tie(st, sa, da) = find_paths(
1816 env, A1, G2, send_amt, std::nullopt, G1["HKD"].currency);
1817 BEAST_EXPECT(equal(da, send_amt));
1818 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1819 BEAST_EXPECT(same(
1820 st,
1821 stpath(G1, M1),
1822 stpath(G1, M2),
1823 stpath(G1, IPE(G2["HKD"])),
1824 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]))));
1825 }
1826
1827 {
1828 // I4) XRP bridge" --
1829 // Source -> AC -> OB to XRP -> OB from XRP -> AC ->
1830 // Destination
1831 auto const& send_amt = A2["HKD"](10);
1832 std::tie(st, sa, da) = find_paths(
1833 env, A1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1834 BEAST_EXPECT(equal(da, send_amt));
1835 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1836 BEAST_EXPECT(same(
1837 st,
1838 stpath(G1, M1, G2),
1839 stpath(G1, M2, G2),
1840 stpath(G1, IPE(G2["HKD"]), G2),
1841 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]), G2)));
1842 }
1843 }
1844
1845 void
1847 {
1848 testcase("Path Find: non-XRP -> non-XRP, same currency");
1849 using namespace jtx;
1850 Env env = pathTestEnv();
1851 Account A1{"A1"};
1852 Account A2{"A2"};
1853 Account A3{"A3"};
1854 Account G1{"G1"};
1855 Account G2{"G2"};
1856 Account M1{"M1"};
1857
1858 env.fund(XRP(11'000), M1);
1859 env.fund(XRP(1'000), A1, A2, A3, G1, G2);
1860 env.close();
1861
1862 env.trust(G1["HKD"](2'000), A1);
1863 env.trust(G2["HKD"](2'000), A2);
1864 env.trust(A2["HKD"](2'000), A3);
1865 env.trust(G1["HKD"](100'000), M1);
1866 env.trust(G2["HKD"](100'000), M1);
1867 env.close();
1868
1869 env(pay(G1, A1, G1["HKD"](1'000)));
1870 env(pay(G2, A2, G2["HKD"](1'000)));
1871 env(pay(G1, M1, G1["HKD"](5'000)));
1872 env(pay(G2, M1, G2["HKD"](5'000)));
1873 env.close();
1874
1875 AMM ammM1(env, M1, G1["HKD"](1'010), G2["HKD"](1'000));
1876
1877 // E) Gateway to user
1878 // Source -> OB -> AC -> Destination
1879 auto const& send_amt = A2["HKD"](10);
1880 STPathSet st;
1881 STAmount sa, da;
1882 std::tie(st, sa, da) =
1883 find_paths(env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1884 BEAST_EXPECT(equal(da, send_amt));
1885 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1886 BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
1887 }
1888
1889 void
1891 {
1892 testcase("falseDryChanges");
1893
1894 using namespace jtx;
1895
1896 Env env(*this, features);
1897
1898 env.fund(XRP(10'000), alice, gw);
1899 // This removes no ripple for carol,
1900 // different from the original test
1901 fund(env, gw, {carol}, XRP(10'000), {}, Fund::Acct);
1902 auto const AMMXRPPool = env.current()->fees().increment * 2;
1903 env.fund(reserve(env, 5) + ammCrtFee(env) + AMMXRPPool, bob);
1904 env.close();
1905 env.trust(USD(1'000), alice, bob, carol);
1906 env.trust(EUR(1'000), alice, bob, carol);
1907
1908 env(pay(gw, alice, EUR(50)));
1909 env(pay(gw, bob, USD(150)));
1910
1911 // Bob has _just_ slightly less than 50 xrp available
1912 // If his owner count changes, he will have more liquidity.
1913 // This is one error case to test (when Flow is used).
1914 // Computing the incoming xrp to the XRP/USD offer will require two
1915 // recursive calls to the EUR/XRP offer. The second call will return
1916 // tecPATH_DRY, but the entire path should not be marked as dry.
1917 // This is the second error case to test (when flowV1 is used).
1918 env(offer(bob, EUR(50), XRP(50)));
1919 AMM ammBob(env, bob, AMMXRPPool, USD(150));
1920
1921 env(pay(alice, carol, USD(1'000'000)),
1922 path(~XRP, ~USD),
1923 sendmax(EUR(500)),
1925
1926 auto const carolUSD = env.balance(carol, USD).value();
1927 BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50));
1928 }
1929
1930 void
1932 {
1933 testcase("Book Step");
1934
1935 using namespace jtx;
1936
1937 {
1938 // simple IOU/IOU offer
1939 Env env(*this, features);
1940
1941 fund(
1942 env,
1943 gw,
1944 {alice, bob, carol},
1945 XRP(10'000),
1946 {BTC(100), USD(150)},
1947 Fund::All);
1948
1949 AMM ammBob(env, bob, BTC(100), USD(150));
1950
1951 env(pay(alice, carol, USD(50)), path(~USD), sendmax(BTC(50)));
1952
1953 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
1954 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
1955 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1956 BEAST_EXPECT(expectLine(env, carol, USD(200)));
1957 BEAST_EXPECT(
1958 ammBob.expectBalances(BTC(150), USD(100), ammBob.tokens()));
1959 }
1960 {
1961 // simple IOU/XRP XRP/IOU offer
1962 Env env(*this, features);
1963
1964 fund(
1965 env,
1966 gw,
1967 {alice, carol, bob},
1968 XRP(10'000),
1969 {BTC(100), USD(150)},
1970 Fund::All);
1971
1972 AMM ammBobBTC_XRP(env, bob, BTC(100), XRP(150));
1973 AMM ammBobXRP_USD(env, bob, XRP(100), USD(150));
1974
1975 env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50)));
1976
1977 BEAST_EXPECT(expectLine(env, alice, BTC(50)));
1978 BEAST_EXPECT(expectLine(env, bob, BTC(0)));
1979 BEAST_EXPECT(expectLine(env, bob, USD(0)));
1980 BEAST_EXPECT(expectLine(env, carol, USD(200)));
1981 BEAST_EXPECT(ammBobBTC_XRP.expectBalances(
1982 BTC(150), XRP(100), ammBobBTC_XRP.tokens()));
1983 BEAST_EXPECT(ammBobXRP_USD.expectBalances(
1984 XRP(150), USD(100), ammBobXRP_USD.tokens()));
1985 }
1986 {
1987 // simple XRP -> USD through offer and sendmax
1988 Env env(*this, features);
1989
1990 fund(
1991 env,
1992 gw,
1993 {alice, carol, bob},
1994 XRP(10'000),
1995 {USD(150)},
1996 Fund::All);
1997
1998 AMM ammBob(env, bob, XRP(100), USD(150));
1999
2000 env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50)));
2001
2002 BEAST_EXPECT(expectLedgerEntryRoot(
2003 env, alice, xrpMinusFee(env, 10'000 - 50)));
2004 BEAST_EXPECT(expectLedgerEntryRoot(
2005 env, bob, XRP(10'000) - XRP(100) - ammCrtFee(env)));
2006 BEAST_EXPECT(expectLine(env, bob, USD(0)));
2007 BEAST_EXPECT(expectLine(env, carol, USD(200)));
2008 BEAST_EXPECT(
2009 ammBob.expectBalances(XRP(150), USD(100), ammBob.tokens()));
2010 }
2011 {
2012 // simple USD -> XRP through offer and sendmax
2013 Env env(*this, features);
2014
2015 fund(
2016 env,
2017 gw,
2018 {alice, carol, bob},
2019 XRP(10'000),
2020 {USD(100)},
2021 Fund::All);
2022
2023 AMM ammBob(env, bob, USD(100), XRP(150));
2024
2025 env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50)));
2026
2027 BEAST_EXPECT(expectLine(env, alice, USD(50)));
2028 BEAST_EXPECT(expectLedgerEntryRoot(
2029 env, bob, XRP(10'000) - XRP(150) - ammCrtFee(env)));
2030 BEAST_EXPECT(expectLine(env, bob, USD(0)));
2031 BEAST_EXPECT(expectLedgerEntryRoot(env, carol, XRP(10'000 + 50)));
2032 BEAST_EXPECT(
2033 ammBob.expectBalances(USD(150), XRP(100), ammBob.tokens()));
2034 }
2035 {
2036 // test unfunded offers are removed when payment succeeds
2037 Env env(*this, features);
2038
2039 env.fund(XRP(10'000), alice, carol, gw);
2040 env.fund(XRP(10'000), bob);
2041 env.close();
2042 env.trust(USD(1'000), alice, bob, carol);
2043 env.trust(BTC(1'000), alice, bob, carol);
2044 env.trust(EUR(1'000), alice, bob, carol);
2045 env.close();
2046
2047 env(pay(gw, alice, BTC(60)));
2048 env(pay(gw, bob, USD(200)));
2049 env(pay(gw, bob, EUR(150)));
2050 env.close();
2051
2052 env(offer(bob, BTC(50), USD(50)));
2053 env(offer(bob, BTC(40), EUR(50)));
2054 env.close();
2055 AMM ammBob(env, bob, EUR(100), USD(150));
2056
2057 // unfund offer
2058 env(pay(bob, gw, EUR(50)));
2059 BEAST_EXPECT(isOffer(env, bob, BTC(50), USD(50)));
2060 BEAST_EXPECT(isOffer(env, bob, BTC(40), EUR(50)));
2061
2062 env(pay(alice, carol, USD(50)),
2063 path(~USD),
2064 path(~EUR, ~USD),
2065 sendmax(BTC(60)));
2066
2067 env.require(balance(alice, BTC(10)));
2068 env.require(balance(bob, BTC(50)));
2069 env.require(balance(bob, USD(0)));
2070 env.require(balance(bob, EUR(0)));
2071 env.require(balance(carol, USD(50)));
2072 // used in the payment
2073 BEAST_EXPECT(!isOffer(env, bob, BTC(50), USD(50)));
2074 // found unfunded
2075 BEAST_EXPECT(!isOffer(env, bob, BTC(40), EUR(50)));
2076 // unchanged
2077 BEAST_EXPECT(
2078 ammBob.expectBalances(EUR(100), USD(150), ammBob.tokens()));
2079 }
2080 {
2081 // test unfunded offers are removed when the payment fails.
2082 // bob makes two offers: a funded 50 USD for 50 BTC and an
2083 // unfunded 50 EUR for 60 BTC. alice pays carol 61 USD with 61
2084 // BTC. alice only has 60 BTC, so the payment will fail. The
2085 // payment uses two paths: one through bob's funded offer and
2086 // one through his unfunded offer. When the payment fails `flow`
2087 // should return the unfunded offer. This test is intentionally
2088 // similar to the one that removes unfunded offers when the
2089 // payment succeeds.
2090 Env env(*this, features);
2091
2092 env.fund(XRP(10'000), bob, carol, gw);
2093 env.close();
2094 // Sets rippling on, this is different from
2095 // the original test
2096 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2097 env.trust(USD(1'000), alice, bob, carol);
2098 env.trust(BTC(1'000), alice, bob, carol);
2099 env.trust(EUR(1'000), alice, bob, carol);
2100 env.close();
2101
2102 env(pay(gw, alice, BTC(60)));
2103 env(pay(gw, bob, BTC(100)));
2104 env(pay(gw, bob, USD(100)));
2105 env(pay(gw, bob, EUR(50)));
2106 env(pay(gw, carol, EUR(1)));
2107 env.close();
2108
2109 // This is multiplath, which generates limited # of offers
2110 AMM ammBobBTC_USD(env, bob, BTC(50), USD(50));
2111 env(offer(bob, BTC(60), EUR(50)));
2112 env(offer(carol, BTC(1'000), EUR(1)));
2113 env(offer(bob, EUR(50), USD(50)));
2114
2115 // unfund offer
2116 env(pay(bob, gw, EUR(50)));
2117 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2118 BTC(50), USD(50), ammBobBTC_USD.tokens()));
2119 BEAST_EXPECT(isOffer(env, bob, BTC(60), EUR(50)));
2120 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
2121 BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50)));
2122
2123 auto flowJournal = env.app().logs().journal("Flow");
2124 auto const flowResult = [&] {
2125 STAmount deliver(USD(51));
2126 STAmount smax(BTC(61));
2127 PaymentSandbox sb(env.current().get(), tapNONE);
2129 auto IPE = [](Issue const& iss) {
2130 return STPathElement(
2132 xrpAccount(),
2133 iss.currency,
2134 iss.account);
2135 };
2136 {
2137 // BTC -> USD
2138 STPath p1({IPE(USD.issue())});
2139 paths.push_back(p1);
2140 // BTC -> EUR -> USD
2141 STPath p2({IPE(EUR.issue()), IPE(USD.issue())});
2142 paths.push_back(p2);
2143 }
2144
2145 return flow(
2146 sb,
2147 deliver,
2148 alice,
2149 carol,
2150 paths,
2151 false,
2152 false,
2153 true,
2155 std::nullopt,
2156 smax,
2157 std::nullopt,
2158 flowJournal);
2159 }();
2160
2161 BEAST_EXPECT(flowResult.removableOffers.size() == 1);
2162 env.app().openLedger().modify(
2163 [&](OpenView& view, beast::Journal j) {
2164 if (flowResult.removableOffers.empty())
2165 return false;
2166 Sandbox sb(&view, tapNONE);
2167 for (auto const& o : flowResult.removableOffers)
2168 if (auto ok = sb.peek(keylet::offer(o)))
2169 offerDelete(sb, ok, flowJournal);
2170 sb.apply(view);
2171 return true;
2172 });
2173
2174 // used in payment, but since payment failed should be untouched
2175 BEAST_EXPECT(ammBobBTC_USD.expectBalances(
2176 BTC(50), USD(50), ammBobBTC_USD.tokens()));
2177 BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(1)));
2178 // found unfunded
2179 BEAST_EXPECT(!isOffer(env, bob, BTC(60), EUR(50)));
2180 }
2181 {
2182 // Do not produce more in the forward pass than the reverse pass
2183 // This test uses a path that whose reverse pass will compute a
2184 // 0.5 USD input required for a 1 EUR output. It sets a sendmax
2185 // of 0.4 USD, so the payment engine will need to do a forward
2186 // pass. Without limits, the 0.4 USD would produce 1000 EUR in
2187 // the forward pass. This test checks that the payment produces
2188 // 1 EUR, as expected.
2189
2190 Env env(*this, features);
2191 env.fund(XRP(10'000), bob, carol, gw);
2192 env.close();
2193 fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct);
2194 env.trust(USD(1'000), alice, bob, carol);
2195 env.trust(EUR(1'000), alice, bob, carol);
2196 env.close();
2197
2198 env(pay(gw, alice, USD(1'000)));
2199 env(pay(gw, bob, EUR(1'000)));
2200 env(pay(gw, bob, USD(1'000)));
2201 env.close();
2202
2203 // env(offer(bob, USD(1), drops(2)), txflags(tfPassive));
2204 AMM ammBob(env, bob, USD(8), XRPAmount{21});
2205 env(offer(bob, drops(1), EUR(1'000)), txflags(tfPassive));
2206
2207 env(pay(alice, carol, EUR(1)),
2208 path(~XRP, ~EUR),
2209 sendmax(USD(0.4)),
2211
2212 BEAST_EXPECT(expectLine(env, carol, EUR(1)));
2213 BEAST_EXPECT(ammBob.expectBalances(
2214 USD(8.4), XRPAmount{20}, ammBob.tokens()));
2215 }
2216 }
2217
2218 void
2220 {
2221 testcase("No Owner Fee");
2222 using namespace jtx;
2223
2224 {
2225 // payment via AMM
2226 Env env(*this, features);
2227
2228 fund(
2229 env,
2230 gw,
2231 {alice, bob, carol},
2232 XRP(1'000),
2233 {USD(1'000), GBP(1'000)});
2234 env(rate(gw, 1.25));
2235 env.close();
2236
2237 AMM amm(env, bob, GBP(1'000), USD(1'000));
2238
2239 env(pay(alice, carol, USD(100)),
2240 path(~USD),
2241 sendmax(GBP(150)),
2243 env.close();
2244
2245 // alice buys 107.1428USD with 120GBP and pays 25% tr fee on 120GBP
2246 // 1,000 - 120*1.25 = 850GBP
2247 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2248 if (!features[fixAMMv1_1])
2249 {
2250 // 120GBP is swapped in for 107.1428USD
2251 BEAST_EXPECT(amm.expectBalances(
2252 GBP(1'120),
2253 STAmount{USD, UINT64_C(892'8571428571428), -13},
2254 amm.tokens()));
2255 }
2256 else
2257 {
2258 BEAST_EXPECT(amm.expectBalances(
2259 GBP(1'120),
2260 STAmount{USD, UINT64_C(892'8571428571429), -13},
2261 amm.tokens()));
2262 }
2263 // 25% of 85.7142USD is paid in tr fee
2264 // 85.7142*1.25 = 107.1428USD
2265 BEAST_EXPECT(expectLine(
2266 env, carol, STAmount(USD, UINT64_C(1'085'714285714286), -12)));
2267 }
2268
2269 {
2270 // Payment via offer and AMM
2271 Env env(*this, features);
2272 Account const ed("ed");
2273
2274 fund(
2275 env,
2276 gw,
2277 {alice, bob, carol, ed},
2278 XRP(1'000),
2279 {USD(1'000), EUR(1'000), GBP(1'000)});
2280 env(rate(gw, 1.25));
2281 env.close();
2282
2283 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
2284 env.close();
2285
2286 AMM amm(env, bob, EUR(1'000), USD(1'000));
2287
2288 env(pay(alice, carol, USD(100)),
2289 path(~EUR, ~USD),
2290 sendmax(GBP(150)),
2292 env.close();
2293
2294 // alice buys 120EUR with 120GBP via the offer
2295 // and pays 25% tr fee on 120GBP
2296 // 1,000 - 120*1.25 = 850GBP
2297 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2298 // consumed offer is 120GBP/120EUR
2299 // ed doesn't pay tr fee
2300 BEAST_EXPECT(expectLine(env, ed, EUR(880), GBP(1'120)));
2301 BEAST_EXPECT(
2302 expectOffers(env, ed, 1, {Amounts{GBP(880), EUR(880)}}));
2303 // 25% on 96EUR is paid in tr fee 96*1.25 = 120EUR
2304 // 96EUR is swapped in for 87.5912USD
2305 BEAST_EXPECT(amm.expectBalances(
2306 EUR(1'096),
2307 STAmount{USD, UINT64_C(912'4087591240876), -13},
2308 amm.tokens()));
2309 // 25% on 70.0729USD is paid in tr fee 70.0729*1.25 = 87.5912USD
2310 BEAST_EXPECT(expectLine(
2311 env, carol, STAmount(USD, UINT64_C(1'070'07299270073), -11)));
2312 }
2313 {
2314 // Payment via AMM, AMM
2315 Env env(*this, features);
2316 Account const ed("ed");
2317
2318 fund(
2319 env,
2320 gw,
2321 {alice, bob, carol, ed},
2322 XRP(1'000),
2323 {USD(1'000), EUR(1'000), GBP(1'000)});
2324 env(rate(gw, 1.25));
2325 env.close();
2326
2327 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
2328 AMM amm2(env, ed, EUR(1'000), USD(1'000));
2329
2330 env(pay(alice, carol, USD(100)),
2331 path(~EUR, ~USD),
2332 sendmax(GBP(150)),
2334 env.close();
2335
2336 BEAST_EXPECT(expectLine(env, alice, GBP(850)));
2337 if (!features[fixAMMv1_1])
2338 {
2339 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
2340 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
2341 // 107.1428EUR
2342 BEAST_EXPECT(amm1.expectBalances(
2343 GBP(1'120),
2344 STAmount{EUR, UINT64_C(892'8571428571428), -13},
2345 amm1.tokens()));
2346 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
2347 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
2348 BEAST_EXPECT(amm2.expectBalances(
2349 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
2350 STAmount{USD, UINT64_C(921'0526315789471), -13},
2351 amm2.tokens()));
2352 }
2353 else
2354 {
2355 // alice buys 107.1428EUR with 120GBP and pays 25% tr fee on
2356 // 120GBP 1,000 - 120*1.25 = 850GBP 120GBP is swapped in for
2357 // 107.1428EUR
2358 BEAST_EXPECT(amm1.expectBalances(
2359 GBP(1'120),
2360 STAmount{EUR, UINT64_C(892'8571428571429), -13},
2361 amm1.tokens()));
2362 // 25% on 85.7142EUR is paid in tr fee 85.7142*1.25 =
2363 // 107.1428EUR 85.7142EUR is swapped in for 78.9473USD
2364 BEAST_EXPECT(amm2.expectBalances(
2365 STAmount(EUR, UINT64_C(1'085'714285714286), -12),
2366 STAmount{USD, UINT64_C(921'052631578948), -12},
2367 amm2.tokens()));
2368 }
2369 // 25% on 63.1578USD is paid in tr fee 63.1578*1.25 = 78.9473USD
2370 BEAST_EXPECT(expectLine(
2371 env, carol, STAmount(USD, UINT64_C(1'063'157894736842), -12)));
2372 }
2373 {
2374 // AMM offer crossing
2375 Env env(*this, features);
2376
2377 fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'100), EUR(1'100)});
2378 env(rate(gw, 1.25));
2379 env.close();
2380
2381 AMM amm(env, bob, USD(1'000), EUR(1'100));
2382 env(offer(alice, EUR(100), USD(100)));
2383 env.close();
2384
2385 // 100USD is swapped in for 100EUR
2386 BEAST_EXPECT(
2387 amm.expectBalances(USD(1'100), EUR(1'000), amm.tokens()));
2388 // alice pays 25% tr fee on 100USD 1100-100*1.25 = 975USD
2389 BEAST_EXPECT(expectLine(env, alice, USD(975), EUR(1'200)));
2390 BEAST_EXPECT(expectOffers(env, alice, 0));
2391 }
2392
2393 {
2394 // Payment via AMM with limit quality
2395 Env env(*this, features);
2396
2397 fund(
2398 env,
2399 gw,
2400 {alice, bob, carol},
2401 XRP(1'000),
2402 {USD(1'000), GBP(1'000)});
2403 env(rate(gw, 1.25));
2404 env.close();
2405
2406 AMM amm(env, bob, GBP(1'000), USD(1'000));
2407
2408 // requested quality limit is 100USD/178.58GBP = 0.55997
2409 // trade quality is 100USD/178.5714 = 0.55999
2410 env(pay(alice, carol, USD(100)),
2411 path(~USD),
2412 sendmax(GBP(178.58)),
2414 env.close();
2415
2416 // alice buys 125USD with 142.8571GBP and pays 25% tr fee
2417 // on 142.8571GBP
2418 // 1,000 - 142.8571*1.25 = 821.4285GBP
2419 BEAST_EXPECT(expectLine(
2420 env, alice, STAmount(GBP, UINT64_C(821'4285714285712), -13)));
2421 // 142.8571GBP is swapped in for 125USD
2422 BEAST_EXPECT(amm.expectBalances(
2423 STAmount{GBP, UINT64_C(1'142'857142857143), -12},
2424 USD(875),
2425 amm.tokens()));
2426 // 25% on 100USD is paid in tr fee
2427 // 100*1.25 = 125USD
2428 BEAST_EXPECT(expectLine(env, carol, USD(1'100)));
2429 }
2430 {
2431 // Payment via AMM with limit quality, deliver less
2432 // than requested
2433 Env env(*this, features);
2434
2435 fund(
2436 env,
2437 gw,
2438 {alice, bob, carol},
2439 XRP(1'000),
2440 {USD(1'200), GBP(1'200)});
2441 env(rate(gw, 1.25));
2442 env.close();
2443
2444 AMM amm(env, bob, GBP(1'000), USD(1'200));
2445
2446 // requested quality limit is 90USD/120GBP = 0.75
2447 // trade quality is 22.5USD/30GBP = 0.75
2448 env(pay(alice, carol, USD(90)),
2449 path(~USD),
2450 sendmax(GBP(120)),
2452 env.close();
2453
2454 if (!features[fixAMMv1_1])
2455 {
2456 // alice buys 28.125USD with 24GBP and pays 25% tr fee
2457 // on 24GBP
2458 // 1,200 - 24*1.25 = 1,170GBP
2459 BEAST_EXPECT(expectLine(env, alice, GBP(1'170)));
2460 // 24GBP is swapped in for 28.125USD
2461 BEAST_EXPECT(amm.expectBalances(
2462 GBP(1'024), USD(1'171.875), amm.tokens()));
2463 }
2464 else
2465 {
2466 // alice buys 28.125USD with 24GBP and pays 25% tr fee
2467 // on 24GBP
2468 // 1,200 - 24*1.25 =~ 1,170GBP
2469 BEAST_EXPECT(expectLine(
2470 env,
2471 alice,
2472 STAmount{GBP, UINT64_C(1'169'999999999999), -12}));
2473 // 24GBP is swapped in for 28.125USD
2474 BEAST_EXPECT(amm.expectBalances(
2475 STAmount{GBP, UINT64_C(1'024'000000000001), -12},
2476 USD(1'171.875),
2477 amm.tokens()));
2478 }
2479 // 25% on 22.5USD is paid in tr fee
2480 // 22.5*1.25 = 28.125USD
2481 BEAST_EXPECT(expectLine(env, carol, USD(1'222.5)));
2482 }
2483 {
2484 // Payment via offer and AMM with limit quality, deliver less
2485 // than requested
2486 Env env(*this, features);
2487 Account const ed("ed");
2488
2489 fund(
2490 env,
2491 gw,
2492 {alice, bob, carol, ed},
2493 XRP(1'000),
2494 {USD(1'400), EUR(1'400), GBP(1'400)});
2495 env(rate(gw, 1.25));
2496 env.close();
2497
2498 env(offer(ed, GBP(1'000), EUR(1'000)), txflags(tfPassive));
2499 env.close();
2500
2501 AMM amm(env, bob, EUR(1'000), USD(1'400));
2502
2503 // requested quality limit is 95USD/140GBP = 0.6785
2504 // trade quality is 59.7321USD/88.0262GBP = 0.6785
2505 env(pay(alice, carol, USD(95)),
2506 path(~EUR, ~USD),
2507 sendmax(GBP(140)),
2509 env.close();
2510
2511 if (!features[fixAMMv1_1])
2512 {
2513 // alice buys 70.4210EUR with 70.4210GBP via the offer
2514 // and pays 25% tr fee on 70.4210GBP
2515 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
2516 BEAST_EXPECT(expectLine(
2517 env,
2518 alice,
2519 STAmount{GBP, UINT64_C(1'311'973684210527), -12}));
2520 // ed doesn't pay tr fee, the balances reflect consumed offer
2521 // 70.4210GBP/70.4210EUR
2522 BEAST_EXPECT(expectLine(
2523 env,
2524 ed,
2525 STAmount{EUR, UINT64_C(1'329'578947368421), -12},
2526 STAmount{GBP, UINT64_C(1'470'421052631579), -12}));
2527 BEAST_EXPECT(expectOffers(
2528 env,
2529 ed,
2530 1,
2531 {Amounts{
2532 STAmount{GBP, UINT64_C(929'5789473684212), -13},
2533 STAmount{EUR, UINT64_C(929'5789473684212), -13}}}));
2534 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
2535 // 56.3368EUR is swapped in for 74.6651USD
2536 BEAST_EXPECT(amm.expectBalances(
2537 STAmount{EUR, UINT64_C(1'056'336842105263), -12},
2538 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2539 amm.tokens()));
2540 }
2541 else
2542 {
2543 // alice buys 70.4210EUR with 70.4210GBP via the offer
2544 // and pays 25% tr fee on 70.4210GBP
2545 // 1,400 - 70.4210*1.25 = 1400 - 88.0262 = 1311.9736GBP
2546 BEAST_EXPECT(expectLine(
2547 env,
2548 alice,
2549 STAmount{GBP, UINT64_C(1'311'973684210525), -12}));
2550 // ed doesn't pay tr fee, the balances reflect consumed offer
2551 // 70.4210GBP/70.4210EUR
2552 BEAST_EXPECT(expectLine(
2553 env,
2554 ed,
2555 STAmount{EUR, UINT64_C(1'329'57894736842), -11},
2556 STAmount{GBP, UINT64_C(1'470'42105263158), -11}));
2557 BEAST_EXPECT(expectOffers(
2558 env,
2559 ed,
2560 1,
2561 {Amounts{
2562 STAmount{GBP, UINT64_C(929'57894736842), -11},
2563 STAmount{EUR, UINT64_C(929'57894736842), -11}}}));
2564 // 25% on 56.3368EUR is paid in tr fee 56.3368*1.25 = 70.4210EUR
2565 // 56.3368EUR is swapped in for 74.6651USD
2566 BEAST_EXPECT(amm.expectBalances(
2567 STAmount{EUR, UINT64_C(1'056'336842105264), -12},
2568 STAmount{USD, UINT64_C(1'325'334821428571), -12},
2569 amm.tokens()));
2570 }
2571 // 25% on 59.7321USD is paid in tr fee 59.7321*1.25 = 74.6651USD
2572 BEAST_EXPECT(expectLine(
2573 env, carol, STAmount(USD, UINT64_C(1'459'732142857143), -12)));
2574 }
2575 {
2576 // Payment via AMM and offer with limit quality, deliver less
2577 // than requested
2578 Env env(*this, features);
2579 Account const ed("ed");
2580
2581 fund(
2582 env,
2583 gw,
2584 {alice, bob, carol, ed},
2585 XRP(1'000),
2586 {USD(1'400), EUR(1'400), GBP(1'400)});
2587 env(rate(gw, 1.25));
2588 env.close();
2589
2590 AMM amm(env, bob, GBP(1'000), EUR(1'000));
2591
2592 env(offer(ed, EUR(1'000), USD(1'400)), txflags(tfPassive));
2593 env.close();
2594
2595 // requested quality limit is 95USD/140GBP = 0.6785
2596 // trade quality is 47.7857USD/70.4210GBP = 0.6785
2597 env(pay(alice, carol, USD(95)),
2598 path(~EUR, ~USD),
2599 sendmax(GBP(140)),
2601 env.close();
2602
2603 if (!features[fixAMMv1_1])
2604 {
2605 // alice buys 53.3322EUR with 56.3368GBP via the amm
2606 // and pays 25% tr fee on 56.3368GBP
2607 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
2608 BEAST_EXPECT(expectLine(
2609 env,
2610 alice,
2611 STAmount{GBP, UINT64_C(1'329'578947368421), -12}));
2614 // 56.3368GBP is swapped in for 53.3322EUR
2615 BEAST_EXPECT(amm.expectBalances(
2616 STAmount{GBP, UINT64_C(1'056'336842105263), -12},
2617 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2618 amm.tokens()));
2619 }
2620 else
2621 {
2622 // alice buys 53.3322EUR with 56.3368GBP via the amm
2623 // and pays 25% tr fee on 56.3368GBP
2624 // 1,400 - 56.3368*1.25 = 1400 - 70.4210 = 1329.5789GBP
2625 BEAST_EXPECT(expectLine(
2626 env,
2627 alice,
2628 STAmount{GBP, UINT64_C(1'329'57894736842), -11}));
2631 // 56.3368GBP is swapped in for 53.3322EUR
2632 BEAST_EXPECT(amm.expectBalances(
2633 STAmount{GBP, UINT64_C(1'056'336842105264), -12},
2634 STAmount{EUR, UINT64_C(946'6677295918366), -13},
2635 amm.tokens()));
2636 }
2637 // 25% on 42.6658EUR is paid in tr fee 42.6658*1.25 = 53.3322EUR
2638 // 42.6658EUR/59.7321USD
2639 BEAST_EXPECT(expectLine(
2640 env,
2641 ed,
2642 STAmount{USD, UINT64_C(1'340'267857142857), -12},
2643 STAmount{EUR, UINT64_C(1'442'665816326531), -12}));
2644 BEAST_EXPECT(expectOffers(
2645 env,
2646 ed,
2647 1,
2648 {Amounts{
2649 STAmount{EUR, UINT64_C(957'3341836734693), -13},
2650 STAmount{USD, UINT64_C(1'340'267857142857), -12}}}));
2651 // 25% on 47.7857USD is paid in tr fee 47.7857*1.25 = 59.7321USD
2652 BEAST_EXPECT(expectLine(
2653 env, carol, STAmount(USD, UINT64_C(1'447'785714285714), -12)));
2654 }
2655 {
2656 // Payment via AMM, AMM with limit quality, deliver less
2657 // than requested
2658 Env env(*this, features);
2659 Account const ed("ed");
2660
2661 fund(
2662 env,
2663 gw,
2664 {alice, bob, carol, ed},
2665 XRP(1'000),
2666 {USD(1'400), EUR(1'400), GBP(1'400)});
2667 env(rate(gw, 1.25));
2668 env.close();
2669
2670 AMM amm1(env, bob, GBP(1'000), EUR(1'000));
2671 AMM amm2(env, ed, EUR(1'000), USD(1'400));
2672
2673 // requested quality limit is 90USD/145GBP = 0.6206
2674 // trade quality is 66.7432USD/107.5308GBP = 0.6206
2675 env(pay(alice, carol, USD(90)),
2676 path(~EUR, ~USD),
2677 sendmax(GBP(145)),
2679 env.close();
2680
2681 if (!features[fixAMMv1_1])
2682 {
2683 // alice buys 53.3322EUR with 107.5308GBP
2684 // 25% on 86.0246GBP is paid in tr fee
2685 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
2686 BEAST_EXPECT(expectLine(
2687 env,
2688 alice,
2689 STAmount{GBP, UINT64_C(1'292'469135802469), -12}));
2690 // 86.0246GBP is swapped in for 79.2106EUR
2691 BEAST_EXPECT(amm1.expectBalances(
2692 STAmount{GBP, UINT64_C(1'086'024691358025), -12},
2693 STAmount{EUR, UINT64_C(920'78937795562), -11},
2694 amm1.tokens()));
2695 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2696 // 63.3684EUR is swapped in for 83.4291USD
2697 BEAST_EXPECT(amm2.expectBalances(
2698 STAmount{EUR, UINT64_C(1'063'368497635504), -12},
2699 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2700 amm2.tokens()));
2701 }
2702 else
2703 {
2704 // alice buys 53.3322EUR with 107.5308GBP
2705 // 25% on 86.0246GBP is paid in tr fee
2706 // 1,400 - 86.0246*1.25 = 1400 - 107.5308 = 1229.4691GBP
2707 BEAST_EXPECT(expectLine(
2708 env,
2709 alice,
2710 STAmount{GBP, UINT64_C(1'292'469135802466), -12}));
2711 // 86.0246GBP is swapped in for 79.2106EUR
2712 BEAST_EXPECT(amm1.expectBalances(
2713 STAmount{GBP, UINT64_C(1'086'024691358027), -12},
2714 STAmount{EUR, UINT64_C(920'7893779556188), -13},
2715 amm1.tokens()));
2716 // 25% on 63.3684EUR is paid in tr fee 63.3684*1.25 = 79.2106EUR
2717 // 63.3684EUR is swapped in for 83.4291USD
2718 BEAST_EXPECT(amm2.expectBalances(
2719 STAmount{EUR, UINT64_C(1'063'368497635505), -12},
2720 STAmount{USD, UINT64_C(1'316'570881226053), -12},
2721 amm2.tokens()));
2722 }
2723 // 25% on 66.7432USD is paid in tr fee 66.7432*1.25 = 83.4291USD
2724 BEAST_EXPECT(expectLine(
2725 env, carol, STAmount(USD, UINT64_C(1'466'743295019157), -12)));
2726 }
2727 {
2728 // Payment by the issuer via AMM, AMM with limit quality,
2729 // deliver less than requested
2730 Env env(*this, features);
2731
2732 fund(
2733 env,
2734 gw,
2735 {alice, bob, carol},
2736 XRP(1'000),
2737 {USD(1'400), EUR(1'400), GBP(1'400)});
2738 env(rate(gw, 1.25));
2739 env.close();
2740
2741 AMM amm1(env, alice, GBP(1'000), EUR(1'000));
2742 AMM amm2(env, bob, EUR(1'000), USD(1'400));
2743
2744 // requested quality limit is 90USD/120GBP = 0.75
2745 // trade quality is 81.1111USD/108.1481GBP = 0.75
2746 env(pay(gw, carol, USD(90)),
2747 path(~EUR, ~USD),
2748 sendmax(GBP(120)),
2750 env.close();
2751
2752 if (!features[fixAMMv1_1])
2753 {
2754 // 108.1481GBP is swapped in for 97.5935EUR
2755 BEAST_EXPECT(amm1.expectBalances(
2756 STAmount{GBP, UINT64_C(1'108'148148148149), -12},
2757 STAmount{EUR, UINT64_C(902'4064171122988), -13},
2758 amm1.tokens()));
2759 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
2760 // 78.0748EUR is swapped in for 101.3888USD
2761 BEAST_EXPECT(amm2.expectBalances(
2762 STAmount{EUR, UINT64_C(1'078'074866310161), -12},
2763 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2764 amm2.tokens()));
2765 }
2766 else
2767 {
2768 // 108.1481GBP is swapped in for 97.5935EUR
2769 BEAST_EXPECT(amm1.expectBalances(
2770 STAmount{GBP, UINT64_C(1'108'148148148151), -12},
2771 STAmount{EUR, UINT64_C(902'4064171122975), -13},
2772 amm1.tokens()));
2773 // 25% on 78.0748EUR is paid in tr fee 78.0748*1.25 = 97.5935EUR
2774 // 78.0748EUR is swapped in for 101.3888USD
2775 BEAST_EXPECT(amm2.expectBalances(
2776 STAmount{EUR, UINT64_C(1'078'074866310162), -12},
2777 STAmount{USD, UINT64_C(1'298'611111111111), -12},
2778 amm2.tokens()));
2779 }
2780 // 25% on 81.1111USD is paid in tr fee 81.1111*1.25 = 101.3888USD
2781 BEAST_EXPECT(expectLine(
2782 env, carol, STAmount{USD, UINT64_C(1'481'111111111111), -12}));
2783 }
2784 }
2785
2786 void
2788 {
2789 // Single path with amm, offer, and limit quality. The quality limit
2790 // is such that the first offer should be taken but the second
2791 // should not. The total amount delivered should be the sum of the
2792 // two offers and sendMax should be more than the first offer.
2793 testcase("limitQuality");
2794 using namespace jtx;
2795
2796 {
2797 Env env(*this);
2798
2799 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
2800
2801 AMM ammBob(env, bob, XRP(1'000), USD(1'050));
2802 env(offer(bob, XRP(100), USD(50)));
2803
2804 env(pay(alice, carol, USD(100)),
2805 path(~USD),
2806 sendmax(XRP(100)),
2808
2809 BEAST_EXPECT(
2810 ammBob.expectBalances(XRP(1'050), USD(1'000), ammBob.tokens()));
2811 BEAST_EXPECT(expectLine(env, carol, USD(2'050)));
2812 BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), USD(50)}}}));
2813 }
2814 }
2815
2816 void
2818 {
2819 testcase("Circular XRP");
2820
2821 using namespace jtx;
2822
2823 for (auto const withFix : {true, false})
2824 {
2825 auto const feats = withFix
2827 : testable_amendments() - FeatureBitset{fix1781};
2828
2829 // Payment path starting with XRP
2830 Env env(*this, feats);
2831 // Note, if alice doesn't have default ripple, then pay
2832 // fails with tecPATH_DRY.
2833 fund(
2834 env,
2835 gw,
2836 {alice, bob},
2837 XRP(10'000),
2838 {USD(200), EUR(200)},
2839 Fund::All);
2840
2841 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(101));
2842 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(101));
2843 env.close();
2844
2845 TER const expectedTer =
2846 withFix ? TER{temBAD_PATH_LOOP} : TER{tesSUCCESS};
2847 env(pay(alice, bob, EUR(1)),
2848 path(~USD, ~XRP, ~EUR),
2849 sendmax(XRP(1)),
2851 ter(expectedTer));
2852 }
2853 {
2854 // Payment path ending with XRP
2855 Env env(*this);
2856 // Note, if alice doesn't have default ripple, then pay fails
2857 // with tecPATH_DRY.
2858 fund(
2859 env,
2860 gw,
2861 {alice, bob},
2862 XRP(10'000),
2863 {USD(200), EUR(200)},
2864 Fund::All);
2865
2866 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
2867 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
2868 // EUR -> //XRP -> //USD ->XRP
2869 env(pay(alice, bob, XRP(1)),
2870 path(~XRP, ~USD, ~XRP),
2871 sendmax(EUR(1)),
2874 }
2875 {
2876 // Payment where loop is formed in the middle of the path, not
2877 // on an endpoint
2878 auto const JPY = gw["JPY"];
2879 Env env(*this);
2880 // Note, if alice doesn't have default ripple, then pay fails
2881 // with tecPATH_DRY.
2882 fund(
2883 env,
2884 gw,
2885 {alice, bob},
2886 XRP(10'000),
2887 {USD(200), EUR(200), JPY(200)},
2888 Fund::All);
2889
2890 AMM ammAliceXRP_USD(env, alice, XRP(100), USD(100));
2891 AMM ammAliceXRP_EUR(env, alice, XRP(100), EUR(100));
2892 AMM ammAliceXRP_JPY(env, alice, XRP(100), JPY(100));
2893
2894 env(pay(alice, bob, JPY(1)),
2895 path(~XRP, ~EUR, ~XRP, ~JPY),
2896 sendmax(USD(1)),
2899 }
2900 }
2901
2902 void
2904 {
2905 testcase("Step Limit");
2906
2907 using namespace jtx;
2908 Env env(*this, features);
2909 auto const dan = Account("dan");
2910 auto const ed = Account("ed");
2911
2912 fund(env, gw, {ed}, XRP(100'000'000), {USD(11)});
2913 env.fund(XRP(100'000'000), alice, bob, carol, dan);
2914 env.close();
2915 env.trust(USD(1), bob);
2916 env(pay(gw, bob, USD(1)));
2917 env.trust(USD(1), dan);
2918 env(pay(gw, dan, USD(1)));
2919 n_offers(env, 2'000, bob, XRP(1), USD(1));
2920 n_offers(env, 1, dan, XRP(1), USD(1));
2921 AMM ammEd(env, ed, XRP(9), USD(11));
2922
2923 // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first
2924 // offer, removes 999 more as unfunded, then hits the step limit.
2925 env(offer(alice, USD(1'000), XRP(1'000)));
2926 if (!features[fixAMMv1_1])
2927 env.require(balance(
2928 alice, STAmount{USD, UINT64_C(2'050126257867561), -15}));
2929 else
2930 env.require(balance(
2931 alice, STAmount{USD, UINT64_C(2'050125257867587), -15}));
2932 env.require(owners(alice, 2));
2933 env.require(balance(bob, USD(0)));
2934 env.require(owners(bob, 1'001));
2935 env.require(balance(dan, USD(1)));
2936 env.require(owners(dan, 2));
2937
2938 // Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next
2939 // 1000 offers as unfunded and hits the step limit.
2940 env(offer(carol, USD(1'000), XRP(1'000)));
2941 env.require(balance(carol, USD(none)));
2942 env.require(owners(carol, 1));
2943 env.require(balance(bob, USD(0)));
2944 env.require(owners(bob, 1));
2945 env.require(balance(dan, USD(1)));
2946 env.require(owners(dan, 2));
2947 }
2948
2949 void
2951 {
2952 testcase("Convert all of an asset using DeliverMin");
2953
2954 using namespace jtx;
2955
2956 {
2957 Env env(*this, features);
2958 fund(env, gw, {alice, bob, carol}, XRP(10'000));
2959 env.trust(USD(100), alice, bob, carol);
2960 env(pay(alice, bob, USD(10)),
2961 delivermin(USD(10)),
2963 env(pay(alice, bob, USD(10)),
2964 delivermin(USD(-5)),
2967 env(pay(alice, bob, USD(10)),
2968 delivermin(XRP(5)),
2971 env(pay(alice, bob, USD(10)),
2972 delivermin(Account(carol)["USD"](5)),
2975 env(pay(alice, bob, USD(10)),
2976 delivermin(USD(15)),
2979 env(pay(gw, carol, USD(50)));
2980 AMM ammCarol(env, carol, XRP(10), USD(15));
2981 env(pay(alice, bob, USD(10)),
2982 paths(XRP),
2983 delivermin(USD(7)),
2985 sendmax(XRP(5)),
2987 env.require(balance(
2988 alice,
2989 drops(10'000'000'000 - env.current()->fees().base.drops())));
2990 env.require(balance(bob, XRP(10'000)));
2991 }
2992
2993 {
2994 Env env(*this, features);
2995 fund(env, gw, {alice, bob}, XRP(10'000));
2996 env.trust(USD(1'100), alice, bob);
2997 env(pay(gw, bob, USD(1'100)));
2998 AMM ammBob(env, bob, XRP(1'000), USD(1'100));
2999 env(pay(alice, alice, USD(10'000)),
3000 paths(XRP),
3001 delivermin(USD(100)),
3003 sendmax(XRP(100)));
3004 env.require(balance(alice, USD(100)));
3005 }
3006
3007 {
3008 Env env(*this, features);
3009 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3010 env.trust(USD(1'200), bob, carol);
3011 env(pay(gw, bob, USD(1'200)));
3012 AMM ammBob(env, bob, XRP(5'500), USD(1'200));
3013 env(pay(alice, carol, USD(10'000)),
3014 paths(XRP),
3015 delivermin(USD(200)),
3017 sendmax(XRP(1'000)),
3019 env(pay(alice, carol, USD(10'000)),
3020 paths(XRP),
3021 delivermin(USD(200)),
3023 sendmax(XRP(1'100)));
3024 BEAST_EXPECT(
3025 ammBob.expectBalances(XRP(6'600), USD(1'000), ammBob.tokens()));
3026 env.require(balance(carol, USD(200)));
3027 }
3028
3029 {
3030 auto const dan = Account("dan");
3031 Env env(*this, features);
3032 fund(env, gw, {alice, bob, carol, dan}, XRP(10'000));
3033 env.close();
3034 env.trust(USD(1'100), bob, carol, dan);
3035 env(pay(gw, bob, USD(100)));
3036 env(pay(gw, dan, USD(1'100)));
3037 env(offer(bob, XRP(100), USD(100)));
3038 env(offer(bob, XRP(1'000), USD(100)));
3039 AMM ammDan(env, dan, XRP(1'000), USD(1'100));
3040 if (!features[fixAMMv1_1])
3041 {
3042 env(pay(alice, carol, USD(10'000)),
3043 paths(XRP),
3044 delivermin(USD(200)),
3046 sendmax(XRP(200)));
3047 env.require(balance(bob, USD(0)));
3048 env.require(balance(carol, USD(200)));
3049 BEAST_EXPECT(ammDan.expectBalances(
3050 XRP(1'100), USD(1'000), ammDan.tokens()));
3051 }
3052 else
3053 {
3054 env(pay(alice, carol, USD(10'000)),
3055 paths(XRP),
3056 delivermin(USD(200)),
3058 sendmax(XRPAmount(200'000'001)));
3059 env.require(balance(bob, USD(0)));
3060 env.require(balance(
3061 carol, STAmount{USD, UINT64_C(200'00000090909), -11}));
3062 BEAST_EXPECT(ammDan.expectBalances(
3063 XRPAmount{1'100'000'001},
3064 STAmount{USD, UINT64_C(999'99999909091), -11},
3065 ammDan.tokens()));
3066 }
3067 }
3068 }
3069
3070 void
3072 {
3073 testcase("Payment");
3074
3075 using namespace jtx;
3076 Account const becky{"becky"};
3077
3078 bool const supportsPreauth = {features[featureDepositPreauth]};
3079
3080 // The initial implementation of DepositAuth had a bug where an
3081 // account with the DepositAuth flag set could not make a payment
3082 // to itself. That bug was fixed in the DepositPreauth amendment.
3083 Env env(*this, features);
3084 fund(env, gw, {alice, becky}, XRP(5'000));
3085 env.close();
3086
3087 env.trust(USD(1'000), alice);
3088 env.trust(USD(1'000), becky);
3089 env.close();
3090
3091 env(pay(gw, alice, USD(500)));
3092 env.close();
3093
3094 AMM ammAlice(env, alice, XRP(100), USD(140));
3095
3096 // becky pays herself USD (10) by consuming part of alice's offer.
3097 // Make sure the payment works if PaymentAuth is not involved.
3098 env(pay(becky, becky, USD(10)), path(~USD), sendmax(XRP(10)));
3099 env.close();
3100 BEAST_EXPECT(ammAlice.expectBalances(
3101 XRPAmount(107'692'308), USD(130), ammAlice.tokens()));
3102
3103 // becky decides to require authorization for deposits.
3104 env(fset(becky, asfDepositAuth));
3105 env.close();
3106
3107 // becky pays herself again. Whether it succeeds depends on
3108 // whether featureDepositPreauth is enabled.
3109 TER const expect{
3110 supportsPreauth ? TER{tesSUCCESS} : TER{tecNO_PERMISSION}};
3111
3112 env(pay(becky, becky, USD(10)),
3113 path(~USD),
3114 sendmax(XRP(10)),
3115 ter(expect));
3116
3117 env.close();
3118 }
3119
3120 void
3122 {
3123 // Exercise IOU payments and non-direct XRP payments to an account
3124 // that has the lsfDepositAuth flag set.
3125 testcase("Pay IOU");
3126
3127 using namespace jtx;
3128
3129 Env env(*this);
3130
3131 fund(env, gw, {alice, bob, carol}, XRP(10'000));
3132 env.trust(USD(1'000), alice, bob, carol);
3133 env.close();
3134
3135 env(pay(gw, alice, USD(150)));
3136 env(pay(gw, carol, USD(150)));
3137 AMM ammCarol(env, carol, USD(100), XRPAmount(101));
3138
3139 // Make sure bob's trust line is all set up so he can receive USD.
3140 env(pay(alice, bob, USD(50)));
3141 env.close();
3142
3143 // bob sets the lsfDepositAuth flag.
3145 env.close();
3146
3147 // None of the following payments should succeed.
3148 auto failedIouPayments = [this, &env]() {
3150
3151 // Capture bob's balances before hand to confirm they don't
3152 // change.
3153 PrettyAmount const bobXrpBalance{env.balance(bob, XRP)};
3154 PrettyAmount const bobUsdBalance{env.balance(bob, USD)};
3155
3156 env(pay(alice, bob, USD(50)), ter(tecNO_PERMISSION));
3157 env.close();
3158
3159 // Note that even though alice is paying bob in XRP, the payment
3160 // is still not allowed since the payment passes through an
3161 // offer.
3162 env(pay(alice, bob, drops(1)),
3163 sendmax(USD(1)),
3165 env.close();
3166
3167 BEAST_EXPECT(bobXrpBalance == env.balance(bob, XRP));
3168 BEAST_EXPECT(bobUsdBalance == env.balance(bob, USD));
3169 };
3170
3171 // Test when bob has an XRP balance > base reserve.
3172 failedIouPayments();
3173
3174 // Set bob's XRP balance == base reserve. Also demonstrate that
3175 // bob can make payments while his lsfDepositAuth flag is set.
3176 env(pay(bob, alice, USD(25)));
3177 env.close();
3178
3179 {
3180 STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)};
3181 XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)};
3182 env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee));
3183 env.close();
3184 }
3185
3186 // Test when bob's XRP balance == base reserve.
3187 BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0));
3188 BEAST_EXPECT(env.balance(bob, USD) == USD(25));
3189 failedIouPayments();
3190
3191 // Test when bob has an XRP balance == 0.
3192 env(noop(bob), fee(reserve(env, 0)));
3193 env.close();
3194
3195 BEAST_EXPECT(env.balance(bob, XRP) == XRP(0));
3196 failedIouPayments();
3197
3198 // Give bob enough XRP for the fee to clear the lsfDepositAuth flag.
3199 env(pay(alice, bob, drops(env.current()->fees().base)));
3200
3201 // bob clears the lsfDepositAuth and the next payment succeeds.
3202 env(fclear(bob, asfDepositAuth));
3203 env.close();
3204
3205 env(pay(alice, bob, USD(50)));
3206 env.close();
3207
3208 env(pay(alice, bob, drops(1)), sendmax(USD(1)));
3209 env.close();
3210 BEAST_EXPECT(ammCarol.expectBalances(
3211 USD(101), XRPAmount(100), ammCarol.tokens()));
3212 }
3213
3214 void
3216 {
3217 testcase("RippleState Freeze");
3218
3219 using namespace test::jtx;
3220 Env env(*this, features);
3221
3222 Account const G1{"G1"};
3223 Account const alice{"alice"};
3224 Account const bob{"bob"};
3225
3226 env.fund(XRP(1'000), G1, alice, bob);
3227 env.close();
3228
3229 env.trust(G1["USD"](100), bob);
3230 env.trust(G1["USD"](205), alice);
3231 env.close();
3232
3233 env(pay(G1, bob, G1["USD"](10)));
3234 env(pay(G1, alice, G1["USD"](205)));
3235 env.close();
3236
3237 AMM ammAlice(env, alice, XRP(500), G1["USD"](105));
3238
3239 {
3240 auto lines = getAccountLines(env, bob);
3241 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3242 return;
3243 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3244 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "100");
3245 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "10");
3246 }
3247
3248 {
3249 auto lines = getAccountLines(env, alice, G1["USD"]);
3250 if (!BEAST_EXPECT(checkArraySize(lines[jss::lines], 1u)))
3251 return;
3252 BEAST_EXPECT(lines[jss::lines][0u][jss::account] == G1.human());
3253 BEAST_EXPECT(lines[jss::lines][0u][jss::limit] == "205");
3254 // 105 transferred to AMM
3255 BEAST_EXPECT(lines[jss::lines][0u][jss::balance] == "100");
3256 }
3257
3258 // Account with line unfrozen (proving operations normally work)
3259 // test: can make Payment on that line
3260 env(pay(alice, bob, G1["USD"](1)));
3261
3262 // test: can receive Payment on that line
3263 env(pay(bob, alice, G1["USD"](1)));
3264 env.close();
3265
3266 // Is created via a TrustSet with SetFreeze flag
3267 // test: sets LowFreeze | HighFreeze flags
3268 env(trust(G1, bob["USD"](0), tfSetFreeze));
3269 env.close();
3270
3271 {
3272 // Account with line frozen by issuer
3273 // test: can buy more assets on that line
3274 env(offer(bob, G1["USD"](5), XRP(25)));
3275 env.close();
3276 BEAST_EXPECT(ammAlice.expectBalances(
3277 XRP(525), G1["USD"](100), ammAlice.tokens()));
3278 }
3279
3280 {
3281 // test: can not sell assets from that line
3282 env(offer(bob, XRP(1), G1["USD"](5)), ter(tecUNFUNDED_OFFER));
3283
3284 // test: can receive Payment on that line
3285 env(pay(alice, bob, G1["USD"](1)));
3286
3287 // test: can not make Payment from that line
3288 env(pay(bob, alice, G1["USD"](1)), ter(tecPATH_DRY));
3289 }
3290
3291 {
3292 // check G1 account lines
3293 // test: shows freeze
3294 auto lines = getAccountLines(env, G1);
3295 Json::Value bobLine;
3296 for (auto const& it : lines[jss::lines])
3297 {
3298 if (it[jss::account] == bob.human())
3299 {
3300 bobLine = it;
3301 break;
3302 }
3303 }
3304 if (!BEAST_EXPECT(bobLine))
3305 return;
3306 BEAST_EXPECT(bobLine[jss::freeze] == true);
3307 BEAST_EXPECT(bobLine[jss::balance] == "-16");
3308 }
3309
3310 {
3311 // test: shows freeze peer
3312 auto lines = getAccountLines(env, bob);
3313 Json::Value g1Line;
3314 for (auto const& it : lines[jss::lines])
3315 {
3316 if (it[jss::account] == G1.human())
3317 {
3318 g1Line = it;
3319 break;
3320 }
3321 }
3322 if (!BEAST_EXPECT(g1Line))
3323 return;
3324 BEAST_EXPECT(g1Line[jss::freeze_peer] == true);
3325 BEAST_EXPECT(g1Line[jss::balance] == "16");
3326 }
3327
3328 {
3329 // Is cleared via a TrustSet with ClearFreeze flag
3330 // test: sets LowFreeze | HighFreeze flags
3331 env(trust(G1, bob["USD"](0), tfClearFreeze));
3332 auto affected = env.meta()->getJson(
3333 JsonOptions::none)[sfAffectedNodes.fieldName];
3334 if (!BEAST_EXPECT(checkArraySize(affected, 2u)))
3335 return;
3336 auto ff =
3337 affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
3338 BEAST_EXPECT(
3339 ff[sfLowLimit.fieldName] ==
3340 G1["USD"](0).value().getJson(JsonOptions::none));
3341 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
3342 BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
3343 env.close();
3344 }
3345 }
3346
3347 void
3349 {
3350 testcase("Global Freeze");
3351
3352 using namespace test::jtx;
3353 Env env(*this, features);
3354
3355 Account G1{"G1"};
3356 Account A1{"A1"};
3357 Account A2{"A2"};
3358 Account A3{"A3"};
3359 Account A4{"A4"};
3360
3361 env.fund(XRP(12'000), G1);
3362 env.fund(XRP(1'000), A1);
3363 env.fund(XRP(20'000), A2, A3, A4);
3364 env.close();
3365
3366 env.trust(G1["USD"](1'200), A1);
3367 env.trust(G1["USD"](200), A2);
3368 env.trust(G1["BTC"](100), A3);
3369 env.trust(G1["BTC"](100), A4);
3370 env.close();
3371
3372 env(pay(G1, A1, G1["USD"](1'000)));
3373 env(pay(G1, A2, G1["USD"](100)));
3374 env(pay(G1, A3, G1["BTC"](100)));
3375 env(pay(G1, A4, G1["BTC"](100)));
3376 env.close();
3377
3378 AMM ammG1(env, G1, XRP(10'000), G1["USD"](100));
3379 env(offer(A1, XRP(10'000), G1["USD"](100)), txflags(tfPassive));
3380 env(offer(A2, G1["USD"](100), XRP(10'000)), txflags(tfPassive));
3381 env.close();
3382
3383 {
3384 // Account without GlobalFreeze (proving operations normally
3385 // work)
3386 // test: visible offers where taker_pays is unfrozen issuer
3387 auto offers = env.rpc(
3388 "book_offers",
3389 std::string("USD/") + G1.human(),
3390 "XRP")[jss::result][jss::offers];
3391 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3392 return;
3393 std::set<std::string> accounts;
3394 for (auto const& offer : offers)
3395 {
3396 accounts.insert(offer[jss::Account].asString());
3397 }
3398 BEAST_EXPECT(accounts.find(A2.human()) != std::end(accounts));
3399
3400 // test: visible offers where taker_gets is unfrozen issuer
3401 offers = env.rpc(
3402 "book_offers",
3403 "XRP",
3404 std::string("USD/") + G1.human())[jss::result][jss::offers];
3405 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3406 return;
3407 accounts.clear();
3408 for (auto const& offer : offers)
3409 {
3410 accounts.insert(offer[jss::Account].asString());
3411 }
3412 BEAST_EXPECT(accounts.find(A1.human()) != std::end(accounts));
3413 }
3414
3415 {
3416 // Offers/Payments
3417 // test: assets can be bought on the market
3418 // env(offer(A3, G1["BTC"](1), XRP(1)));
3419 AMM ammA3(env, A3, G1["BTC"](1), XRP(1));
3420
3421 // test: assets can be sold on the market
3422 // AMM is bidirectional
3423
3424 // test: direct issues can be sent
3425 env(pay(G1, A2, G1["USD"](1)));
3426
3427 // test: direct redemptions can be sent
3428 env(pay(A2, G1, G1["USD"](1)));
3429
3430 // test: via rippling can be sent
3431 env(pay(A2, A1, G1["USD"](1)));
3432
3433 // test: via rippling can be sent back
3434 env(pay(A1, A2, G1["USD"](1)));
3435 ammA3.withdrawAll(std::nullopt);
3436 }
3437
3438 {
3439 // Account with GlobalFreeze
3440 // set GlobalFreeze first
3441 // test: SetFlag GlobalFreeze will toggle back to freeze
3442 env.require(nflags(G1, asfGlobalFreeze));
3443 env(fset(G1, asfGlobalFreeze));
3444 env.require(flags(G1, asfGlobalFreeze));
3445 env.require(nflags(G1, asfNoFreeze));
3446
3447 // test: assets can't be bought on the market
3448 AMM ammA3(env, A3, G1["BTC"](1), XRP(1), ter(tecFROZEN));
3449
3450 // test: assets can't be sold on the market
3451 // AMM is bidirectional
3452 }
3453
3454 {
3455 // test: book_offers shows offers
3456 // (should these actually be filtered?)
3457 auto offers = env.rpc(
3458 "book_offers",
3459 "XRP",
3460 std::string("USD/") + G1.human())[jss::result][jss::offers];
3461 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3462 return;
3463
3464 offers = env.rpc(
3465 "book_offers",
3466 std::string("USD/") + G1.human(),
3467 "XRP")[jss::result][jss::offers];
3468 if (!BEAST_EXPECT(checkArraySize(offers, 1u)))
3469 return;
3470 }
3471
3472 {
3473 // Payments
3474 // test: direct issues can be sent
3475 env(pay(G1, A2, G1["USD"](1)));
3476
3477 // test: direct redemptions can be sent
3478 env(pay(A2, G1, G1["USD"](1)));
3479
3480 // test: via rippling cant be sent
3481 env(pay(A2, A1, G1["USD"](1)), ter(tecPATH_DRY));
3482 }
3483 }
3484
3485 void
3487 {
3488 testcase("Offers for Frozen Trust Lines");
3489
3490 using namespace test::jtx;
3491 Env env(*this, features);
3492
3493 Account G1{"G1"};
3494 Account A2{"A2"};
3495 Account A3{"A3"};
3496 Account A4{"A4"};
3497
3498 env.fund(XRP(2'000), G1, A3, A4);
3499 env.fund(XRP(2'000), A2);
3500 env.close();
3501
3502 env.trust(G1["USD"](1'000), A2);
3503 env.trust(G1["USD"](2'000), A3);
3504 env.trust(G1["USD"](2'001), A4);
3505 env.close();
3506
3507 env(pay(G1, A3, G1["USD"](2'000)));
3508 env(pay(G1, A4, G1["USD"](2'001)));
3509 env.close();
3510
3511 AMM ammA3(env, A3, XRP(1'000), G1["USD"](1'001));
3512
3513 // removal after successful payment
3514 // test: make a payment with partially consuming offer
3515 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3516 env.close();
3517
3518 BEAST_EXPECT(
3519 ammA3.expectBalances(XRP(1'001), G1["USD"](1'000), ammA3.tokens()));
3520
3521 // test: someone else creates an offer providing liquidity
3522 env(offer(A4, XRP(999), G1["USD"](999)));
3523 env.close();
3524 // The offer consumes AMM offer
3525 BEAST_EXPECT(
3526 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
3527
3528 // test: AMM line is frozen
3529 auto const a3am =
3530 STAmount{Issue{to_currency("USD"), ammA3.ammAccount()}, 0};
3531 env(trust(G1, a3am, tfSetFreeze));
3532 auto const info = ammA3.ammRpcInfo();
3533 BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool());
3534 env.close();
3535
3536 // test: Can make a payment via the new offer
3537 env(pay(A2, G1, G1["USD"](1)), paths(G1["USD"]), sendmax(XRP(1)));
3538 env.close();
3539 // AMM is not consumed
3540 BEAST_EXPECT(
3541 ammA3.expectBalances(XRP(1'000), G1["USD"](1'001), ammA3.tokens()));
3542
3543 // removal buy successful OfferCreate
3544 // test: freeze the new offer
3545 env(trust(G1, A4["USD"](0), tfSetFreeze));
3546 env.close();
3547
3548 // test: can no longer create a crossing offer
3549 env(offer(A2, G1["USD"](999), XRP(999)));
3550 env.close();
3551
3552 // test: offer was removed by offer_create
3553 auto offers = getAccountOffers(env, A4)[jss::offers];
3554 if (!BEAST_EXPECT(checkArraySize(offers, 0u)))
3555 return;
3556 }
3557
3558 void
3560 {
3561 testcase("Multisign AMM Transactions");
3562
3563 using namespace jtx;
3564 Env env{*this, features};
3565 Account const bogie{"bogie", KeyType::secp256k1};
3566 Account const alice{"alice", KeyType::secp256k1};
3567 Account const becky{"becky", KeyType::ed25519};
3568 Account const zelda{"zelda", KeyType::secp256k1};
3569 fund(env, gw, {alice, becky, zelda}, XRP(20'000), {USD(20'000)});
3570
3571 // alice uses a regular key with the master disabled.
3572 Account const alie{"alie", KeyType::secp256k1};
3573 env(regkey(alice, alie));
3575
3576 // Attach signers to alice.
3577 env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie));
3578 env.close();
3579 int const signerListOwners{features[featureMultiSignReserve] ? 2 : 5};
3580 env.require(owners(alice, signerListOwners + 0));
3581
3582 msig const ms{becky, bogie};
3583
3584 // Multisign all AMM transactions
3585 AMM ammAlice(
3586 env,
3587 alice,
3588 XRP(10'000),
3589 USD(10'000),
3590 false,
3591 0,
3592 ammCrtFee(env).drops(),
3593 std::nullopt,
3594 std::nullopt,
3595 ms,
3596 ter(tesSUCCESS));
3597 BEAST_EXPECT(ammAlice.expectBalances(
3598 XRP(10'000), USD(10'000), ammAlice.tokens()));
3599
3600 ammAlice.deposit(alice, 1'000'000);
3601 BEAST_EXPECT(ammAlice.expectBalances(
3602 XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
3603
3604 ammAlice.withdraw(alice, 1'000'000);
3605 BEAST_EXPECT(ammAlice.expectBalances(
3606 XRP(10'000), USD(10'000), ammAlice.tokens()));
3607
3608 ammAlice.vote({}, 1'000);
3609 BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3610
3611 env(ammAlice.bid({.account = alice, .bidMin = 100}), ms).close();
3612 BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000}));
3613 // 4000 tokens burnt
3614 BEAST_EXPECT(ammAlice.expectBalances(
3615 XRP(10'000), USD(10'000), IOUAmount{9'996'000, 0}));
3616 }
3617
3618 void
3620 {
3621 testcase("To Strand");
3622
3623 using namespace jtx;
3624
3625 // cannot have more than one offer with the same output issue
3626
3627 Env env(*this, features);
3628
3629 fund(
3630 env,
3631 gw,
3632 {alice, bob, carol},
3633 XRP(10'000),
3634 {USD(2'000), EUR(1'000)});
3635
3636 AMM bobXRP_USD(env, bob, XRP(1'000), USD(1'000));
3637 AMM bobUSD_EUR(env, bob, USD(1'000), EUR(1'000));
3638
3639 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
3640 env(pay(alice, carol, USD(100)),
3641 path(~USD, ~EUR, ~USD),
3642 sendmax(XRP(200)),
3645 }
3646
3647 void
3649 {
3650 using namespace jtx;
3651 testcase("RIPD1373");
3652
3653 {
3654 Env env(*this, features);
3655 auto const BobUSD = bob["USD"];
3656 auto const BobEUR = bob["EUR"];
3657 fund(env, gw, {alice, bob}, XRP(10'000));
3658 env.trust(USD(1'000), alice, bob);
3659 env.trust(EUR(1'000), alice, bob);
3660 env.close();
3661 fund(
3662 env,
3663 bob,
3664 {alice, gw},
3665 {BobUSD(100), BobEUR(100)},
3666 Fund::IOUOnly);
3667 env.close();
3668
3669 AMM ammBobXRP_USD(env, bob, XRP(100), BobUSD(100));
3670 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
3671
3672 AMM ammBobUSD_EUR(env, bob, BobUSD(100), BobEUR(100));
3673 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
3674
3675 Path const p = [&] {
3676 Path result;
3677 result.push_back(allpe(gw, BobUSD));
3678 result.push_back(cpe(EUR.currency));
3679 return result;
3680 }();
3681
3682 PathSet paths(p);
3683
3684 env(pay(alice, alice, EUR(1)),
3685 json(paths.json()),
3686 sendmax(XRP(10)),
3688 ter(temBAD_PATH));
3689 }
3690
3691 {
3692 Env env(*this, features);
3693
3694 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
3695
3696 AMM ammBob(env, bob, XRP(100), USD(100));
3697
3698 // payment path: XRP -> XRP/USD -> USD/XRP
3699 env(pay(alice, carol, XRP(100)),
3700 path(~USD, ~XRP),
3703 }
3704
3705 {
3706 Env env(*this, features);
3707
3708 fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(100)});
3709
3710 AMM ammBob(env, bob, XRP(100), USD(100));
3711
3712 // payment path: XRP -> XRP/USD -> USD/XRP
3713 env(pay(alice, carol, XRP(100)),
3714 path(~USD, ~XRP),
3715 sendmax(XRP(200)),
3718 }
3719 }
3720
3721 void
3723 {
3724 testcase("test loop");
3725 using namespace jtx;
3726
3727 auto const CNY = gw["CNY"];
3728
3729 {
3730 Env env(*this, features);
3731
3732 env.fund(XRP(10'000), alice, bob, carol, gw);
3733 env.close();
3734 env.trust(USD(10'000), alice, bob, carol);
3735 env.close();
3736 env(pay(gw, bob, USD(100)));
3737 env(pay(gw, alice, USD(100)));
3738 env.close();
3739
3740 AMM ammBob(env, bob, XRP(100), USD(100));
3741
3742 // payment path: USD -> USD/XRP -> XRP/USD
3743 env(pay(alice, carol, USD(100)),
3744 sendmax(USD(100)),
3745 path(~XRP, ~USD),
3748 }
3749
3750 {
3751 Env env(*this, features);
3752
3753 env.fund(XRP(10'000), alice, bob, carol, gw);
3754 env.close();
3755 env.trust(USD(10'000), alice, bob, carol);
3756 env.trust(EUR(10'000), alice, bob, carol);
3757 env.trust(CNY(10'000), alice, bob, carol);
3758
3759 env(pay(gw, bob, USD(200)));
3760 env(pay(gw, bob, EUR(200)));
3761 env(pay(gw, bob, CNY(100)));
3762
3763 AMM ammBobXRP_USD(env, bob, XRP(100), USD(100));
3764 AMM ammBobUSD_EUR(env, bob, USD(100), EUR(100));
3765 AMM ammBobEUR_CNY(env, bob, EUR(100), CNY(100));
3766
3767 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
3768 env(pay(alice, carol, CNY(100)),
3769 sendmax(XRP(100)),
3770 path(~USD, ~EUR, ~USD, ~CNY),
3773 }
3774 }
3775
3776 void
3778 {
3781 receive_max();
3782 path_find_01();
3783 path_find_02();
3784 path_find_05();
3785 path_find_06();
3786 }
3787
3788 void
3790 {
3791 using namespace jtx;
3793
3797 testTransferRateNoOwnerFee(all - fixAMMv1_1 - fixAMMv1_3);
3800 }
3801
3802 void
3804 {
3805 using namespace jtx;
3808 testStepLimit(all - fixAMMv1_1 - fixAMMv1_3);
3809 }
3810
3811 void
3813 {
3814 using namespace jtx;
3817 test_convert_all_of_an_asset(all - fixAMMv1_1 - fixAMMv1_3);
3818 }
3819
3820 void
3822 {
3823 auto const supported{jtx::testable_amendments()};
3824 testPayment(supported - featureDepositPreauth);
3825 testPayment(supported);
3826 testPayIOU();
3827 }
3828
3829 void
3831 {
3832 using namespace test::jtx;
3833 auto const sa = testable_amendments();
3834 testRippleState(sa);
3835 testGlobalFreeze(sa);
3837 }
3838
3839 void
3841 {
3842 using namespace jtx;
3843 auto const all = testable_amendments();
3844
3846 all - featureMultiSignReserve - featureExpandedSignerList);
3847 testTxMultisign(all - featureExpandedSignerList);
3849 }
3850
3851 void
3853 {
3854 using namespace jtx;
3855 auto const all = testable_amendments();
3856
3859 testLoop(all);
3860 }
3861
3862 void
3863 run() override
3864 {
3865 testOffers();
3866 testPaths();
3867 testFlow();
3871 testFreeze();
3872 testMultisign();
3873 testPayStrand();
3874 }
3875};
3876
3877BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app, ripple, 1);
3878
3879} // namespace test
3880} // namespace ripple
Represents a JSON value.
Definition: json_value.h:149
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:33
beast::Journal journal(std::string const &name)
Definition: Log.cpp:160
bool modify(modify_type const &f)
Modify the open ledger.
Definition: OpenLedger.cpp:58
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:66
A wrapper which makes credits unavailable to balances.
bool empty() const
Definition: STPathSet.h:508
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
Path & push_back(Issue const &iss)
Definition: PathSet.h:134
jtx::Account const alice
Definition: AMMTest.h:77
jtx::Account const gw
Definition: AMMTest.h:75
jtx::Account const bob
Definition: AMMTest.h:78
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={testable_amendments()})
testAMM() funds 30,000XRP and 30,000IOU for each non-XRP asset to Alice and Carol
Definition: AMMTest.cpp:103
jtx::Account const carol
Definition: AMMTest.h:76
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt)
Definition: AMMTest.cpp:256
XRPAmount ammCrtFee(jtx::Env &env) const
Definition: AMMTest.cpp:177
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:171
Convenience class to test AMM functionality.
Definition: AMM.h:124
Json::Value ammRpcInfo(std::optional< AccountID > const &account=std::nullopt, std::optional< std::string > const &ledgerIndex=std::nullopt, std::optional< Issue > issue1=std::nullopt, std::optional< Issue > issue2=std::nullopt, std::optional< AccountID > const &ammAccount=std::nullopt, bool ignoreParams=false, unsigned apiVersion=RPC::apiInvalidVersion) const
Send amm_info RPC command.
Definition: AMM.cpp:166
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue > > const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:642
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:280
IOUAmount tokens() const
Definition: AMM.h:343
AccountID const & ammAccount() const
Definition: AMM.h:331
bool expectTradingFee(std::uint16_t fee) const
Definition: AMM.cpp:317
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:542
bool expectBalances(STAmount const &asset1, STAmount const &asset2, IOUAmount const &lpt, std::optional< AccountID > const &account=std::nullopt) const
Verify the AMM balances.
Definition: AMM.cpp:237
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:416
Json::Value bid(BidArg const &arg)
Definition: AMM.cpp:669
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.h:279
Immutable cryptographic account descriptor.
Definition: Account.h:39
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:121
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:544
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:306
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:788
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:275
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition: Env.cpp:489
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:128
Inject raw JSON.
Definition: jtx_json.h:33
Set a multisignature on a JTx.
Definition: multisign.h:67
Match clear account flags.
Definition: flags.h:145
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:44
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:274
bool checkArraySize(Json::Value const &val, unsigned int size)
Definition: TestHelpers.cpp:48
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition: flags.h:41
Json::Value ledgerEntryRoot(Env &env, Account const &acct)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:29
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:34
static none_t const none
Definition: tags.h:34
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition: owners.h:92
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
PrettyAmount xrpMinusFee(Env const &env, std::int64_t xrpAmount)
Definition: TestHelpers.cpp:99
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:32
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:29
bool expectLine(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Definition: TestHelpers.cpp:40
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
bool same(STPathSet const &st1, Args const &... args)
Definition: TestHelpers.h:165
Json::Value ledgerEntryState(Env &env, Account const &acct_a, Account const &acct_b, std::string const &currency)
void fund(jtx::Env &env, jtx::Account const &gw, std::vector< jtx::Account > const &accounts, std::vector< STAmount > const &amts, Fund how)
Definition: AMMTest.cpp:37
void n_offers(Env &env, std::size_t n, Account const &account, STAmount const &in, STAmount const &out)
FeatureBitset testable_amendments()
Definition: Env.h:74
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
STPath stpath(Args const &... args)
Definition: TestHelpers.h:156
Json::Value getAccountOffers(Env &env, AccountID const &acct, bool current)
Definition: TestHelpers.cpp:32
bool isOffer(jtx::Env &env, jtx::Account const &account, STAmount const &takerPays, STAmount const &takerGets)
An offer exists.
Definition: PathSet.h:72
bool equal(std::unique_ptr< Step > const &s1, DirectStepInfo const &dsi)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:115
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
constexpr std::uint32_t asfGlobalFreeze
Definition: TxFlags.h:83
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:85
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
@ lsfHighFreeze
@ lsfLowFreeze
constexpr std::uint32_t asfNoFreeze
Definition: TxFlags.h:82
constexpr std::uint32_t tfFillOrKill
Definition: TxFlags.h:100
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:98
constexpr std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:99
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1471
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:80
@ no
Definition: Steps.h:45
constexpr std::uint32_t tfPartialPayment
Definition: TxFlags.h:108
constexpr std::uint32_t tfSetfAuth
Definition: TxFlags.h:115
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:119
constexpr std::uint32_t tfClearFreeze
Definition: TxFlags.h:119
@ 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:107
@ tesSUCCESS
Definition: TER.h:244
constexpr std::uint32_t tfLimitQuality
Definition: TxFlags.h:109
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:101
constexpr std::uint32_t asfRequireAuth
Definition: TxFlags.h:78
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition: Seed.cpp:76
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:118
constexpr std::uint32_t tfSetNoRipple
Definition: TxFlags.h:116
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 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)