rippled
Loading...
Searching...
No Matches
AMMClawback_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5 Permission to use, copy, modify, and/or distribute this software for any
6 purpose with or without fee is hereby granted, provided that the above
7 copyright notice and this permission notice appear in all copies.
8 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15*/
16//==============================================================================
17
18#include <test/jtx.h>
19#include <test/jtx/AMM.h>
20#include <test/jtx/CaptureLogs.h>
21
22#include <xrpld/app/misc/AMMUtils.h>
23
24#include <xrpl/protocol/Feature.h>
25
26namespace ripple {
27namespace test {
29{
30 void
32 {
33 testcase("test invalid request");
34 using namespace jtx;
35
36 // Test if holder does not exist.
37 {
38 Env env(*this);
39 Account gw{"gateway"};
40 Account alice{"alice"};
41 env.fund(XRP(100000), gw, alice);
42 env.close();
43
44 // gw sets asfAllowTrustLineClawback.
46 env.close();
48
49 auto const USD = gw["USD"];
50 env.trust(USD(10000), alice);
51 env(pay(gw, alice, USD(100)));
52
53 AMM amm(env, alice, XRP(100), USD(100));
54 env.close();
55
57 gw, Account("unknown"), USD, XRP, std::nullopt),
59 }
60
61 // Test if asset pair provided does not exist. This should
62 // return terNO_AMM error.
63 {
64 Env env(*this);
65 Account gw{"gateway"};
66 Account alice{"alice"};
67 env.fund(XRP(100000), gw, alice);
68 env.close();
69
70 // gw sets asfAllowTrustLineClawback.
72 env.close();
74
75 // gw issues 100 USD to Alice.
76 auto const USD = gw["USD"];
77 env.trust(USD(10000), alice);
78 env(pay(gw, alice, USD(100)));
79 env.close();
80
81 // Withdraw all the tokens from the AMMAccount.
82 // The AMMAccount will be auto deleted.
83 AMM amm(env, gw, XRP(100), USD(100));
84 amm.withdrawAll(gw);
85 BEAST_EXPECT(!amm.ammExists());
86 env.close();
87
88 // The AMM account does not exist at all now.
89 // It should return terNO_AMM error.
90 env(amm::ammClawback(gw, alice, USD, gw["EUR"], std::nullopt),
91 ter(terNO_AMM));
92 }
93
94 // Test if the issuer field and holder field is the same. This should
95 // return temMALFORMED error.
96 {
97 Env env(*this);
98 Account gw{"gateway"};
99 Account alice{"alice"};
100 env.fund(XRP(10000), gw, alice);
101 env.close();
102
103 // gw sets asfAllowTrustLineClawback.
105 env.close();
107
108 // gw issues 100 USD to Alice.
109 auto const USD = gw["USD"];
110 env.trust(USD(1000), alice);
111 env(pay(gw, alice, USD(100)));
112 env.close();
113
114 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
115
116 // Issuer can not clawback from himself.
117 env(amm::ammClawback(gw, gw, USD, XRP, std::nullopt),
119
120 // Holder can not clawback from himself.
121 env(amm::ammClawback(alice, alice, USD, XRP, std::nullopt),
123 }
124
125 // Test if the Asset field matches the Account field.
126 {
127 Env env(*this);
128 Account gw{"gateway"};
129 Account alice{"alice"};
130 env.fund(XRP(10000), gw, alice);
131 env.close();
132
133 // gw sets asfAllowTrustLineClawback.
135 env.close();
137
138 // gw issues 100 USD to Alice.
139 auto const USD = gw["USD"];
140 env.trust(USD(1000), alice);
141 env(pay(gw, alice, USD(100)));
142 env.close();
143
144 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
145
146 // The Asset's issuer field is alice, while the Account field is gw.
147 // This should return temMALFORMED because they do not match.
149 gw,
150 alice,
151 Issue{gw["USD"].currency, alice.id()},
152 XRP,
155 }
156
157 // Test if the Amount field matches the Asset field.
158 {
159 Env env(*this);
160 Account gw{"gateway"};
161 Account alice{"alice"};
162 env.fund(XRP(10000), gw, alice);
163 env.close();
164
165 // gw sets asfAllowTrustLineClawback.
167 env.close();
169
170 // gw issues 100 USD to Alice.
171 auto const USD = gw["USD"];
172 env.trust(USD(1000), alice);
173 env(pay(gw, alice, USD(100)));
174 env.close();
175
176 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
177
178 // The Asset's issuer subfield is gw account and Amount's issuer
179 // subfield is alice account. Return temBAD_AMOUNT because
180 // they do not match.
182 gw,
183 alice,
184 USD,
185 XRP,
186 STAmount{Issue{gw["USD"].currency, alice.id()}, 1}),
188 }
189
190 // Test if the Amount is invalid, which is less than zero.
191 {
192 Env env(*this);
193 Account gw{"gateway"};
194 Account alice{"alice"};
195 env.fund(XRP(10000), gw, alice);
196 env.close();
197
198 // gw sets asfAllowTrustLineClawback.
200 env.close();
202
203 // gw issues 100 USD to Alice.
204 auto const USD = gw["USD"];
205 env.trust(USD(1000), alice);
206 env(pay(gw, alice, USD(100)));
207 env.close();
208
209 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
210
211 // Return temBAD_AMOUNT if the Amount value is less than 0.
213 gw,
214 alice,
215 USD,
216 XRP,
217 STAmount{Issue{gw["USD"].currency, gw.id()}, -1}),
219
220 // Return temBAD_AMOUNT if the Amount value is 0.
222 gw,
223 alice,
224 USD,
225 XRP,
226 STAmount{Issue{gw["USD"].currency, gw.id()}, 0}),
228 }
229
230 // Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback
231 // transaction is prohibited.
232 {
233 Env env(*this);
234 Account gw{"gateway"};
235 Account alice{"alice"};
236 env.fund(XRP(10000), gw, alice);
237 env.close();
238
239 // gw issues 100 USD to Alice.
240 auto const USD = gw["USD"];
241 env.trust(USD(1000), alice);
242 env(pay(gw, alice, USD(100)));
243 env.close();
244 env.require(balance(alice, USD(100)));
245 env.require(balance(gw, alice["USD"](-100)));
246
247 // gw creates AMM pool of XRP/USD.
248 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
249
250 // If asfAllowTrustLineClawback is not set, the issuer is not
251 // allowed to send the AMMClawback transaction.
252 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
254 }
255
256 // Test invalid flag.
257 {
258 Env env(*this);
259 Account gw{"gateway"};
260 Account alice{"alice"};
261 env.fund(XRP(10000), gw, alice);
262 env.close();
263
264 // gw sets asfAllowTrustLineClawback.
266 env.close();
268
269 // gw issues 100 USD to Alice.
270 auto const USD = gw["USD"];
271 env.trust(USD(1000), alice);
272 env(pay(gw, alice, USD(100)));
273 env.close();
274
275 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
276
277 // Return temINVALID_FLAG when providing invalid flag.
278 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
281 }
282
283 // Test if tfClawTwoAssets is set when the two assets in the AMM pool
284 // are not issued by the same issuer.
285 {
286 Env env(*this);
287 Account gw{"gateway"};
288 Account alice{"alice"};
289 env.fund(XRP(10000), gw, alice);
290 env.close();
291
292 // gw sets asfAllowTrustLineClawback.
294 env.close();
296
297 // gw issues 100 USD to Alice.
298 auto const USD = gw["USD"];
299 env.trust(USD(1000), alice);
300 env(pay(gw, alice, USD(100)));
301 env.close();
302
303 // gw creates AMM pool of XRP/USD.
304 AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS));
305
306 // Return temINVALID_FLAG because the issuer set tfClawTwoAssets,
307 // but the issuer only issues USD in the pool. The issuer is not
308 // allowed to set tfClawTwoAssets flag if he did not issue both
309 // assets in the pool.
310 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
313 }
314
315 // Test clawing back XRP is being prohibited.
316 {
317 Env env(*this);
318 Account gw{"gateway"};
319 Account alice{"alice"};
320 env.fund(XRP(1000000), gw, alice);
321 env.close();
322
323 // gw sets asfAllowTrustLineClawback.
325 env.close();
327
328 // gw issues 3000 USD to Alice.
329 auto const USD = gw["USD"];
330 env.trust(USD(100000), alice);
331 env(pay(gw, alice, USD(3000)));
332 env.close();
333
334 // Alice creates AMM pool of XRP/USD.
335 AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS));
336 env.close();
337
338 // Clawback XRP is prohibited.
339 env(amm::ammClawback(gw, alice, XRP, USD, std::nullopt),
341 }
342 }
343
344 void
346 {
347 testcase("test featureAMMClawback is not enabled.");
348 using namespace jtx;
349 if (!features[featureAMMClawback])
350 {
351 Env env(*this, features);
352 Account gw{"gateway"};
353 Account alice{"alice"};
354 env.fund(XRP(1000000), gw, alice);
355 env.close();
356
357 // gw sets asfAllowTrustLineClawback.
359 env.close();
361
362 // gw issues 3000 USD to Alice.
363 auto const USD = gw["USD"];
364 env.trust(USD(100000), alice);
365 env(pay(gw, alice, USD(3000)));
366 env.close();
367
368 // When featureAMMClawback is not enabled, AMMClawback is disabled.
369 // Because when featureAMMClawback is disabled, we can not create
370 // amm account, call amm::ammClawback directly for testing purpose.
371 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
373 }
374 }
375
376 void
378 {
379 testcase("test AMMClawback specific amount");
380 using namespace jtx;
381
382 // Test AMMClawback for USD/EUR pool. The assets are issued by different
383 // issuer. Claw back USD, and EUR goes back to the holder.
384 {
385 Env env(*this, features);
386 Account gw{"gateway"};
387 Account gw2{"gateway2"};
388 Account alice{"alice"};
389 env.fund(XRP(1000000), gw, gw2, alice);
390 env.close();
391
392 // gw sets asfAllowTrustLineClawback.
394 env.close();
396
397 // gw issues 3000 USD to Alice.
398 auto const USD = gw["USD"];
399 env.trust(USD(100000), alice);
400 env(pay(gw, alice, USD(3000)));
401 env.close();
402 env.require(balance(gw, alice["USD"](-3000)));
403 env.require(balance(alice, USD(3000)));
404
405 // gw2 issues 3000 EUR to Alice.
406 auto const EUR = gw2["EUR"];
407 env.trust(EUR(100000), alice);
408 env(pay(gw2, alice, EUR(3000)));
409 env.close();
410 env.require(balance(gw2, alice["EUR"](-3000)));
411 env.require(balance(alice, EUR(3000)));
412
413 // Alice creates AMM pool of EUR/USD.
414 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
415 env.close();
416
417 BEAST_EXPECT(amm.expectBalances(
418 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
419
420 // gw clawback 1000 USD from the AMM pool.
421 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
422 ter(tesSUCCESS));
423 env.close();
424
425 // Alice's initial balance for USD is 3000 USD. Alice deposited 2000
426 // USD into the pool, then she has 1000 USD. And 1000 USD was clawed
427 // back from the AMM pool, so she still has 1000 USD.
428 env.require(balance(gw, alice["USD"](-1000)));
429 env.require(balance(alice, USD(1000)));
430
431 // Alice's initial balance for EUR is 3000 EUR. Alice deposited 1000
432 // EUR into the pool, 500 EUR was withdrawn proportionally. So she
433 // has 2500 EUR now.
434 env.require(balance(gw2, alice["EUR"](-2500)));
435 env.require(balance(alice, EUR(2500)));
436
437 // 1000 USD and 500 EUR was withdrawn from the AMM pool, so the
438 // current balance is 1000 USD and 500 EUR.
439 BEAST_EXPECT(amm.expectBalances(
440 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
441
442 // Alice has half of its initial lptokens Left.
443 BEAST_EXPECT(
444 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
445
446 // gw clawback another 1000 USD from the AMM pool. The AMM pool will
447 // be empty and get deleted.
448 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
449 ter(tesSUCCESS));
450 env.close();
451
452 // Alice should still has 1000 USD because gw clawed back from the
453 // AMM pool.
454 env.require(balance(gw, alice["USD"](-1000)));
455 env.require(balance(alice, USD(1000)));
456
457 // Alice should has 3000 EUR now because another 500 EUR was
458 // withdrawn.
459 env.require(balance(gw2, alice["EUR"](-3000)));
460 env.require(balance(alice, EUR(3000)));
461
462 // amm is automatically deleted.
463 BEAST_EXPECT(!amm.ammExists());
464 }
465
466 // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back
467 // to the holder.
468 {
469 Env env(*this, features);
470 Account gw{"gateway"};
471 Account alice{"alice"};
472 env.fund(XRP(1000000), gw, alice);
473 env.close();
474
475 // gw sets asfAllowTrustLineClawback.
477 env.close();
479
480 // gw issues 3000 USD to Alice.
481 auto const USD = gw["USD"];
482 env.trust(USD(100000), alice);
483 env(pay(gw, alice, USD(3000)));
484 env.close();
485 env.require(balance(gw, alice["USD"](-3000)));
486 env.require(balance(alice, USD(3000)));
487
488 // Alice creates AMM pool of XRP/USD.
489 AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS));
490 env.close();
491
492 BEAST_EXPECT(amm.expectBalances(
493 USD(2000), XRP(1000), IOUAmount{1414213562373095, -9}));
494
495 auto aliceXrpBalance = env.balance(alice, XRP);
496
497 // gw clawback 1000 USD from the AMM pool.
498 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
499 ter(tesSUCCESS));
500 env.close();
501
502 // Alice's initial balance for USD is 3000 USD. Alice deposited 2000
503 // USD into the pool, then she has 1000 USD. And 1000 USD was clawed
504 // back from the AMM pool, so she still has 1000 USD.
505 env.require(balance(gw, alice["USD"](-1000)));
506 env.require(balance(alice, USD(1000)));
507
508 // Alice will get 500 XRP back.
509 BEAST_EXPECT(
510 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500)));
511 aliceXrpBalance = env.balance(alice, XRP);
512
513 // 1000 USD and 500 XRP was withdrawn from the AMM pool, so the
514 // current balance is 1000 USD and 500 XRP.
515 BEAST_EXPECT(amm.expectBalances(
516 USD(1000), XRP(500), IOUAmount{7071067811865475, -10}));
517
518 // Alice has half of its initial lptokens Left.
519 BEAST_EXPECT(
520 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -10}));
521
522 // gw clawback another 1000 USD from the AMM pool. The AMM pool will
523 // be empty and get deleted.
524 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
525 ter(tesSUCCESS));
526 env.close();
527
528 // Alice should still has 1000 USD because gw clawed back from the
529 // AMM pool.
530 env.require(balance(gw, alice["USD"](-1000)));
531 env.require(balance(alice, USD(1000)));
532
533 // Alice will get another 500 XRP back.
534 BEAST_EXPECT(
535 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500)));
536
537 // amm is automatically deleted.
538 BEAST_EXPECT(!amm.ammExists());
539 }
540 }
541
542 void
544 {
545 testcase(
546 "test AMMClawback specific amount which exceeds the current "
547 "balance");
548 using namespace jtx;
549
550 // Test AMMClawback for USD/EUR pool. The assets are issued by different
551 // issuer. Claw back USD for multiple times, and EUR goes back to the
552 // holder. The last AMMClawback transaction exceeds the holder's USD
553 // balance in AMM pool.
554 {
555 Env env(*this, features);
556 Account gw{"gateway"};
557 Account gw2{"gateway2"};
558 Account alice{"alice"};
559 env.fund(XRP(1000000), gw, gw2, alice);
560 env.close();
561
562 // gw sets asfAllowTrustLineClawback.
564 env.close();
566
567 // gw issues 6000 USD to Alice.
568 auto const USD = gw["USD"];
569 env.trust(USD(100000), alice);
570 env(pay(gw, alice, USD(6000)));
571 env.close();
572 env.require(balance(alice, USD(6000)));
573
574 // gw2 issues 6000 EUR to Alice.
575 auto const EUR = gw2["EUR"];
576 env.trust(EUR(100000), alice);
577 env(pay(gw2, alice, EUR(6000)));
578 env.close();
579 env.require(balance(alice, EUR(6000)));
580
581 // Alice creates AMM pool of EUR/USD
582 AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
583 env.close();
584
585 if (!features[fixAMMv1_3])
586 BEAST_EXPECT(amm.expectBalances(
587 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
588 else
589 BEAST_EXPECT(amm.expectBalances(
590 USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
591
592 // gw clawback 1000 USD from the AMM pool
593 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
594 ter(tesSUCCESS));
595 env.close();
596
597 // Alice's initial balance for USD is 6000 USD. Alice deposited 4000
598 // USD into the pool, then she has 2000 USD. And 1000 USD was clawed
599 // back from the AMM pool, so she still has 2000 USD.
600 env.require(balance(alice, USD(2000)));
601
602 // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000
603 // EUR into the pool, 1250 EUR was withdrawn proportionally. So she
604 // has 2500 EUR now.
605 env.require(balance(alice, EUR(2250)));
606
607 // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the
608 // current balance is 3000 USD and 3750 EUR.
609 if (!features[fixAMMv1_3])
610 BEAST_EXPECT(amm.expectBalances(
611 USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
612 else
613 BEAST_EXPECT(amm.expectBalances(
614 USD(3000), EUR(3750), IOUAmount{3354101966249684, -12}));
615
616 // Alice has 3/4 of its initial lptokens Left.
617 if (!features[fixAMMv1_3])
618 BEAST_EXPECT(amm.expectLPTokens(
619 alice, IOUAmount{3354101966249685, -12}));
620 else
621 BEAST_EXPECT(amm.expectLPTokens(
622 alice, IOUAmount{3354101966249684, -12}));
623
624 // gw clawback another 500 USD from the AMM pool.
625 env(amm::ammClawback(gw, alice, USD, EUR, USD(500)),
626 ter(tesSUCCESS));
627 env.close();
628
629 // Alice should still has 2000 USD because gw clawed back from the
630 // AMM pool.
631 env.require(balance(alice, USD(2000)));
632
633 if (!features[fixAMMv1_3])
634 BEAST_EXPECT(amm.expectBalances(
635 STAmount{USD, UINT64_C(2500000000000001), -12},
636 STAmount{EUR, UINT64_C(3125000000000001), -12},
637 IOUAmount{2795084971874738, -12}));
638 else
639 BEAST_EXPECT(amm.expectBalances(
640 USD(2500), EUR(3125), IOUAmount{2795084971874737, -12}));
641
642 if (!features[fixAMMv1_3])
643 BEAST_EXPECT(
644 env.balance(alice, EUR) ==
645 STAmount(EUR, UINT64_C(2874999999999999), -12));
646 else
647 BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875));
648
649 // gw clawback small amount, 1 USD.
650 env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS));
651 env.close();
652
653 // Another 1 USD / 1.25 EUR was withdrawn.
654 env.require(balance(alice, USD(2000)));
655
656 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
657 BEAST_EXPECT(amm.expectBalances(
658 STAmount{USD, UINT64_C(2499000000000002), -12},
659 STAmount{EUR, UINT64_C(3123750000000002), -12},
660 IOUAmount{2793966937885989, -12}));
661 else if (!features[fixAMMClawbackRounding])
662 BEAST_EXPECT(amm.expectBalances(
663 USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12}));
664 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
665 BEAST_EXPECT(amm.expectBalances(
666 STAmount{USD, UINT64_C(2499000000000001), -12},
667 STAmount{EUR, UINT64_C(3123750000000001), -12},
668 IOUAmount{2793966937885988, -12}));
669
670 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
671 BEAST_EXPECT(
672 env.balance(alice, EUR) ==
673 STAmount(EUR, UINT64_C(2876'249999999998), -12));
674 else if (!features[fixAMMClawbackRounding])
675 BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25));
676 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
677 BEAST_EXPECT(
678 env.balance(alice, EUR) ==
679 STAmount(EUR, UINT64_C(2876'249999999999), -12));
680
681 // gw clawback 4000 USD, exceeding the current balance. We
682 // will clawback all.
683 env(amm::ammClawback(gw, alice, USD, EUR, USD(4000)),
684 ter(tesSUCCESS));
685 env.close();
686
687 env.require(balance(alice, USD(2000)));
688
689 // All alice's EUR in the pool goes back to alice.
690 BEAST_EXPECT(
691 env.balance(alice, EUR) ==
692 STAmount(EUR, UINT64_C(6000000000000000), -12));
693
694 // amm is automatically deleted.
695 BEAST_EXPECT(!amm.ammExists());
696 }
697
698 // Test AMMClawback for USD/XRP pool. Claw back USD for multiple times,
699 // and XRP goes back to the holder. The last AMMClawback transaction
700 // exceeds the holder's USD balance in AMM pool. In this case, gw
701 // creates the AMM pool USD/XRP, both alice and bob deposit into it. gw2
702 // creates the AMM pool EUR/XRP.
703 {
704 Env env(*this, features);
705 Account gw{"gateway"};
706 Account gw2{"gateway2"};
707 Account alice{"alice"};
708 Account bob{"bob"};
709 env.fund(XRP(1000000), gw, gw2, alice, bob);
710 env.close();
711
712 // gw sets asfAllowTrustLineClawback.
714 env.close();
716
717 // gw2 sets asfAllowTrustLineClawback.
719 env.close();
721
722 // gw issues 6000 USD to Alice and 5000 USD to Bob.
723 auto const USD = gw["USD"];
724 env.trust(USD(100000), alice);
725 env(pay(gw, alice, USD(6000)));
726 env.trust(USD(100000), bob);
727 env(pay(gw, bob, USD(5000)));
728 env.close();
729
730 // gw2 issues 5000 EUR to Alice and 4000 EUR to Bob.
731 auto const EUR = gw2["EUR"];
732 env.trust(EUR(100000), alice);
733 env(pay(gw2, alice, EUR(5000)));
734 env.trust(EUR(100000), bob);
735 env(pay(gw2, bob, EUR(4000)));
736 env.close();
737
738 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
739 AMM amm(env, gw, XRP(2000), USD(1000), ter(tesSUCCESS));
740 BEAST_EXPECT(amm.expectBalances(
741 USD(1000), XRP(2000), IOUAmount{1414213562373095, -9}));
742 amm.deposit(alice, USD(1000), XRP(2000));
743 BEAST_EXPECT(amm.expectBalances(
744 USD(2000), XRP(4000), IOUAmount{2828427124746190, -9}));
745 amm.deposit(bob, USD(1000), XRP(2000));
746 BEAST_EXPECT(amm.expectBalances(
747 USD(3000), XRP(6000), IOUAmount{4242640687119285, -9}));
748 env.close();
749
750 // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR.
751 AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS));
752 if (!features[fixAMMv1_3])
753 BEAST_EXPECT(amm2.expectBalances(
754 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
755 else
756 BEAST_EXPECT(amm2.expectBalances(
757 EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
758
759 amm2.deposit(alice, EUR(1000), XRP(3000));
760 if (!features[fixAMMv1_3])
761 BEAST_EXPECT(amm2.expectBalances(
762 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
763 else
764 BEAST_EXPECT(amm2.expectBalances(
765 EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
766
767 amm2.deposit(bob, EUR(1000), XRP(3000));
768 if (!features[fixAMMv1_3])
769 BEAST_EXPECT(amm2.expectBalances(
770 EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
771 else
772 BEAST_EXPECT(amm2.expectBalances(
773 EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9}));
774 env.close();
775
776 auto aliceXrpBalance = env.balance(alice, XRP);
777 auto bobXrpBalance = env.balance(bob, XRP);
778
779 // gw clawback 500 USD from alice in amm
780 env(amm::ammClawback(gw, alice, USD, XRP, USD(500)),
781 ter(tesSUCCESS));
782 env.close();
783
784 // Alice's initial balance for USD is 6000 USD. Alice deposited 1000
785 // USD into the pool, then she has 5000 USD. And 500 USD was clawed
786 // back from the AMM pool, so she still has 5000 USD.
787 env.require(balance(alice, USD(5000)));
788
789 // Bob's balance is not changed.
790 env.require(balance(bob, USD(4000)));
791
792 // Alice gets 1000 XRP back.
793 if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
794 BEAST_EXPECT(expectLedgerEntryRoot(
795 env, alice, aliceXrpBalance + XRP(1000) - XRPAmount(1)));
796 else
797 BEAST_EXPECT(expectLedgerEntryRoot(
798 env, alice, aliceXrpBalance + XRP(1000)));
799 aliceXrpBalance = env.balance(alice, XRP);
800
801 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
802 BEAST_EXPECT(amm.expectBalances(
803 USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
804 else if (!features[fixAMMClawbackRounding])
805 BEAST_EXPECT(amm.expectBalances(
806 USD(2500), XRP(5000), IOUAmount{3535533905932737, -9}));
807 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
808 BEAST_EXPECT(amm.expectBalances(
809 USD(2500),
810 XRPAmount(5000000001),
811 IOUAmount{3'535'533'905932738, -9}));
812
813 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
814 BEAST_EXPECT(amm.expectLPTokens(
815 alice, IOUAmount{7071067811865480, -10}));
816 else if (!features[fixAMMClawbackRounding])
817 BEAST_EXPECT(amm.expectLPTokens(
818 alice, IOUAmount{7071067811865474, -10}));
819 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
820 BEAST_EXPECT(
821 amm.expectLPTokens(alice, IOUAmount{707106781186548, -9}));
822
823 BEAST_EXPECT(
824 amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9}));
825
826 // gw clawback 10 USD from bob in amm.
827 env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS));
828 env.close();
829
830 env.require(balance(alice, USD(5000)));
831 env.require(balance(bob, USD(4000)));
832
833 // Bob gets 20 XRP back.
834 BEAST_EXPECT(
835 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20)));
836 bobXrpBalance = env.balance(bob, XRP);
837
838 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
839 BEAST_EXPECT(amm.expectBalances(
840 STAmount{USD, UINT64_C(2490000000000001), -12},
841 XRP(4980),
842 IOUAmount{3521391770309008, -9}));
843 else if (!features[fixAMMClawbackRounding])
844 BEAST_EXPECT(amm.expectBalances(
845 USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9}));
846 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
847 BEAST_EXPECT(amm.expectBalances(
848 STAmount{USD, UINT64_C(2490000000000001), -12},
849 XRPAmount(4980000001),
850 IOUAmount{3521391'770309008, -9}));
851
852 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
853 BEAST_EXPECT(amm.expectLPTokens(
854 alice, IOUAmount{7071067811865480, -10}));
855 else if (!features[fixAMMClawbackRounding])
856 BEAST_EXPECT(amm.expectLPTokens(
857 alice, IOUAmount{7071067811865474, -10}));
858 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
859 BEAST_EXPECT(
860 amm.expectLPTokens(alice, IOUAmount{707106781186548, -9}));
861
862 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
863 BEAST_EXPECT(
864 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
865 else if (!features[fixAMMClawbackRounding])
866 BEAST_EXPECT(
867 amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
868 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
869 BEAST_EXPECT(
870 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
871
872 // gw2 clawback 200 EUR from amm2.
873 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)),
874 ter(tesSUCCESS));
875 env.close();
876
877 env.require(balance(alice, EUR(4000)));
878 env.require(balance(bob, EUR(3000)));
879
880 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
881 BEAST_EXPECT(expectLedgerEntryRoot(
882 env, alice, aliceXrpBalance + XRP(600)));
883 else if (!features[fixAMMClawbackRounding])
884 BEAST_EXPECT(expectLedgerEntryRoot(
885 env, alice, aliceXrpBalance + XRP(600)));
886 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
887 BEAST_EXPECT(expectLedgerEntryRoot(
888 env, alice, aliceXrpBalance + XRP(600) - XRPAmount{1}));
889 aliceXrpBalance = env.balance(alice, XRP);
890
891 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
892 BEAST_EXPECT(amm2.expectBalances(
893 EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
894 else if (!features[fixAMMClawbackRounding])
895 BEAST_EXPECT(amm2.expectBalances(
896 EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9}));
897 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
898 BEAST_EXPECT(amm2.expectBalances(
899 EUR(2800),
900 XRPAmount(8400000001),
901 IOUAmount{4849742261192856, -9}));
902
903 if (!features[fixAMMv1_3])
904 BEAST_EXPECT(amm2.expectLPTokens(
905 alice, IOUAmount{1385640646055103, -9}));
906 else
907 BEAST_EXPECT(amm2.expectLPTokens(
908 alice, IOUAmount{1385640646055102, -9}));
909 if (!features[fixAMMv1_3])
910 BEAST_EXPECT(
911 amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
912 else
913 BEAST_EXPECT(
914 amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9}));
915
916 // gw claw back 1000 USD from alice in amm, which exceeds alice's
917 // balance. This will clawback all the remaining LP tokens of alice
918 // (corresponding 500 USD / 1000 XRP).
919 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
920 ter(tesSUCCESS));
921 env.close();
922
923 env.require(balance(alice, USD(5000)));
924 env.require(balance(bob, USD(4000)));
925
926 // Alice gets 1000 XRP back.
927 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
928 BEAST_EXPECT(expectLedgerEntryRoot(
929 env, alice, aliceXrpBalance + XRP(1000)));
930 else if (!features[fixAMMClawbackRounding])
931 BEAST_EXPECT(expectLedgerEntryRoot(
932 env, alice, aliceXrpBalance + XRP(1000) - XRPAmount{1}));
933 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
934 BEAST_EXPECT(expectLedgerEntryRoot(
935 env, alice, aliceXrpBalance + XRP(1000)));
936 aliceXrpBalance = env.balance(alice, XRP);
937
938 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
939 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
940 BEAST_EXPECT(
941 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
942 else if (!features[fixAMMClawbackRounding])
943 BEAST_EXPECT(
944 amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
945 else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3])
946 BEAST_EXPECT(
947 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
948
949 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
950 BEAST_EXPECT(amm.expectBalances(
951 STAmount{USD, UINT64_C(1990000000000001), -12},
952 XRP(3980),
953 IOUAmount{2814284989122460, -9}));
954 else if (!features[fixAMMClawbackRounding])
955 BEAST_EXPECT(amm.expectBalances(
956 USD(1'990),
957 XRPAmount{3'980'000'001},
958 IOUAmount{2814284989122459, -9}));
959 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
960 BEAST_EXPECT(amm.expectBalances(
961 STAmount{USD, UINT64_C(1990000000000001), -12},
962 XRPAmount{3'980'000'001},
963 IOUAmount{2814284989122460, -9}));
964
965 // gw clawback 1000 USD from bob in amm, which also exceeds bob's
966 // balance in amm. All bob's lptoken in amm will be consumed, which
967 // corresponds to 990 USD / 1980 XRP
968 env(amm::ammClawback(gw, bob, USD, XRP, USD(1000)),
969 ter(tesSUCCESS));
970 env.close();
971
972 env.require(balance(alice, USD(5000)));
973 env.require(balance(bob, USD(4000)));
974
975 BEAST_EXPECT(expectLedgerEntryRoot(env, alice, aliceXrpBalance));
976
977 BEAST_EXPECT(
978 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(1980)));
979 bobXrpBalance = env.balance(bob, XRP);
980
981 // Now neither alice nor bob has any lptoken in amm.
982 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
983 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
984
985 // gw2 claw back 1000 EUR from alice in amm2, which exceeds alice's
986 // balance. All alice's lptokens will be consumed, which corresponds
987 // to 800EUR / 2400 XRP.
988 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(1000)),
989 ter(tesSUCCESS));
990 env.close();
991
992 env.require(balance(alice, EUR(4000)));
993 env.require(balance(bob, EUR(3000)));
994
995 // Alice gets another 2400 XRP back, bob's XRP balance remains the
996 // same.
997 BEAST_EXPECT(
998 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(2400)));
999
1000 BEAST_EXPECT(expectLedgerEntryRoot(env, bob, bobXrpBalance));
1001 aliceXrpBalance = env.balance(alice, XRP);
1002
1003 // Alice now does not have any lptoken in amm2
1004 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
1005
1006 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
1007 BEAST_EXPECT(amm2.expectBalances(
1008 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
1009 else if (!features[fixAMMClawbackRounding])
1010 BEAST_EXPECT(amm2.expectBalances(
1011 EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
1012 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1013 BEAST_EXPECT(amm2.expectBalances(
1014 EUR(2000),
1015 XRPAmount(6000000001),
1016 IOUAmount{3464101615137754, -9}));
1017
1018 // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's
1019 // balance. All bob's lptokens will be consumed, which corresponds
1020 // to 1000EUR / 3000 XRP.
1021 env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)),
1022 ter(tesSUCCESS));
1023 env.close();
1024
1025 env.require(balance(alice, EUR(4000)));
1026 env.require(balance(bob, EUR(3000)));
1027
1028 // Bob gets another 3000 XRP back. Alice's XRP balance remains the
1029 // same.
1030 BEAST_EXPECT(expectLedgerEntryRoot(env, alice, aliceXrpBalance));
1031
1032 BEAST_EXPECT(
1033 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(3000)));
1034 bobXrpBalance = env.balance(bob, XRP);
1035
1036 // Neither alice nor bob has any lptoken in amm2
1037 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
1038 BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0)));
1039
1040 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
1041 BEAST_EXPECT(amm2.expectBalances(
1042 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
1043 else if (!features[fixAMMClawbackRounding])
1044 BEAST_EXPECT(amm2.expectBalances(
1045 EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
1046 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1047 BEAST_EXPECT(amm2.expectBalances(
1048 EUR(1000),
1049 XRPAmount(3000000001),
1050 IOUAmount{1732050807568877, -9}));
1051 }
1052 }
1053
1054 void
1056 {
1057 testcase("test AMMClawback all the tokens in the AMM pool");
1058 using namespace jtx;
1059
1060 // Test AMMClawback for USD/EUR pool. The assets are issued by different
1061 // issuer. Claw back all the USD for different users.
1062 {
1063 Env env(*this, features);
1064 Account gw{"gateway"};
1065 Account gw2{"gateway2"};
1066 Account alice{"alice"};
1067 Account bob{"bob"};
1068 Account carol{"carol"};
1069 env.fund(XRP(1000000), gw, gw2, alice, bob, carol);
1070 env.close();
1071
1072 // gw sets asfAllowTrustLineClawback.
1074 env.close();
1076
1077 // gw2 sets asfAllowTrustLineClawback.
1079 env.close();
1081
1082 // gw issues 6000 USD to Alice, 5000 USD to Bob, and 4000 USD
1083 // to Carol.
1084 auto const USD = gw["USD"];
1085 env.trust(USD(100000), alice);
1086 env(pay(gw, alice, USD(6000)));
1087 env.trust(USD(100000), bob);
1088 env(pay(gw, bob, USD(5000)));
1089 env.trust(USD(100000), carol);
1090 env(pay(gw, carol, USD(4000)));
1091 env.close();
1092
1093 // gw2 issues 6000 EUR to Alice and 5000 EUR to Bob and 4000
1094 // EUR to Carol.
1095 auto const EUR = gw2["EUR"];
1096 env.trust(EUR(100000), alice);
1097 env(pay(gw2, alice, EUR(6000)));
1098 env.trust(EUR(100000), bob);
1099 env(pay(gw2, bob, EUR(5000)));
1100 env.trust(EUR(100000), carol);
1101 env(pay(gw2, carol, EUR(4000)));
1102 env.close();
1103
1104 // Alice creates AMM pool of EUR/USD
1105 AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
1106 env.close();
1107
1108 if (!features[fixAMMv1_3])
1109 BEAST_EXPECT(amm.expectBalances(
1110 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
1111 else
1112 BEAST_EXPECT(amm.expectBalances(
1113 USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
1114 amm.deposit(bob, USD(2000), EUR(2500));
1115 if (!features[fixAMMv1_3])
1116 BEAST_EXPECT(amm.expectBalances(
1117 USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
1118 else
1119 BEAST_EXPECT(amm.expectBalances(
1120 USD(6000), EUR(7500), IOUAmount{6708203932499368, -12}));
1121 amm.deposit(carol, USD(1000), EUR(1250));
1122 if (!features[fixAMMv1_3])
1123 BEAST_EXPECT(amm.expectBalances(
1124 USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
1125 else
1126 BEAST_EXPECT(amm.expectBalances(
1127 USD(7000), EUR(8750), IOUAmount{7826237921249262, -12}));
1128
1129 if (!features[fixAMMv1_3])
1130 BEAST_EXPECT(amm.expectLPTokens(
1131 alice, IOUAmount{4472135954999580, -12}));
1132 else
1133 BEAST_EXPECT(amm.expectLPTokens(
1134 alice, IOUAmount{4472135954999579, -12}));
1135 if (!features[fixAMMv1_3])
1136 BEAST_EXPECT(
1137 amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
1138 else
1139 BEAST_EXPECT(
1140 amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12}));
1141 if (!features[fixAMMv1_3])
1142 BEAST_EXPECT(amm.expectLPTokens(
1143 carol, IOUAmount{1118033988749895, -12}));
1144 else
1145 BEAST_EXPECT(amm.expectLPTokens(
1146 carol, IOUAmount{1118033988749894, -12}));
1147
1148 env.require(balance(alice, USD(2000)));
1149 env.require(balance(alice, EUR(1000)));
1150 env.require(balance(bob, USD(3000)));
1151 env.require(balance(bob, EUR(2500)));
1152 env.require(balance(carol, USD(3000)));
1153 env.require(balance(carol, EUR(2750)));
1154
1155 // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR)
1156 env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt),
1157 ter(tesSUCCESS));
1158 env.close();
1159
1160 if (!features[fixAMMv1_3])
1161 BEAST_EXPECT(amm.expectBalances(
1162 STAmount{USD, UINT64_C(4999999999999999), -12},
1163 STAmount{EUR, UINT64_C(6249999999999999), -12},
1164 IOUAmount{5590169943749475, -12}));
1165 else
1166 BEAST_EXPECT(amm.expectBalances(
1167 STAmount{USD, UINT64_C(5000000000000001), -12},
1168 STAmount{EUR, UINT64_C(6250000000000001), -12},
1169 IOUAmount{5590169943749473, -12}));
1170
1171 if (!features[fixAMMv1_3])
1172 BEAST_EXPECT(amm.expectLPTokens(
1173 alice, IOUAmount{4472135954999580, -12}));
1174 else
1175 BEAST_EXPECT(amm.expectLPTokens(
1176 alice, IOUAmount{4472135954999579, -12}));
1177 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1178 if (!features[fixAMMv1_3])
1179 BEAST_EXPECT(amm.expectLPTokens(
1180 carol, IOUAmount{1118033988749895, -12}));
1181 else
1182 BEAST_EXPECT(amm.expectLPTokens(
1183 carol, IOUAmount{1118033988749894, -12}));
1184
1185 // Bob will get 2500 EUR back.
1186 env.require(balance(alice, USD(2000)));
1187 env.require(balance(alice, EUR(1000)));
1188 BEAST_EXPECT(
1189 env.balance(bob, USD) ==
1190 STAmount(USD, UINT64_C(3000000000000000), -12));
1191
1192 if (!features[fixAMMv1_3])
1193 BEAST_EXPECT(
1194 env.balance(bob, EUR) ==
1195 STAmount(EUR, UINT64_C(5000000000000001), -12));
1196 else
1197 BEAST_EXPECT(
1198 env.balance(bob, EUR) ==
1199 STAmount(EUR, UINT64_C(4999999999999999), -12));
1200 env.require(balance(carol, USD(3000)));
1201 env.require(balance(carol, EUR(2750)));
1202
1203 // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR)
1204 env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt),
1205 ter(tesSUCCESS));
1206 env.close();
1207 if (!features[fixAMMv1_3])
1208 BEAST_EXPECT(amm.expectBalances(
1209 STAmount{USD, UINT64_C(3999999999999999), -12},
1210 STAmount{EUR, UINT64_C(4999999999999999), -12},
1211 IOUAmount{4472135954999580, -12}));
1212 else
1213 BEAST_EXPECT(amm.expectBalances(
1214 STAmount{USD, UINT64_C(4000000000000001), -12},
1215 STAmount{EUR, UINT64_C(5000000000000002), -12},
1216 IOUAmount{4472135954999579, -12}));
1217
1218 if (!features[fixAMMv1_3])
1219 BEAST_EXPECT(amm.expectLPTokens(
1220 alice, IOUAmount{4472135954999580, -12}));
1221 else
1222 BEAST_EXPECT(amm.expectLPTokens(
1223 alice, IOUAmount{4472135954999579, -12}));
1224 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1225 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0)));
1226
1227 // gw2 clawback all alice's EUR in amm. (4000 USD / 5000 EUR)
1228 env(amm::ammClawback(gw2, alice, EUR, USD, std::nullopt),
1229 ter(tesSUCCESS));
1230 env.close();
1231
1232 env.require(balance(carol, EUR(2750)));
1233 env.require(balance(carol, USD(4000)));
1234 BEAST_EXPECT(!amm.ammExists());
1235 }
1236
1237 // Test AMMClawback for USD/XRP pool. Claw back all the USD for
1238 // different users.
1239 {
1240 Env env(*this, features);
1241 Account gw{"gateway"};
1242 Account alice{"alice"};
1243 Account bob{"bob"};
1244 env.fund(XRP(1000000), gw, alice, bob);
1245 env.close();
1246
1247 // gw sets asfAllowTrustLineClawback
1249 env.close();
1251
1252 // gw issues 600000 USD to Alice and 500000 USD to Bob.
1253 auto const USD = gw["USD"];
1254 env.trust(USD(1000000), alice);
1255 env(pay(gw, alice, USD(600000)));
1256 env.trust(USD(1000000), bob);
1257 env(pay(gw, bob, USD(500000)));
1258 env.close();
1259
1260 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
1261 AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS));
1262 if (!features[fixAMMv1_3])
1263 BEAST_EXPECT(amm.expectBalances(
1264 USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
1265 else
1266 BEAST_EXPECT(amm.expectBalances(
1267 USD(10000), XRP(2000), IOUAmount{4472135954999579, -9}));
1268 amm.deposit(alice, USD(1000), XRP(200));
1269 if (!features[fixAMMv1_3])
1270 BEAST_EXPECT(amm.expectBalances(
1271 USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
1272 else
1273 BEAST_EXPECT(amm.expectBalances(
1274 USD(11000), XRP(2200), IOUAmount{4919349550499536, -9}));
1275 amm.deposit(bob, USD(2000), XRP(400));
1276 if (!features[fixAMMv1_3])
1277 BEAST_EXPECT(amm.expectBalances(
1278 USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
1279 else
1280 BEAST_EXPECT(amm.expectBalances(
1281 USD(13000), XRP(2600), IOUAmount{5813776741499451, -9}));
1282 env.close();
1283
1284 auto aliceXrpBalance = env.balance(alice, XRP);
1285 auto bobXrpBalance = env.balance(bob, XRP);
1286
1287 // gw clawback all alice's USD in amm. (1000 USD / 200 XRP)
1288 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
1289 ter(tesSUCCESS));
1290 env.close();
1291 if (!features[fixAMMv1_3])
1292 BEAST_EXPECT(amm.expectBalances(
1293 USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
1294 else
1295 BEAST_EXPECT(amm.expectBalances(
1296 USD(12000),
1297 XRPAmount(2400000001),
1298 IOUAmount{5366563145999494, -9}));
1299 if (!features[fixAMMv1_3])
1300 BEAST_EXPECT(expectLedgerEntryRoot(
1301 env, alice, aliceXrpBalance + XRP(200)));
1302 else
1303 BEAST_EXPECT(expectLedgerEntryRoot(
1304 env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1}));
1305 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1306
1307 // gw clawback all bob's USD in amm. (2000 USD / 400 XRP)
1308 env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt),
1309 ter(tesSUCCESS));
1310 env.close();
1311 if (!features[fixAMMv1_3])
1312 BEAST_EXPECT(amm.expectBalances(
1313 USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
1314 else
1315 BEAST_EXPECT(amm.expectBalances(
1316 USD(10000),
1317 XRPAmount(2000000001),
1318 IOUAmount{4472135954999579, -9}));
1319 BEAST_EXPECT(
1320 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400)));
1321 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1322 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1323 }
1324 }
1325
1326 void
1328 {
1329 testcase(
1330 "test AMMClawback from AMM pool with assets having the same "
1331 "issuer");
1332 using namespace jtx;
1333
1334 // Test AMMClawback for USD/EUR pool. The assets are issued by different
1335 // issuer. Claw back all the USD for different users.
1336 Env env(*this, features);
1337 Account gw{"gateway"};
1338 Account alice{"alice"};
1339 Account bob{"bob"};
1340 Account carol{"carol"};
1341 env.fund(XRP(1000000), gw, alice, bob, carol);
1342 env.close();
1343
1344 // gw sets asfAllowTrustLineClawback.
1346 env.close();
1348
1349 auto const USD = gw["USD"];
1350 env.trust(USD(100000), alice);
1351 env(pay(gw, alice, USD(10000)));
1352 env.trust(USD(100000), bob);
1353 env(pay(gw, bob, USD(9000)));
1354 env.trust(USD(100000), carol);
1355 env(pay(gw, carol, USD(8000)));
1356 env.close();
1357
1358 auto const EUR = gw["EUR"];
1359 env.trust(EUR(100000), alice);
1360 env(pay(gw, alice, EUR(10000)));
1361 env.trust(EUR(100000), bob);
1362 env(pay(gw, bob, EUR(9000)));
1363 env.trust(EUR(100000), carol);
1364 env(pay(gw, carol, EUR(8000)));
1365 env.close();
1366
1367 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
1368 env.close();
1369
1370 BEAST_EXPECT(amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
1371 amm.deposit(bob, USD(4000), EUR(1000));
1372 BEAST_EXPECT(
1373 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
1374 if (!features[fixAMMv1_3])
1375 amm.deposit(carol, USD(2000), EUR(500));
1376 else
1377 amm.deposit(carol, USD(2000.25), EUR(500));
1378 BEAST_EXPECT(
1379 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
1380 // gw clawback 1000 USD from carol.
1381 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS));
1382 env.close();
1383 BEAST_EXPECT(
1384 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
1385
1386 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
1387 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
1388 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1389 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1390 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1391 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1392 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1393 if (!features[fixAMMv1_3])
1394 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1395 else
1396 BEAST_EXPECT(
1397 env.balance(carol, USD) ==
1398 STAmount(USD, UINT64_C(5999'999999999999), -12));
1399 // 250 EUR goes back to carol.
1400 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1401
1402 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
1403 // then the corresponding EUR will also be clawed back
1404 // by gw.
1405 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
1407 ter(tesSUCCESS));
1408 env.close();
1409 BEAST_EXPECT(
1410 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
1411
1412 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
1413 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
1414 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1415 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1416 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1417 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1418 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
1419 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1420 if (!features[fixAMMv1_3])
1421 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1422 else
1423 BEAST_EXPECT(
1424 env.balance(carol, USD) ==
1425 STAmount(USD, UINT64_C(5999'999999999999), -12));
1426 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1427
1428 // gw clawback all USD from alice and set tfClawTwoAssets.
1429 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
1431 ter(tesSUCCESS));
1432 env.close();
1433 BEAST_EXPECT(amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
1434
1435 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1436 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
1437 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1438 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1439 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1440 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1441 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1442 if (!features[fixAMMv1_3])
1443 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1444 else
1445 BEAST_EXPECT(
1446 env.balance(carol, USD) ==
1447 STAmount(USD, UINT64_C(5999'999999999999), -12));
1448 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1449 }
1450
1451 void
1453 {
1454 testcase(
1455 "test AMMClawback from AMM pool with assets having the same "
1456 "currency, but from different issuer");
1457 using namespace jtx;
1458
1459 // Test AMMClawback for USD/EUR pool. The assets are issued by different
1460 // issuer. Claw back all the USD for different users.
1461 Env env(*this, features);
1462 Account gw{"gateway"};
1463 Account gw2{"gateway2"};
1464 Account alice{"alice"};
1465 Account bob{"bob"};
1466 env.fund(XRP(1000000), gw, gw2, alice, bob);
1467 env.close();
1468
1469 // gw sets asfAllowTrustLineClawback.
1471 env.close();
1473
1474 // gw2 sets asfAllowTrustLineClawback.
1476 env.close();
1478
1479 env.trust(gw["USD"](100000), alice);
1480 env(pay(gw, alice, gw["USD"](8000)));
1481 env.trust(gw["USD"](100000), bob);
1482 env(pay(gw, bob, gw["USD"](7000)));
1483
1484 env.trust(gw2["USD"](100000), alice);
1485 env(pay(gw2, alice, gw2["USD"](6000)));
1486 env.trust(gw2["USD"](100000), bob);
1487 env(pay(gw2, bob, gw2["USD"](5000)));
1488 env.close();
1489
1490 AMM amm(env, alice, gw["USD"](1000), gw2["USD"](1500), ter(tesSUCCESS));
1491 env.close();
1492
1493 BEAST_EXPECT(amm.expectBalances(
1494 gw["USD"](1000),
1495 gw2["USD"](1500),
1496 IOUAmount{1224744871391589, -12}));
1497 amm.deposit(bob, gw["USD"](2000), gw2["USD"](3000));
1498 BEAST_EXPECT(amm.expectBalances(
1499 gw["USD"](3000),
1500 gw2["USD"](4500),
1501 IOUAmount{3674234614174767, -12}));
1502
1503 // Issuer does not match with asset.
1504 env(amm::ammClawback(
1505 gw,
1506 alice,
1507 gw2["USD"],
1508 gw["USD"],
1509 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
1510 ter(temMALFORMED));
1511
1512 // gw2 clawback 500 gw2[USD] from alice.
1513 env(amm::ammClawback(
1514 gw2,
1515 alice,
1516 gw2["USD"],
1517 gw["USD"],
1518 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
1519 ter(tesSUCCESS));
1520 env.close();
1521 BEAST_EXPECT(amm.expectBalances(
1522 STAmount{gw["USD"], UINT64_C(2666666666666667), -12},
1523 gw2["USD"](4000),
1524 IOUAmount{3265986323710904, -12}));
1525
1526 BEAST_EXPECT(
1527 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
1528 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2449489742783178, -12}));
1529 BEAST_EXPECT(
1530 env.balance(alice, gw["USD"]) ==
1531 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
1532 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
1533 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
1534 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](2000));
1535
1536 // gw clawback all gw["USD"] from bob.
1537 env(amm::ammClawback(gw, bob, gw["USD"], gw2["USD"], std::nullopt),
1538 ter(tesSUCCESS));
1539 env.close();
1540 BEAST_EXPECT(amm.expectBalances(
1541 STAmount{gw["USD"], UINT64_C(6666666666666670), -13},
1542 gw2["USD"](1000),
1543 IOUAmount{8164965809277260, -13}));
1544
1545 BEAST_EXPECT(
1546 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
1547 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1548 BEAST_EXPECT(
1549 env.balance(alice, gw["USD"]) ==
1550 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
1551 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
1552 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
1553 // Bob gets 3000 gw2["USD"] back and now his balance is 5000.
1554 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](5000));
1555 }
1556
1557 void
1559 {
1560 testcase("test AMMClawback when issuing token for each other");
1561 using namespace jtx;
1562
1563 // gw and gw2 issues token for each other. Test AMMClawback from
1564 // each other.
1565 Env env(*this, features);
1566 Account gw{"gateway"};
1567 Account gw2{"gateway2"};
1568 Account alice{"alice"};
1569 env.fund(XRP(1000000), gw, gw2, alice);
1570 env.close();
1571
1572 // gw sets asfAllowTrustLineClawback.
1574 env.close();
1576
1577 // gw2 sets asfAllowTrustLineClawback.
1579 env.close();
1581
1582 auto const USD = gw["USD"];
1583 env.trust(USD(100000), gw2);
1584 env(pay(gw, gw2, USD(5000)));
1585 env.trust(USD(100000), alice);
1586 env(pay(gw, alice, USD(5000)));
1587
1588 auto const EUR = gw2["EUR"];
1589 env.trust(EUR(100000), gw);
1590 env(pay(gw2, gw, EUR(6000)));
1591 env.trust(EUR(100000), alice);
1592 env(pay(gw2, alice, EUR(6000)));
1593 env.close();
1594
1595 AMM amm(env, gw, USD(1000), EUR(2000), ter(tesSUCCESS));
1596 env.close();
1597 BEAST_EXPECT(amm.expectBalances(
1598 USD(1000), EUR(2000), IOUAmount{1414213562373095, -12}));
1599
1600 amm.deposit(gw2, USD(2000), EUR(4000));
1601 BEAST_EXPECT(amm.expectBalances(
1602 USD(3000), EUR(6000), IOUAmount{4242640687119285, -12}));
1603
1604 amm.deposit(alice, USD(3000), EUR(6000));
1605 BEAST_EXPECT(amm.expectBalances(
1606 USD(6000), EUR(12000), IOUAmount{8485281374238570, -12}));
1607
1608 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
1609 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828427124746190, -12}));
1610 BEAST_EXPECT(
1611 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
1612
1613 // gw claws back 1000 USD from gw2.
1614 env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS));
1615 env.close();
1616 if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding])
1617 BEAST_EXPECT(amm.expectBalances(
1618 USD(5000), EUR(10000), IOUAmount{7071067811865475, -12}));
1619 else
1620 BEAST_EXPECT(amm.expectBalances(
1621 USD(5000), EUR(10000), IOUAmount{7071067811865474, -12}));
1622
1623 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
1624 if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding])
1625 BEAST_EXPECT(
1626 amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
1627 else
1628 BEAST_EXPECT(
1629 amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12}));
1630 BEAST_EXPECT(
1631 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
1632
1633 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1634 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
1635 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
1636 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
1637
1638 // gw2 claws back 1000 EUR from gw.
1639 env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS));
1640 env.close();
1641 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
1642 BEAST_EXPECT(amm.expectBalances(
1643 USD(4500),
1644 STAmount(EUR, UINT64_C(9000000000000001), -12),
1645 IOUAmount{6363961030678928, -12}));
1646 else if (!features[fixAMMClawbackRounding])
1647 BEAST_EXPECT(amm.expectBalances(
1648 USD(4500), EUR(9000), IOUAmount{6363961030678928, -12}));
1649 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1650 BEAST_EXPECT(amm.expectBalances(
1651 USD(4500),
1652 STAmount(EUR, UINT64_C(9000000000000001), -12),
1653 IOUAmount{6363961030678927, -12}));
1654
1655 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
1656 BEAST_EXPECT(
1657 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
1658 else if (!features[fixAMMClawbackRounding])
1659 BEAST_EXPECT(
1660 amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
1661 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1662 BEAST_EXPECT(
1663 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
1664
1665 if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding])
1666 BEAST_EXPECT(
1667 amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
1668 else
1669 BEAST_EXPECT(
1670 amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12}));
1671
1672 BEAST_EXPECT(
1673 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
1674
1675 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1676 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
1677 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
1678 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
1679
1680 // gw2 claws back 4000 EUR from alice.
1681 env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS));
1682 env.close();
1683 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
1684 BEAST_EXPECT(amm.expectBalances(
1685 USD(2500),
1686 STAmount(EUR, UINT64_C(5000000000000001), -12),
1687 IOUAmount{3535533905932738, -12}));
1688 else if (!features[fixAMMClawbackRounding])
1689 BEAST_EXPECT(amm.expectBalances(
1690 USD(2500), EUR(5000), IOUAmount{3535533905932738, -12}));
1691 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1692 BEAST_EXPECT(amm.expectBalances(
1693 USD(2500),
1694 STAmount(EUR, UINT64_C(5000000000000001), -12),
1695 IOUAmount{3535533905932737, -12}));
1696
1697 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
1698 BEAST_EXPECT(
1699 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
1700 else if (!features[fixAMMClawbackRounding])
1701 BEAST_EXPECT(
1702 amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
1703 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1704 BEAST_EXPECT(
1705 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
1706
1707 if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding])
1708 BEAST_EXPECT(
1709 amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
1710 else
1711 BEAST_EXPECT(
1712 amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12}));
1713 BEAST_EXPECT(
1714 amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12}));
1715
1716 BEAST_EXPECT(env.balance(alice, USD) == USD(4000));
1717 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
1718 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
1719 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
1720 }
1721
1722 void
1724 {
1725 testcase(
1726 "test AMMClawback from account which does not own any lptoken in "
1727 "the pool");
1728 using namespace jtx;
1729
1730 Env env(*this, features);
1731 Account gw{"gateway"};
1732 Account alice{"alice"};
1733 env.fund(XRP(1000000), gw, alice);
1734 env.close();
1735
1736 // gw sets asfAllowTrustLineClawback.
1738 env.close();
1740
1741 auto const USD = gw["USD"];
1742 env.trust(USD(100000), alice);
1743 env(pay(gw, alice, USD(5000)));
1744
1745 AMM amm(env, gw, USD(1000), XRP(2000), ter(tesSUCCESS));
1746 env.close();
1747
1748 // Alice did not deposit in the amm pool. So AMMClawback from Alice
1749 // will fail.
1750 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
1752 }
1753
1754 void
1756 {
1757 testcase("test assets frozen");
1758 using namespace jtx;
1759
1760 // test individually frozen trustline.
1761 {
1762 Env env(*this, features);
1763 Account gw{"gateway"};
1764 Account gw2{"gateway2"};
1765 Account alice{"alice"};
1766 env.fund(XRP(1000000), gw, gw2, alice);
1767 env.close();
1768
1769 // gw sets asfAllowTrustLineClawback.
1771 env.close();
1773
1774 // gw issues 3000 USD to Alice.
1775 auto const USD = gw["USD"];
1776 env.trust(USD(100000), alice);
1777 env(pay(gw, alice, USD(3000)));
1778 env.close();
1779 env.require(balance(alice, USD(3000)));
1780
1781 // gw2 issues 3000 EUR to Alice.
1782 auto const EUR = gw2["EUR"];
1783 env.trust(EUR(100000), alice);
1784 env(pay(gw2, alice, EUR(3000)));
1785 env.close();
1786 env.require(balance(alice, EUR(3000)));
1787
1788 // Alice creates AMM pool of EUR/USD.
1789 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
1790 env.close();
1791
1792 BEAST_EXPECT(amm.expectBalances(
1793 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
1794
1795 // freeze trustline
1796 env(trust(gw, alice["USD"](0), tfSetFreeze));
1797 env.close();
1798
1799 // gw clawback 1000 USD from the AMM pool.
1800 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1801 ter(tesSUCCESS));
1802 env.close();
1803
1804 env.require(balance(alice, USD(1000)));
1805 env.require(balance(alice, EUR(2500)));
1806 BEAST_EXPECT(amm.expectBalances(
1807 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
1808
1809 // Alice has half of its initial lptokens Left.
1810 BEAST_EXPECT(
1811 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
1812
1813 // gw clawback another 1000 USD from the AMM pool. The AMM pool will
1814 // be empty and get deleted.
1815 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1816 ter(tesSUCCESS));
1817 env.close();
1818
1819 // Alice should still has 1000 USD because gw clawed back from the
1820 // AMM pool.
1821 env.require(balance(alice, USD(1000)));
1822 env.require(balance(alice, EUR(3000)));
1823
1824 // amm is automatically deleted.
1825 BEAST_EXPECT(!amm.ammExists());
1826 }
1827
1828 // test individually frozen trustline of both USD and EUR currency.
1829 {
1830 Env env(*this, features);
1831 Account gw{"gateway"};
1832 Account gw2{"gateway2"};
1833 Account alice{"alice"};
1834 env.fund(XRP(1000000), gw, gw2, alice);
1835 env.close();
1836
1837 // gw sets asfAllowTrustLineClawback.
1839 env.close();
1841
1842 // gw issues 3000 USD to Alice.
1843 auto const USD = gw["USD"];
1844 env.trust(USD(100000), alice);
1845 env(pay(gw, alice, USD(3000)));
1846 env.close();
1847 env.require(balance(alice, USD(3000)));
1848
1849 // gw2 issues 3000 EUR to Alice.
1850 auto const EUR = gw2["EUR"];
1851 env.trust(EUR(100000), alice);
1852 env(pay(gw2, alice, EUR(3000)));
1853 env.close();
1854 env.require(balance(alice, EUR(3000)));
1855
1856 // Alice creates AMM pool of EUR/USD.
1857 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
1858 env.close();
1859
1860 BEAST_EXPECT(amm.expectBalances(
1861 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
1862
1863 // freeze trustlines
1864 env(trust(gw, alice["USD"](0), tfSetFreeze));
1865 env(trust(gw2, alice["EUR"](0), tfSetFreeze));
1866 env.close();
1867
1868 // gw clawback 1000 USD from the AMM pool.
1869 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1870 ter(tesSUCCESS));
1871 env.close();
1872
1873 env.require(balance(alice, USD(1000)));
1874 env.require(balance(alice, EUR(2500)));
1875 BEAST_EXPECT(amm.expectBalances(
1876 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
1877 BEAST_EXPECT(
1878 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
1879 }
1880
1881 // test gw global freeze.
1882 {
1883 Env env(*this, features);
1884 Account gw{"gateway"};
1885 Account gw2{"gateway2"};
1886 Account alice{"alice"};
1887 env.fund(XRP(1000000), gw, gw2, alice);
1888 env.close();
1889
1890 // gw sets asfAllowTrustLineClawback.
1892 env.close();
1894
1895 // gw issues 3000 USD to Alice.
1896 auto const USD = gw["USD"];
1897 env.trust(USD(100000), alice);
1898 env(pay(gw, alice, USD(3000)));
1899 env.close();
1900 env.require(balance(alice, USD(3000)));
1901
1902 // gw2 issues 3000 EUR to Alice.
1903 auto const EUR = gw2["EUR"];
1904 env.trust(EUR(100000), alice);
1905 env(pay(gw2, alice, EUR(3000)));
1906 env.close();
1907 env.require(balance(alice, EUR(3000)));
1908
1909 // Alice creates AMM pool of EUR/USD.
1910 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
1911 env.close();
1912
1913 BEAST_EXPECT(amm.expectBalances(
1914 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
1915
1916 // global freeze
1917 env(fset(gw, asfGlobalFreeze));
1918 env.close();
1919
1920 // gw clawback 1000 USD from the AMM pool.
1921 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1922 ter(tesSUCCESS));
1923 env.close();
1924
1925 env.require(balance(alice, USD(1000)));
1926 env.require(balance(alice, EUR(2500)));
1927 BEAST_EXPECT(amm.expectBalances(
1928 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
1929 BEAST_EXPECT(
1930 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
1931 }
1932
1933 // Test both assets are issued by the same issuer. And issuer sets
1934 // global freeze.
1935 {
1936 Env env(*this, features);
1937 Account gw{"gateway"};
1938 Account alice{"alice"};
1939 Account bob{"bob"};
1940 Account carol{"carol"};
1941 env.fund(XRP(1000000), gw, alice, bob, carol);
1942 env.close();
1943
1944 // gw sets asfAllowTrustLineClawback.
1946 env.close();
1948
1949 auto const USD = gw["USD"];
1950 env.trust(USD(100000), alice);
1951 env(pay(gw, alice, USD(10000)));
1952 env.trust(USD(100000), bob);
1953 env(pay(gw, bob, USD(9000)));
1954 env.trust(USD(100000), carol);
1955 env(pay(gw, carol, USD(8000)));
1956 env.close();
1957
1958 auto const EUR = gw["EUR"];
1959 env.trust(EUR(100000), alice);
1960 env(pay(gw, alice, EUR(10000)));
1961 env.trust(EUR(100000), bob);
1962 env(pay(gw, bob, EUR(9000)));
1963 env.trust(EUR(100000), carol);
1964 env(pay(gw, carol, EUR(8000)));
1965 env.close();
1966
1967 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
1968 env.close();
1969
1970 BEAST_EXPECT(
1971 amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
1972 amm.deposit(bob, USD(4000), EUR(1000));
1973 BEAST_EXPECT(
1974 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
1975 if (!features[fixAMMv1_3])
1976 amm.deposit(carol, USD(2000), EUR(500));
1977 else
1978 amm.deposit(carol, USD(2000.25), EUR(500));
1979 BEAST_EXPECT(
1980 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
1981
1982 // global freeze
1983 env(fset(gw, asfGlobalFreeze));
1984 env.close();
1985
1986 // gw clawback 1000 USD from carol.
1987 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)),
1988 ter(tesSUCCESS));
1989 env.close();
1990 BEAST_EXPECT(
1991 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
1992
1993 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
1994 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
1995 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1996 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1997 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1998 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1999 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
2000 if (!features[fixAMMv1_3])
2001 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
2002 else
2003 BEAST_EXPECT(
2004 env.balance(carol, USD) ==
2005 STAmount(USD, UINT64_C(5999'999999999999), -12));
2006 // 250 EUR goes back to carol.
2007 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
2008
2009 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
2010 // then the corresponding EUR will also be clawed back
2011 // by gw.
2012 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
2014 ter(tesSUCCESS));
2015 env.close();
2016 BEAST_EXPECT(
2017 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
2018
2019 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
2020 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
2021 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
2022 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
2023 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
2024 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
2025 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
2026 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
2027 if (!features[fixAMMv1_3])
2028 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
2029 else
2030 BEAST_EXPECT(
2031 env.balance(carol, USD) ==
2032 STAmount(USD, UINT64_C(5999'999999999999), -12));
2033 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
2034
2035 // gw clawback all USD from alice and set tfClawTwoAssets.
2036 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
2038 ter(tesSUCCESS));
2039 env.close();
2040 BEAST_EXPECT(
2041 amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
2042
2043 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
2044 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
2045 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
2046 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
2047 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
2048 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
2049 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
2050 if (!features[fixAMMv1_3])
2051 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
2052 else
2053 BEAST_EXPECT(
2054 env.balance(carol, USD) ==
2055 STAmount(USD, UINT64_C(5999'999999999999), -12));
2056 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
2057 }
2058 }
2059
2060 void
2062 {
2063 testcase("test single depoit and clawback");
2064 using namespace jtx;
2065 std::string logs;
2066
2067 // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back
2068 // to the holder.
2069 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
2070 Account gw{"gateway"};
2071 Account alice{"alice"};
2072 env.fund(XRP(1000000000), gw, alice);
2073 env.close();
2074
2075 // gw sets asfAllowTrustLineClawback.
2077 env.close();
2079
2080 // gw issues 1000 USD to Alice.
2081 auto const USD = gw["USD"];
2082 env.trust(USD(100000), alice);
2083 env(pay(gw, alice, USD(1000)));
2084 env.close();
2085 env.require(balance(alice, USD(1000)));
2086
2087 // gw creates AMM pool of XRP/USD.
2088 AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS));
2089 env.close();
2090
2091 BEAST_EXPECT(amm.expectBalances(USD(400), XRP(100), IOUAmount(200000)));
2092
2093 amm.deposit(alice, USD(400));
2094 env.close();
2095
2096 BEAST_EXPECT(amm.expectBalances(
2097 USD(800), XRP(100), IOUAmount{2828427124746190, -10}));
2098
2099 auto aliceXrpBalance = env.balance(alice, XRP);
2100
2101 env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS));
2102 env.close();
2103
2104 if (!features[fixAMMv1_3])
2105 BEAST_EXPECT(amm.expectBalances(
2106 STAmount(USD, UINT64_C(5656854249492380), -13),
2107 XRP(70.710678),
2108 IOUAmount(200000)));
2109 else
2110 BEAST_EXPECT(amm.expectBalances(
2111 STAmount(USD, UINT64_C(565'685424949238), -12),
2112 XRP(70.710679),
2113 IOUAmount(200000)));
2114 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
2115 if (!features[fixAMMv1_3])
2116 BEAST_EXPECT(expectLedgerEntryRoot(
2117 env, alice, aliceXrpBalance + XRP(29.289322)));
2118 else
2119 BEAST_EXPECT(expectLedgerEntryRoot(
2120 env, alice, aliceXrpBalance + XRP(29.289321)));
2121 }
2122
2123 void
2125 {
2126 testcase(
2127 "test last holder's lptoken balance not equal to AMM's lptoken "
2128 "balance before clawback");
2129 using namespace jtx;
2130 std::string logs;
2131
2132 auto setupAccounts =
2133 [&](Env& env, Account& gw, Account& alice, Account& bob) {
2134 env.fund(XRP(100000), gw, alice, bob);
2135 env.close();
2137 env.close();
2138
2139 auto const USD = gw["USD"];
2140 env.trust(USD(100000), alice);
2141 env(pay(gw, alice, USD(50000)));
2142 env.trust(USD(100000), bob);
2143 env(pay(gw, bob, USD(40000)));
2144 env.close();
2145
2146 return USD;
2147 };
2148
2149 auto getLPTokenBalances =
2150 [&](auto& env,
2151 auto const& amm,
2152 auto const& account) -> std::pair<std::string, std::string> {
2153 auto const lpToken =
2155 env, account, amm.lptIssue())[jss::lines][0u][jss::balance]
2156 .asString();
2157 auto const lpTokenBalance =
2158 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value]
2159 .asString();
2160 return {lpToken, lpTokenBalance};
2161 };
2162
2163 // IOU/XRP pool. AMMClawback almost last holder's USD balance
2164 {
2165 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
2166 Account gw{"gateway"}, alice{"alice"}, bob{"bob"};
2167 auto const USD = setupAccounts(env, gw, alice, bob);
2168
2169 AMM amm(env, alice, XRP(2), USD(1));
2170 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
2171 amm.deposit(bob, IOUAmount{1'000'000});
2172 amm.withdraw(alice, IOUAmount{1'876123487565916, -15});
2173 amm.withdrawAll(bob);
2174
2175 auto [lpToken, lpTokenBalance] =
2176 getLPTokenBalances(env, amm, alice);
2177 BEAST_EXPECT(
2178 lpToken == "1414.21356237366" &&
2179 lpTokenBalance == "1414.213562374");
2180
2181 auto res =
2182 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
2183 BEAST_EXPECT(res && res.value());
2184
2185 if (!features[fixAMMClawbackRounding] || !features[fixAMMv1_3])
2186 {
2187 env(amm::ammClawback(gw, alice, USD, XRP, USD(1)),
2189 BEAST_EXPECT(amm.ammExists());
2190 }
2191 else
2192 {
2193 auto const lpBalance = IOUAmount{989, -12};
2194 env(amm::ammClawback(gw, alice, USD, XRP, USD(1)));
2195 BEAST_EXPECT(amm.expectBalances(
2196 STAmount(USD, UINT64_C(7000000000000000), -28),
2197 XRPAmount(1),
2198 lpBalance));
2199 BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance));
2200 }
2201 }
2202
2203 // IOU/XRP pool. AMMClawback part of last holder's USD balance
2204 {
2205 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
2206 Account gw{"gateway"}, alice{"alice"}, bob{"bob"};
2207 auto const USD = setupAccounts(env, gw, alice, bob);
2208
2209 AMM amm(env, alice, XRP(2), USD(1));
2210 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
2211 amm.deposit(bob, IOUAmount{1'000'000});
2212 amm.withdrawAll(bob);
2213
2214 auto [lpToken, lpTokenBalance] =
2215 getLPTokenBalances(env, amm, alice);
2216 BEAST_EXPECT(
2217 lpToken == "1416.08968586066" &&
2218 lpTokenBalance == "1416.089685861");
2219
2220 auto res =
2221 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
2222 BEAST_EXPECT(res && res.value());
2223
2224 env(amm::ammClawback(gw, alice, USD, XRP, USD(0.5)));
2225
2226 if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding])
2227 {
2228 BEAST_EXPECT(amm.expectBalances(
2229 STAmount(USD, UINT64_C(5013266196406), -13),
2230 XRPAmount(1002653),
2231 IOUAmount{708'9829046744236, -13}));
2232 }
2233 else if (!features[fixAMMClawbackRounding])
2234 {
2235 BEAST_EXPECT(amm.expectBalances(
2236 STAmount(USD, UINT64_C(5013266196407), -13),
2237 XRPAmount(1002654),
2238 IOUAmount{708'9829046744941, -13}));
2239 }
2240 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
2241 {
2242 auto const lpBalance = IOUAmount{708'9829046743238, -13};
2243 BEAST_EXPECT(amm.expectBalances(
2244 STAmount(USD, UINT64_C(5013266196406999), -16),
2245 XRPAmount(1002655),
2246 lpBalance));
2247 BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance));
2248 }
2249 }
2250
2251 // IOU/XRP pool. AMMClawback all of last holder's USD balance
2252 {
2253 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
2254 Account gw{"gateway"}, alice{"alice"}, bob{"bob"};
2255 auto const USD = setupAccounts(env, gw, alice, bob);
2256
2257 AMM amm(env, alice, XRP(2), USD(1));
2258 amm.deposit(alice, IOUAmount{1'876123487565916, -15});
2259 amm.deposit(bob, IOUAmount{1'000'000});
2260 amm.withdraw(alice, IOUAmount{1'876123487565916, -15});
2261 amm.withdrawAll(bob);
2262
2263 auto [lpToken, lpTokenBalance] =
2264 getLPTokenBalances(env, amm, alice);
2265 BEAST_EXPECT(
2266 lpToken == "1414.21356237366" &&
2267 lpTokenBalance == "1414.213562374");
2268
2269 auto res =
2270 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
2271 BEAST_EXPECT(res && res.value());
2272
2273 if (!features[fixAMMClawbackRounding] && !features[fixAMMv1_3])
2274 {
2275 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
2277 }
2278 else if (!features[fixAMMClawbackRounding])
2279 {
2280 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt));
2281 BEAST_EXPECT(amm.expectBalances(
2282 STAmount(USD, UINT64_C(2410000000000000), -28),
2283 XRPAmount(1),
2284 IOUAmount{34, -11}));
2285 }
2286 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
2287 {
2288 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt));
2289 BEAST_EXPECT(!amm.ammExists());
2290 }
2291 }
2292
2293 // IOU/IOU pool, different issuers
2294 {
2295 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
2296 Account gw{"gateway"}, alice{"alice"}, bob{"bob"};
2297 auto const USD = setupAccounts(env, gw, alice, bob);
2298
2299 Account gw2{"gateway2"};
2300 env.fund(XRP(100000), gw2);
2301 env.close();
2302 auto const EUR = gw2["EUR"];
2303 env.trust(EUR(100000), alice);
2304 env(pay(gw2, alice, EUR(50000)));
2305 env.trust(EUR(100000), bob);
2306 env(pay(gw2, bob, EUR(50000)));
2307 env.close();
2308
2309 AMM amm(env, alice, USD(2), EUR(1));
2310 amm.deposit(alice, IOUAmount{1'576123487565916, -15});
2311 amm.deposit(bob, IOUAmount{1'000});
2312 amm.withdraw(alice, IOUAmount{1'576123487565916, -15});
2313 amm.withdrawAll(bob);
2314
2315 auto [lpToken, lpTokenBalance] =
2316 getLPTokenBalances(env, amm, alice);
2317 BEAST_EXPECT(
2318 lpToken == "1.414213562374011" &&
2319 lpTokenBalance == "1.414213562374");
2320
2321 auto res =
2322 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
2323 BEAST_EXPECT(res && res.value());
2324
2325 if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
2326 {
2327 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt));
2328 BEAST_EXPECT(!amm.ammExists());
2329 }
2330 else
2331 {
2332 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
2333 ter(tecINTERNAL));
2334 BEAST_EXPECT(amm.ammExists());
2335 }
2336 }
2337
2338 // IOU/IOU pool, same issuer
2339 {
2340 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
2341 Account gw{"gateway"}, alice{"alice"}, bob{"bob"};
2342 auto const USD = setupAccounts(env, gw, alice, bob);
2343
2344 auto const EUR = gw["EUR"];
2345 env.trust(EUR(100000), alice);
2346 env(pay(gw, alice, EUR(50000)));
2347 env.trust(EUR(100000), bob);
2348 env(pay(gw, bob, EUR(50000)));
2349 env.close();
2350
2351 AMM amm(env, alice, USD(1), EUR(2));
2352 amm.deposit(alice, IOUAmount{1'076123487565916, -15});
2353 amm.deposit(bob, IOUAmount{1'000});
2354 amm.withdraw(alice, IOUAmount{1'076123487565916, -15});
2355 amm.withdrawAll(bob);
2356
2357 auto [lpToken, lpTokenBalance] =
2358 getLPTokenBalances(env, amm, alice);
2359 BEAST_EXPECT(
2360 lpToken == "1.414213562374011" &&
2361 lpTokenBalance == "1.414213562374");
2362
2363 auto res =
2364 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
2365 BEAST_EXPECT(res && res.value());
2366
2367 if (features[fixAMMClawbackRounding])
2368 {
2369 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
2371 BEAST_EXPECT(!amm.ammExists());
2372 }
2373 else
2374 {
2375 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
2377 ter(tecINTERNAL));
2378 BEAST_EXPECT(amm.ammExists());
2379 }
2380 }
2381
2382 // IOU/IOU pool, larger asset ratio
2383 {
2384 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
2385 Account gw{"gateway"}, alice{"alice"}, bob{"bob"};
2386 auto const USD = setupAccounts(env, gw, alice, bob);
2387
2388 auto const EUR = gw["EUR"];
2389 env.trust(EUR(1000000000), alice);
2390 env(pay(gw, alice, EUR(500000000)));
2391 env.trust(EUR(1000000000), bob);
2392 env(pay(gw, bob, EUR(500000000)));
2393 env.close();
2394
2395 AMM amm(env, alice, USD(1), EUR(2000000));
2396 amm.deposit(alice, IOUAmount{1'076123487565916, -12});
2397 amm.deposit(bob, IOUAmount{10000});
2398 amm.withdraw(alice, IOUAmount{1'076123487565916, -12});
2399 amm.withdrawAll(bob);
2400
2401 auto [lpToken, lpTokenBalance] =
2402 getLPTokenBalances(env, amm, alice);
2403
2404 BEAST_EXPECT(
2405 lpToken == "1414.213562373101" &&
2406 lpTokenBalance == "1414.2135623731");
2407
2408 auto res =
2409 isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
2410 BEAST_EXPECT(res && res.value());
2411
2412 if (!features[fixAMMClawbackRounding] && !features[fixAMMv1_3])
2413 {
2414 env(amm::ammClawback(gw, alice, USD, EUR, USD(1)));
2415 BEAST_EXPECT(amm.expectBalances(
2416 STAmount(USD, UINT64_C(4), -15),
2417 STAmount(EUR, UINT64_C(8), -9),
2418 IOUAmount{6, -12}));
2419 }
2420 else if (!features[fixAMMClawbackRounding])
2421 {
2422 // sqrt(amount * amount2) >= LPTokens and exceeds the allowed
2423 // tolerance
2424 env(amm::ammClawback(gw, alice, USD, EUR, USD(1)),
2426 BEAST_EXPECT(amm.ammExists());
2427 }
2428 else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
2429 {
2430 env(amm::ammClawback(gw, alice, USD, EUR, USD(1)),
2432 auto const lpBalance = IOUAmount{5, -12};
2433 BEAST_EXPECT(amm.expectBalances(
2434 STAmount(USD, UINT64_C(4), -15),
2435 STAmount(EUR, UINT64_C(8), -9),
2436 lpBalance));
2437 BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance));
2438 }
2439 }
2440 }
2441
2442 void
2443 run() override
2444 {
2446
2448 testFeatureDisabled(all - featureAMMClawback);
2449 for (auto const& features :
2450 {all - fixAMMv1_3 - fixAMMClawbackRounding,
2451 all - fixAMMClawbackRounding,
2452 all})
2453 {
2456 testAMMClawbackAll(features);
2460 testNotHoldingLptoken(features);
2461 testAssetFrozen(features);
2464 }
2465 }
2466};
2467BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple);
2468} // namespace test
2469} // namespace ripple
std::string asString() const
Returns the unquoted string value.
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:46
A currency issued by an account.
Definition Issue.h:33
Currency currency
Definition Issue.h:35
void testAMMClawbackSameCurrency(FeatureBitset features)
void testNotHoldingLptoken(FeatureBitset features)
void testSingleDepositAndClawback(FeatureBitset features)
void testAMMClawbackAll(FeatureBitset features)
void testAMMClawbackSpecificAmount(FeatureBitset features)
void testFeatureDisabled(FeatureBitset features)
void testAMMClawbackExceedBalance(FeatureBitset features)
void run() override
Runs the suite.
void testAssetFrozen(FeatureBitset features)
void testAMMClawbackSameIssuerAssets(FeatureBitset features)
void testAMMClawbackIssuesEachOther(FeatureBitset features)
void testLastHolderLPTokenBalance(FeatureBitset features)
Convenience class to test AMM functionality.
Definition AMM.h:124
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:237
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:416
bool expectLPTokens(AccountID const &account, IOUAmount const &tokens) const
Definition AMM.cpp:267
Immutable cryptographic account descriptor.
Definition Account.h:39
A transaction testing environment.
Definition Env.h:121
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:547
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:320
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:183
A balance matches.
Definition balance.h:39
Match set account flags.
Definition flags.h:128
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set the flags on a JTx.
Definition txflags.h:31
T is_same_v
Json::Value ammClawback(Account const &issuer, Account const &holder, Issue const &asset, Issue const &asset2, std::optional< STAmount > const &amount)
Definition AMM.cpp:833
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:32
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:29
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
FeatureBitset testable_amendments()
Definition Env.h:74
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:83
constexpr std::uint32_t tfClawTwoAssets
Definition TxFlags.h:262
Expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
Definition AMMUtils.cpp:387
@ tecINTERNAL
Definition TER.h:310
@ tecNO_PERMISSION
Definition TER.h:305
@ tecINVARIANT_FAILED
Definition TER.h:313
@ tecAMM_BALANCE
Definition TER.h:329
@ tesSUCCESS
Definition TER.h:244
constexpr std::uint32_t tfTwoAssetIfEmpty
Definition TxFlags.h:251
constexpr std::uint32_t asfAllowTrustLineClawback
Definition TxFlags.h:94
@ terNO_ACCOUNT
Definition TER.h:217
@ terNO_AMM
Definition TER.h:227
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:118
@ temBAD_AMOUNT
Definition TER.h:89
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114