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