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