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