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