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