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