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