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