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