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