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