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