rippled
AMM_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/paths/AMMContext.h>
21 #include <ripple/app/paths/AMMOffer.h>
22 #include <ripple/protocol/AMMCore.h>
23 #include <ripple/protocol/STParsedJSON.h>
24 #include <ripple/resource/Fees.h>
25 #include <ripple/rpc/RPCHandler.h>
26 #include <ripple/rpc/impl/RPCHelpers.h>
27 #include <test/jtx.h>
28 #include <test/jtx/AMM.h>
29 #include <test/jtx/AMMTest.h>
30 #include <test/jtx/TestHelpers.h>
31 #include <test/jtx/amount.h>
32 #include <test/jtx/sendmax.h>
33 
34 #include <chrono>
35 #include <utility>
36 #include <vector>
37 
38 namespace ripple {
39 namespace test {
40 
41 struct AMM_test : public jtx::AMMTest
42 {
43 private:
44  void
46  {
47  testcase("Instance Create");
48 
49  using namespace jtx;
50 
51  // XRP to IOU
52  testAMM([&](AMM& ammAlice, Env&) {
53  BEAST_EXPECT(ammAlice.expectBalances(
54  XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
55  });
56 
57  // IOU to IOU
58  testAMM(
59  [&](AMM& ammAlice, Env&) {
60  BEAST_EXPECT(ammAlice.expectBalances(
61  USD(20'000), BTC(0.5), IOUAmount{100, 0}));
62  },
63  {{USD(20'000), BTC(0.5)}});
64 
65  // IOU to IOU + transfer fee
66  {
67  Env env{*this};
68  fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
69  env(rate(gw, 1.25));
70  env.close();
71  // no transfer fee on create
72  AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
73  BEAST_EXPECT(ammAlice.expectBalances(
74  USD(20'000), BTC(0.5), IOUAmount{100, 0}));
75  BEAST_EXPECT(expectLine(env, alice, USD(0)));
76  BEAST_EXPECT(expectLine(env, alice, BTC(0)));
77  }
78 
79  // Require authorization is set, account is authorized
80  {
81  Env env{*this};
82  env.fund(XRP(30'000), gw, alice);
83  env.close();
84  env(fset(gw, asfRequireAuth));
85  env.close();
86  env.trust(USD(30'000), alice);
87  env.close();
88  env(trust(gw, alice["USD"](30'000)), txflags(tfSetfAuth));
89  env.close();
90  env(pay(gw, alice, USD(10'000)));
91  env.close();
92  AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
93  }
94 
95  // Cleared global freeze
96  {
97  Env env{*this};
98  env.fund(XRP(30'000), gw, alice);
99  env.close();
100  env.trust(USD(30'000), alice);
101  env.close();
102  env(pay(gw, alice, USD(10'000)));
103  env.close();
104  env(fset(gw, asfGlobalFreeze));
105  env.close();
106  AMM ammAliceFail(
107  env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
108  env(fclear(gw, asfGlobalFreeze));
109  env.close();
110  AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
111  }
112  }
113 
114  void
116  {
117  testcase("Invalid Instance");
118 
119  using namespace jtx;
120 
121  // Can't have both XRP tokens
122  {
123  Env env{*this};
124  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
125  AMM ammAlice(
126  env, alice, XRP(10'000), XRP(10'000), ter(temBAD_AMM_TOKENS));
127  BEAST_EXPECT(!ammAlice.ammExists());
128  }
129 
130  // Can't have both tokens the same IOU
131  {
132  Env env{*this};
133  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
134  AMM ammAlice(
135  env, alice, USD(10'000), USD(10'000), ter(temBAD_AMM_TOKENS));
136  BEAST_EXPECT(!ammAlice.ammExists());
137  }
138 
139  // Can't have zero or negative amounts
140  {
141  Env env{*this};
142  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
143  AMM ammAlice(env, alice, XRP(0), USD(10'000), ter(temBAD_AMOUNT));
144  BEAST_EXPECT(!ammAlice.ammExists());
145  AMM ammAlice1(env, alice, XRP(10'000), USD(0), ter(temBAD_AMOUNT));
146  BEAST_EXPECT(!ammAlice1.ammExists());
147  AMM ammAlice2(
148  env, alice, XRP(10'000), USD(-10'000), ter(temBAD_AMOUNT));
149  BEAST_EXPECT(!ammAlice2.ammExists());
150  AMM ammAlice3(
151  env, alice, XRP(-10'000), USD(10'000), ter(temBAD_AMOUNT));
152  BEAST_EXPECT(!ammAlice3.ammExists());
153  }
154 
155  // Bad currency
156  {
157  Env env{*this};
158  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
159  AMM ammAlice(
160  env, alice, XRP(10'000), BAD(10'000), ter(temBAD_CURRENCY));
161  BEAST_EXPECT(!ammAlice.ammExists());
162  }
163 
164  // Insufficient IOU balance
165  {
166  Env env{*this};
167  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
168  AMM ammAlice(
169  env, alice, XRP(10'000), USD(40'000), ter(tecUNFUNDED_AMM));
170  BEAST_EXPECT(!ammAlice.ammExists());
171  }
172 
173  // Insufficient XRP balance
174  {
175  Env env{*this};
176  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
177  AMM ammAlice(
178  env, alice, XRP(40'000), USD(10'000), ter(tecUNFUNDED_AMM));
179  BEAST_EXPECT(!ammAlice.ammExists());
180  }
181 
182  // Invalid trading fee
183  {
184  Env env{*this};
185  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
186  AMM ammAlice(
187  env,
188  alice,
189  XRP(10'000),
190  USD(10'000),
191  false,
192  65'001,
193  10,
194  std::nullopt,
195  std::nullopt,
196  std::nullopt,
197  ter(temBAD_FEE));
198  BEAST_EXPECT(!ammAlice.ammExists());
199  }
200 
201  // AMM already exists
202  testAMM([&](AMM& ammAlice, Env& env) {
203  AMM ammCarol(
204  env, carol, XRP(10'000), USD(10'000), ter(tecDUPLICATE));
205  });
206 
207  // Invalid flags
208  {
209  Env env{*this};
210  fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
211  AMM ammAlice(
212  env,
213  alice,
214  XRP(10'000),
215  USD(10'000),
216  false,
217  0,
218  10,
219  tfWithdrawAll,
220  std::nullopt,
221  std::nullopt,
222  ter(temINVALID_FLAG));
223  BEAST_EXPECT(!ammAlice.ammExists());
224  }
225 
226  // Invalid Account
227  {
228  Env env{*this};
229  Account bad("bad");
230  env.memoize(bad);
231  AMM ammAlice(
232  env,
233  bad,
234  XRP(10'000),
235  USD(10'000),
236  false,
237  0,
238  10,
239  std::nullopt,
240  seq(1),
241  std::nullopt,
242  ter(terNO_ACCOUNT));
243  BEAST_EXPECT(!ammAlice.ammExists());
244  }
245 
246  // Require authorization is set
247  {
248  Env env{*this};
249  env.fund(XRP(30'000), gw, alice);
250  env.close();
251  env(fset(gw, asfRequireAuth));
252  env.close();
253  env(trust(gw, alice["USD"](30'000)));
254  env.close();
255  AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecNO_AUTH));
256  BEAST_EXPECT(!ammAlice.ammExists());
257  }
258 
259  // Globally frozen
260  {
261  Env env{*this};
262  env.fund(XRP(30'000), gw, alice);
263  env.close();
264  env(fset(gw, asfGlobalFreeze));
265  env.close();
266  env(trust(gw, alice["USD"](30'000)));
267  env.close();
268  AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
269  BEAST_EXPECT(!ammAlice.ammExists());
270  }
271 
272  // Individually frozen
273  {
274  Env env{*this};
275  env.fund(XRP(30'000), gw, alice);
276  env.close();
277  env(trust(gw, alice["USD"](30'000)));
278  env.close();
279  env(trust(gw, alice["USD"](0), tfSetFreeze));
280  env.close();
281  AMM ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN));
282  BEAST_EXPECT(!ammAlice.ammExists());
283  }
284 
285  // Insufficient reserve, XRP/IOU
286  {
287  Env env(*this);
288  auto const starting_xrp =
289  XRP(1'000) + reserve(env, 3) + env.current()->fees().base * 4;
290  env.fund(starting_xrp, gw);
291  env.fund(starting_xrp, alice);
292  env.trust(USD(2'000), alice);
293  env.close();
294  env(pay(gw, alice, USD(2'000)));
295  env.close();
296  env(offer(alice, XRP(101), USD(100)));
297  env(offer(alice, XRP(102), USD(100)));
298  AMM ammAlice(
299  env, alice, XRP(1'000), USD(1'000), ter(tecUNFUNDED_AMM));
300  }
301 
302  // Insufficient reserve, IOU/IOU
303  {
304  Env env(*this);
305  auto const starting_xrp =
306  reserve(env, 4) + env.current()->fees().base * 5;
307  env.fund(starting_xrp, gw);
308  env.fund(starting_xrp, alice);
309  env.trust(USD(2'000), alice);
310  env.trust(EUR(2'000), alice);
311  env.close();
312  env(pay(gw, alice, USD(2'000)));
313  env(pay(gw, alice, EUR(2'000)));
314  env.close();
315  env(offer(alice, EUR(101), USD(100)));
316  env(offer(alice, EUR(102), USD(100)));
317  AMM ammAlice(
318  env, alice, EUR(1'000), USD(1'000), ter(tecINSUF_RESERVE_LINE));
319  }
320 
321  // Insufficient fee
322  {
323  Env env(*this);
324  fund(env, gw, {alice}, XRP(2'000), {USD(2'000), EUR(2'000)});
325  AMM ammAlice(
326  env,
327  alice,
328  EUR(1'000),
329  USD(1'000),
330  false,
331  0,
332  ammCrtFee(env).drops() - 1,
333  std::nullopt,
334  std::nullopt,
335  std::nullopt,
336  ter(telINSUF_FEE_P));
337  }
338 
339  // AMM with LPTokens
340 
341  // AMM with one LPToken from another AMM.
342  testAMM([&](AMM& ammAlice, Env& env) {
343  fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
344  AMM ammAMMToken(
345  env,
346  alice,
347  EUR(10'000),
348  STAmount{ammAlice.lptIssue(), 1'000'000},
349  ter(tecAMM_INVALID_TOKENS));
350  AMM ammAMMToken1(
351  env,
352  alice,
353  STAmount{ammAlice.lptIssue(), 1'000'000},
354  EUR(10'000),
356  });
357 
358  // AMM with two LPTokens from other AMMs.
359  testAMM([&](AMM& ammAlice, Env& env) {
360  fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly);
361  AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
362  auto const token1 = ammAlice.lptIssue();
363  auto const token2 = ammAlice1.lptIssue();
364  AMM ammAMMTokens(
365  env,
366  alice,
367  STAmount{token1, 1'000'000},
368  STAmount{token2, 1'000'000},
369  ter(tecAMM_INVALID_TOKENS));
370  });
371 
372  // Issuer has DefaultRipple disabled
373  {
374  Env env(*this);
375  env.fund(XRP(30'000), gw);
376  env(fclear(gw, asfDefaultRipple));
377  AMM ammGw(env, gw, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
378  env.fund(XRP(30'000), alice);
379  env.trust(USD(30'000), alice);
380  env(pay(gw, alice, USD(30'000)));
381  AMM ammAlice(
382  env, alice, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
383  Account const gw1("gw1");
384  env.fund(XRP(30'000), gw1);
385  env(fclear(gw1, asfDefaultRipple));
386  env.trust(USD(30'000), gw1);
387  env(pay(gw, gw1, USD(30'000)));
388  auto const USD1 = gw1["USD"];
389  AMM ammGwGw1(env, gw, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
390  env.trust(USD1(30'000), alice);
391  env(pay(gw1, alice, USD1(30'000)));
392  AMM ammAlice1(
393  env, alice, USD(10'000), USD1(10'000), ter(terNO_RIPPLE));
394  }
395  }
396 
397  void
399  {
400  testcase("Invalid Deposit");
401 
402  using namespace jtx;
403 
404  testAMM([&](AMM& ammAlice, Env& env) {
405  // Invalid flags
406  ammAlice.deposit(
407  alice,
408  1'000'000,
409  std::nullopt,
412 
413  // Invalid options
420  invalidOptions = {
421  // flags, tokens, asset1In, asset2in, EPrice
422  {tfLPToken, 1'000, std::nullopt, USD(100), std::nullopt},
423  {tfLPToken, 1'000, XRP(100), std::nullopt, std::nullopt},
424  {tfLPToken,
425  1'000,
426  std::nullopt,
427  std::nullopt,
428  STAmount{USD, 1, -1}},
429  {tfLPToken,
430  std::nullopt,
431  USD(100),
432  std::nullopt,
433  STAmount{USD, 1, -1}},
434  {tfLPToken,
435  1'000,
436  XRP(100),
437  std::nullopt,
438  STAmount{USD, 1, -1}},
439  {tfSingleAsset,
440  1'000,
441  std::nullopt,
442  std::nullopt,
443  std::nullopt},
444  {tfSingleAsset,
445  std::nullopt,
446  std::nullopt,
447  USD(100),
448  std::nullopt},
449  {tfSingleAsset,
450  std::nullopt,
451  std::nullopt,
452  std::nullopt,
453  STAmount{USD, 1, -1}},
454  {tfTwoAsset,
455  1'000,
456  std::nullopt,
457  std::nullopt,
458  std::nullopt},
459  {tfTwoAsset,
460  std::nullopt,
461  XRP(100),
462  USD(100),
463  STAmount{USD, 1, -1}},
464  {tfTwoAsset,
465  std::nullopt,
466  XRP(100),
467  std::nullopt,
468  std::nullopt},
469  {tfTwoAsset,
470  std::nullopt,
471  std::nullopt,
472  USD(100),
473  STAmount{USD, 1, -1}},
475  1'000,
476  std::nullopt,
477  std::nullopt,
478  std::nullopt},
479  {tfOneAssetLPToken,
480  std::nullopt,
481  XRP(100),
482  USD(100),
483  std::nullopt},
484  {tfOneAssetLPToken,
485  std::nullopt,
486  XRP(100),
487  std::nullopt,
488  STAmount{USD, 1, -1}},
489  {tfLimitLPToken,
490  1'000,
491  std::nullopt,
492  std::nullopt,
493  std::nullopt},
495  1'000,
496  USD(100),
497  std::nullopt,
498  std::nullopt},
499  {tfLimitLPToken,
500  std::nullopt,
501  USD(100),
502  XRP(100),
503  std::nullopt},
504  {tfLimitLPToken,
505  std::nullopt,
506  std::nullopt,
507  std::nullopt,
508  STAmount{USD, 1, -1}}};
509  for (auto const& it : invalidOptions)
510  {
511  ammAlice.deposit(
512  alice,
513  std::get<1>(it),
514  std::get<2>(it),
515  std::get<3>(it),
516  std::get<4>(it),
517  std::get<0>(it),
518  std::nullopt,
519  std::nullopt,
520  ter(temMALFORMED));
521  }
522 
523  // Invalid tokens
524  ammAlice.deposit(
525  alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
526  ammAlice.deposit(
527  alice,
528  IOUAmount{-1},
529  std::nullopt,
530  std::nullopt,
531  ter(temBAD_AMM_TOKENS));
532 
533  // Invalid tokens - bogus currency
534  {
535  auto const iss1 = Issue{Currency(0xabc), gw.id()};
536  auto const iss2 = Issue{Currency(0xdef), gw.id()};
537  ammAlice.deposit(
538  alice,
539  1'000,
540  std::nullopt,
541  std::nullopt,
542  std::nullopt,
543  std::nullopt,
544  {{iss1, iss2}},
545  std::nullopt,
546  ter(terNO_AMM));
547  }
548 
549  // Depositing mismatched token, invalid Asset1In.issue
550  ammAlice.deposit(
551  alice,
552  GBP(100),
553  std::nullopt,
554  std::nullopt,
555  std::nullopt,
557 
558  // Depositing mismatched token, invalid Asset2In.issue
559  ammAlice.deposit(
560  alice,
561  USD(100),
562  GBP(100),
563  std::nullopt,
564  std::nullopt,
566 
567  // Depositing mismatched token, Asset1In.issue == Asset2In.issue
568  ammAlice.deposit(
569  alice,
570  USD(100),
571  USD(100),
572  std::nullopt,
573  std::nullopt,
575 
576  // Invalid amount value
577  ammAlice.deposit(
578  alice,
579  USD(0),
580  std::nullopt,
581  std::nullopt,
582  std::nullopt,
583  ter(temBAD_AMOUNT));
584  ammAlice.deposit(
585  alice,
586  USD(-1'000),
587  std::nullopt,
588  std::nullopt,
589  std::nullopt,
590  ter(temBAD_AMOUNT));
591  ammAlice.deposit(
592  alice,
593  USD(10),
594  std::nullopt,
595  USD(-1),
596  std::nullopt,
597  ter(temBAD_AMOUNT));
598 
599  // Bad currency
600  ammAlice.deposit(
601  alice,
602  BAD(100),
603  std::nullopt,
604  std::nullopt,
605  std::nullopt,
606  ter(temBAD_CURRENCY));
607 
608  // Invalid Account
609  Account bad("bad");
610  env.memoize(bad);
611  ammAlice.deposit(
612  bad,
613  1'000'000,
614  std::nullopt,
615  std::nullopt,
616  std::nullopt,
617  std::nullopt,
618  std::nullopt,
619  seq(1),
620  ter(terNO_ACCOUNT));
621 
622  // Invalid AMM
623  ammAlice.deposit(
624  alice,
625  1'000,
626  std::nullopt,
627  std::nullopt,
628  std::nullopt,
629  std::nullopt,
630  {{USD, GBP}},
631  std::nullopt,
632  ter(terNO_AMM));
633 
634  // Single deposit: 100000 tokens worth of USD
635  // Amount to deposit exceeds Max
636  ammAlice.deposit(
637  carol,
638  100'000,
639  USD(200),
640  std::nullopt,
641  std::nullopt,
642  std::nullopt,
643  std::nullopt,
644  std::nullopt,
645  ter(tecAMM_FAILED));
646 
647  // Single deposit: 100000 tokens worth of XRP
648  // Amount to deposit exceeds Max
649  ammAlice.deposit(
650  carol,
651  100'000,
652  XRP(200),
653  std::nullopt,
654  std::nullopt,
655  std::nullopt,
656  std::nullopt,
657  std::nullopt,
658  ter(tecAMM_FAILED));
659 
660  // Deposit amount is invalid
661  // Calculated amount to deposit is 98,000,000
662  ammAlice.deposit(
663  alice,
664  USD(0),
665  std::nullopt,
666  STAmount{USD, 1, -1},
667  std::nullopt,
669  // Calculated amount is 0
670  ammAlice.deposit(
671  alice,
672  USD(0),
673  std::nullopt,
674  STAmount{USD, 2'000, -6},
675  std::nullopt,
676  ter(tecAMM_FAILED));
677 
678  // Tiny deposit
679  ammAlice.deposit(
680  carol,
681  IOUAmount{1, -4},
682  std::nullopt,
683  std::nullopt,
684  ter(temBAD_AMOUNT));
685  ammAlice.deposit(
686  carol,
687  STAmount{USD, 1, -12},
688  std::nullopt,
689  std::nullopt,
690  std::nullopt,
691  ter(tecAMM_INVALID_TOKENS));
692  });
693 
694  // Invalid AMM
695  testAMM([&](AMM& ammAlice, Env& env) {
696  ammAlice.withdrawAll(alice);
697  ammAlice.deposit(
698  alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
699  });
700 
701  // Globally frozen asset
702  testAMM([&](AMM& ammAlice, Env& env) {
703  env(fset(gw, asfGlobalFreeze));
704  // Can deposit non-frozen token
705  ammAlice.deposit(carol, XRP(100));
706  ammAlice.deposit(
707  carol,
708  USD(100),
709  std::nullopt,
710  std::nullopt,
711  std::nullopt,
712  ter(tecFROZEN));
713  ammAlice.deposit(
714  carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
715  });
716 
717  // Individually frozen (AMM) account
718  testAMM([&](AMM& ammAlice, Env& env) {
719  env(trust(gw, carol["USD"](0), tfSetFreeze));
720  env.close();
721  // Can deposit non-frozen token
722  ammAlice.deposit(carol, XRP(100));
723  ammAlice.deposit(
724  carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
725  ammAlice.deposit(
726  carol,
727  USD(100),
728  std::nullopt,
729  std::nullopt,
730  std::nullopt,
731  ter(tecFROZEN));
732  env(trust(gw, carol["USD"](0), tfClearFreeze));
733  // Individually frozen AMM
734  env(trust(
735  gw,
736  STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
737  tfSetFreeze));
738  env.close();
739  // Can deposit non-frozen token
740  ammAlice.deposit(carol, XRP(100));
741  ammAlice.deposit(
742  carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN));
743  ammAlice.deposit(
744  carol,
745  USD(100),
746  std::nullopt,
747  std::nullopt,
748  std::nullopt,
749  ter(tecFROZEN));
750  });
751 
752  // Insufficient XRP balance
753  testAMM([&](AMM& ammAlice, Env& env) {
754  env.fund(XRP(1'000), bob);
755  env.close();
756  // Adds LPT trustline
757  ammAlice.deposit(bob, XRP(10));
758  ammAlice.deposit(
759  bob,
760  XRP(1'000),
761  std::nullopt,
762  std::nullopt,
763  std::nullopt,
765  });
766 
767  // Insufficient USD balance
768  testAMM([&](AMM& ammAlice, Env& env) {
769  fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
770  env.close();
771  ammAlice.deposit(
772  bob,
773  USD(1'001),
774  std::nullopt,
775  std::nullopt,
776  std::nullopt,
778  });
779 
780  // Insufficient USD balance by tokens
781  testAMM([&](AMM& ammAlice, Env& env) {
782  fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
783  env.close();
784  ammAlice.deposit(
785  bob,
786  10'000'000,
787  std::nullopt,
788  std::nullopt,
789  std::nullopt,
790  std::nullopt,
791  std::nullopt,
792  std::nullopt,
793  ter(tecUNFUNDED_AMM));
794  });
795 
796  // Insufficient XRP balance by tokens
797  testAMM([&](AMM& ammAlice, Env& env) {
798  env.fund(XRP(1'000), bob);
799  env.trust(USD(100'000), bob);
800  env.close();
801  env(pay(gw, bob, USD(90'000)));
802  env.close();
803  ammAlice.deposit(
804  bob,
805  10'000'000,
806  std::nullopt,
807  std::nullopt,
808  std::nullopt,
809  std::nullopt,
810  std::nullopt,
811  std::nullopt,
813  });
814 
815  // Insufficient reserve, XRP/IOU
816  {
817  Env env(*this);
818  auto const starting_xrp =
819  reserve(env, 4) + env.current()->fees().base * 4;
820  env.fund(XRP(10'000), gw);
821  env.fund(XRP(10'000), alice);
822  env.fund(starting_xrp, carol);
823  env.trust(USD(2'000), alice);
824  env.trust(USD(2'000), carol);
825  env.close();
826  env(pay(gw, alice, USD(2'000)));
827  env(pay(gw, carol, USD(2'000)));
828  env.close();
829  env(offer(carol, XRP(100), USD(101)));
830  env(offer(carol, XRP(100), USD(102)));
831  AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
832  ammAlice.deposit(
833  carol,
834  XRP(100),
835  std::nullopt,
836  std::nullopt,
837  std::nullopt,
839  }
840 
841  // Insufficient reserve, IOU/IOU
842  {
843  Env env(*this);
844  auto const starting_xrp =
845  reserve(env, 4) + env.current()->fees().base * 4;
846  env.fund(XRP(10'000), gw);
847  env.fund(XRP(10'000), alice);
848  env.fund(starting_xrp, carol);
849  env.trust(USD(2'000), alice);
850  env.trust(EUR(2'000), alice);
851  env.trust(USD(2'000), carol);
852  env.trust(EUR(2'000), carol);
853  env.close();
854  env(pay(gw, alice, USD(2'000)));
855  env(pay(gw, alice, EUR(2'000)));
856  env(pay(gw, carol, USD(2'000)));
857  env(pay(gw, carol, EUR(2'000)));
858  env.close();
859  env(offer(carol, XRP(100), USD(101)));
860  env(offer(carol, XRP(100), USD(102)));
861  AMM ammAlice(env, alice, XRP(1'000), USD(1'000));
862  ammAlice.deposit(
863  carol,
864  XRP(100),
865  std::nullopt,
866  std::nullopt,
867  std::nullopt,
869  }
870 
871  // Invalid min
872  testAMM([&](AMM& ammAlice, Env& env) {
873  // min tokens can't be <= zero
874  ammAlice.deposit(
876  ammAlice.deposit(
878  ammAlice.deposit(
879  carol,
880  0,
881  XRP(100),
882  USD(100),
883  std::nullopt,
884  tfTwoAsset,
885  std::nullopt,
886  std::nullopt,
888  // min amounts can't be <= zero
889  ammAlice.deposit(
890  carol,
891  1'000,
892  XRP(0),
893  USD(100),
894  std::nullopt,
895  tfTwoAsset,
896  std::nullopt,
897  std::nullopt,
898  ter(temBAD_AMOUNT));
899  ammAlice.deposit(
900  carol,
901  1'000,
902  XRP(100),
903  USD(-1),
904  std::nullopt,
905  tfTwoAsset,
906  std::nullopt,
907  std::nullopt,
908  ter(temBAD_AMOUNT));
909  // min amount bad currency
910  ammAlice.deposit(
911  carol,
912  1'000,
913  XRP(100),
914  BAD(100),
915  std::nullopt,
916  tfTwoAsset,
917  std::nullopt,
918  std::nullopt,
919  ter(temBAD_CURRENCY));
920  // min amount bad token pair
921  ammAlice.deposit(
922  carol,
923  1'000,
924  XRP(100),
925  XRP(100),
926  std::nullopt,
927  tfTwoAsset,
928  std::nullopt,
929  std::nullopt,
931  ammAlice.deposit(
932  carol,
933  1'000,
934  XRP(100),
935  GBP(100),
936  std::nullopt,
937  tfTwoAsset,
938  std::nullopt,
939  std::nullopt,
940  ter(temBAD_AMM_TOKENS));
941  });
942 
943  // Min deposit
944  testAMM([&](AMM& ammAlice, Env& env) {
945  // Equal deposit by tokens
946  ammAlice.deposit(
947  carol,
948  1'000'000,
949  XRP(1'000),
950  USD(1'001),
951  std::nullopt,
952  tfLPToken,
953  std::nullopt,
954  std::nullopt,
955  ter(tecAMM_FAILED));
956  ammAlice.deposit(
957  carol,
958  1'000'000,
959  XRP(1'001),
960  USD(1'000),
961  std::nullopt,
962  tfLPToken,
963  std::nullopt,
964  std::nullopt,
965  ter(tecAMM_FAILED));
966  // Equal deposit by asset
967  ammAlice.deposit(
968  carol,
969  100'001,
970  XRP(100),
971  USD(100),
972  std::nullopt,
973  tfTwoAsset,
974  std::nullopt,
975  std::nullopt,
976  ter(tecAMM_FAILED));
977  // Single deposit by asset
978  ammAlice.deposit(
979  carol,
980  488'090,
981  XRP(1'000),
982  std::nullopt,
983  std::nullopt,
985  std::nullopt,
986  std::nullopt,
987  ter(tecAMM_FAILED));
988  });
989  }
990 
991  void
993  {
994  testcase("Deposit");
995 
996  using namespace jtx;
997 
998  // Equal deposit: 1000000 tokens, 10% of the current pool
999  testAMM([&](AMM& ammAlice, Env& env) {
1000  ammAlice.deposit(carol, 1'000'000);
1001  BEAST_EXPECT(ammAlice.expectBalances(
1002  XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
1003  // 30,000 less deposited 1,000
1004  BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
1005  // 30,000 less deposited 1,000 and 10 drops tx fee
1006  BEAST_EXPECT(
1007  expectLedgerEntryRoot(env, carol, XRPAmount{28'999'999'990}));
1008  });
1009 
1010  // Equal limit deposit: deposit USD100 and XRP proportionally
1011  // to the pool composition not to exceed 100XRP. If the amount
1012  // exceeds 100XRP then deposit 100XRP and USD proportionally
1013  // to the pool composition not to exceed 100USD. Fail if exceeded.
1014  // Deposit 100USD/100XRP
1015  testAMM([&](AMM& ammAlice, Env&) {
1016  ammAlice.deposit(carol, USD(100), XRP(100));
1017  BEAST_EXPECT(ammAlice.expectBalances(
1018  XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
1019  });
1020 
1021  // Equal limit deposit.
1022  // Try to deposit 200USD/100XRP. Is truncated to 100USD/100XRP.
1023  testAMM([&](AMM& ammAlice, Env&) {
1024  ammAlice.deposit(carol, USD(200), XRP(100));
1025  BEAST_EXPECT(ammAlice.expectBalances(
1026  XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
1027  });
1028  // Try to deposit 100USD/200XRP. Is truncated to 100USD/100XRP.
1029  testAMM([&](AMM& ammAlice, Env&) {
1030  ammAlice.deposit(carol, USD(100), XRP(200));
1031  BEAST_EXPECT(ammAlice.expectBalances(
1032  XRP(10'100), USD(10'100), IOUAmount{10'100'000, 0}));
1033  });
1034 
1035  // Single deposit: 1000 USD
1036  testAMM([&](AMM& ammAlice, Env&) {
1037  ammAlice.deposit(carol, USD(1'000));
1038  BEAST_EXPECT(ammAlice.expectBalances(
1039  XRP(10'000),
1040  STAmount{USD, UINT64_C(10'999'99999999999), -11},
1041  IOUAmount{10'488'088'48170151, -8}));
1042  });
1043 
1044  // Single deposit: 1000 XRP
1045  testAMM([&](AMM& ammAlice, Env&) {
1046  ammAlice.deposit(carol, XRP(1'000));
1047  BEAST_EXPECT(ammAlice.expectBalances(
1048  XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
1049  });
1050 
1051  // Single deposit: 100000 tokens worth of USD
1052  testAMM([&](AMM& ammAlice, Env&) {
1053  ammAlice.deposit(carol, 100000, USD(205));
1054  BEAST_EXPECT(ammAlice.expectBalances(
1055  XRP(10'000), USD(10'201), IOUAmount{10'100'000, 0}));
1056  });
1057 
1058  // Single deposit: 100000 tokens worth of XRP
1059  testAMM([&](AMM& ammAlice, Env&) {
1060  ammAlice.deposit(carol, 100'000, XRP(205));
1061  BEAST_EXPECT(ammAlice.expectBalances(
1062  XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0}));
1063  });
1064 
1065  // Single deposit with EP not exceeding specified:
1066  // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut)
1067  testAMM([&](AMM& ammAlice, Env&) {
1068  ammAlice.deposit(
1069  carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
1070  BEAST_EXPECT(ammAlice.expectBalances(
1071  XRP(10'000),
1072  STAmount{USD, UINT64_C(10'999'99999999999), -11},
1073  IOUAmount{10'488'088'48170151, -8}));
1074  });
1075 
1076  // Single deposit with EP not exceeding specified:
1077  // 100USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
1078  testAMM([&](AMM& ammAlice, Env&) {
1079  ammAlice.deposit(
1080  carol, USD(100), std::nullopt, STAmount{USD, 2004, -6});
1081  BEAST_EXPECT(ammAlice.expectBalances(
1082  XRP(10'000),
1083  STAmount{USD, 10'080'16, -2},
1084  IOUAmount{10'040'000, 0}));
1085  });
1086 
1087  // Single deposit with EP not exceeding specified:
1088  // 0USD with EP not to exceed 0.002004 (AssetIn/TokensOut)
1089  testAMM([&](AMM& ammAlice, Env&) {
1090  ammAlice.deposit(
1091  carol, USD(0), std::nullopt, STAmount{USD, 2004, -6});
1092  BEAST_EXPECT(ammAlice.expectBalances(
1093  XRP(10'000),
1094  STAmount{USD, 10'080'16, -2},
1095  IOUAmount{10'040'000, 0}));
1096  });
1097 
1098  // IOU to IOU + transfer fee
1099  {
1100  Env env{*this};
1101  fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
1102  env(rate(gw, 1.25));
1103  env.close();
1104  AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
1105  BEAST_EXPECT(ammAlice.expectBalances(
1106  USD(20'000), BTC(0.5), IOUAmount{100, 0}));
1107  BEAST_EXPECT(expectLine(env, alice, USD(0)));
1108  BEAST_EXPECT(expectLine(env, alice, BTC(0)));
1109  fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
1110  // no transfer fee on deposit
1111  ammAlice.deposit(carol, 10);
1112  BEAST_EXPECT(ammAlice.expectBalances(
1113  USD(22'000), BTC(0.55), IOUAmount{110, 0}));
1114  BEAST_EXPECT(expectLine(env, carol, USD(0)));
1115  BEAST_EXPECT(expectLine(env, carol, BTC(0)));
1116  }
1117 
1118  // Tiny deposits
1119  testAMM([&](AMM& ammAlice, Env&) {
1120  ammAlice.deposit(carol, IOUAmount{1, -3});
1121  BEAST_EXPECT(ammAlice.expectBalances(
1122  XRPAmount{10'000'000'001},
1123  STAmount{USD, UINT64_C(10'000'000001), -6},
1124  IOUAmount{10'000'000'001, -3}));
1125  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1, -3}));
1126  });
1127  testAMM([&](AMM& ammAlice, Env&) {
1128  ammAlice.deposit(carol, XRPAmount{1});
1129  BEAST_EXPECT(ammAlice.expectBalances(
1130  XRPAmount{10'000'000'001},
1131  USD(10'000),
1132  IOUAmount{1'000'000'000049999, -8}));
1133  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{49999, -8}));
1134  });
1135  testAMM([&](AMM& ammAlice, Env&) {
1136  ammAlice.deposit(carol, STAmount{USD, 1, -10});
1137  BEAST_EXPECT(ammAlice.expectBalances(
1138  XRP(10'000),
1139  STAmount{USD, UINT64_C(10'000'00000000008), -11},
1140  IOUAmount{10'000'000'00000004, -8}));
1141  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4, -8}));
1142  });
1143 
1144  // Issuer create/deposit
1145  {
1146  Env env(*this);
1147  env.fund(XRP(30000), gw);
1148  AMM ammGw(env, gw, XRP(10'000), USD(10'000));
1149  BEAST_EXPECT(
1150  ammGw.expectBalances(XRP(10'000), USD(10'000), ammGw.tokens()));
1151  ammGw.deposit(gw, 1'000'000);
1152  BEAST_EXPECT(ammGw.expectBalances(
1153  XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
1154  ammGw.deposit(gw, USD(1'000));
1155  BEAST_EXPECT(ammGw.expectBalances(
1156  XRP(11'000),
1157  STAmount{USD, UINT64_C(11'999'99999999998), -11},
1158  IOUAmount{11'489'125'29307605, -8}));
1159  }
1160 
1161  // Issuer deposit
1162  testAMM([&](AMM& ammAlice, Env& env) {
1163  ammAlice.deposit(gw, 1'000'000);
1164  BEAST_EXPECT(ammAlice.expectBalances(
1165  XRP(11'000), USD(11'000), IOUAmount{11'000'000}));
1166  ammAlice.deposit(gw, USD(1'000));
1167  BEAST_EXPECT(ammAlice.expectBalances(
1168  XRP(11'000),
1169  STAmount{USD, UINT64_C(11'999'99999999998), -11},
1170  IOUAmount{11'489'125'29307605, -8}));
1171  });
1172 
1173  // Min deposit
1174  testAMM([&](AMM& ammAlice, Env& env) {
1175  // Equal deposit by tokens
1176  ammAlice.deposit(
1177  carol,
1178  1'000'000,
1179  XRP(1'000),
1180  USD(1'000),
1181  std::nullopt,
1182  tfLPToken,
1183  std::nullopt,
1184  std::nullopt);
1185  BEAST_EXPECT(ammAlice.expectBalances(
1186  XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
1187  });
1188  testAMM([&](AMM& ammAlice, Env& env) {
1189  // Equal deposit by asset
1190  ammAlice.deposit(
1191  carol,
1192  1'000'000,
1193  XRP(1'000),
1194  USD(1'000),
1195  std::nullopt,
1196  tfTwoAsset,
1197  std::nullopt,
1198  std::nullopt);
1199  BEAST_EXPECT(ammAlice.expectBalances(
1200  XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
1201  });
1202  testAMM([&](AMM& ammAlice, Env& env) {
1203  // Single deposit by asset
1204  ammAlice.deposit(
1205  carol,
1206  488'088,
1207  XRP(1'000),
1208  std::nullopt,
1209  std::nullopt,
1210  tfSingleAsset,
1211  std::nullopt,
1212  std::nullopt);
1213  BEAST_EXPECT(ammAlice.expectBalances(
1214  XRP(11'000), USD(10'000), IOUAmount{10'488'088'48170151, -8}));
1215  });
1216  testAMM([&](AMM& ammAlice, Env& env) {
1217  // Single deposit by asset
1218  ammAlice.deposit(
1219  carol,
1220  488'088,
1221  USD(1'000),
1222  std::nullopt,
1223  std::nullopt,
1224  tfSingleAsset,
1225  std::nullopt,
1226  std::nullopt);
1227  BEAST_EXPECT(ammAlice.expectBalances(
1228  XRP(10'000),
1229  STAmount{USD, UINT64_C(10'999'99999999999), -11},
1230  IOUAmount{10'488'088'48170151, -8}));
1231  });
1232  }
1233 
1234  void
1236  {
1237  testcase("Invalid Withdraw");
1238 
1239  using namespace jtx;
1240 
1241  testAMM([&](AMM& ammAlice, Env& env) {
1242  // Invalid flags
1243  ammAlice.withdraw(
1244  alice,
1245  1'000'000,
1246  std::nullopt,
1247  std::nullopt,
1248  std::nullopt,
1249  tfBurnable,
1250  std::nullopt,
1251  std::nullopt,
1252  ter(temINVALID_FLAG));
1253 
1254  // Invalid options
1261  NotTEC>>
1262  invalidOptions = {
1263  // tokens, asset1Out, asset2Out, EPrice, flags, ter
1264  {std::nullopt,
1265  std::nullopt,
1266  std::nullopt,
1267  std::nullopt,
1268  std::nullopt,
1269  temMALFORMED},
1270  {std::nullopt,
1271  std::nullopt,
1272  std::nullopt,
1273  std::nullopt,
1275  temMALFORMED},
1276  {1'000,
1277  std::nullopt,
1278  std::nullopt,
1279  std::nullopt,
1280  tfWithdrawAll,
1281  temMALFORMED},
1282  {std::nullopt,
1283  USD(0),
1284  XRP(100),
1285  std::nullopt,
1286  tfWithdrawAll | tfLPToken,
1287  temMALFORMED},
1288  {std::nullopt,
1289  std::nullopt,
1290  USD(100),
1291  std::nullopt,
1292  tfWithdrawAll,
1293  temMALFORMED},
1294  {std::nullopt,
1295  std::nullopt,
1296  std::nullopt,
1297  std::nullopt,
1298  tfWithdrawAll | tfOneAssetWithdrawAll,
1299  temMALFORMED},
1300  {std::nullopt,
1301  USD(100),
1302  std::nullopt,
1303  std::nullopt,
1304  tfWithdrawAll,
1305  temMALFORMED},
1306  {std::nullopt,
1307  std::nullopt,
1308  std::nullopt,
1309  std::nullopt,
1310  tfOneAssetWithdrawAll,
1311  temMALFORMED},
1312  {1'000,
1313  std::nullopt,
1314  USD(100),
1315  std::nullopt,
1316  std::nullopt,
1317  temMALFORMED},
1318  {std::nullopt,
1319  std::nullopt,
1320  std::nullopt,
1321  IOUAmount{250, 0},
1322  tfWithdrawAll,
1323  temMALFORMED},
1324  {1'000,
1325  std::nullopt,
1326  std::nullopt,
1327  IOUAmount{250, 0},
1328  std::nullopt,
1329  temMALFORMED},
1330  {std::nullopt,
1331  std::nullopt,
1332  USD(100),
1333  IOUAmount{250, 0},
1334  std::nullopt,
1335  temMALFORMED},
1336  {std::nullopt,
1337  XRP(100),
1338  USD(100),
1339  IOUAmount{250, 0},
1340  std::nullopt,
1341  temMALFORMED},
1342  {1'000,
1343  XRP(100),
1344  USD(100),
1345  std::nullopt,
1346  std::nullopt,
1347  temMALFORMED},
1348  {std::nullopt,
1349  XRP(100),
1350  USD(100),
1351  std::nullopt,
1352  tfWithdrawAll,
1353  temMALFORMED}};
1354  for (auto const& it : invalidOptions)
1355  {
1356  ammAlice.withdraw(
1357  alice,
1358  std::get<0>(it),
1359  std::get<1>(it),
1360  std::get<2>(it),
1361  std::get<3>(it),
1362  std::get<4>(it),
1363  std::nullopt,
1364  std::nullopt,
1365  ter(std::get<5>(it)));
1366  }
1367 
1368  // Invalid tokens
1369  ammAlice.withdraw(
1370  alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS));
1371  ammAlice.withdraw(
1372  alice,
1373  IOUAmount{-1},
1374  std::nullopt,
1375  std::nullopt,
1377 
1378  // Mismatched token, invalid Asset1Out issue
1379  ammAlice.withdraw(
1380  alice,
1381  GBP(100),
1382  std::nullopt,
1383  std::nullopt,
1385 
1386  // Mismatched token, invalid Asset2Out issue
1387  ammAlice.withdraw(
1388  alice,
1389  USD(100),
1390  GBP(100),
1391  std::nullopt,
1393 
1394  // Mismatched token, Asset1Out.issue == Asset2Out.issue
1395  ammAlice.withdraw(
1396  alice,
1397  USD(100),
1398  USD(100),
1399  std::nullopt,
1401 
1402  // Invalid amount value
1403  ammAlice.withdraw(
1404  alice, USD(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT));
1405  ammAlice.withdraw(
1406  alice,
1407  USD(-100),
1408  std::nullopt,
1409  std::nullopt,
1410  ter(temBAD_AMOUNT));
1411  ammAlice.withdraw(
1412  alice,
1413  USD(10),
1414  std::nullopt,
1415  IOUAmount{-1},
1416  ter(temBAD_AMOUNT));
1417 
1418  // Invalid amount/token value, withdraw all tokens from one side
1419  // of the pool.
1420  ammAlice.withdraw(
1421  alice,
1422  USD(10'000),
1423  std::nullopt,
1424  std::nullopt,
1425  ter(tecAMM_BALANCE));
1426  ammAlice.withdraw(
1427  alice,
1428  XRP(10'000),
1429  std::nullopt,
1430  std::nullopt,
1431  ter(tecAMM_BALANCE));
1432  ammAlice.withdraw(
1433  alice,
1434  std::nullopt,
1435  USD(0),
1436  std::nullopt,
1437  std::nullopt,
1439  std::nullopt,
1440  std::nullopt,
1441  ter(tecAMM_BALANCE));
1442 
1443  // Bad currency
1444  ammAlice.withdraw(
1445  alice,
1446  BAD(100),
1447  std::nullopt,
1448  std::nullopt,
1449  ter(temBAD_CURRENCY));
1450 
1451  // Invalid Account
1452  Account bad("bad");
1453  env.memoize(bad);
1454  ammAlice.withdraw(
1455  bad,
1456  1'000'000,
1457  std::nullopt,
1458  std::nullopt,
1459  std::nullopt,
1460  std::nullopt,
1461  std::nullopt,
1462  seq(1),
1463  ter(terNO_ACCOUNT));
1464 
1465  // Invalid AMM
1466  ammAlice.withdraw(
1467  alice,
1468  1'000,
1469  std::nullopt,
1470  std::nullopt,
1471  std::nullopt,
1472  std::nullopt,
1473  {{USD, GBP}},
1474  std::nullopt,
1475  ter(terNO_AMM));
1476 
1477  // Carol is not a Liquidity Provider
1478  ammAlice.withdraw(
1479  carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
1480 
1481  // Withdraw entire one side of the pool.
1482  // Equal withdraw but due to XRP precision limit,
1483  // this results in full withdraw of XRP pool only,
1484  // while leaving a tiny amount in USD pool.
1485  ammAlice.withdraw(
1486  alice,
1487  IOUAmount{9'999'999'9999, -4},
1488  std::nullopt,
1489  std::nullopt,
1490  ter(tecAMM_BALANCE));
1491  // Withdrawing from one side.
1492  // XRP by tokens
1493  ammAlice.withdraw(
1494  alice,
1495  IOUAmount(9'999'999'9999, -4),
1496  XRP(0),
1497  std::nullopt,
1498  ter(tecAMM_BALANCE));
1499  // USD by tokens
1500  ammAlice.withdraw(
1501  alice,
1502  IOUAmount(9'999'999'9, -1),
1503  USD(0),
1504  std::nullopt,
1505  ter(tecAMM_BALANCE));
1506  // XRP
1507  ammAlice.withdraw(
1508  alice,
1509  XRP(10'000),
1510  std::nullopt,
1511  std::nullopt,
1512  ter(tecAMM_BALANCE));
1513  // USD
1514  ammAlice.withdraw(
1515  alice,
1516  STAmount{USD, UINT64_C(9'999'9999999999999), -13},
1517  std::nullopt,
1518  std::nullopt,
1519  ter(tecAMM_BALANCE));
1520  });
1521 
1522  // Invalid AMM
1523  testAMM([&](AMM& ammAlice, Env& env) {
1524  ammAlice.withdrawAll(alice);
1525  ammAlice.withdraw(
1526  alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM));
1527  });
1528 
1529  // Globally frozen asset
1530  testAMM([&](AMM& ammAlice, Env& env) {
1531  env(fset(gw, asfGlobalFreeze));
1532  env.close();
1533  // Can withdraw non-frozen token
1534  ammAlice.withdraw(alice, XRP(100));
1535  ammAlice.withdraw(
1536  alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
1537  ammAlice.withdraw(
1538  alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
1539  });
1540 
1541  // Individually frozen (AMM) account
1542  testAMM([&](AMM& ammAlice, Env& env) {
1543  env(trust(gw, alice["USD"](0), tfSetFreeze));
1544  env.close();
1545  // Can withdraw non-frozen token
1546  ammAlice.withdraw(alice, XRP(100));
1547  ammAlice.withdraw(
1548  alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
1549  ammAlice.withdraw(
1550  alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
1551  env(trust(gw, alice["USD"](0), tfClearFreeze));
1552  // Individually frozen AMM
1553  env(trust(
1554  gw,
1555  STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
1556  tfSetFreeze));
1557  // Can withdraw non-frozen token
1558  ammAlice.withdraw(alice, XRP(100));
1559  ammAlice.withdraw(
1560  alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN));
1561  ammAlice.withdraw(
1562  alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN));
1563  });
1564 
1565  // Carol withdraws more than she owns
1566  testAMM([&](AMM& ammAlice, Env&) {
1567  // Single deposit of 100000 worth of tokens,
1568  // which is 10% of the pool. Carol is LP now.
1569  ammAlice.deposit(carol, 1'000'000);
1570  BEAST_EXPECT(ammAlice.expectBalances(
1571  XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
1572 
1573  ammAlice.withdraw(
1574  carol,
1575  2'000'000,
1576  std::nullopt,
1577  std::nullopt,
1579  BEAST_EXPECT(ammAlice.expectBalances(
1580  XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
1581  });
1582 
1583  // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
1584  // to withdraw are 0.
1585  testAMM([&](AMM& ammAlice, Env&) {
1586  ammAlice.deposit(carol, 1'000'000);
1587  ammAlice.withdraw(
1588  carol,
1589  USD(100),
1590  std::nullopt,
1591  IOUAmount{500, 0},
1592  ter(tecAMM_FAILED));
1593  });
1594 
1595  // Withdraw with EPrice limit. Fails to withdraw, calculated tokens
1596  // to withdraw are greater than the LP shares.
1597  testAMM([&](AMM& ammAlice, Env&) {
1598  ammAlice.deposit(carol, 1'000'000);
1599  ammAlice.withdraw(
1600  carol,
1601  USD(100),
1602  std::nullopt,
1603  IOUAmount{600, 0},
1605  });
1606 
1607  // Withdraw with EPrice limit. Fails to withdraw, amount1
1608  // to withdraw is less than 1700USD.
1609  testAMM([&](AMM& ammAlice, Env&) {
1610  ammAlice.deposit(carol, 1'000'000);
1611  ammAlice.withdraw(
1612  carol,
1613  USD(1'700),
1614  std::nullopt,
1615  IOUAmount{520, 0},
1616  ter(tecAMM_FAILED));
1617  });
1618 
1619  // Deposit/Withdraw the same amount with the trading fee
1620  testAMM(
1621  [&](AMM& ammAlice, Env&) {
1622  ammAlice.deposit(carol, USD(1'000));
1623  ammAlice.withdraw(
1624  carol,
1625  USD(1'000),
1626  std::nullopt,
1627  std::nullopt,
1628  ter(tecAMM_INVALID_TOKENS));
1629  },
1630  std::nullopt,
1631  1'000);
1632  testAMM(
1633  [&](AMM& ammAlice, Env&) {
1634  ammAlice.deposit(carol, XRP(1'000));
1635  ammAlice.withdraw(
1636  carol,
1637  XRP(1'000),
1638  std::nullopt,
1639  std::nullopt,
1641  },
1642  std::nullopt,
1643  1'000);
1644 
1645  // Deposit/Withdraw the same amount fails due to the tokens adjustment
1646  testAMM([&](AMM& ammAlice, Env&) {
1647  ammAlice.deposit(carol, STAmount{USD, 1, -6});
1648  ammAlice.withdraw(
1649  carol,
1650  STAmount{USD, 1, -6},
1651  std::nullopt,
1652  std::nullopt,
1653  ter(tecAMM_INVALID_TOKENS));
1654  });
1655 
1656  // Withdraw close to one side of the pool. Account's LP tokens
1657  // are rounded to all LP tokens.
1658  testAMM([&](AMM& ammAlice, Env&) {
1659  ammAlice.withdraw(
1660  alice,
1661  STAmount{USD, UINT64_C(9'999'999999999999), -12},
1662  std::nullopt,
1663  std::nullopt,
1664  ter(tecAMM_BALANCE));
1665  });
1666 
1667  // Tiny withdraw
1668  testAMM([&](AMM& ammAlice, Env&) {
1669  // XRP amount to withdraw is 0
1670  ammAlice.withdraw(
1671  alice,
1672  IOUAmount{1, -5},
1673  std::nullopt,
1674  std::nullopt,
1675  ter(tecAMM_FAILED));
1676  // Calculated tokens to withdraw are 0
1677  ammAlice.withdraw(
1678  alice,
1679  std::nullopt,
1680  STAmount{USD, 1, -11},
1681  std::nullopt,
1683  ammAlice.deposit(carol, STAmount{USD, 1, -10});
1684  ammAlice.withdraw(
1685  carol,
1686  std::nullopt,
1687  STAmount{USD, 1, -9},
1688  std::nullopt,
1690  ammAlice.withdraw(
1691  carol,
1692  std::nullopt,
1693  XRPAmount{1},
1694  std::nullopt,
1696  });
1697  }
1698 
1699  void
1701  {
1702  testcase("Withdraw");
1703 
1704  using namespace jtx;
1705 
1706  // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
1707  // pool
1708  testAMM([&](AMM& ammAlice, Env& env) {
1709  // Single deposit of 100000 worth of tokens,
1710  // which is 10% of the pool. Carol is LP now.
1711  ammAlice.deposit(carol, 1'000'000);
1712  BEAST_EXPECT(ammAlice.expectBalances(
1713  XRP(11'000), USD(11'000), IOUAmount{11'000'000, 0}));
1714  BEAST_EXPECT(
1715  ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
1716  // 30,000 less deposited 1,000
1717  BEAST_EXPECT(expectLine(env, carol, USD(29'000)));
1718  // 30,000 less deposited 1,000 and 10 drops tx fee
1719  BEAST_EXPECT(
1720  expectLedgerEntryRoot(env, carol, XRPAmount{28'999'999'990}));
1721 
1722  // Carol withdraws all tokens
1723  ammAlice.withdraw(carol, 1'000'000);
1724  BEAST_EXPECT(
1725  ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
1726  BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
1727  BEAST_EXPECT(
1728  expectLedgerEntryRoot(env, carol, XRPAmount{29'999'999'980}));
1729  });
1730 
1731  // Equal withdrawal by tokens 1000000, 10%
1732  // of the current pool
1733  testAMM([&](AMM& ammAlice, Env&) {
1734  ammAlice.withdraw(alice, 1'000'000);
1735  BEAST_EXPECT(ammAlice.expectBalances(
1736  XRP(9'000), USD(9'000), IOUAmount{9'000'000, 0}));
1737  });
1738 
1739  // Equal withdrawal with a limit. Withdraw XRP200.
1740  // If proportional withdraw of USD is less than 100
1741  // then withdraw that amount, otherwise withdraw USD100
1742  // and proportionally withdraw XRP. It's the latter
1743  // in this case - XRP100/USD100.
1744  testAMM([&](AMM& ammAlice, Env&) {
1745  ammAlice.withdraw(alice, XRP(200), USD(100));
1746  BEAST_EXPECT(ammAlice.expectBalances(
1747  XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
1748  });
1749 
1750  // Equal withdrawal with a limit. XRP100/USD100.
1751  testAMM([&](AMM& ammAlice, Env&) {
1752  ammAlice.withdraw(alice, XRP(100), USD(200));
1753  BEAST_EXPECT(ammAlice.expectBalances(
1754  XRP(9'900), USD(9'900), IOUAmount{9'900'000, 0}));
1755  });
1756 
1757  // Single withdrawal by amount XRP1000
1758  testAMM([&](AMM& ammAlice, Env&) {
1759  ammAlice.withdraw(alice, XRP(1'000));
1760  BEAST_EXPECT(ammAlice.expectBalances(
1761  XRP(9'000), USD(10'000), IOUAmount{9'486'832'98050514, -8}));
1762  });
1763 
1764  // Single withdrawal by tokens 10000.
1765  testAMM([&](AMM& ammAlice, Env&) {
1766  ammAlice.withdraw(alice, 10'000, USD(0));
1767  BEAST_EXPECT(ammAlice.expectBalances(
1768  XRP(10'000), USD(9980.01), IOUAmount{9'990'000, 0}));
1769  });
1770 
1771  // Withdraw all tokens.
1772  testAMM([&](AMM& ammAlice, Env& env) {
1773  ammAlice.withdrawAll(alice);
1774  BEAST_EXPECT(!ammAlice.ammExists());
1775 
1776  // Can create AMM for the XRP/USD pair
1777  AMM ammCarol(env, carol, XRP(10'000), USD(10'000));
1778  BEAST_EXPECT(ammCarol.expectBalances(
1779  XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
1780  });
1781 
1782  // Single deposit 1000USD, withdraw all tokens in USD
1783  testAMM([&](AMM& ammAlice, Env& env) {
1784  ammAlice.deposit(carol, USD(1'000));
1785  ammAlice.withdrawAll(carol, USD(0));
1786  BEAST_EXPECT(ammAlice.expectBalances(
1787  XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
1788  BEAST_EXPECT(
1789  ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero())));
1790  });
1791 
1792  // Single deposit 1000USD, withdraw all tokens in XRP
1793  testAMM([&](AMM& ammAlice, Env&) {
1794  ammAlice.deposit(carol, USD(1'000));
1795  ammAlice.withdrawAll(carol, XRP(0));
1796  BEAST_EXPECT(ammAlice.expectBalances(
1797  XRPAmount(9'090'909'091),
1798  STAmount{USD, UINT64_C(10'999'99999999999), -11},
1799  IOUAmount{10'000'000, 0}));
1800  });
1801 
1802  // Single deposit/withdraw by the same account
1803  testAMM([&](AMM& ammAlice, Env&) {
1804  // Since a smaller amount might be deposited due to
1805  // the lp tokens adjustment, withdrawing by tokens
1806  // is generally preferred to withdrawing by amount.
1807  auto lpTokens = ammAlice.deposit(carol, USD(1'000));
1808  ammAlice.withdraw(carol, lpTokens, USD(0));
1809  lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6));
1810  ammAlice.withdraw(carol, lpTokens, USD(0));
1811  lpTokens = ammAlice.deposit(carol, XRPAmount(1));
1812  ammAlice.withdraw(carol, lpTokens, XRPAmount(0));
1813  BEAST_EXPECT(ammAlice.expectBalances(
1814  XRP(10'000), USD(10'000), ammAlice.tokens()));
1815  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
1816  });
1817 
1818  // Single deposit by different accounts and then withdraw
1819  // in reverse.
1820  testAMM([&](AMM& ammAlice, Env&) {
1821  auto const carolTokens = ammAlice.deposit(carol, USD(1'000));
1822  auto const aliceTokens = ammAlice.deposit(alice, USD(1'000));
1823  ammAlice.withdraw(alice, aliceTokens, USD(0));
1824  ammAlice.withdraw(carol, carolTokens, USD(0));
1825  BEAST_EXPECT(ammAlice.expectBalances(
1826  XRP(10'000), USD(10'000), ammAlice.tokens()));
1827  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
1828  BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens()));
1829  });
1830 
1831  // Equal deposit 10%, withdraw all tokens
1832  testAMM([&](AMM& ammAlice, Env&) {
1833  ammAlice.deposit(carol, 1'000'000);
1834  ammAlice.withdrawAll(carol);
1835  BEAST_EXPECT(ammAlice.expectBalances(
1836  XRP(10'000), USD(10'000), IOUAmount{10'000'000, 0}));
1837  });
1838 
1839  // Equal deposit 10%, withdraw all tokens in USD
1840  testAMM([&](AMM& ammAlice, Env&) {
1841  ammAlice.deposit(carol, 1'000'000);
1842  ammAlice.withdrawAll(carol, USD(0));
1843  BEAST_EXPECT(ammAlice.expectBalances(
1844  XRP(11'000),
1845  STAmount{USD, UINT64_C(9'090'909090909092), -12},
1846  IOUAmount{10'000'000, 0}));
1847  });
1848 
1849  // Equal deposit 10%, withdraw all tokens in XRP
1850  testAMM([&](AMM& ammAlice, Env&) {
1851  ammAlice.deposit(carol, 1'000'000);
1852  ammAlice.withdrawAll(carol, XRP(0));
1853  BEAST_EXPECT(ammAlice.expectBalances(
1854  XRPAmount(9'090'909'091),
1855  USD(11'000),
1856  IOUAmount{10'000'000, 0}));
1857  });
1858 
1859  // Withdraw with EPrice limit.
1860  testAMM([&](AMM& ammAlice, Env&) {
1861  ammAlice.deposit(carol, 1'000'000);
1862  ammAlice.withdraw(carol, USD(100), std::nullopt, IOUAmount{520, 0});
1863  BEAST_EXPECT(
1864  ammAlice.expectBalances(
1865  XRPAmount(11'000'000'000),
1866  STAmount{USD, UINT64_C(9'372'781065088757), -12},
1867  IOUAmount{10'153'846'15384616, -8}) &&
1868  ammAlice.expectLPTokens(
1869  carol, IOUAmount{153'846'15384616, -8}));
1870  ammAlice.withdrawAll(carol);
1871  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
1872  });
1873 
1874  // Withdraw with EPrice limit. AssetOut is 0.
1875  testAMM([&](AMM& ammAlice, Env&) {
1876  ammAlice.deposit(carol, 1'000'000);
1877  ammAlice.withdraw(carol, USD(0), std::nullopt, IOUAmount{520, 0});
1878  BEAST_EXPECT(
1879  ammAlice.expectBalances(
1880  XRPAmount(11'000'000'000),
1881  STAmount{USD, UINT64_C(9'372'781065088757), -12},
1882  IOUAmount{10'153'846'15384616, -8}) &&
1883  ammAlice.expectLPTokens(
1884  carol, IOUAmount{153'846'15384616, -8}));
1885  });
1886 
1887  // IOU to IOU + transfer fee
1888  {
1889  Env env{*this};
1890  fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
1891  env(rate(gw, 1.25));
1892  env.close();
1893  // no transfer fee on create
1894  AMM ammAlice(env, alice, USD(20'000), BTC(0.5));
1895  BEAST_EXPECT(ammAlice.expectBalances(
1896  USD(20'000), BTC(0.5), IOUAmount{100, 0}));
1897  BEAST_EXPECT(expectLine(env, alice, USD(0)));
1898  BEAST_EXPECT(expectLine(env, alice, BTC(0)));
1899  fund(env, gw, {carol}, {USD(2'000), BTC(0.05)}, Fund::Acct);
1900  // no transfer fee on deposit
1901  ammAlice.deposit(carol, 10);
1902  BEAST_EXPECT(ammAlice.expectBalances(
1903  USD(22'000), BTC(0.55), IOUAmount{110, 0}));
1904  BEAST_EXPECT(expectLine(env, carol, USD(0)));
1905  BEAST_EXPECT(expectLine(env, carol, BTC(0)));
1906  // no transfer fee on withdraw
1907  ammAlice.withdraw(carol, 10);
1908  BEAST_EXPECT(ammAlice.expectBalances(
1909  USD(20'000), BTC(0.5), IOUAmount{100, 0}));
1910  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0}));
1911  BEAST_EXPECT(expectLine(env, carol, USD(2'000)));
1912  BEAST_EXPECT(expectLine(env, carol, BTC(0.05)));
1913  }
1914 
1915  // Tiny withdraw
1916  testAMM([&](AMM& ammAlice, Env&) {
1917  // By tokens
1918  ammAlice.withdraw(alice, IOUAmount{1, -3});
1919  BEAST_EXPECT(ammAlice.expectBalances(
1920  XRPAmount{9'999'999'999},
1921  STAmount{USD, UINT64_C(9'999'999999), -6},
1922  IOUAmount{9'999'999'999, -3}));
1923  });
1924  testAMM([&](AMM& ammAlice, Env&) {
1925  // Single XRP pool
1926  ammAlice.withdraw(alice, std::nullopt, XRPAmount{1});
1927  BEAST_EXPECT(ammAlice.expectBalances(
1928  XRPAmount{9'999'999'999},
1929  USD(10'000),
1930  IOUAmount{9'999'999'9995, -4}));
1931  });
1932  testAMM([&](AMM& ammAlice, Env&) {
1933  // Single USD pool
1934  ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10});
1935  BEAST_EXPECT(ammAlice.expectBalances(
1936  XRP(10'000),
1937  STAmount{USD, UINT64_C(9'999'9999999999), -10},
1938  IOUAmount{9'999'999'99999995, -8}));
1939  });
1940 
1941  // Withdraw close to entire pool
1942  // Equal by tokens
1943  testAMM([&](AMM& ammAlice, Env&) {
1944  ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3});
1945  BEAST_EXPECT(ammAlice.expectBalances(
1946  XRPAmount{1}, STAmount{USD, 1, -6}, IOUAmount{1, -3}));
1947  });
1948  // USD by tokens
1949  testAMM([&](AMM& ammAlice, Env&) {
1950  ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0));
1951  BEAST_EXPECT(ammAlice.expectBalances(
1952  XRP(10'000), STAmount{USD, 1, -10}, IOUAmount{1}));
1953  });
1954  // XRP by tokens
1955  testAMM([&](AMM& ammAlice, Env&) {
1956  ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0));
1957  BEAST_EXPECT(ammAlice.expectBalances(
1958  XRPAmount{1}, USD(10'000), IOUAmount{100}));
1959  });
1960  // USD
1961  testAMM([&](AMM& ammAlice, Env&) {
1962  ammAlice.withdraw(
1963  alice, STAmount{USD, UINT64_C(9'999'99999999999), -11});
1964  BEAST_EXPECT(ammAlice.expectBalances(
1965  XRP(10000), STAmount{USD, 1, -11}, IOUAmount{316227765, -9}));
1966  });
1967  // XRP
1968  testAMM([&](AMM& ammAlice, Env&) {
1969  ammAlice.withdraw(alice, XRPAmount{9'999'999'999});
1970  BEAST_EXPECT(ammAlice.expectBalances(
1971  XRPAmount{1}, USD(10'000), IOUAmount{100}));
1972  });
1973  }
1974 
1975  void
1977  {
1978  testcase("Invalid Fee Vote");
1979  using namespace jtx;
1980 
1981  testAMM([&](AMM& ammAlice, Env& env) {
1982  // Invalid flags
1983  ammAlice.vote(
1984  std::nullopt,
1985  1'000,
1986  tfWithdrawAll,
1987  std::nullopt,
1988  std::nullopt,
1989  ter(temINVALID_FLAG));
1990 
1991  // Invalid fee.
1992  ammAlice.vote(
1993  std::nullopt,
1994  1'001,
1995  std::nullopt,
1996  std::nullopt,
1997  std::nullopt,
1998  ter(temBAD_FEE));
1999  BEAST_EXPECT(ammAlice.expectTradingFee(0));
2000 
2001  // Invalid Account
2002  Account bad("bad");
2003  env.memoize(bad);
2004  ammAlice.vote(
2005  bad,
2006  1'000,
2007  std::nullopt,
2008  seq(1),
2009  std::nullopt,
2010  ter(terNO_ACCOUNT));
2011 
2012  // Invalid AMM
2013  ammAlice.vote(
2014  alice,
2015  1'000,
2016  std::nullopt,
2017  std::nullopt,
2018  {{USD, GBP}},
2019  ter(terNO_AMM));
2020 
2021  // Account is not LP
2022  ammAlice.vote(
2023  carol,
2024  1'000,
2025  std::nullopt,
2026  std::nullopt,
2027  std::nullopt,
2028  ter(tecAMM_INVALID_TOKENS));
2029  });
2030 
2031  // Invalid AMM
2032  testAMM([&](AMM& ammAlice, Env& env) {
2033  ammAlice.withdrawAll(alice);
2034  ammAlice.vote(
2035  alice,
2036  1'000,
2037  std::nullopt,
2038  std::nullopt,
2039  std::nullopt,
2040  ter(terNO_AMM));
2041  });
2042  }
2043 
2044  void
2046  {
2047  testcase("Fee Vote");
2048  using namespace jtx;
2049 
2050  // One vote sets fee to 1%.
2051  testAMM([&](AMM& ammAlice, Env& env) {
2052  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0}));
2053  ammAlice.vote({}, 1'000);
2054  BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
2055  // Discounted fee is 1/10 of trading fee.
2056  BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0}));
2057  });
2058 
2059  auto vote = [&](AMM& ammAlice,
2060  Env& env,
2061  int i,
2062  int fundUSD = 100'000,
2063  std::uint32_t tokens = 10'000'000,
2064  std::vector<Account>* accounts = nullptr) {
2065  Account a(std::to_string(i));
2066  fund(env, gw, {a}, {USD(fundUSD)}, Fund::Acct);
2067  ammAlice.deposit(a, tokens);
2068  ammAlice.vote(a, 50 * (i + 1));
2069  if (accounts)
2070  accounts->push_back(std::move(a));
2071  };
2072 
2073  // Eight votes fill all voting slots, set fee 0.175%.
2074  testAMM([&](AMM& ammAlice, Env& env) {
2075  for (int i = 0; i < 7; ++i)
2076  vote(ammAlice, env, i, 10'000);
2077  BEAST_EXPECT(ammAlice.expectTradingFee(175));
2078  });
2079 
2080  // Eight votes fill all voting slots, set fee 0.175%.
2081  // New vote, same account, sets fee 0.225%
2082  testAMM([&](AMM& ammAlice, Env& env) {
2083  for (int i = 0; i < 7; ++i)
2084  vote(ammAlice, env, i);
2085  BEAST_EXPECT(ammAlice.expectTradingFee(175));
2086  Account const a("0");
2087  ammAlice.vote(a, 450);
2088  BEAST_EXPECT(ammAlice.expectTradingFee(225));
2089  });
2090 
2091  // Eight votes fill all voting slots, set fee 0.175%.
2092  // New vote, new account, higher vote weight, set higher fee 0.244%
2093  testAMM([&](AMM& ammAlice, Env& env) {
2094  for (int i = 0; i < 7; ++i)
2095  vote(ammAlice, env, i);
2096  BEAST_EXPECT(ammAlice.expectTradingFee(175));
2097  vote(ammAlice, env, 7, 100'000, 20'000'000);
2098  BEAST_EXPECT(ammAlice.expectTradingFee(244));
2099  });
2100 
2101  // Eight votes fill all voting slots, set fee 0.219%.
2102  // New vote, new account, higher vote weight, set smaller fee 0.206%
2103  testAMM([&](AMM& ammAlice, Env& env) {
2104  for (int i = 7; i > 0; --i)
2105  vote(ammAlice, env, i);
2106  BEAST_EXPECT(ammAlice.expectTradingFee(219));
2107  vote(ammAlice, env, 0, 100'000, 20'000'000);
2108  BEAST_EXPECT(ammAlice.expectTradingFee(206));
2109  });
2110 
2111  // Eight votes fill all voting slots. The accounts then withdraw all
2112  // tokens. An account sets a new fee and the previous slots are
2113  // deleted.
2114  testAMM([&](AMM& ammAlice, Env& env) {
2115  std::vector<Account> accounts;
2116  for (int i = 0; i < 7; ++i)
2117  vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2118  BEAST_EXPECT(ammAlice.expectTradingFee(175));
2119  for (int i = 0; i < 7; ++i)
2120  ammAlice.withdrawAll(accounts[i]);
2121  ammAlice.deposit(carol, 10'000'000);
2122  ammAlice.vote(carol, 1'000);
2123  // The initial LP set the fee to 1000. Carol gets 50% voting
2124  // power, and the new fee is 500.
2125  BEAST_EXPECT(ammAlice.expectTradingFee(500));
2126  });
2127 
2128  // Eight votes fill all voting slots. The accounts then withdraw some
2129  // tokens. The new vote doesn't get the voting power but
2130  // the slots are refreshed and the fee is updated.
2131  testAMM([&](AMM& ammAlice, Env& env) {
2132  std::vector<Account> accounts;
2133  for (int i = 0; i < 7; ++i)
2134  vote(ammAlice, env, i, 100'000, 10'000'000, &accounts);
2135  BEAST_EXPECT(ammAlice.expectTradingFee(175));
2136  for (int i = 0; i < 7; ++i)
2137  ammAlice.withdraw(accounts[i], 9'000'000);
2138  ammAlice.deposit(carol, 1'000);
2139  // The vote is not added to the slots
2140  ammAlice.vote(carol, 1'000);
2141  auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
2142  for (std::uint16_t i = 0; i < info.size(); ++i)
2143  BEAST_EXPECT(info[i][jss::account] != carol.human());
2144  // But the slots are refreshed and the fee is changed
2145  BEAST_EXPECT(ammAlice.expectTradingFee(82));
2146  });
2147  }
2148 
2149  void
2150  testInvalidBid()
2151  {
2152  testcase("Invalid Bid");
2153  using namespace jtx;
2154  using namespace std::chrono;
2155 
2156  testAMM([&](AMM& ammAlice, Env& env) {
2157  // Invalid flags
2158  ammAlice.bid(
2159  carol,
2160  0,
2161  std::nullopt,
2162  {},
2163  tfWithdrawAll,
2164  std::nullopt,
2165  std::nullopt,
2166  ter(temINVALID_FLAG));
2167 
2168  ammAlice.deposit(carol, 1'000'000);
2169  // Invalid Bid price <= 0
2170  for (auto bid : {0, -100})
2171  {
2172  ammAlice.bid(
2173  carol,
2174  bid,
2175  std::nullopt,
2176  {},
2177  std::nullopt,
2178  std::nullopt,
2179  std::nullopt,
2180  ter(temBAD_AMOUNT));
2181  ammAlice.bid(
2182  carol,
2183  std::nullopt,
2184  bid,
2185  {},
2186  std::nullopt,
2187  std::nullopt,
2188  std::nullopt,
2189  ter(temBAD_AMOUNT));
2190  }
2191 
2192  // Invlaid Min/Max combination
2193  ammAlice.bid(
2194  carol,
2195  200,
2196  100,
2197  {},
2198  std::nullopt,
2199  std::nullopt,
2200  std::nullopt,
2201  ter(tecAMM_INVALID_TOKENS));
2202 
2203  // Invalid Account
2204  Account bad("bad");
2205  env.memoize(bad);
2206  ammAlice.bid(
2207  bad,
2208  std::nullopt,
2209  100,
2210  {},
2211  std::nullopt,
2212  seq(1),
2213  std::nullopt,
2214  ter(terNO_ACCOUNT));
2215 
2216  // Account is not LP
2217  Account const dan("dan");
2218  env.fund(XRP(1'000), dan);
2219  ammAlice.bid(
2220  dan,
2221  100,
2222  std::nullopt,
2223  {},
2224  std::nullopt,
2225  std::nullopt,
2226  std::nullopt,
2228  ammAlice.bid(
2229  dan,
2230  std::nullopt,
2231  std::nullopt,
2232  {},
2233  std::nullopt,
2234  std::nullopt,
2235  std::nullopt,
2236  ter(tecAMM_INVALID_TOKENS));
2237 
2238  // Auth account is invalid.
2239  ammAlice.bid(
2240  carol,
2241  100,
2242  std::nullopt,
2243  {bob},
2244  std::nullopt,
2245  std::nullopt,
2246  std::nullopt,
2247  ter(terNO_ACCOUNT));
2248 
2249  // Invalid Assets
2250  ammAlice.bid(
2251  alice,
2252  std::nullopt,
2253  100,
2254  {},
2255  std::nullopt,
2256  std::nullopt,
2257  {{USD, GBP}},
2258  ter(terNO_AMM));
2259 
2260  // Invalid Min/Max issue
2261  ammAlice.bid(
2262  alice,
2263  std::nullopt,
2264  STAmount{USD, 100},
2265  {},
2266  std::nullopt,
2267  std::nullopt,
2268  std::nullopt,
2269  ter(temBAD_AMM_TOKENS));
2270  ammAlice.bid(
2271  alice,
2272  STAmount{USD, 100},
2273  std::nullopt,
2274  {},
2275  std::nullopt,
2276  std::nullopt,
2277  std::nullopt,
2278  ter(temBAD_AMM_TOKENS));
2279  });
2280 
2281  // Invalid AMM
2282  testAMM([&](AMM& ammAlice, Env& env) {
2283  ammAlice.withdrawAll(alice);
2284  ammAlice.bid(
2285  alice,
2286  std::nullopt,
2287  100,
2288  {},
2289  std::nullopt,
2290  std::nullopt,
2291  std::nullopt,
2292  ter(terNO_AMM));
2293  });
2294 
2295  // More than four Auth accounts.
2296  testAMM([&](AMM& ammAlice, Env& env) {
2297  Account ed("ed");
2298  Account bill("bill");
2299  Account scott("scott");
2300  Account james("james");
2301  env.fund(XRP(1'000), bob, ed, bill, scott, james);
2302  env.close();
2303  ammAlice.deposit(carol, 1'000'000);
2304  ammAlice.bid(
2305  carol,
2306  100,
2307  std::nullopt,
2308  {bob, ed, bill, scott, james},
2309  std::nullopt,
2310  std::nullopt,
2311  std::nullopt,
2312  ter(temMALFORMED));
2313  });
2314 
2315  // Bid price exceeds LP owned tokens
2316  testAMM([&](AMM& ammAlice, Env& env) {
2317  fund(env, gw, {bob}, XRP(1'000), {USD(100)}, Fund::Acct);
2318  ammAlice.deposit(carol, 1'000'000);
2319  ammAlice.deposit(bob, 10);
2320  ammAlice.bid(
2321  carol,
2322  1'000'001,
2323  std::nullopt,
2324  {},
2325  std::nullopt,
2326  std::nullopt,
2327  std::nullopt,
2328  ter(tecAMM_INVALID_TOKENS));
2329  ammAlice.bid(
2330  carol,
2331  std::nullopt,
2332  1'000'001,
2333  {},
2334  std::nullopt,
2335  std::nullopt,
2336  std::nullopt,
2337  ter(tecAMM_INVALID_TOKENS));
2338  ammAlice.bid(carol, 1'000);
2339  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000}));
2340  // Slot purchase price is more than 1000 but bob only has 10 tokens
2341  ammAlice.bid(
2342  bob,
2343  std::nullopt,
2344  std::nullopt,
2345  {},
2346  std::nullopt,
2347  std::nullopt,
2348  std::nullopt,
2349  ter(tecAMM_INVALID_TOKENS));
2350  });
2351 
2352  // Bid all tokens, still own the slot
2353  {
2354  Env env(*this);
2355  fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
2356  AMM amm(env, gw, XRP(10), USD(1'000));
2357  auto const lpIssue = amm.lptIssue();
2358  env.trust(STAmount{lpIssue, 100}, alice);
2359  env.trust(STAmount{lpIssue, 50}, bob);
2360  env(pay(gw, alice, STAmount{lpIssue, 100}));
2361  env(pay(gw, bob, STAmount{lpIssue, 50}));
2362  amm.bid(alice, 100);
2363  // Alice doesn't have any more tokens, but
2364  // she still owns the slot.
2365  amm.bid(
2366  bob,
2367  std::nullopt,
2368  50,
2369  {},
2370  std::nullopt,
2371  std::nullopt,
2372  std::nullopt,
2373  ter(tecAMM_FAILED));
2374  }
2375  }
2376 
2377  void
2379  {
2380  testcase("Bid");
2381  using namespace jtx;
2382  using namespace std::chrono;
2383 
2384  // Auction slot initially is owned by AMM creator, who pays 0 price.
2385 
2386  // Bid 110 tokens. Pay bidMin.
2387  testAMM([&](AMM& ammAlice, Env& env) {
2388  ammAlice.deposit(carol, 1'000'000);
2389  ammAlice.bid(carol, 110);
2390  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2391  // 110 tokens are burned.
2392  BEAST_EXPECT(ammAlice.expectBalances(
2393  XRP(11'000), USD(11'000), IOUAmount{10'999'890, 0}));
2394  });
2395 
2396  // Bid with min/max when the pay price is less than min.
2397  testAMM([&](AMM& ammAlice, Env& env) {
2398  ammAlice.deposit(carol, 1'000'000);
2399  // Bid exactly 110. Pay 110 because the pay price is < 110.
2400  ammAlice.bid(carol, 110, 110);
2401  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2402  BEAST_EXPECT(ammAlice.expectBalances(
2403  XRP(11'000), USD(11'000), IOUAmount{10'999'890}));
2404  // Bid exactly 180-200. Pay 180 because the pay price is < 180.
2405  ammAlice.bid(alice, 180, 200);
2406  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180}));
2407  BEAST_EXPECT(ammAlice.expectBalances(
2408  XRP(11'000), USD(11'000), IOUAmount{10'999'814'5, -1}));
2409  });
2410 
2411  // Start bid at bidMin 110.
2412  testAMM([&](AMM& ammAlice, Env& env) {
2413  ammAlice.deposit(carol, 1'000'000);
2414  // Bid, pay bidMin.
2415  ammAlice.bid(carol, 110);
2416  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2417 
2418  fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2419  ammAlice.deposit(bob, 1'000'000);
2420  // Bid, pay the computed price.
2421  ammAlice.bid(bob);
2422  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1)));
2423 
2424  // Bid bidMax fails because the computed price is higher.
2425  ammAlice.bid(
2426  carol,
2427  std::nullopt,
2428  120,
2429  {},
2430  std::nullopt,
2431  std::nullopt,
2432  std::nullopt,
2433  ter(tecAMM_FAILED));
2434  // Bid MaxSlotPrice succeeds - pay computed price
2435  ammAlice.bid(carol, std::nullopt, 600);
2436  BEAST_EXPECT(
2437  ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3}));
2438 
2439  // Bid Min/MaxSlotPrice fails because the computed price is not in
2440  // range
2441  ammAlice.bid(
2442  carol,
2443  10,
2444  100,
2445  {},
2446  std::nullopt,
2447  std::nullopt,
2448  std::nullopt,
2449  ter(tecAMM_FAILED));
2450  // Bid Min/MaxSlotPrice succeeds - pay computed price
2451  ammAlice.bid(carol, 100, 600);
2452  BEAST_EXPECT(
2453  ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5}));
2454  });
2455 
2456  // Slot states.
2457  testAMM([&](AMM& ammAlice, Env& env) {
2458  ammAlice.deposit(carol, 1'000'000);
2459 
2460  fund(env, gw, {bob}, {USD(10'000)}, Fund::Acct);
2461  ammAlice.deposit(bob, 1'000'000);
2462  BEAST_EXPECT(ammAlice.expectBalances(
2463  XRP(12'000), USD(12'000), IOUAmount{12'000'000, 0}));
2464 
2465  // Initial state. Pay bidMin.
2466  ammAlice.bid(carol, 110);
2467  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110}));
2468 
2469  // 1st Interval after close, price for 0th interval.
2470  ammAlice.bid(bob);
2471  env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1));
2472  BEAST_EXPECT(
2473  ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1}));
2474 
2475  // 10th Interval after close, price for 1st interval.
2476  ammAlice.bid(carol);
2478  BEAST_EXPECT(
2479  ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3}));
2480 
2481  // 20th Interval (expired) after close, price for 10th interval.
2482  ammAlice.bid(bob);
2483  env.close(seconds(
2484  AUCTION_SLOT_TIME_INTERVALS * AUCTION_SLOT_INTERVAL_DURATION +
2485  1));
2486  BEAST_EXPECT(ammAlice.expectAuctionSlot(
2487  0, std::nullopt, IOUAmount{127'33875, -5}));
2488 
2489  // 0 Interval.
2490  ammAlice.bid(carol, 110);
2491  BEAST_EXPECT(
2492  ammAlice.expectAuctionSlot(0, std::nullopt, IOUAmount{110}));
2493  // ~321.09 tokens burnt on bidding fees.
2494  BEAST_EXPECT(ammAlice.expectBalances(
2495  XRP(12'000), USD(12'000), IOUAmount{11'999'678'91, -2}));
2496  });
2497 
2498  // Pool's fee 1%. Bid bidMin.
2499  // Auction slot owner and auth account trade at discounted fee -
2500  // 1/10 of the trading fee.
2501  // Other accounts trade at 1% fee.
2502  testAMM(
2503  [&](AMM& ammAlice, Env& env) {
2504  Account const dan("dan");
2505  Account const ed("ed");
2506  fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::Acct);
2507  ammAlice.deposit(bob, 1'000'000);
2508  ammAlice.deposit(ed, 1'000'000);
2509  ammAlice.deposit(carol, 500'000);
2510  ammAlice.deposit(dan, 500'000);
2511  auto ammTokens = ammAlice.getLPTokensBalance();
2512  ammAlice.bid(carol, 120, std::nullopt, {bob, ed});
2513  auto const slotPrice = IOUAmount{5'200};
2514  ammTokens -= slotPrice;
2515  BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice));
2516  BEAST_EXPECT(ammAlice.expectBalances(
2517  XRP(13'000), USD(13'000), ammTokens));
2518  // Discounted trade
2519  for (int i = 0; i < 10; ++i)
2520  {
2521  auto tokens = ammAlice.deposit(carol, USD(100));
2522  ammAlice.withdraw(carol, tokens, USD(0));
2523  tokens = ammAlice.deposit(bob, USD(100));
2524  ammAlice.withdraw(bob, tokens, USD(0));
2525  tokens = ammAlice.deposit(ed, USD(100));
2526  ammAlice.withdraw(ed, tokens, USD(0));
2527  }
2528  // carol, bob, and ed pay ~0.99USD in fees.
2529  BEAST_EXPECT(
2530  env.balance(carol, USD) ==
2531  STAmount(USD, UINT64_C(29'499'00572620545), -11));
2532  BEAST_EXPECT(
2533  env.balance(bob, USD) ==
2534  STAmount(USD, UINT64_C(18'999'00572616195), -11));
2535  BEAST_EXPECT(
2536  env.balance(ed, USD) ==
2537  STAmount(USD, UINT64_C(18'999'00572611841), -11));
2538  // USD pool is slightly higher because of the fees.
2539  BEAST_EXPECT(ammAlice.expectBalances(
2540  XRP(13'000),
2541  STAmount(USD, UINT64_C(13'002'98282151419), -11),
2542  ammTokens));
2543  ammTokens = ammAlice.getLPTokensBalance();
2544  // Trade with the fee
2545  for (int i = 0; i < 10; ++i)
2546  {
2547  auto const tokens = ammAlice.deposit(dan, USD(100));
2548  ammAlice.withdraw(dan, tokens, USD(0));
2549  }
2550  // dan pays ~9.94USD, which is ~10 times more in fees than
2551  // carol, bob, ed. the discounted fee is 10 times less
2552  // than the trading fee.
2553  BEAST_EXPECT(
2554  env.balance(dan, USD) ==
2555  STAmount(USD, UINT64_C(19'490'056722744), -9));
2556  // USD pool gains more in dan's fees.
2557  BEAST_EXPECT(ammAlice.expectBalances(
2558  XRP(13'000),
2559  STAmount{USD, UINT64_C(13'012'92609877019), -11},
2560  ammTokens));
2561  // Discounted fee payment
2562  ammAlice.deposit(carol, USD(100));
2563  ammTokens = ammAlice.getLPTokensBalance();
2564  BEAST_EXPECT(ammAlice.expectBalances(
2565  XRP(13'000),
2566  STAmount{USD, UINT64_C(13'112'92609877019), -11},
2567  ammTokens));
2568  env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
2569  env.close();
2570  // carol pays 100000 drops in fees
2571  // 99900668XRP swapped in for 100USD
2572  BEAST_EXPECT(ammAlice.expectBalances(
2573  XRPAmount{13'100'000'668},
2574  STAmount{USD, UINT64_C(13'012'92609877019), -11},
2575  ammTokens));
2576  // Payment with the trading fee
2577  env(pay(alice, carol, XRP(100)), path(~XRP), sendmax(USD(110)));
2578  env.close();
2579  // alice pays ~1.011USD in fees, which is ~10 times more
2580  // than carol's fee
2581  // 100.099431529USD swapped in for 100XRP
2582  BEAST_EXPECT(ammAlice.expectBalances(
2583  XRPAmount{13'000'000'668},
2584  STAmount{USD, UINT64_C(13'114'03663047264), -11},
2585  ammTokens));
2586  // Auction slot expired, no discounted fee
2587  env.close(seconds(TOTAL_TIME_SLOT_SECS + 1));
2588  // clock is parent's based
2589  env.close();
2590  BEAST_EXPECT(
2591  env.balance(carol, USD) ==
2592  STAmount(USD, UINT64_C(29'399'00572620545), -11));
2593  ammTokens = ammAlice.getLPTokensBalance();
2594  for (int i = 0; i < 10; ++i)
2595  {
2596  auto const tokens = ammAlice.deposit(carol, USD(100));
2597  ammAlice.withdraw(carol, tokens, USD(0));
2598  }
2599  // carol pays ~9.94USD in fees, which is ~10 times more in
2600  // trading fees vs discounted fee.
2601  BEAST_EXPECT(
2602  env.balance(carol, USD) ==
2603  STAmount(USD, UINT64_C(29'389'06197177128), -11));
2604  BEAST_EXPECT(ammAlice.expectBalances(
2605  XRPAmount{13'000'000'668},
2606  STAmount{USD, UINT64_C(13'123'98038490681), -11},
2607  ammTokens));
2608  env(pay(carol, bob, USD(100)), path(~USD), sendmax(XRP(110)));
2609  env.close();
2610  // carol pays ~1.008XRP in trading fee, which is
2611  // ~10 times more than the discounted fee.
2612  // 99.815876XRP is swapped in for 100USD
2613  BEAST_EXPECT(ammAlice.expectBalances(
2614  XRPAmount(13'100'824'790),
2615  STAmount{USD, UINT64_C(13'023'98038490681), -11},
2616  ammTokens));
2617  },
2618  std::nullopt,
2619  1'000);
2620 
2621  // Bid tiny amount
2622  testAMM([&](AMM& ammAlice, Env&) {
2623  // Bid a tiny amount
2624  auto const tiny = Number{STAmount::cMinValue, STAmount::cMinOffset};
2625  ammAlice.bid(alice, IOUAmount{tiny});
2626  // Auction slot purchase price is equal to the tiny amount
2627  // since the minSlotPrice is 0 with no trading fee.
2628  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny}));
2629  // The purchase price is too small to affect the total tokens
2630  BEAST_EXPECT(ammAlice.expectBalances(
2631  XRP(10'000), USD(10'000), ammAlice.tokens()));
2632  // Bid the tiny amount
2633  ammAlice.bid(
2634  alice, IOUAmount{STAmount::cMinValue, STAmount::cMinOffset});
2635  // Pay slightly higher price
2636  BEAST_EXPECT(ammAlice.expectAuctionSlot(
2637  0, 0, IOUAmount{tiny * Number{105, -2}}));
2638  // The purchase price is still too small to affect the total tokens
2639  BEAST_EXPECT(ammAlice.expectBalances(
2640  XRP(10'000), USD(10'000), ammAlice.tokens()));
2641  });
2642 
2643  // Reset auth account
2644  testAMM([&](AMM& ammAlice, Env& env) {
2645  ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {carol});
2646  BEAST_EXPECT(ammAlice.expectAuctionSlot({carol}));
2647  ammAlice.bid(alice, IOUAmount{100});
2648  BEAST_EXPECT(ammAlice.expectAuctionSlot({}));
2649  Account bob("bob");
2650  Account dan("dan");
2651  fund(env, {bob, dan}, XRP(1'000));
2652  ammAlice.bid(alice, IOUAmount{100}, std::nullopt, {bob, dan});
2653  BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan}));
2654  });
2655 
2656  // Bid all tokens, still own the slot and trade at a discount
2657  {
2658  Env env(*this);
2659  fund(env, gw, {alice, bob}, XRP(2'000), {USD(2'000)});
2660  AMM amm(env, gw, XRP(1'000), USD(1'010), false, 1'000);
2661  auto const lpIssue = amm.lptIssue();
2662  env.trust(STAmount{lpIssue, 500}, alice);
2663  env.trust(STAmount{lpIssue, 50}, bob);
2664  env(pay(gw, alice, STAmount{lpIssue, 500}));
2665  env(pay(gw, bob, STAmount{lpIssue, 50}));
2666  // Alice doesn't have anymore lp tokens
2667  amm.bid(alice, 500);
2668  BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500}));
2669  BEAST_EXPECT(expectLine(env, alice, STAmount{lpIssue, 0}));
2670  // But trades with the discounted fee since she still owns the slot.
2671  // Alice pays 10011 drops in fees
2672  env(pay(alice, bob, USD(10)), path(~USD), sendmax(XRP(11)));
2673  BEAST_EXPECT(amm.expectBalances(
2674  XRPAmount{1'010'010'011},
2675  USD(1'000),
2676  IOUAmount{1'004'487'562112089, -9}));
2677  // Bob pays the full fee ~0.1USD
2678  env(pay(bob, alice, XRP(10)), path(~XRP), sendmax(USD(11)));
2679  BEAST_EXPECT(amm.expectBalances(
2680  XRPAmount{1'000'010'011},
2681  STAmount{USD, UINT64_C(1'010'10090898081), -11},
2682  IOUAmount{1'004'487'562112089, -9}));
2683  }
2684  }
2685 
2686  void
2687  testInvalidAMMPayment()
2688  {
2689  testcase("Invalid AMM Payment");
2690  using namespace jtx;
2691  using namespace std::chrono;
2692  using namespace std::literals::chrono_literals;
2693 
2694  // Can't pay into AMM account.
2695  // Can't pay out since there is no keys
2696  for (auto const& acct : {gw, alice})
2697  {
2698  {
2699  Env env(*this);
2700  fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
2701  // XRP balance is below reserve
2702  AMM ammAlice(env, acct, XRP(10), USD(10));
2703  // Pay below reserve
2704  env(pay(carol, ammAlice.ammAccount(), XRP(10)),
2705  ter(tecNO_PERMISSION));
2706  // Pay above reserve
2707  env(pay(carol, ammAlice.ammAccount(), XRP(300)),
2708  ter(tecNO_PERMISSION));
2709  // Pay IOU
2710  env(pay(carol, ammAlice.ammAccount(), USD(10)),
2711  ter(tecNO_PERMISSION));
2712  }
2713  {
2714  Env env(*this);
2715  fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
2716  // XRP balance is above reserve
2717  AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
2718  // Pay below reserve
2719  env(pay(carol, ammAlice.ammAccount(), XRP(10)),
2721  // Pay above reserve
2722  env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)),
2724  }
2725  }
2726 
2727  // Can't pay into AMM with escrow.
2728  testAMM([&](AMM& ammAlice, Env& env) {
2729  env(escrow(carol, ammAlice.ammAccount(), XRP(1)),
2730  condition(cb1),
2731  finish_time(env.now() + 1s),
2732  cancel_time(env.now() + 2s),
2733  fee(1'500),
2734  ter(tecNO_PERMISSION));
2735  });
2736 
2737  // Can't pay into AMM with paychan.
2738  testAMM([&](AMM& ammAlice, Env& env) {
2739  auto const pk = carol.pk();
2740  auto const settleDelay = 100s;
2741  NetClock::time_point const cancelAfter =
2742  env.current()->info().parentCloseTime + 200s;
2743  env(create(
2744  carol,
2745  ammAlice.ammAccount(),
2746  XRP(1'000),
2747  settleDelay,
2748  pk,
2749  cancelAfter),
2750  ter(tecNO_PERMISSION));
2751  });
2752 
2753  // Pay amounts close to one side of the pool
2754  testAMM(
2755  [&](AMM& ammAlice, Env& env) {
2756  // Can't consume whole pool
2757  env(pay(alice, carol, USD(100)),
2758  path(~USD),
2759  sendmax(XRP(1'000'000'000)),
2760  ter(tecPATH_PARTIAL));
2761  env(pay(alice, carol, XRP(100)),
2762  path(~XRP),
2763  sendmax(USD(1'000'000'000)),
2764  ter(tecPATH_PARTIAL));
2765  // Overflow
2766  env(pay(alice,
2767  carol,
2768  STAmount{USD, UINT64_C(99'999999999), -9}),
2769  path(~USD),
2770  sendmax(XRP(1'000'000'000)),
2771  ter(tecPATH_PARTIAL));
2772  env(pay(alice,
2773  carol,
2774  STAmount{USD, UINT64_C(999'99999999), -8}),
2775  path(~USD),
2776  sendmax(XRP(1'000'000'000)),
2777  ter(tecPATH_PARTIAL));
2778  env(pay(alice, carol, STAmount{xrpIssue(), 99'999'999}),
2779  path(~XRP),
2780  sendmax(USD(1'000'000'000)),
2781  ter(tecPATH_PARTIAL));
2782  // Sender doesn't have enough funds
2783  env(pay(alice, carol, USD(99.99)),
2784  path(~USD),
2785  sendmax(XRP(1'000'000'000)),
2786  ter(tecPATH_PARTIAL));
2787  env(pay(alice, carol, STAmount{xrpIssue(), 99'990'000}),
2788  path(~XRP),
2789  sendmax(USD(1'000'000'000)),
2790  ter(tecPATH_PARTIAL));
2791  },
2792  {{XRP(100), USD(100)}});
2793 
2794  // Globally frozen
2795  testAMM([&](AMM& ammAlice, Env& env) {
2796  env(fset(gw, asfGlobalFreeze));
2797  env.close();
2798  env(pay(alice, carol, USD(1)),
2799  path(~USD),
2801  sendmax(XRP(10)),
2802  ter(tecPATH_DRY));
2803  env(pay(alice, carol, XRP(1)),
2804  path(~XRP),
2806  sendmax(USD(10)),
2807  ter(tecPATH_DRY));
2808  });
2809 
2810  // Individually frozen AMM
2811  testAMM([&](AMM& ammAlice, Env& env) {
2812  env(trust(
2813  gw,
2814  STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0},
2815  tfSetFreeze));
2816  env.close();
2817  env(pay(alice, carol, USD(1)),
2818  path(~USD),
2820  sendmax(XRP(10)),
2821  ter(tecPATH_DRY));
2822  env(pay(alice, carol, XRP(1)),
2823  path(~XRP),
2825  sendmax(USD(10)),
2826  ter(tecPATH_DRY));
2827  });
2828 
2829  // Individually frozen accounts
2830  testAMM([&](AMM& ammAlice, Env& env) {
2831  env(trust(gw, carol["USD"](0), tfSetFreeze));
2832  env(trust(gw, alice["USD"](0), tfSetFreeze));
2833  env.close();
2834  env(pay(alice, carol, XRP(1)),
2835  path(~XRP),
2836  sendmax(USD(10)),
2838  ter(tecPATH_DRY));
2839  });
2840  }
2841 
2842  void
2844  {
2845  testcase("Basic Payment");
2846  using namespace jtx;
2847 
2848  // Payment 100USD for 100XRP.
2849  // Force one path with tfNoRippleDirect.
2850  testAMM(
2851  [&](AMM& ammAlice, Env& env) {
2852  env.fund(jtx::XRP(30'000), bob);
2853  env.close();
2854  env(pay(bob, carol, USD(100)),
2855  path(~USD),
2856  sendmax(XRP(100)),
2857  txflags(tfNoRippleDirect));
2858  env.close();
2859  BEAST_EXPECT(ammAlice.expectBalances(
2860  XRP(10'100), USD(10'000), ammAlice.tokens()));
2861  // Initial balance 30,000 + 100
2862  BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
2863  // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
2864  BEAST_EXPECT(expectLedgerEntryRoot(
2865  env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
2866  },
2867  {{XRP(10'000), USD(10'100)}});
2868 
2869  // Payment 100USD for 100XRP, use default path.
2870  testAMM(
2871  [&](AMM& ammAlice, Env& env) {
2872  env.fund(jtx::XRP(30'000), bob);
2873  env.close();
2874  env(pay(bob, carol, USD(100)), sendmax(XRP(100)));
2875  env.close();
2876  BEAST_EXPECT(ammAlice.expectBalances(
2877  XRP(10'100), USD(10'000), ammAlice.tokens()));
2878  // Initial balance 30,000 + 100
2879  BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
2880  // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
2881  BEAST_EXPECT(expectLedgerEntryRoot(
2882  env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
2883  },
2884  {{XRP(10'000), USD(10'100)}});
2885 
2886  // This payment is identical to above. While it has
2887  // both default path and path, activeStrands has one path.
2888  testAMM(
2889  [&](AMM& ammAlice, Env& env) {
2890  env.fund(jtx::XRP(30'000), bob);
2891  env.close();
2892  env(pay(bob, carol, USD(100)), path(~USD), sendmax(XRP(100)));
2893  env.close();
2894  BEAST_EXPECT(ammAlice.expectBalances(
2895  XRP(10'100), USD(10'000), ammAlice.tokens()));
2896  // Initial balance 30,000 + 100
2897  BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
2898  // Initial balance 30,000 - 100(sendmax) - 10(tx fee)
2899  BEAST_EXPECT(expectLedgerEntryRoot(
2900  env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
2901  },
2902  {{XRP(10'000), USD(10'100)}});
2903 
2904  // Payment with limitQuality set.
2905  testAMM(
2906  [&](AMM& ammAlice, Env& env) {
2907  env.fund(jtx::XRP(30'000), bob);
2908  env.close();
2909  // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
2910  // would have been sent has it not been for limitQuality.
2911  env(pay(bob, carol, USD(100)),
2912  path(~USD),
2913  sendmax(XRP(100)),
2914  txflags(
2916  env.close();
2917  BEAST_EXPECT(ammAlice.expectBalances(
2918  XRP(10'010), USD(10'000), ammAlice.tokens()));
2919  // Initial balance 30,000 + 10(limited by limitQuality)
2920  BEAST_EXPECT(expectLine(env, carol, USD(30'010)));
2921  // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx
2922  // fee)
2923  BEAST_EXPECT(expectLedgerEntryRoot(
2924  env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
2925 
2926  // Fails because of limitQuality. Would have sent
2927  // ~98.91USD/110XRP has it not been for limitQuality.
2928  env(pay(bob, carol, USD(100)),
2929  path(~USD),
2930  sendmax(XRP(100)),
2931  txflags(
2933  ter(tecPATH_DRY));
2934  env.close();
2935  },
2936  {{XRP(10'000), USD(10'010)}});
2937 
2938  // Payment with limitQuality and transfer fee set.
2939  testAMM(
2940  [&](AMM& ammAlice, Env& env) {
2941  env(rate(gw, 1.1));
2942  env.close();
2943  env.fund(jtx::XRP(30'000), bob);
2944  env.close();
2945  // Pays 10USD for 10XRP. A larger payment of ~99.11USD/100XRP
2946  // would have been sent has it not been for limitQuality and
2947  // the transfer fee.
2948  env(pay(bob, carol, USD(100)),
2949  path(~USD),
2950  sendmax(XRP(110)),
2951  txflags(
2952  tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
2953  env.close();
2954  BEAST_EXPECT(ammAlice.expectBalances(
2955  XRP(10'010), USD(10'000), ammAlice.tokens()));
2956  // 10USD - 10% transfer fee
2957  BEAST_EXPECT(expectLine(
2958  env,
2959  carol,
2960  STAmount{USD, UINT64_C(30'009'09090909091), -11}));
2961  BEAST_EXPECT(expectLedgerEntryRoot(
2962  env, bob, XRP(30'000) - XRP(10) - txfee(env, 1)));
2963  },
2964  {{XRP(10'000), USD(10'010)}});
2965 
2966  // Fail when partial payment is not set.
2967  testAMM(
2968  [&](AMM& ammAlice, Env& env) {
2969  env.fund(jtx::XRP(30'000), bob);
2970  env.close();
2971  env(pay(bob, carol, USD(100)),
2972  path(~USD),
2973  sendmax(XRP(100)),
2974  txflags(tfNoRippleDirect),
2975  ter(tecPATH_PARTIAL));
2976  },
2977  {{XRP(10'000), USD(10'000)}});
2978 
2979  // Non-default path (with AMM) has a better quality than default path.
2980  // The max possible liquidity is taken out of non-default
2981  // path ~29.9XRP/29.9EUR, ~29.9EUR/~29.99USD. The rest
2982  // is taken from the offer.
2983  {
2984  Env env(*this);
2985  fund(
2986  env, gw, {alice, carol}, {USD(30'000), EUR(30'000)}, Fund::All);
2987  env.close();
2988  env.fund(XRP(1'000), bob);
2989  env.close();
2990  auto ammEUR_XRP = AMM(env, alice, XRP(10'000), EUR(10'000));
2991  auto ammUSD_EUR = AMM(env, alice, EUR(10'000), USD(10'000));
2992  env(offer(alice, XRP(101), USD(100)), txflags(tfPassive));
2993  env.close();
2994  env(pay(bob, carol, USD(100)),
2995  path(~EUR, ~USD),
2996  sendmax(XRP(102)),
2998  env.close();
2999  BEAST_EXPECT(ammEUR_XRP.expectBalances(
3000  XRPAmount(10'030'082'730),
3001  STAmount(EUR, UINT64_C(9'970'007498125468), -12),
3002  ammEUR_XRP.tokens()));
3003  BEAST_EXPECT(ammUSD_EUR.expectBalances(
3004  STAmount(USD, UINT64_C(9'970'097277662122), -12),
3005  STAmount(EUR, UINT64_C(10'029'99250187452), -11),
3006  ammUSD_EUR.tokens()));
3007  BEAST_EXPECT(expectOffers(
3008  env,
3009  alice,
3010  1,
3011  {{Amounts{
3012  XRPAmount(30'201'749),
3013  STAmount(USD, UINT64_C(29'90272233787818), -14)}}}));
3014  // Initial 30,000 + 100
3015  BEAST_EXPECT(expectLine(env, carol, STAmount{USD, 30'100}));
3016  // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee)
3017  BEAST_EXPECT(expectLedgerEntryRoot(
3018  env,
3019  bob,
3020  XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} -
3021  txfee(env, 1)));
3022  }
3023 
3024  // Default path (with AMM) has a better quality than a non-default path.
3025  // The max possible liquidity is taken out of default
3026  // path ~49XRP/49USD. The rest is taken from the offer.
3027  testAMM([&](AMM& ammAlice, Env& env) {
3028  env.fund(XRP(1'000), bob);
3029  env.close();
3030  env.trust(EUR(2'000), alice);
3031  env.close();
3032  env(pay(gw, alice, EUR(1'000)));
3033  env(offer(alice, XRP(101), EUR(100)), txflags(tfPassive));
3034  env.close();
3035  env(offer(alice, EUR(100), USD(100)), txflags(tfPassive));
3036  env.close();
3037  env(pay(bob, carol, USD(100)),
3038  path(~EUR, ~USD),
3039  sendmax(XRP(102)),
3040  txflags(tfPartialPayment));
3041  env.close();
3042  BEAST_EXPECT(ammAlice.expectBalances(
3043  XRPAmount(10'050'238'637),
3044  STAmount(USD, UINT64_C(9'950'01249687578), -11),
3045  ammAlice.tokens()));
3046  BEAST_EXPECT(expectOffers(
3047  env,
3048  alice,
3049  2,
3050  {{Amounts{
3051  XRPAmount(50'487'378),
3052  STAmount(EUR, UINT64_C(49'98750312422), -11)},
3053  Amounts{
3054  STAmount(EUR, UINT64_C(49'98750312422), -11),
3055  STAmount(USD, UINT64_C(49'98750312422), -11)}}}));
3056  // Initial 30,000 + 99.99999999999
3057  BEAST_EXPECT(expectLine(
3058  env, carol, STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3059  // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx
3060  // fee)
3061  BEAST_EXPECT(expectLedgerEntryRoot(
3062  env,
3063  bob,
3064  XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} -
3065  txfee(env, 1)));
3066  });
3067 
3068  // Default path with AMM and Order Book offer. AMM is consumed first,
3069  // remaining amount is consumed by the offer.
3070  testAMM(
3071  [&](AMM& ammAlice, Env& env) {
3072  fund(env, gw, {bob}, {USD(100)}, Fund::Acct);
3073  env.close();
3074  env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
3075  env.close();
3076  env(pay(alice, carol, USD(200)),
3077  sendmax(XRP(200)),
3078  txflags(tfPartialPayment));
3079  env.close();
3080  BEAST_EXPECT(ammAlice.expectBalances(
3081  XRP(10'100), USD(10'000), ammAlice.tokens()));
3082  // Initial 30,000 + 200
3083  BEAST_EXPECT(expectLine(env, carol, USD(30'200)));
3084  // Initial 30,000 - 10000(AMM pool LP) - 100(AMM offer) -
3085  // - 100(offer) - 10(tx fee) - one reserve
3086  BEAST_EXPECT(expectLedgerEntryRoot(
3087  env,
3088  alice,
3089  XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) -
3090  ammCrtFee(env) - txfee(env, 1)));
3091  BEAST_EXPECT(expectOffers(env, bob, 0));
3092  },
3093  {{XRP(10'000), USD(10'100)}});
3094 
3095  // Default path with AMM and Order Book offer.
3096  // Order Book offer is consumed first.
3097  // Remaining amount is consumed by AMM.
3098  {
3099  Env env(*this);
3100  fund(env, gw, {alice, bob, carol}, XRP(20'000), {USD(2'000)});
3101  env(offer(bob, XRP(50), USD(150)), txflags(tfPassive));
3102  AMM ammAlice(env, alice, XRP(1'000), USD(1'050));
3103  env(pay(alice, carol, USD(200)),
3104  sendmax(XRP(200)),
3105  txflags(tfPartialPayment));
3106  BEAST_EXPECT(ammAlice.expectBalances(
3107  XRP(1'050), USD(1'000), ammAlice.tokens()));
3108  BEAST_EXPECT(expectLine(env, carol, USD(2'200)));
3109  BEAST_EXPECT(expectOffers(env, bob, 0));
3110  }
3111 
3112  // Offer crossing XRP/IOU
3113  testAMM(
3114  [&](AMM& ammAlice, Env& env) {
3115  fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct);
3116  env.close();
3117  env(offer(bob, USD(100), XRP(100)));
3118  env.close();
3119  BEAST_EXPECT(ammAlice.expectBalances(
3120  XRP(10'100), USD(10'000), ammAlice.tokens()));
3121  // Initial 1,000 + 100
3122  BEAST_EXPECT(expectLine(env, bob, USD(1'100)));
3123  // Initial 30,000 - 100(offer) - 10(tx fee)
3124  BEAST_EXPECT(expectLedgerEntryRoot(
3125  env, bob, XRP(30'000) - XRP(100) - txfee(env, 1)));
3126  BEAST_EXPECT(expectOffers(env, bob, 0));
3127  },
3128  {{XRP(10'000), USD(10'100)}});
3129 
3130  // Offer crossing IOU/IOU and transfer rate
3131  testAMM(
3132  [&](AMM& ammAlice, Env& env) {
3133  env(rate(gw, 1.25));
3134  env.close();
3135  env(offer(carol, EUR(100), GBP(100)));
3136  env.close();
3137  // No transfer fee
3138  BEAST_EXPECT(ammAlice.expectBalances(
3139  GBP(1'100), EUR(1'000), ammAlice.tokens()));
3140  // Initial 30,000 - 100(offer) - 25% transfer fee
3141  BEAST_EXPECT(expectLine(env, carol, GBP(29'875)));
3142  // Initial 30,000 + 100(offer)
3143  BEAST_EXPECT(expectLine(env, carol, EUR(30'100)));
3144  BEAST_EXPECT(expectOffers(env, bob, 0));
3145  },
3146  {{GBP(1'000), EUR(1'100)}});
3147 
3148  // Payment and transfer fee
3149  // Scenario:
3150  // Bob sends 125GBP to pay 80EUR to Carol
3151  // Payment execution:
3152  // bob's 125GBP/1.25 = 100GBP
3153  // 100GBP/100EUR AMM offer
3154  // 100EUR/1.25 = 80EUR paid to carol
3155  testAMM(
3156  [&](AMM& ammAlice, Env& env) {
3157  fund(env, gw, {bob}, {GBP(200), EUR(200)}, Fund::Acct);
3158  env(rate(gw, 1.25));
3159  env.close();
3160  env(pay(bob, carol, EUR(100)),
3161  path(~EUR),
3162  sendmax(GBP(125)),
3163  txflags(tfPartialPayment));
3164  env.close();
3165  BEAST_EXPECT(ammAlice.expectBalances(
3166  GBP(1'100), EUR(1'000), ammAlice.tokens()));
3167  BEAST_EXPECT(expectLine(env, bob, GBP(75)));
3168  BEAST_EXPECT(expectLine(env, carol, EUR(30'080)));
3169  },
3170  {{GBP(1'000), EUR(1'100)}});
3171 
3172  // Payment and transfer fee, multiple steps
3173  // Scenario:
3174  // Dan's offer 200CAN/200GBP
3175  // AMM 1000GBP/10125EUR
3176  // Ed's offer 200EUR/200USD
3177  // Bob sends 195.3125CAN to pay 100USD to Carol
3178  // Payment execution:
3179  // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer
3180  // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer
3181  // 125GBP/125EUR 125EUR/1.25 = 100EUR -> ed's offer
3182  // 100EUR/100USD 100USD/1.25 = 80USD paid to carol
3183  testAMM(
3184  [&](AMM& ammAlice, Env& env) {
3185  Account const dan("dan");
3186  Account const ed("ed");
3187  auto const CAN = gw["CAN"];
3188  fund(env, gw, {dan}, {CAN(200), GBP(200)}, Fund::Acct);
3189  fund(env, gw, {ed}, {EUR(200), USD(200)}, Fund::Acct);
3190  fund(env, gw, {bob}, {CAN(195.3125)}, Fund::Acct);
3191  env(trust(carol, USD(100)));
3192  env(rate(gw, 1.25));
3193  env.close();
3194  env(offer(dan, CAN(200), GBP(200)));
3195  env(offer(ed, EUR(200), USD(200)));
3196  env.close();
3197  env(pay(bob, carol, USD(100)),
3198  path(~GBP, ~EUR, ~USD),
3199  sendmax(CAN(195.3125)),
3200  txflags(tfPartialPayment));
3201  env.close();
3202  BEAST_EXPECT(expectLine(env, bob, CAN(0)));
3203  BEAST_EXPECT(expectLine(env, dan, CAN(356.25), GBP(43.75)));
3204  BEAST_EXPECT(ammAlice.expectBalances(
3205  GBP(10'125), EUR(10'000), ammAlice.tokens()));
3206  BEAST_EXPECT(expectLine(env, ed, EUR(300), USD(100)));
3207  BEAST_EXPECT(expectLine(env, carol, USD(80)));
3208  },
3209  {{GBP(10'000), EUR(10'125)}});
3210 
3211  // Pay amounts close to one side of the pool
3212  testAMM(
3213  [&](AMM& ammAlice, Env& env) {
3214  env(pay(alice, carol, USD(99.99)),
3215  path(~USD),
3216  sendmax(XRP(1)),
3217  txflags(tfPartialPayment),
3218  ter(tesSUCCESS));
3219  env(pay(alice, carol, USD(100)),
3220  path(~USD),
3221  sendmax(XRP(1)),
3222  txflags(tfPartialPayment),
3223  ter(tesSUCCESS));
3224  env(pay(alice, carol, XRP(100)),
3225  path(~XRP),
3226  sendmax(USD(1)),
3227  txflags(tfPartialPayment),
3228  ter(tesSUCCESS));
3229  env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}),
3230  path(~XRP),
3231  sendmax(USD(1)),
3232  txflags(tfPartialPayment),
3233  ter(tesSUCCESS));
3234  },
3235  {{XRP(100), USD(100)}});
3236 
3237  // Multiple paths/steps
3238  {
3239  Env env(*this);
3240  auto const ETH = gw["ETH"];
3241  fund(
3242  env,
3243  gw,
3244  {alice},
3245  XRP(100'000),
3246  {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
3247  fund(env, gw, {carol, bob}, XRP(1'000), {USD(200)}, Fund::Acct);
3248  AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
3249  AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
3250  AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
3251  AMM xrp_usd(env, alice, XRP(10'150), USD(10'200));
3252  AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
3253  AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
3254  AMM eur_usd(env, alice, EUR(10'100), USD(10'000));
3255  env(pay(bob, carol, USD(100)),
3256  path(~EUR, ~BTC, ~USD),
3257  path(~USD),
3258  path(~ETH, ~EUR, ~USD),
3259  sendmax(XRP(200)));
3260  // XRP-ETH-EUR-USD
3261  // This path provides ~26.06USD/26.2XRP
3262  BEAST_EXPECT(xrp_eth.expectBalances(
3263  XRPAmount(10'026'208'900),
3264  STAmount{ETH, UINT64_C(10'073'65779244494), -11},
3265  xrp_eth.tokens()));
3266  BEAST_EXPECT(eth_eur.expectBalances(
3267  STAmount{ETH, UINT64_C(10'926'34220755506), -11},
3268  STAmount{EUR, UINT64_C(10'973'54232078752), -11},
3269  eth_eur.tokens()));
3270  BEAST_EXPECT(eur_usd.expectBalances(
3271  STAmount{EUR, UINT64_C(10'126'45767921248), -11},
3272  STAmount{USD, UINT64_C(9'973'93151712086), -11},
3273  eur_usd.tokens()));
3274 
3275  // XRP-USD path
3276  // This path provides ~73.9USD/74.1XRP
3277  BEAST_EXPECT(xrp_usd.expectBalances(
3278  XRPAmount(10'224'106'246),
3279  STAmount{USD, UINT64_C(10'126'06848287914), -11},
3280  xrp_usd.tokens()));
3281 
3282  // XRP-EUR-BTC-USD
3283  // This path doesn't provide any liquidity due to how
3284  // offers are generated in multi-path. Analytical solution
3285  // shows a different distribution:
3286  // XRP-EUR-BTC-USD 11.6USD/11.64XRP, XRP-USD 60.7USD/60.8XRP,
3287  // XRP-ETH-EUR-USD 27.6USD/27.6XRP
3288  BEAST_EXPECT(xrp_eur.expectBalances(
3289  XRP(10'100), EUR(10'000), xrp_eur.tokens()));
3290  BEAST_EXPECT(eur_btc.expectBalances(
3291  EUR(10'000), BTC(10'200), eur_btc.tokens()));
3292  BEAST_EXPECT(btc_usd.expectBalances(
3293  BTC(10'100), USD(10'000), btc_usd.tokens()));
3294 
3295  BEAST_EXPECT(expectLine(env, carol, USD(300)));
3296  }
3297 
3298  // Dependent AMM
3299  {
3300  Env env(*this);
3301  auto const ETH = gw["ETH"];
3302  fund(
3303  env,
3304  gw,
3305  {alice},
3306  XRP(40'000),
3307  {EUR(50'000), BTC(50'000), ETH(50'000), USD(50'000)});
3308  fund(env, gw, {carol, bob}, XRP(1000), {USD(200)}, Fund::Acct);
3309  AMM xrp_eur(env, alice, XRP(10'100), EUR(10'000));
3310  AMM eur_btc(env, alice, EUR(10'000), BTC(10'200));
3311  AMM btc_usd(env, alice, BTC(10'100), USD(10'000));
3312  AMM xrp_eth(env, alice, XRP(10'000), ETH(10'100));
3313  AMM eth_eur(env, alice, ETH(10'900), EUR(11'000));
3314  env(pay(bob, carol, USD(100)),
3315  path(~EUR, ~BTC, ~USD),
3316  path(~ETH, ~EUR, ~BTC, ~USD),
3317  sendmax(XRP(200)));
3318  // XRP-EUR-BTC-USD path provides ~17.8USD/~18.7XRP
3319  // XRP-ETH-EUR-BTC-USD path provides ~82.2USD/82.4XRP
3320  BEAST_EXPECT(xrp_eur.expectBalances(
3321  XRPAmount(10'118'738'472),
3322  STAmount{EUR, UINT64_C(9'981'544436337968), -12},
3323  xrp_eur.tokens()));
3324  BEAST_EXPECT(eur_btc.expectBalances(
3325  STAmount{EUR, UINT64_C(10'101'16096785173), -11},
3326  STAmount{BTC, UINT64_C(10'097'91426968066), -11},
3327  eur_btc.tokens()));
3328  BEAST_EXPECT(btc_usd.expectBalances(
3329  STAmount{BTC, UINT64_C(10'202'08573031934), -11},
3330  USD(9'900),
3331  btc_usd.tokens()));
3332  BEAST_EXPECT(xrp_eth.expectBalances(
3333  XRPAmount(10'082'446'397),
3334  STAmount{ETH, UINT64_C(10'017'41072778012), -11},
3335  xrp_eth.tokens()));
3336  BEAST_EXPECT(eth_eur.expectBalances(
3337  STAmount{ETH, UINT64_C(10'982'58927221988), -11},
3338  STAmount{EUR, UINT64_C(10'917'2945958103), -10},
3339  eth_eur.tokens()));
3340  BEAST_EXPECT(expectLine(env, carol, USD(300)));
3341  }
3342 
3343  // AMM offers limit
3344  // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit.
3345  testAMM([&](AMM& ammAlice, Env& env) {
3346  env.fund(XRP(1'000), bob);
3347  fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
3348  env(trust(alice, EUR(200)));
3349  for (int i = 0; i < 30; ++i)
3350  env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
3351  // This is worse quality offer than 30 offers above.
3352  // It will not be consumed because of AMM offers limit.
3353  env(offer(alice, EUR(140), XRP(100)));
3354  env(pay(bob, carol, USD(100)),
3355  path(~XRP, ~USD),
3356  sendmax(EUR(400)),
3357  txflags(tfPartialPayment | tfNoRippleDirect));
3358  // Carol gets ~29.91USD because of the AMM offers limit
3359  BEAST_EXPECT(ammAlice.expectBalances(
3360  XRP(10'030),
3361  STAmount{USD, UINT64_C(9'970'089730807577), -12},
3362  ammAlice.tokens()));
3363  BEAST_EXPECT(expectLine(
3364  env, carol, STAmount{USD, UINT64_C(30'029'91026919241), -11}));
3365  BEAST_EXPECT(expectOffers(env, alice, 1, {{{EUR(140), XRP(100)}}}));
3366  });
3367  // This payment is fulfilled
3368  testAMM([&](AMM& ammAlice, Env& env) {
3369  env.fund(XRP(1'000), bob);
3370  fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly);
3371  env(trust(alice, EUR(200)));
3372  for (int i = 0; i < 29; ++i)
3373  env(offer(alice, EUR(1.0 + 0.01 * i), XRP(1)));
3374  // This is worse quality offer than 30 offers above.
3375  // It will not be consumed because of AMM offers limit.
3376  env(offer(alice, EUR(140), XRP(100)));
3377  env(pay(bob, carol, USD(100)),
3378  path(~XRP, ~USD),
3379  sendmax(EUR(400)),
3380  txflags(tfPartialPayment | tfNoRippleDirect));
3381  BEAST_EXPECT(ammAlice.expectBalances(
3382  XRPAmount{10'101'010'102}, USD(9'900), ammAlice.tokens()));
3383  // Carol gets ~100USD
3384  BEAST_EXPECT(expectLine(
3385  env, carol, STAmount{USD, UINT64_C(30'099'99999999999), -11}));
3386  BEAST_EXPECT(expectOffers(
3387  env,
3388  alice,
3389  1,
3390  {{{STAmount{EUR, 39'1858572, -7}, XRPAmount{27'989'898}}}}));
3391  });
3392 
3393  // Offer crossing with AMM and another offer. AMM has a better
3394  // quality and is consumed first.
3395  {
3396  Env env(*this);
3397  fund(env, gw, {alice, carol, bob}, XRP(30'000), {USD(30'000)});
3398  env(offer(bob, XRP(100), USD(100.001)));
3399  AMM ammAlice(env, alice, XRP(10'000), USD(10'100));
3400  env(offer(carol, USD(100), XRP(100)));
3401  BEAST_EXPECT(ammAlice.expectBalances(
3402  XRPAmount{10'049'825'373},
3403  STAmount{USD, UINT64_C(10'049'92586949302), -11},
3404  ammAlice.tokens()));
3405  BEAST_EXPECT(expectOffers(
3406  env,
3407  bob,
3408  1,
3409  {{{XRPAmount{50'074'629},
3410  STAmount{USD, UINT64_C(50'07513050698), -11}}}}));
3411  BEAST_EXPECT(expectLine(env, carol, USD(30'100)));
3412  }
3413 
3414  // Individually frozen account
3415  testAMM([&](AMM& ammAlice, Env& env) {
3416  env(trust(gw, carol["USD"](0), tfSetFreeze));
3417  env(trust(gw, alice["USD"](0), tfSetFreeze));
3418  env.close();
3419  env(pay(alice, carol, USD(1)),
3420  path(~USD),
3421  sendmax(XRP(10)),
3422  txflags(tfNoRippleDirect | tfPartialPayment),
3423  ter(tesSUCCESS));
3424  });
3425  }
3426 
3427  void
3428  testAMMTokens()
3429  {
3430  testcase("AMM Tokens");
3431  using namespace jtx;
3432 
3433  // Offer crossing with AMM LPTokens and XRP.
3434  testAMM([&](AMM& ammAlice, Env& env) {
3435  auto const token1 = ammAlice.lptIssue();
3436  auto priceXRP = withdrawByTokens(
3437  STAmount{XRPAmount{10'000'000'000}},
3438  STAmount{token1, 10'000'000},
3439  STAmount{token1, 5'000'000},
3440  0);
3441  // Carol places an order to buy LPTokens
3442  env(offer(carol, STAmount{token1, 5'000'000}, priceXRP));
3443  // Alice places an order to sell LPTokens
3444  env(offer(alice, priceXRP, STAmount{token1, 5'000'000}));
3445  // Pool's LPTokens balance doesn't change
3446  BEAST_EXPECT(ammAlice.expectBalances(
3447  XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
3448  // Carol is Liquidity Provider
3449  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000}));
3450  BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
3451  // Carol votes
3452  ammAlice.vote(carol, 1'000);
3453  BEAST_EXPECT(ammAlice.expectTradingFee(500));
3454  ammAlice.vote(carol, 0);
3455  BEAST_EXPECT(ammAlice.expectTradingFee(0));
3456  // Carol bids
3457  ammAlice.bid(carol, 100);
3458  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900}));
3459  BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100}));
3460  BEAST_EXPECT(accountBalance(env, carol) == "22499999960");
3461  priceXRP = withdrawByTokens(
3462  STAmount{XRPAmount{10'000'000'000}},
3463  STAmount{token1, 9'999'900},
3464  STAmount{token1, 4'999'900},
3465  0);
3466  // Carol withdraws
3467  ammAlice.withdrawAll(carol, XRP(0));
3468  BEAST_EXPECT(accountBalance(env, carol) == "29999949949");
3469  BEAST_EXPECT(ammAlice.expectBalances(
3470  XRPAmount{10'000'000'000} - priceXRP,
3471  USD(10'000),
3472  IOUAmount{5'000'000}));
3473  BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000}));
3474  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
3475  });
3476 
3477  // Offer crossing with two AMM LPTokens.
3478  testAMM([&](AMM& ammAlice, Env& env) {
3479  ammAlice.deposit(carol, 1'000'000);
3480  fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly);
3481  AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000));
3482  ammAlice1.deposit(carol, 1'000'000);
3483  auto const token1 = ammAlice.lptIssue();
3484  auto const token2 = ammAlice1.lptIssue();
3485  env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}),
3486  txflags(tfPassive));
3487  env.close();
3488  BEAST_EXPECT(expectOffers(env, alice, 1));
3489  env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100}));
3490  env.close();
3491  BEAST_EXPECT(
3492  expectLine(env, alice, STAmount{token1, 10'000'100}) &&
3493  expectLine(env, alice, STAmount{token2, 9'999'900}));
3494  BEAST_EXPECT(
3495  expectLine(env, carol, STAmount{token2, 1'000'100}) &&
3496  expectLine(env, carol, STAmount{token1, 999'900}));
3497  BEAST_EXPECT(
3498  expectOffers(env, alice, 0) && expectOffers(env, carol, 0));
3499  });
3500 
3501  // LPs pay LPTokens directly. Must trust set because the trust line
3502  // is checked for the limit, which is 0 in the AMM auto-created
3503  // trust line.
3504  testAMM([&](AMM& ammAlice, Env& env) {
3505  auto const token1 = ammAlice.lptIssue();
3506  env.trust(STAmount{token1, 2'000'000}, carol);
3507  env.close();
3508  ammAlice.deposit(carol, 1'000'000);
3509  BEAST_EXPECT(
3510  ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
3511  ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
3512  // Pool balance doesn't change, only tokens moved from
3513  // one line to another.
3514  env(pay(alice, carol, STAmount{token1, 100}));
3515  env.close();
3516  BEAST_EXPECT(
3517  // Alice initial token1 10,000,000 - 100
3518  ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) &&
3519  // Carol initial token1 1,000,000 + 100
3520  ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0}));
3521 
3522  env.trust(STAmount{token1, 20'000'000}, alice);
3523  env.close();
3524  env(pay(carol, alice, STAmount{token1, 100}));
3525  env.close();
3526  // Back to the original balance
3527  BEAST_EXPECT(
3528  ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) &&
3529  ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0}));
3530  });
3531  }
3532 
3533  void
3535  {
3536  testcase("Amendment");
3537  using namespace jtx;
3539  FeatureBitset const noAMM{all - featureAMM};
3540  FeatureBitset const noNumber{all - fixUniversalNumber};
3541  FeatureBitset const noAMMAndNumber{
3543 
3544  for (auto const& feature : {noAMM, noNumber, noAMMAndNumber})
3545  {
3546  Env env{*this, feature};
3547  fund(env, gw, {alice}, {USD(1'000)}, Fund::All);
3548  AMM amm(env, alice, XRP(1'000), USD(1'000), ter(temDISABLED));
3549  }
3550  }
3551 
3552  void
3553  testFlags()
3554  {
3555  testcase("Flags");
3556  using namespace jtx;
3557 
3558  testAMM([&](AMM& ammAlice, Env& env) {
3559  auto const info = env.rpc(
3560  "json",
3561  "account_info",
3562  std::string(
3563  "{\"account\": \"" + to_string(ammAlice.ammAccount()) +
3564  "\"}"));
3565  auto const flags =
3566  info[jss::result][jss::account_data][jss::Flags].asUInt();
3567  BEAST_EXPECT(
3568  flags ==
3569  (lsfAMM | lsfDisableMaster | lsfDefaultRipple |
3570  lsfDepositAuth));
3571  });
3572  }
3573 
3574  void
3575  testRippling()
3576  {
3577  testcase("Rippling");
3578  using namespace jtx;
3579 
3580  // Rippling via AMM fails because AMM trust line has 0 limit.
3581  // Set up two issuers, A and B. Have each issue a token called TST.
3582  // Have another account C hold TST from both issuers,
3583  // and create an AMM for this pair.
3584  // Have a fourth account, D, create a trust line to the AMM for TST.
3585  // Send a payment delivering TST.AMM from C to D, using SendMax in
3586  // TST.A (or B) and a path through the AMM account. By normal
3587  // rippling rules, this would have caused the AMM's balances
3588  // to shift at a 1:1 rate with no fee applied has it not been
3589  // for 0 limit.
3590  {
3591  Env env(*this);
3592  auto const A = Account("A");
3593  auto const B = Account("B");
3594  auto const TSTA = A["TST"];
3595  auto const TSTB = B["TST"];
3596  auto const C = Account("C");
3597  auto const D = Account("D");
3598 
3599  env.fund(XRP(10'000), A);
3600  env.fund(XRP(10'000), B);
3601  env.fund(XRP(10'000), C);
3602  env.fund(XRP(10'000), D);
3603 
3604  env.trust(TSTA(10'000), C);
3605  env.trust(TSTB(10'000), C);
3606  env(pay(A, C, TSTA(10'000)));
3607  env(pay(B, C, TSTB(10'000)));
3608  AMM amm(env, C, TSTA(5'000), TSTB(5'000));
3609  auto const ammIss = Issue(TSTA.currency, amm.ammAccount());
3610 
3611  env.trust(STAmount{ammIss, 10'000}, D);
3612  env.close();
3613 
3614  env(pay(C, D, STAmount{ammIss, 10}),
3615  sendmax(TSTA(100)),
3616  path(amm.ammAccount()),
3617  txflags(tfPartialPayment | tfNoRippleDirect),
3618  ter(tecPATH_DRY));
3619  }
3620  }
3621 
3622  void
3623  testAMMAndCLOB()
3624  {
3625  testcase("AMMAndCLOB, offer quality change");
3626  using namespace jtx;
3627  auto const gw = Account("gw");
3628  auto const TST = gw["TST"];
3629  auto const LP1 = Account("LP1");
3630  auto const LP2 = Account("LP2");
3631 
3632  auto prep = [&](auto const& offerCb, auto const& expectCb) {
3633  Env env(*this);
3634  env.fund(XRP(30'000'000'000), gw);
3635  env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000)));
3636 
3637  env.fund(XRP(10'000), LP1);
3638  env.fund(XRP(10'000), LP2);
3639  env(offer(LP1, TST(25), XRPAmount(287'500'000)));
3640 
3641  // Either AMM or CLOB offer
3642  offerCb(env);
3643 
3644  env(offer(LP2, TST(25), XRPAmount(287'500'000)));
3645 
3646  expectCb(env);
3647  };
3648 
3649  // If we replace AMM with equivalent CLOB offer, which
3650  // AMM generates when it is consumed, then the
3651  // result must be identical.
3652  std::string lp2TSTBalance;
3653  std::string lp2TakerGets;
3654  std::string lp2TakerPays;
3655  // Execute with AMM first
3656  prep(
3657  [&](Env& env) { AMM amm(env, LP1, TST(25), XRP(250)); },
3658  [&](Env& env) {
3659  lp2TSTBalance =
3660  getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
3661  .asString();
3662  auto const offer = getAccountOffers(env, LP2)["offers"][0u];
3663  lp2TakerGets = offer["taker_gets"].asString();
3664  lp2TakerPays = offer["taker_pays"]["value"].asString();
3665  });
3666  // Execute with CLOB offer
3667  prep(
3668  [&](Env& env) {
3669  env(offer(
3670  LP1,
3671  XRPAmount{18'095'133},
3672  STAmount{TST, UINT64_C(1'68737984885388), -14}),
3673  txflags(tfPassive));
3674  },
3675  [&](Env& env) {
3676  BEAST_EXPECT(
3677  lp2TSTBalance ==
3678  getAccountLines(env, LP2, TST)["lines"][0u]["balance"]
3679  .asString());
3680  auto const offer = getAccountOffers(env, LP2)["offers"][0u];
3681  BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString());
3682  BEAST_EXPECT(
3683  lp2TakerPays == offer["taker_pays"]["value"].asString());
3684  });
3685  }
3686 
3687  void
3688  testTradingFee()
3689  {
3690  testcase("Trading Fee");
3691  using namespace jtx;
3692 
3693  // Single Deposit, 1% fee
3694  testAMM(
3695  [&](AMM& ammAlice, Env& env) {
3696  // No fee
3697  ammAlice.deposit(carol, USD(3'000));
3698  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
3699  ammAlice.withdrawAll(carol, USD(3'000));
3700  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0}));
3701  BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
3702  // Set fee to 1%
3703  ammAlice.vote(alice, 1'000);
3704  BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3705  // Carol gets fewer LPToken ~994, because of the single deposit
3706  // fee
3707  ammAlice.deposit(carol, USD(3'000));
3708  BEAST_EXPECT(ammAlice.expectLPTokens(
3709  carol, IOUAmount{994'981155689671, -12}));
3710  BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
3711  // Set fee to 0
3712  ammAlice.vote(alice, 0);
3713  ammAlice.withdrawAll(carol, USD(0));
3714  // Carol gets back less than the original deposit
3715  BEAST_EXPECT(expectLine(
3716  env,
3717  carol,
3718  STAmount{USD, UINT64_C(29'994'96220068281), -11}));
3719  },
3720  {{USD(1'000), EUR(1'000)}});
3721 
3722  // Single deposit with EP not exceeding specified:
3723  // 100USD with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee.
3724  testAMM(
3725  [&](AMM& ammAlice, Env& env) {
3726  auto const balance = env.balance(carol, USD);
3727  auto tokensFee = ammAlice.deposit(
3728  carol, USD(1'000), std::nullopt, STAmount{USD, 1, -1});
3729  auto const deposit = balance - env.balance(carol, USD);
3730  ammAlice.withdrawAll(carol, USD(0));
3731  ammAlice.vote(alice, 0);
3732  BEAST_EXPECT(ammAlice.expectTradingFee(0));
3733  auto const tokensNoFee = ammAlice.deposit(carol, deposit);
3734  // carol pays ~2008 LPTokens in fees or ~0.5% of the no-fee
3735  // LPTokens
3736  BEAST_EXPECT(tokensFee == IOUAmount(485'636'0611129, -7));
3737  BEAST_EXPECT(tokensNoFee == IOUAmount(487'644'85901109, -8));
3738  },
3739  std::nullopt,
3740  1'000);
3741 
3742  // Single deposit with EP not exceeding specified:
3743  // 200USD with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee
3744  testAMM(
3745  [&](AMM& ammAlice, Env& env) {
3746  auto const balance = env.balance(carol, USD);
3747  auto const tokensFee = ammAlice.deposit(
3748  carol, USD(200), std::nullopt, STAmount{USD, 2020, -6});
3749  auto const deposit = balance - env.balance(carol, USD);
3750  ammAlice.withdrawAll(carol, USD(0));
3751  ammAlice.vote(alice, 0);
3752  BEAST_EXPECT(ammAlice.expectTradingFee(0));
3753  auto const tokensNoFee = ammAlice.deposit(carol, deposit);
3754  // carol pays ~475 LPTokens in fees or ~0.5% of the no-fee
3755  // LPTokens
3756  BEAST_EXPECT(tokensFee == IOUAmount(98'000'00000002, -8));
3757  BEAST_EXPECT(tokensNoFee == IOUAmount(98'475'81871545, -8));
3758  },
3759  std::nullopt,
3760  1'000);
3761 
3762  // Single Withdrawal, 1% fee
3763  testAMM(
3764  [&](AMM& ammAlice, Env& env) {
3765  // No fee
3766  ammAlice.deposit(carol, USD(3'000));
3767 
3768  BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000}));
3769  BEAST_EXPECT(expectLine(env, carol, USD(27'000)));
3770  // Set fee to 1%
3771  ammAlice.vote(alice, 1'000);
3772  BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3773  // Single withdrawal. Carol gets ~5USD less than deposited.
3774  ammAlice.withdrawAll(carol, USD(0));
3775  BEAST_EXPECT(expectLine(
3776  env,
3777  carol,
3778  STAmount{USD, UINT64_C(29'994'97487437186), -11}));
3779  },
3780  {{USD(1'000), EUR(1'000)}});
3781 
3782  // Withdraw with EPrice limit, 1% fee.
3783  testAMM(
3784  [&](AMM& ammAlice, Env& env) {
3785  ammAlice.deposit(carol, 1'000'000);
3786  auto const tokensFee = ammAlice.withdraw(
3787  carol, USD(100), std::nullopt, IOUAmount{520, 0});
3788  // carol withdraws ~1,443.44USD
3789  auto const balanceAfterWithdraw =
3790  STAmount(USD, UINT64_C(30'443'43891402715), -11);
3791  BEAST_EXPECT(env.balance(carol, USD) == balanceAfterWithdraw);
3792  // Set to original pool size
3793  auto const deposit = balanceAfterWithdraw - USD(29'000);
3794  ammAlice.deposit(carol, deposit);
3795  // fee 0%
3796  ammAlice.vote(alice, 0);
3797  BEAST_EXPECT(ammAlice.expectTradingFee(0));
3798  auto const tokensNoFee = ammAlice.withdraw(carol, deposit);
3799  BEAST_EXPECT(
3800  env.balance(carol, USD) ==
3801  STAmount(USD, UINT64_C(30'443'43891402717), -11));
3802  // carol pays ~4008 LPTokens in fees or ~0.5% of the no-fee
3803  // LPTokens
3804  BEAST_EXPECT(tokensNoFee == IOUAmount(746'579'80779913, -8));
3805  BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8));
3806  },
3807  std::nullopt,
3808  1'000);
3809 
3810  // Payment, 1% fee
3811  testAMM(
3812  [&](AMM& ammAlice, Env& env) {
3813  fund(
3814  env,
3815  gw,
3816  {bob},
3817  XRP(1'000),
3818  {USD(1'000), EUR(1'000)},
3819  Fund::Acct);
3820  // Alice contributed 1010EUR and 1000USD to the pool
3821  BEAST_EXPECT(expectLine(env, alice, EUR(28'990)));
3822  BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
3823  BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
3824  // Carol pays to Alice with no fee
3825  env(pay(carol, alice, EUR(10)),
3826  path(~EUR),
3827  sendmax(USD(10)),
3828  txflags(tfNoRippleDirect));
3829  env.close();
3830  // Alice has 10EUR more and Carol has 10USD less
3831  BEAST_EXPECT(expectLine(env, alice, EUR(29'000)));
3832  BEAST_EXPECT(expectLine(env, alice, USD(29'000)));
3833  BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
3834 
3835  // Set fee to 1%
3836  ammAlice.vote(alice, 1'000);
3837  BEAST_EXPECT(ammAlice.expectTradingFee(1'000));
3838  // Bob pays to Carol with 1% fee
3839  env(pay(bob, carol, USD(10)),
3840  path(~USD),
3841  sendmax(EUR(15)),
3842  txflags(tfNoRippleDirect));
3843  env.close();
3844  // Bob sends 10.1~EUR to pay 10USD
3845  BEAST_EXPECT(expectLine(
3846  env, bob, STAmount{EUR, UINT64_C(989'8989898989899), -13}));
3847  // Carol got 10USD
3848  BEAST_EXPECT(expectLine(env, carol, USD(30'000)));
3849  BEAST_EXPECT(ammAlice.expectBalances(
3850  USD(1'000),
3851  STAmount{EUR, UINT64_C(1'010'10101010101), -11},
3852  ammAlice.tokens()));
3853  },
3854  {{USD(1'000), EUR(1'010)}});
3855 
3856  // Offer crossing, 0.5% fee
3857  testAMM(
3858  [&](AMM& ammAlice, Env& env) {
3859  // No fee
3860  env(offer(carol, EUR(10), USD(10)));
3861  env.close();
3862  BEAST_EXPECT(expectLine(env, carol, USD(29'990)));
3863  BEAST_EXPECT(expectLine(env, carol, EUR(30'010)));
3864  // Change pool composition back
3865  env(offer(carol, USD(10), EUR(10)));
3866  env.close();
3867  // Set fee to 0.5%
3868  ammAlice.vote(alice, 500);
3869  BEAST_EXPECT(ammAlice.expectTradingFee(500));
3870  env(offer(carol, EUR(10), USD(10)));
3871  env.close();
3872  // Alice gets fewer ~4.97EUR for ~5.02USD, the difference goes
3873  // to the pool
3874  BEAST_EXPECT(expectLine(
3875  env,
3876  carol,
3877  STAmount{USD, UINT64_C(29'995'02512562814), -11}));
3878  BEAST_EXPECT(expectLine(
3879  env,
3880  carol,
3881  STAmount{EUR, UINT64_C(30'004'97487437186), -11}));
3882  BEAST_EXPECT(expectOffers(
3883  env,
3884  carol,
3885  1,
3886  {{Amounts{
3887  STAmount{EUR, UINT64_C(5'025125628140703), -15},
3888  STAmount{USD, UINT64_C(5'025125628140703), -15}}}}));
3889  BEAST_EXPECT(ammAlice.expectBalances(
3890  STAmount{USD, UINT64_C(1'004'974874371859), -12},
3891  STAmount{EUR, UINT64_C(1'005'025125628141), -12},
3892  ammAlice.tokens()));
3893  },
3894  {{USD(1'000), EUR(1'010)}});
3895 
3896  // Payment with AMM and CLOB offer, 0 fee
3897  // AMM liquidity is consumed first up to CLOB offer quality
3898  // CLOB offer is fully consumed next
3899  // Remaining amount is consumed via AMM liquidity
3900  {
3901  Env env(*this);
3902  Account const ed("ed");
3903  fund(
3904  env,
3905  gw,
3906  {alice, bob, carol, ed},
3907  XRP(1'000),
3908  {USD(2'000), EUR(2'000)});
3909  env(offer(carol, EUR(5), USD(5)));
3910  AMM ammAlice(env, alice, USD(1'005), EUR(1'000));
3911  env(pay(bob, ed, USD(10)),
3912  path(~USD),
3913  sendmax(EUR(15)),
3914  txflags(tfNoRippleDirect));
3915  BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
3916  BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
3917  BEAST_EXPECT(ammAlice.expectBalances(
3918  USD(1'000), EUR(1'005), ammAlice.tokens()));
3919  BEAST_EXPECT(expectOffers(env, carol, 0));
3920  }
3921 
3922  // Payment with AMM and CLOB offer. Same as above but with 0.25% fee.
3923  {
3924  Env env(*this);
3925  Account const ed("ed");
3926  fund(
3927  env,
3928  gw,
3929  {alice, bob, carol, ed},
3930  XRP(1'000),
3931  {USD(2'000), EUR(2'000)});
3932  env(offer(carol, EUR(5), USD(5)));
3933  // Set 0.25% fee
3934  AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 250);
3935  env(pay(bob, ed, USD(10)),
3936  path(~USD),
3937  sendmax(EUR(15)),
3938  txflags(tfNoRippleDirect));
3939  BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
3940  BEAST_EXPECT(expectLine(
3941  env, bob, STAmount{EUR, UINT64_C(1'989'987453007618), -12}));
3942  BEAST_EXPECT(ammAlice.expectBalances(
3943  USD(1'000),
3944  STAmount{EUR, UINT64_C(1'005'012546992382), -12},
3945  ammAlice.tokens()));
3946  BEAST_EXPECT(expectOffers(env, carol, 0));
3947  }
3948 
3949  // Payment with AMM and CLOB offer. AMM has a better
3950  // spot price quality, but 1% fee offsets that. As the result
3951  // the entire trade is executed via LOB.
3952  {
3953  Env env(*this);
3954  Account const ed("ed");
3955  fund(
3956  env,
3957  gw,
3958  {alice, bob, carol, ed},
3959  XRP(1'000),
3960  {USD(2'000), EUR(2'000)});
3961  env(offer(carol, EUR(10), USD(10)));
3962  // Set 1% fee
3963  AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
3964  env(pay(bob, ed, USD(10)),
3965  path(~USD),
3966  sendmax(EUR(15)),
3967  txflags(tfNoRippleDirect));
3968  BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
3969  BEAST_EXPECT(expectLine(env, bob, EUR(1'990)));
3970  BEAST_EXPECT(ammAlice.expectBalances(
3971  USD(1'005), EUR(1'000), ammAlice.tokens()));
3972  BEAST_EXPECT(expectOffers(env, carol, 0));
3973  }
3974 
3975  // Payment with AMM and CLOB offer. AMM has a better
3976  // spot price quality, but 1% fee offsets that.
3977  // The CLOB offer is consumed first and the remaining
3978  // amount is consumed via AMM liquidity.
3979  {
3980  Env env(*this);
3981  Account const ed("ed");
3982  fund(
3983  env,
3984  gw,
3985  {alice, bob, carol, ed},
3986  XRP(1'000),
3987  {USD(2'000), EUR(2'000)});
3988  env(offer(carol, EUR(9), USD(9)));
3989  // Set 1% fee
3990  AMM ammAlice(env, alice, USD(1'005), EUR(1'000), false, 1'000);
3991  env(pay(bob, ed, USD(10)),
3992  path(~USD),
3993  sendmax(EUR(15)),
3994  txflags(tfNoRippleDirect));
3995  BEAST_EXPECT(expectLine(env, ed, USD(2'010)));
3996  BEAST_EXPECT(expectLine(
3997  env, bob, STAmount{EUR, UINT64_C(1'989'993923296712), -12}));
3998  BEAST_EXPECT(ammAlice.expectBalances(
3999  USD(1'004),
4000  STAmount{EUR, UINT64_C(1'001'006076703288), -12},
4001  ammAlice.tokens()));
4002  BEAST_EXPECT(expectOffers(env, carol, 0));
4003  }
4004  }
4005 
4006  void
4008  {
4009  testcase("Adjusted Deposit/Withdraw Tokens");
4010 
4011  using namespace jtx;
4012 
4013  // Deposit/Withdraw in USD
4014  testAMM([&](AMM& ammAlice, Env& env) {
4015  Account const bob("bob");
4016  Account const ed("ed");
4017  Account const paul("paul");
4018  Account const dan("dan");
4019  Account const chris("chris");
4020  Account const simon("simon");
4021  Account const ben("ben");
4022  Account const nataly("nataly");
4023  fund(
4024  env,
4025  gw,
4026  {bob, ed, paul, dan, chris, simon, ben, nataly},
4027  {USD(1'500'000)},
4028  Fund::Acct);
4029  for (int i = 0; i < 10; ++i)
4030  {
4031  ammAlice.deposit(ben, STAmount{USD, 1, -10});
4032  ammAlice.withdrawAll(ben, USD(0));
4033  ammAlice.deposit(simon, USD(0.1));
4034  ammAlice.withdrawAll(simon, USD(0));
4035  ammAlice.deposit(chris, USD(1));
4036  ammAlice.withdrawAll(chris, USD(0));
4037  ammAlice.deposit(dan, USD(10));
4038  ammAlice.withdrawAll(dan, USD(0));
4039  ammAlice.deposit(bob, USD(100));
4040  ammAlice.withdrawAll(bob, USD(0));
4041  ammAlice.deposit(carol, USD(1'000));
4042  ammAlice.withdrawAll(carol, USD(0));
4043  ammAlice.deposit(ed, USD(10'000));
4044  ammAlice.withdrawAll(ed, USD(0));
4045  ammAlice.deposit(paul, USD(100'000));
4046  ammAlice.withdrawAll(paul, USD(0));
4047  ammAlice.deposit(nataly, USD(1'000'000));
4048  ammAlice.withdrawAll(nataly, USD(0));
4049  }
4050  // Due to round off some accounts have a tiny gain, while
4051  // other have a tiny loss. The last account to withdraw
4052  // gets everything in the pool.
4053  BEAST_EXPECT(ammAlice.expectBalances(
4054  XRP(10'000),
4055  STAmount{USD, UINT64_C(10'000'0000000013), -10},
4056  IOUAmount{10'000'000}));
4057  BEAST_EXPECT(expectLine(env, ben, USD(1'500'000)));
4058  BEAST_EXPECT(expectLine(env, simon, USD(1'500'000)));
4059  BEAST_EXPECT(expectLine(env, chris, USD(1'500'000)));
4060  BEAST_EXPECT(expectLine(env, dan, USD(1'500'000)));
4061  BEAST_EXPECT(expectLine(
4062  env, carol, STAmount{USD, UINT64_C(30'000'00000000001), -11}));
4063  BEAST_EXPECT(expectLine(env, ed, USD(1'500'000)));
4064  BEAST_EXPECT(expectLine(env, paul, USD(1'500'000)));
4065  BEAST_EXPECT(expectLine(
4066  env, nataly, STAmount{USD, UINT64_C(1'500'000'000000002), -9}));
4067  ammAlice.withdrawAll(alice);
4068  BEAST_EXPECT(!ammAlice.ammExists());
4069  BEAST_EXPECT(expectLine(
4070  env, alice, STAmount{USD, UINT64_C(30'000'0000000013), -10}));
4071  // alice XRP balance is 30,000initial - 50 ammcreate fee - 10drops
4072  // fee
4073  BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
4074  });
4075 
4076  // Same as above but deposit/withdraw in XRP
4077  testAMM([&](AMM& ammAlice, Env& env) {
4078  Account const bob("bob");
4079  Account const ed("ed");
4080  Account const paul("paul");
4081  Account const dan("dan");
4082  Account const chris("chris");
4083  Account const simon("simon");
4084  Account const ben("ben");
4085  Account const nataly("nataly");
4086  fund(
4087  env,
4088  gw,
4089  {bob, ed, paul, dan, chris, simon, ben, nataly},
4090  XRP(2'000'000),
4091  {},
4092  Fund::Acct);
4093  for (int i = 0; i < 10; ++i)
4094  {
4095  ammAlice.deposit(ben, XRPAmount{1});
4096  ammAlice.withdrawAll(ben, XRP(0));
4097  ammAlice.deposit(simon, XRPAmount(1'000));
4098  ammAlice.withdrawAll(simon, XRP(0));
4099  ammAlice.deposit(chris, XRP(1));
4100  ammAlice.withdrawAll(chris, XRP(0));
4101  ammAlice.deposit(dan, XRP(10));
4102  ammAlice.withdrawAll(dan, XRP(0));
4103  ammAlice.deposit(bob, XRP(100));
4104  ammAlice.withdrawAll(bob, XRP(0));
4105  ammAlice.deposit(carol, XRP(1'000));
4106  ammAlice.withdrawAll(carol, XRP(0));
4107  ammAlice.deposit(ed, XRP(10'000));
4108  ammAlice.withdrawAll(ed, XRP(0));
4109  ammAlice.deposit(paul, XRP(100'000));
4110  ammAlice.withdrawAll(paul, XRP(0));
4111  ammAlice.deposit(nataly, XRP(1'000'000));
4112  ammAlice.withdrawAll(nataly, XRP(0));
4113  }
4114  // No round off with XRP in this test
4115  BEAST_EXPECT(ammAlice.expectBalances(
4116  XRP(10'000), USD(10'000), IOUAmount{10'000'000}));
4117  ammAlice.withdrawAll(alice);
4118  BEAST_EXPECT(!ammAlice.ammExists());
4119  // 20,000 initial - (deposit+withdraw) * 10
4120  auto const xrpBalance = (XRP(2'000'000) - txfee(env, 20)).getText();
4121  BEAST_EXPECT(accountBalance(env, ben) == xrpBalance);
4122  BEAST_EXPECT(accountBalance(env, simon) == xrpBalance);
4123  BEAST_EXPECT(accountBalance(env, chris) == xrpBalance);
4124  BEAST_EXPECT(accountBalance(env, dan) == xrpBalance);
4125  // 30,000 initial - (deposit+withdraw) * 10
4126  BEAST_EXPECT(accountBalance(env, carol) == "29999999800");
4127  BEAST_EXPECT(accountBalance(env, ed) == xrpBalance);
4128  BEAST_EXPECT(accountBalance(env, paul) == xrpBalance);
4129  BEAST_EXPECT(accountBalance(env, nataly) == xrpBalance);
4130  // 30,000 initial - 50 ammcreate fee - 10drops withdraw fee
4131  BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
4132  });
4133  }
4134 
4135  void
4136  testCore()
4137  {
4138  testInvalidInstance();
4139  testInstanceCreate();
4140  testInvalidDeposit();
4141  testDeposit();
4142  testInvalidWithdraw();
4143  testWithdraw();
4144  testInvalidFeeVote();
4145  testFeeVote();
4146  testInvalidBid();
4147  testBid();
4148  testInvalidAMMPayment();
4149  testBasicPaymentEngine();
4150  testAMMTokens();
4151  testAmendment();
4152  testFlags();
4153  testRippling();
4154  testAMMAndCLOB();
4155  testTradingFee();
4156  testAdjustedTokens();
4157  }
4158 
4159  void
4160  run() override
4161  {
4162  testCore();
4163  }
4164 };
4165 
4166 BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, ripple, 1);
4167 
4168 } // namespace test
4169 } // namespace ripple
ripple::test::jtx::AMMTestBase::USD
const jtx::IOU USD
Definition: AMMTest.h:68
ripple::test::AMM_test::testInvalidFeeVote
void testInvalidFeeVote()
Definition: AMM_test.cpp:1976
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::Issue
A currency issued by an account.
Definition: Issue.h:35
ripple::tecINSUF_RESERVE_LINE
@ tecINSUF_RESERVE_LINE
Definition: TER.h:258
std::string
STL class.
utility
ripple::test::AMM_test::testBid
void testBid()
Definition: AMM_test.cpp:2378
ripple::test::AMM_test::testAmendment
void testAmendment()
Definition: AMM_test.cpp:3534
ripple::test::jtx::same
bool same(STPathSet const &st1, Args const &... args)
Definition: TestHelpers.h:132
ripple::temBAD_CURRENCY
@ temBAD_CURRENCY
Definition: TER.h:88
ripple::test::jtx::AMM::ammAccount
AccountID const & ammAccount() const
Definition: AMM.h:248
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::jtx::AMMTest
Definition: AMMTest.h:90
ripple::TxSearched::all
@ all
vector
ripple::keylet::amm
Keylet amm(Issue const &issue1, Issue const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:375
ripple::test::AMM_test::testDeposit
void testDeposit()
Definition: AMM_test.cpp:992
std::chrono::seconds
ripple::test::AMM_test::testInvalidWithdraw
void testInvalidWithdraw()
Definition: AMM_test.cpp:1235
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::AMM::vote
void vote(std::optional< Account > const &account, std::uint32_t feeVal, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue >> const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:559
ripple::test::jtx::amm::pay
Json::Value pay(Account const &account, AccountID const &to, STAmount const &amount)
Definition: AMM.cpp:714
ripple::test::jtx::AMMTestBase::gw
const jtx::Account gw
Definition: AMMTest.h:64
ripple::test::jtx::AMMTestBase::carol
const jtx::Account carol
Definition: AMMTest.h:65
ripple::test::jtx::AMM::deposit
IOUAmount deposit(std::optional< Account > const &account, LPToken tokens, std::optional< STAmount > const &asset1InDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:369
ripple::test::jtx::AMMTestBase::alice
const jtx::Account alice
Definition: AMMTest.h:66
std::tuple
ripple::test::jtx::Env::balance
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:183
ripple::test::jtx::AMM::bid
void bid(std::optional< Account > const &account, std::optional< std::variant< int, IOUAmount, STAmount >> const &bidMin=std::nullopt, std::optional< std::variant< int, IOUAmount, STAmount >> const &bidMax=std::nullopt, std::vector< Account > const &authAccounts={}, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< jtx::seq > const &seq=std::nullopt, std::optional< std::pair< Issue, Issue >> const &assets=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:580
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::terNO_RIPPLE
@ terNO_RIPPLE
Definition: TER.h:207
ripple::test::jtx::AMM
Convenience class to test AMM functionality.
Definition: AMM.h:62
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:259
ripple::tecAMM_FAILED
@ tecAMM_FAILED
Definition: TER.h:300
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::tfOneAssetWithdrawAll
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition: TxFlags.h:169
ripple::tfLimitQuality
constexpr std::uint32_t tfLimitQuality
Definition: TxFlags.h:104
ripple::test::jtx::AMMTestBase::BAD
const jtx::IOU BAD
Definition: AMMTest.h:72
ripple::test::AMM_test::testFeeVote
void testFeeVote()
Definition: AMM_test.cpp:2045
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:109
ripple::tfWithdrawAll
constexpr std::uint32_t tfWithdrawAll
Definition: TxFlags.h:168
ripple::temBAD_AMM_TOKENS
@ temBAD_AMM_TOKENS
Definition: TER.h:127
ripple::test::jtx::AMMTestBase::bob
const jtx::Account bob
Definition: AMMTest.h:67
ripple::tfBurnable
constexpr const std::uint32_t tfBurnable
Definition: TxFlags.h:128
ripple::test::jtx::AMM::expectLPTokens
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition: AMM.cpp:222
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::AMM_test::testInvalidInstance
void testInvalidInstance()
Definition: AMM_test.cpp:115
chrono
ripple::tecDUPLICATE
@ tecDUPLICATE
Definition: TER.h:285
ripple::tecAMM_INVALID_TOKENS
@ tecAMM_INVALID_TOKENS
Definition: TER.h:301
ripple::TERSubset
Definition: TER.h:347
ripple::test::jtx::sendmax
Sets the SendMax on a JTx.
Definition: sendmax.h:31
ripple::test::AMM_test::testAdjustedTokens
void testAdjustedTokens()
Definition: AMM_test.cpp:4007
ripple::test::jtx::AMMTest::reserve
XRPAmount reserve(jtx::Env &env, std::uint32_t count) const
Definition: AMMTest.cpp:147
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::tecAMM_BALANCE
@ tecAMM_BALANCE
Definition: TER.h:299
ripple::test::jtx::txflags
Set the flags on a JTx.
Definition: txflags.h:30
beast::Zero
Zero allows classes to offer efficient comparisons to zero.
Definition: Zero.h:42
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::STAmount
Definition: STAmount.h:45
ripple::test::jtx::amm::trust
Json::Value trust(AccountID const &account, STAmount const &amount, std::uint32_t flags=0)
Definition: AMM.cpp:702
std::chrono::time_point
ripple::test::jtx::path
Add a path.
Definition: paths.h:55
ripple::temBAD_AMOUNT
@ temBAD_AMOUNT
Definition: TER.h:87
ripple::test::AMM_test::testInvalidDeposit
void testInvalidDeposit()
Definition: AMM_test.cpp:398
ripple::test::jtx::supported_amendments
FeatureBitset supported_amendments()
Definition: Env.h:70
ripple::tecPATH_PARTIAL
@ tecPATH_PARTIAL
Definition: TER.h:252
ripple::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:90
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::tfSingleAsset
constexpr std::uint32_t tfSingleAsset
Definition: TxFlags.h:170
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::test::AMM_test::testBasicPaymentEngine
void testBasicPaymentEngine()
Definition: AMM_test.cpp:2843
ripple::terNO_ACCOUNT
@ terNO_ACCOUNT
Definition: TER.h:200
ripple::test::jtx::AMM::withdrawAll
IOUAmount withdrawAll(std::optional< Account > const &account, std::optional< STAmount > const &asset1OutDetails=std::nullopt)
Definition: AMM.h:194
ripple::test::jtx::AMM::withdraw
IOUAmount withdraw(std::optional< Account > const &account, std::optional< LPToken > const &tokens, std::optional< STAmount > const &asset1OutDetails=std::nullopt, std::optional< std::uint32_t > const &flags=std::nullopt, std::optional< ter > const &ter=std::nullopt)
Definition: AMM.cpp:474
ripple::test::jtx::seq
Set the sequence number on a JTx.
Definition: seq.h:33
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::tfLPToken
constexpr std::uint32_t tfLPToken
Definition: TxFlags.h:167
ripple::test::jtx::AMMTestBase::EUR
const jtx::IOU EUR
Definition: AMMTest.h:69
ripple::test::jtx::IOU
Converts to IOU Issue or STAmount.
Definition: amount.h:291
ripple::tfSetFreeze
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:112
ripple::test::jtx::AMM::tokens
IOUAmount tokens() const
Definition: AMM.h:260
ripple::tfSetfAuth
constexpr std::uint32_t tfSetfAuth
Definition: TxFlags.h:109
ripple::terNO_AMM
@ terNO_AMM
Definition: TER.h:210
ripple::featureAMM
const uint256 featureAMM
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::tfTwoAsset
constexpr std::uint32_t tfTwoAsset
Definition: TxFlags.h:171
ripple::AUCTION_SLOT_INTERVAL_DURATION
constexpr std::uint32_t AUCTION_SLOT_INTERVAL_DURATION
Definition: AMMCore.h:40
ripple::asfDefaultRipple
constexpr std::uint32_t asfDefaultRipple
Definition: TxFlags.h:81
ripple::fixUniversalNumber
const uint256 fixUniversalNumber
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:275
ripple::test::jtx::AMM::expectAuctionSlot
bool expectAuctionSlot(std::uint32_t fee, std::optional< std::uint8_t > timeSlot, IOUAmount expectedPrice) const
Definition: AMM.cpp:235
ripple::test::AMM_test::testInstanceCreate
void testInstanceCreate()
Definition: AMM_test.cpp:45
ripple::test::jtx::accountBalance
Json::Value accountBalance(Env &env, Account const &acct)
Definition: TestHelpers.cpp:193
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::FeatureBitset
Definition: Feature.h:113
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::AMM_test
Definition: AMM_test.cpp:41
ripple::asfGlobalFreeze
constexpr std::uint32_t asfGlobalFreeze
Definition: TxFlags.h:80
std::optional< std::uint32_t >
ripple::tfClearFreeze
constexpr std::uint32_t tfClearFreeze
Definition: TxFlags.h:113
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:85
ripple::tfOneAssetLPToken
constexpr std::uint32_t tfOneAssetLPToken
Definition: TxFlags.h:172
ripple::test::jtx::Env::memoize
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:156
ripple::test::AMM_test::testWithdraw
void testWithdraw()
Definition: AMM_test.cpp:1700
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::jtx::Account::pk
PublicKey const & pk() const
Return the public key.
Definition: Account.h:89
ripple::test::jtx::expectLine
bool expectLine(Env &env, AccountID const &account, STAmount const &value, bool defaultLimits)
Definition: TestHelpers.cpp:100
ripple::test::jtx::Env::current
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:300
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::tfNoRippleDirect
constexpr std::uint32_t tfNoRippleDirect
Definition: TxFlags.h:102
ripple::XRPAmount
Definition: XRPAmount.h:46
ripple::tfLimitLPToken
constexpr std::uint32_t tfLimitLPToken
Definition: TxFlags.h:173
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
ripple::test::jtx::rate
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:30
std::chrono