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