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