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/AMMTest.h>
21#include <test/jtx/trust.h>
22
23#include <xrpl/protocol/Feature.h>
24
25#include <initializer_list>
26
27namespace ripple {
28namespace test {
30{
31 void
33 {
34 testcase("test invalid request");
35 using namespace jtx;
36
37 // Test if holder does not exist.
38 {
39 Env env(*this, features);
40 Account gw{"gateway"};
41 Account alice{"alice"};
42 env.fund(XRP(100000), gw, alice);
43 env.close();
44
45 // gw sets asfAllowTrustLineClawback.
47 env.close();
49
50 env.trust(USD(10000), alice);
51 env(pay(gw, alice, gw["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, features);
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, 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, features);
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, features);
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,
153 std::nullopt),
155 }
156
157 // Test if the Amount field matches the Asset field.
158 {
159 Env env(*this, features);
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, features);
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, features);
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, gw["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, features);
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, features);
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, features);
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, gw["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, gw2["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, gw["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, gw2["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, gw["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, gw2["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, gw["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, gw["USD"](1000)));
507
508 // Alice will get 500 XRP back.
509 BEAST_EXPECT(
510 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500)));
511
512 // 1000 USD and 500 XRP was withdrawn from the AMM pool, so the
513 // current balance is 1000 USD and 500 XRP.
514 BEAST_EXPECT(amm.expectBalances(
515 USD(1000), XRP(500), IOUAmount{7071067811865475, -10}));
516
517 // Alice has half of its initial lptokens Left.
518 BEAST_EXPECT(
519 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -10}));
520
521 // gw clawback another 1000 USD from the AMM pool. The AMM pool will
522 // be empty and get deleted.
523 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
524 ter(tesSUCCESS));
525 env.close();
526
527 // Alice should still has 1000 USD because gw clawed back from the
528 // AMM pool.
529 env.require(balance(gw, alice["USD"](-1000)));
530 env.require(balance(alice, gw["USD"](1000)));
531
532 // Alice will get another 1000 XRP back.
533 BEAST_EXPECT(
534 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
535
536 // amm is automatically deleted.
537 BEAST_EXPECT(!amm.ammExists());
538 }
539 }
540
541 void
543 {
544 testcase(
545 "test AMMClawback specific amount which exceeds the current "
546 "balance");
547 using namespace jtx;
548
549 // Test AMMClawback for USD/EUR pool. The assets are issued by different
550 // issuer. Claw back USD for multiple times, and EUR goes back to the
551 // holder. The last AMMClawback transaction exceeds the holder's USD
552 // balance in AMM pool.
553 {
554 Env env(*this, features);
555 Account gw{"gateway"};
556 Account gw2{"gateway2"};
557 Account alice{"alice"};
558 env.fund(XRP(1000000), gw, gw2, alice);
559 env.close();
560
561 // gw sets asfAllowTrustLineClawback.
563 env.close();
565
566 // gw issues 6000 USD to Alice.
567 auto const USD = gw["USD"];
568 env.trust(USD(100000), alice);
569 env(pay(gw, alice, USD(6000)));
570 env.close();
571 env.require(balance(alice, gw["USD"](6000)));
572
573 // gw2 issues 6000 EUR to Alice.
574 auto const EUR = gw2["EUR"];
575 env.trust(EUR(100000), alice);
576 env(pay(gw2, alice, EUR(6000)));
577 env.close();
578 env.require(balance(alice, gw2["EUR"](6000)));
579
580 // Alice creates AMM pool of EUR/USD
581 AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
582 env.close();
583
584 if (!features[fixAMMv1_3])
585 BEAST_EXPECT(amm.expectBalances(
586 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
587 else
588 BEAST_EXPECT(amm.expectBalances(
589 USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
590
591 // gw clawback 1000 USD from the AMM pool
592 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
593 ter(tesSUCCESS));
594 env.close();
595
596 // Alice's initial balance for USD is 6000 USD. Alice deposited 4000
597 // USD into the pool, then she has 2000 USD. And 1000 USD was clawed
598 // back from the AMM pool, so she still has 2000 USD.
599 env.require(balance(alice, gw["USD"](2000)));
600
601 // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000
602 // EUR into the pool, 1250 EUR was withdrawn proportionally. So she
603 // has 2500 EUR now.
604 env.require(balance(alice, gw2["EUR"](2250)));
605
606 // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the
607 // current balance is 3000 USD and 3750 EUR.
608 if (!features[fixAMMv1_3])
609 BEAST_EXPECT(amm.expectBalances(
610 USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
611 else
612 BEAST_EXPECT(amm.expectBalances(
613 USD(3000), EUR(3750), IOUAmount{3354101966249684, -12}));
614
615 // Alice has 3/4 of its initial lptokens Left.
616 if (!features[fixAMMv1_3])
617 BEAST_EXPECT(amm.expectLPTokens(
618 alice, IOUAmount{3354101966249685, -12}));
619 else
620 BEAST_EXPECT(amm.expectLPTokens(
621 alice, IOUAmount{3354101966249684, -12}));
622
623 // gw clawback another 500 USD from the AMM pool.
624 env(amm::ammClawback(gw, alice, USD, EUR, USD(500)),
625 ter(tesSUCCESS));
626 env.close();
627
628 // Alice should still has 2000 USD because gw clawed back from the
629 // AMM pool.
630 env.require(balance(alice, gw["USD"](2000)));
631
632 if (!features[fixAMMv1_3])
633 BEAST_EXPECT(amm.expectBalances(
634 STAmount{USD, UINT64_C(2500000000000001), -12},
635 STAmount{EUR, UINT64_C(3125000000000001), -12},
636 IOUAmount{2795084971874738, -12}));
637 else
638 BEAST_EXPECT(amm.expectBalances(
639 USD(2500), EUR(3125), IOUAmount{2795084971874737, -12}));
640
641 if (!features[fixAMMv1_3])
642 BEAST_EXPECT(
643 env.balance(alice, EUR) ==
644 STAmount(EUR, UINT64_C(2874999999999999), -12));
645 else
646 BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875));
647
648 // gw clawback small amount, 1 USD.
650 env.close();
651
652 // Another 1 USD / 1.25 EUR was withdrawn.
653 env.require(balance(alice, gw["USD"](2000)));
654
655 if (!features[fixAMMv1_3])
656 BEAST_EXPECT(amm.expectBalances(
657 STAmount{USD, UINT64_C(2499000000000002), -12},
658 STAmount{EUR, UINT64_C(3123750000000002), -12},
659 IOUAmount{2793966937885989, -12}));
660 else
661 BEAST_EXPECT(amm.expectBalances(
662 USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12}));
663
664 if (!features[fixAMMv1_3])
665 BEAST_EXPECT(
666 env.balance(alice, EUR) ==
667 STAmount(EUR, UINT64_C(2'876'249999999998), -12));
668 else
669 BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25));
670
671 // gw clawback 4000 USD, exceeding the current balance. We
672 // will clawback all.
673 env(amm::ammClawback(gw, alice, USD, EUR, USD(4000)),
674 ter(tesSUCCESS));
675 env.close();
676
677 env.require(balance(alice, gw["USD"](2000)));
678
679 // All alice's EUR in the pool goes back to alice.
680 BEAST_EXPECT(
681 env.balance(alice, EUR) ==
682 STAmount(EUR, UINT64_C(6000000000000000), -12));
683
684 // amm is automatically deleted.
685 BEAST_EXPECT(!amm.ammExists());
686 }
687
688 // Test AMMClawback for USD/XRP pool. Claw back USD for multiple times,
689 // and XRP goes back to the holder. The last AMMClawback transaction
690 // exceeds the holder's USD balance in AMM pool. In this case, gw
691 // creates the AMM pool USD/XRP, both alice and bob deposit into it. gw2
692 // creates the AMM pool EUR/XRP.
693 {
694 Env env(*this, features);
695 Account gw{"gateway"};
696 Account gw2{"gateway2"};
697 Account alice{"alice"};
698 Account bob{"bob"};
699 env.fund(XRP(1000000), gw, gw2, alice, bob);
700 env.close();
701
702 // gw sets asfAllowTrustLineClawback.
704 env.close();
706
707 // gw2 sets asfAllowTrustLineClawback.
709 env.close();
711
712 // gw issues 6000 USD to Alice and 5000 USD to Bob.
713 auto const USD = gw["USD"];
714 env.trust(USD(100000), alice);
715 env(pay(gw, alice, USD(6000)));
716 env.trust(USD(100000), bob);
717 env(pay(gw, bob, USD(5000)));
718 env.close();
719
720 // gw2 issues 5000 EUR to Alice and 4000 EUR to Bob.
721 auto const EUR = gw2["EUR"];
722 env.trust(EUR(100000), alice);
723 env(pay(gw2, alice, EUR(5000)));
724 env.trust(EUR(100000), bob);
725 env(pay(gw2, bob, EUR(4000)));
726 env.close();
727
728 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
729 AMM amm(env, gw, XRP(2000), USD(1000), ter(tesSUCCESS));
730 BEAST_EXPECT(amm.expectBalances(
731 USD(1000), XRP(2000), IOUAmount{1414213562373095, -9}));
732 amm.deposit(alice, USD(1000), XRP(2000));
733 BEAST_EXPECT(amm.expectBalances(
734 USD(2000), XRP(4000), IOUAmount{2828427124746190, -9}));
735 amm.deposit(bob, USD(1000), XRP(2000));
736 BEAST_EXPECT(amm.expectBalances(
737 USD(3000), XRP(6000), IOUAmount{4242640687119285, -9}));
738 env.close();
739
740 // gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR.
741 AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS));
742 if (!features[fixAMMv1_3])
743 BEAST_EXPECT(amm2.expectBalances(
744 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
745 else
746 BEAST_EXPECT(amm2.expectBalances(
747 EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
748 amm2.deposit(alice, EUR(1000), XRP(3000));
749 if (!features[fixAMMv1_3])
750 BEAST_EXPECT(amm2.expectBalances(
751 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
752 else
753 BEAST_EXPECT(amm2.expectBalances(
754 EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
755 amm2.deposit(bob, EUR(1000), XRP(3000));
756 if (!features[fixAMMv1_3])
757 BEAST_EXPECT(amm2.expectBalances(
758 EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
759 else
760 BEAST_EXPECT(amm2.expectBalances(
761 EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9}));
762 env.close();
763
764 auto aliceXrpBalance = env.balance(alice, XRP);
765 auto bobXrpBalance = env.balance(bob, XRP);
766
767 // gw clawback 500 USD from alice in amm
768 env(amm::ammClawback(gw, alice, USD, XRP, USD(500)),
769 ter(tesSUCCESS));
770 env.close();
771
772 // Alice's initial balance for USD is 6000 USD. Alice deposited 1000
773 // USD into the pool, then she has 5000 USD. And 500 USD was clawed
774 // back from the AMM pool, so she still has 5000 USD.
775 env.require(balance(alice, gw["USD"](5000)));
776
777 // Bob's balance is not changed.
778 env.require(balance(bob, gw["USD"](4000)));
779
780 // Alice gets 1000 XRP back.
781 BEAST_EXPECT(
782 expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
783
784 if (!features[fixAMMv1_3])
785 BEAST_EXPECT(amm.expectBalances(
786 USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
787 else
788 BEAST_EXPECT(amm.expectBalances(
789 USD(2500), XRP(5000), IOUAmount{3535533905932737, -9}));
790 if (!features[fixAMMv1_3])
791 BEAST_EXPECT(amm.expectLPTokens(
792 alice, IOUAmount{7071067811865480, -10}));
793 else
794 BEAST_EXPECT(amm.expectLPTokens(
795 alice, IOUAmount{7071067811865474, -10}));
796 BEAST_EXPECT(
797 amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9}));
798
799 // gw clawback 10 USD from bob in amm.
800 env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS));
801 env.close();
802
803 env.require(balance(alice, gw["USD"](5000)));
804 env.require(balance(bob, gw["USD"](4000)));
805
806 // Bob gets 20 XRP back.
807 BEAST_EXPECT(
808 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20)));
809 if (!features[fixAMMv1_3])
810 BEAST_EXPECT(amm.expectBalances(
811 STAmount{USD, UINT64_C(2490000000000001), -12},
812 XRP(4980),
813 IOUAmount{3521391770309008, -9}));
814 else
815 BEAST_EXPECT(amm.expectBalances(
816 USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9}));
817 if (!features[fixAMMv1_3])
818 BEAST_EXPECT(amm.expectLPTokens(
819 alice, IOUAmount{7071067811865480, -10}));
820 else
821 BEAST_EXPECT(amm.expectLPTokens(
822 alice, IOUAmount{7071067811865474, -10}));
823 if (!features[fixAMMv1_3])
824 BEAST_EXPECT(
825 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
826 else
827 BEAST_EXPECT(
828 amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
829
830 // gw2 clawback 200 EUR from amm2.
831 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)),
832 ter(tesSUCCESS));
833 env.close();
834
835 env.require(balance(alice, gw2["EUR"](4000)));
836 env.require(balance(bob, gw2["EUR"](3000)));
837
838 // Alice gets 600 XRP back.
839 BEAST_EXPECT(expectLedgerEntryRoot(
840 env, alice, aliceXrpBalance + XRP(1000) + XRP(600)));
841 if (!features[fixAMMv1_3])
842 BEAST_EXPECT(amm2.expectBalances(
843 EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
844 else
845 BEAST_EXPECT(amm2.expectBalances(
846 EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9}));
847 if (!features[fixAMMv1_3])
848 BEAST_EXPECT(amm2.expectLPTokens(
849 alice, IOUAmount{1385640646055103, -9}));
850 else
851 BEAST_EXPECT(amm2.expectLPTokens(
852 alice, IOUAmount{1385640646055102, -9}));
853 if (!features[fixAMMv1_3])
854 BEAST_EXPECT(
855 amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
856 else
857 BEAST_EXPECT(
858 amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9}));
859
860 // gw claw back 1000 USD from alice in amm, which exceeds alice's
861 // balance. This will clawback all the remaining LP tokens of alice
862 // (corresponding 500 USD / 1000 XRP).
863 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
864 ter(tesSUCCESS));
865 env.close();
866
867 env.require(balance(alice, gw["USD"](5000)));
868 env.require(balance(bob, gw["USD"](4000)));
869
870 // Alice gets 1000 XRP back.
871 if (!features[fixAMMv1_3])
872 BEAST_EXPECT(expectLedgerEntryRoot(
873 env,
874 alice,
875 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
876 else
877 BEAST_EXPECT(expectLedgerEntryRoot(
878 env,
879 alice,
880 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
881 XRPAmount{1}));
882 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
883 if (!features[fixAMMv1_3])
884 BEAST_EXPECT(
885 amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
886 else
887 BEAST_EXPECT(
888 amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
889 if (!features[fixAMMv1_3])
890 BEAST_EXPECT(amm.expectBalances(
891 STAmount{USD, UINT64_C(1990000000000001), -12},
892 XRP(3980),
893 IOUAmount{2814284989122460, -9}));
894 else
895 BEAST_EXPECT(amm.expectBalances(
896 USD(1'990),
897 XRPAmount{3'980'000'001},
898 IOUAmount{2814284989122459, -9}));
899
900 // gw clawback 1000 USD from bob in amm, which also exceeds bob's
901 // balance in amm. All bob's lptoken in amm will be consumed, which
902 // corresponds to 990 USD / 1980 XRP
903 env(amm::ammClawback(gw, bob, USD, XRP, USD(1000)),
904 ter(tesSUCCESS));
905 env.close();
906
907 env.require(balance(alice, gw["USD"](5000)));
908 env.require(balance(bob, gw["USD"](4000)));
909
910 if (!features[fixAMMv1_3])
911 BEAST_EXPECT(expectLedgerEntryRoot(
912 env,
913 alice,
914 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
915 else
916 BEAST_EXPECT(expectLedgerEntryRoot(
917 env,
918 alice,
919 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
920 XRPAmount{1}));
921 BEAST_EXPECT(expectLedgerEntryRoot(
922 env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
923
924 // Now neither alice nor bob has any lptoken in amm.
925 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
926 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
927
928 // gw2 claw back 1000 EUR from alice in amm2, which exceeds alice's
929 // balance. All alice's lptokens will be consumed, which corresponds
930 // to 800EUR / 2400 XRP.
931 env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(1000)),
932 ter(tesSUCCESS));
933 env.close();
934
935 env.require(balance(alice, gw2["EUR"](4000)));
936 env.require(balance(bob, gw2["EUR"](3000)));
937
938 // Alice gets another 2400 XRP back, bob's XRP balance remains the
939 // same.
940 if (!features[fixAMMv1_3])
941 BEAST_EXPECT(expectLedgerEntryRoot(
942 env,
943 alice,
944 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
945 XRP(2400)));
946 else
947 BEAST_EXPECT(expectLedgerEntryRoot(
948 env,
949 alice,
950 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
951 XRP(2400) - XRPAmount{1}));
952 BEAST_EXPECT(expectLedgerEntryRoot(
953 env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
954
955 // Alice now does not have any lptoken in amm2
956 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
957
958 if (!features[fixAMMv1_3])
959 BEAST_EXPECT(amm2.expectBalances(
960 EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
961 else
962 BEAST_EXPECT(amm2.expectBalances(
963 EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
964
965 // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's
966 // balance. All bob's lptokens will be consumed, which corresponds
967 // to 1000EUR / 3000 XRP.
968 env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)),
969 ter(tesSUCCESS));
970 env.close();
971
972 env.require(balance(alice, gw2["EUR"](4000)));
973 env.require(balance(bob, gw2["EUR"](3000)));
974
975 // Bob gets another 3000 XRP back. Alice's XRP balance remains the
976 // same.
977 if (!features[fixAMMv1_3])
978 BEAST_EXPECT(expectLedgerEntryRoot(
979 env,
980 alice,
981 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
982 XRP(2400)));
983 else
984 BEAST_EXPECT(expectLedgerEntryRoot(
985 env,
986 alice,
987 aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
988 XRP(2400) - XRPAmount{1}));
989 BEAST_EXPECT(expectLedgerEntryRoot(
990 env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000)));
991
992 // Neither alice nor bob has any lptoken in amm2
993 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
994 BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0)));
995
996 if (!features[fixAMMv1_3])
997 BEAST_EXPECT(amm2.expectBalances(
998 EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
999 else
1000 BEAST_EXPECT(amm2.expectBalances(
1001 EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
1002 }
1003 }
1004
1005 void
1007 {
1008 testcase("test AMMClawback all the tokens in the AMM pool");
1009 using namespace jtx;
1010
1011 // Test AMMClawback for USD/EUR pool. The assets are issued by different
1012 // issuer. Claw back all the USD for different users.
1013 {
1014 Env env(*this, features);
1015 Account gw{"gateway"};
1016 Account gw2{"gateway2"};
1017 Account alice{"alice"};
1018 Account bob{"bob"};
1019 Account carol{"carol"};
1020 env.fund(XRP(1000000), gw, gw2, alice, bob, carol);
1021 env.close();
1022
1023 // gw sets asfAllowTrustLineClawback.
1025 env.close();
1027
1028 // gw2 sets asfAllowTrustLineClawback.
1030 env.close();
1032
1033 // gw issues 6000 USD to Alice, 5000 USD to Bob, and 4000 USD
1034 // to Carol.
1035 auto const USD = gw["USD"];
1036 env.trust(USD(100000), alice);
1037 env(pay(gw, alice, USD(6000)));
1038 env.trust(USD(100000), bob);
1039 env(pay(gw, bob, USD(5000)));
1040 env.trust(USD(100000), carol);
1041 env(pay(gw, carol, USD(4000)));
1042 env.close();
1043
1044 // gw2 issues 6000 EUR to Alice and 5000 EUR to Bob and 4000
1045 // EUR to Carol.
1046 auto const EUR = gw2["EUR"];
1047 env.trust(EUR(100000), alice);
1048 env(pay(gw2, alice, EUR(6000)));
1049 env.trust(EUR(100000), bob);
1050 env(pay(gw2, bob, EUR(5000)));
1051 env.trust(EUR(100000), carol);
1052 env(pay(gw2, carol, EUR(4000)));
1053 env.close();
1054
1055 // Alice creates AMM pool of EUR/USD
1056 AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
1057 env.close();
1058
1059 if (!features[fixAMMv1_3])
1060 BEAST_EXPECT(amm.expectBalances(
1061 USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
1062 else
1063 BEAST_EXPECT(amm.expectBalances(
1064 USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
1065 amm.deposit(bob, USD(2000), EUR(2500));
1066 if (!features[fixAMMv1_3])
1067 BEAST_EXPECT(amm.expectBalances(
1068 USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
1069 else
1070 BEAST_EXPECT(amm.expectBalances(
1071 USD(6000), EUR(7500), IOUAmount{6708203932499368, -12}));
1072 amm.deposit(carol, USD(1000), EUR(1250));
1073 if (!features[fixAMMv1_3])
1074 BEAST_EXPECT(amm.expectBalances(
1075 USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
1076 else
1077 BEAST_EXPECT(amm.expectBalances(
1078 USD(7000), EUR(8750), IOUAmount{7826237921249262, -12}));
1079
1080 if (!features[fixAMMv1_3])
1081 BEAST_EXPECT(amm.expectLPTokens(
1082 alice, IOUAmount{4472135954999580, -12}));
1083 else
1084 BEAST_EXPECT(amm.expectLPTokens(
1085 alice, IOUAmount{4472135954999579, -12}));
1086 if (!features[fixAMMv1_3])
1087 BEAST_EXPECT(
1088 amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
1089 else
1090 BEAST_EXPECT(
1091 amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12}));
1092 if (!features[fixAMMv1_3])
1093 BEAST_EXPECT(amm.expectLPTokens(
1094 carol, IOUAmount{1118033988749895, -12}));
1095 else
1096 BEAST_EXPECT(amm.expectLPTokens(
1097 carol, IOUAmount{1118033988749894, -12}));
1098
1099 env.require(balance(alice, gw["USD"](2000)));
1100 env.require(balance(alice, gw2["EUR"](1000)));
1101 env.require(balance(bob, gw["USD"](3000)));
1102 env.require(balance(bob, gw2["EUR"](2500)));
1103 env.require(balance(carol, gw["USD"](3000)));
1104 env.require(balance(carol, gw2["EUR"](2750)));
1105
1106 // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR)
1107 env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt),
1108 ter(tesSUCCESS));
1109 env.close();
1110
1111 if (!features[fixAMMv1_3])
1112 BEAST_EXPECT(amm.expectBalances(
1113 STAmount{USD, UINT64_C(4999999999999999), -12},
1114 STAmount{EUR, UINT64_C(6249999999999999), -12},
1115 IOUAmount{5590169943749475, -12}));
1116 else
1117 BEAST_EXPECT(amm.expectBalances(
1118 STAmount{USD, UINT64_C(5000000000000001), -12},
1119 STAmount{EUR, UINT64_C(6250000000000001), -12},
1120 IOUAmount{5590169943749473, -12}));
1121
1122 if (!features[fixAMMv1_3])
1123 BEAST_EXPECT(amm.expectLPTokens(
1124 alice, IOUAmount{4472135954999580, -12}));
1125 else
1126 BEAST_EXPECT(amm.expectLPTokens(
1127 alice, IOUAmount{4472135954999579, -12}));
1128 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1129 if (!features[fixAMMv1_3])
1130 BEAST_EXPECT(amm.expectLPTokens(
1131 carol, IOUAmount{1118033988749895, -12}));
1132 else
1133 BEAST_EXPECT(amm.expectLPTokens(
1134 carol, IOUAmount{1118033988749894, -12}));
1135
1136 // Bob will get 2500 EUR back.
1137 env.require(balance(alice, gw["USD"](2000)));
1138 env.require(balance(alice, gw2["EUR"](1000)));
1139 BEAST_EXPECT(
1140 env.balance(bob, USD) ==
1141 STAmount(USD, UINT64_C(3000000000000000), -12));
1142
1143 if (!features[fixAMMv1_3])
1144 BEAST_EXPECT(
1145 env.balance(bob, EUR) ==
1146 STAmount(EUR, UINT64_C(5000000000000001), -12));
1147 else
1148 BEAST_EXPECT(
1149 env.balance(bob, EUR) ==
1150 STAmount(EUR, UINT64_C(4999999999999999), -12));
1151 env.require(balance(carol, gw["USD"](3000)));
1152 env.require(balance(carol, gw2["EUR"](2750)));
1153
1154 // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR)
1155 env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt),
1156 ter(tesSUCCESS));
1157 env.close();
1158 if (!features[fixAMMv1_3])
1159 BEAST_EXPECT(amm.expectBalances(
1160 STAmount{USD, UINT64_C(3999999999999999), -12},
1161 STAmount{EUR, UINT64_C(4999999999999999), -12},
1162 IOUAmount{4472135954999580, -12}));
1163 else
1164 BEAST_EXPECT(amm.expectBalances(
1165 STAmount{USD, UINT64_C(4000000000000001), -12},
1166 STAmount{EUR, UINT64_C(5000000000000002), -12},
1167 IOUAmount{4472135954999579, -12}));
1168
1169 if (!features[fixAMMv1_3])
1170 BEAST_EXPECT(amm.expectLPTokens(
1171 alice, IOUAmount{4472135954999580, -12}));
1172 else
1173 BEAST_EXPECT(amm.expectLPTokens(
1174 alice, IOUAmount{4472135954999579, -12}));
1175 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1176 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0)));
1177
1178 // gw2 clawback all alice's EUR in amm. (4000 USD / 5000 EUR)
1179 env(amm::ammClawback(gw2, alice, EUR, USD, std::nullopt),
1180 ter(tesSUCCESS));
1181 env.close();
1182
1183 env.require(balance(carol, gw2["EUR"](2750)));
1184 env.require(balance(carol, gw["USD"](4000)));
1185 BEAST_EXPECT(!amm.ammExists());
1186 }
1187
1188 // Test AMMClawback for USD/XRP pool. Claw back all the USD for
1189 // different users.
1190 {
1191 Env env(*this, features);
1192 Account gw{"gateway"};
1193 Account alice{"alice"};
1194 Account bob{"bob"};
1195 env.fund(XRP(1000000), gw, alice, bob);
1196 env.close();
1197
1198 // gw sets asfAllowTrustLineClawback
1200 env.close();
1202
1203 // gw issues 600000 USD to Alice and 500000 USD to Bob.
1204 auto const USD = gw["USD"];
1205 env.trust(USD(1000000), alice);
1206 env(pay(gw, alice, USD(600000)));
1207 env.trust(USD(1000000), bob);
1208 env(pay(gw, bob, USD(500000)));
1209 env.close();
1210
1211 // gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
1212 AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS));
1213 if (!features[fixAMMv1_3])
1214 BEAST_EXPECT(amm.expectBalances(
1215 USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
1216 else
1217 BEAST_EXPECT(amm.expectBalances(
1218 USD(10000), XRP(2000), IOUAmount{4472135954999579, -9}));
1219 amm.deposit(alice, USD(1000), XRP(200));
1220 if (!features[fixAMMv1_3])
1221 BEAST_EXPECT(amm.expectBalances(
1222 USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
1223 else
1224 BEAST_EXPECT(amm.expectBalances(
1225 USD(11000), XRP(2200), IOUAmount{4919349550499536, -9}));
1226 amm.deposit(bob, USD(2000), XRP(400));
1227 if (!features[fixAMMv1_3])
1228 BEAST_EXPECT(amm.expectBalances(
1229 USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
1230 else
1231 BEAST_EXPECT(amm.expectBalances(
1232 USD(13000), XRP(2600), IOUAmount{5813776741499451, -9}));
1233 env.close();
1234
1235 auto aliceXrpBalance = env.balance(alice, XRP);
1236 auto bobXrpBalance = env.balance(bob, XRP);
1237
1238 // gw clawback all alice's USD in amm. (1000 USD / 200 XRP)
1239 env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
1240 ter(tesSUCCESS));
1241 env.close();
1242 if (!features[fixAMMv1_3])
1243 BEAST_EXPECT(amm.expectBalances(
1244 USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
1245 else
1246 BEAST_EXPECT(amm.expectBalances(
1247 USD(12000),
1248 XRPAmount(2400000001),
1249 IOUAmount{5366563145999494, -9}));
1250 if (!features[fixAMMv1_3])
1251 BEAST_EXPECT(expectLedgerEntryRoot(
1252 env, alice, aliceXrpBalance + XRP(200)));
1253 else
1254 BEAST_EXPECT(expectLedgerEntryRoot(
1255 env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1}));
1256 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1257
1258 // gw clawback all bob's USD in amm. (2000 USD / 400 XRP)
1259 env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt),
1260 ter(tesSUCCESS));
1261 env.close();
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),
1268 XRPAmount(2000000001),
1269 IOUAmount{4472135954999579, -9}));
1270 BEAST_EXPECT(
1271 expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400)));
1272 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1273 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1274 }
1275 }
1276
1277 void
1279 {
1280 testcase(
1281 "test AMMClawback from AMM pool with assets having the same "
1282 "issuer");
1283 using namespace jtx;
1284
1285 // Test AMMClawback for USD/EUR pool. The assets are issued by different
1286 // issuer. Claw back all the USD for different users.
1287 Env env(*this, features);
1288 Account gw{"gateway"};
1289 Account alice{"alice"};
1290 Account bob{"bob"};
1291 Account carol{"carol"};
1292 env.fund(XRP(1000000), gw, alice, bob, carol);
1293 env.close();
1294
1295 // gw sets asfAllowTrustLineClawback.
1297 env.close();
1299
1300 auto const USD = gw["USD"];
1301 env.trust(USD(100000), alice);
1302 env(pay(gw, alice, USD(10000)));
1303 env.trust(USD(100000), bob);
1304 env(pay(gw, bob, USD(9000)));
1305 env.trust(USD(100000), carol);
1306 env(pay(gw, carol, USD(8000)));
1307 env.close();
1308
1309 auto const EUR = gw["EUR"];
1310 env.trust(EUR(100000), alice);
1311 env(pay(gw, alice, EUR(10000)));
1312 env.trust(EUR(100000), bob);
1313 env(pay(gw, bob, EUR(9000)));
1314 env.trust(EUR(100000), carol);
1315 env(pay(gw, carol, EUR(8000)));
1316 env.close();
1317
1318 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
1319 env.close();
1320
1321 BEAST_EXPECT(amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
1322 amm.deposit(bob, USD(4000), EUR(1000));
1323 BEAST_EXPECT(
1324 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
1325 if (!features[fixAMMv1_3])
1326 amm.deposit(carol, USD(2000), EUR(500));
1327 else
1328 amm.deposit(carol, USD(2000.25), EUR(500));
1329 BEAST_EXPECT(
1330 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
1331 // gw clawback 1000 USD from carol.
1332 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS));
1333 env.close();
1334 BEAST_EXPECT(
1335 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
1336
1337 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
1338 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
1339 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1340 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1341 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1342 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1343 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1344 if (!features[fixAMMv1_3])
1345 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1346 else
1347 BEAST_EXPECT(
1348 env.balance(carol, USD) ==
1349 STAmount(USD, UINT64_C(5999'999999999999), -12));
1350 // 250 EUR goes back to carol.
1351 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1352
1353 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
1354 // then the corresponding EUR will also be clawed back
1355 // by gw.
1356 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
1358 ter(tesSUCCESS));
1359 env.close();
1360 BEAST_EXPECT(
1361 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
1362
1363 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
1364 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
1365 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1366 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1367 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1368 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1369 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
1370 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1371 if (!features[fixAMMv1_3])
1372 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1373 else
1374 BEAST_EXPECT(
1375 env.balance(carol, USD) ==
1376 STAmount(USD, UINT64_C(5999'999999999999), -12));
1377 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1378
1379 // gw clawback all USD from alice and set tfClawTwoAssets.
1380 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
1382 ter(tesSUCCESS));
1383 env.close();
1384 BEAST_EXPECT(amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
1385
1386 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1387 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
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 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1400 }
1401
1402 void
1404 {
1405 testcase(
1406 "test AMMClawback from AMM pool with assets having the same "
1407 "currency, but from different issuer");
1408 using namespace jtx;
1409
1410 // Test AMMClawback for USD/EUR pool. The assets are issued by different
1411 // issuer. Claw back all the USD for different users.
1412 Env env(*this, features);
1413 Account gw{"gateway"};
1414 Account gw2{"gateway2"};
1415 Account alice{"alice"};
1416 Account bob{"bob"};
1417 env.fund(XRP(1000000), gw, gw2, alice, bob);
1418 env.close();
1419
1420 // gw sets asfAllowTrustLineClawback.
1422 env.close();
1424
1425 // gw2 sets asfAllowTrustLineClawback.
1427 env.close();
1429
1430 env.trust(gw["USD"](100000), alice);
1431 env(pay(gw, alice, gw["USD"](8000)));
1432 env.trust(gw["USD"](100000), bob);
1433 env(pay(gw, bob, gw["USD"](7000)));
1434
1435 env.trust(gw2["USD"](100000), alice);
1436 env(pay(gw2, alice, gw2["USD"](6000)));
1437 env.trust(gw2["USD"](100000), bob);
1438 env(pay(gw2, bob, gw2["USD"](5000)));
1439 env.close();
1440
1441 AMM amm(env, alice, gw["USD"](1000), gw2["USD"](1500), ter(tesSUCCESS));
1442 env.close();
1443
1444 BEAST_EXPECT(amm.expectBalances(
1445 gw["USD"](1000),
1446 gw2["USD"](1500),
1447 IOUAmount{1224744871391589, -12}));
1448 amm.deposit(bob, gw["USD"](2000), gw2["USD"](3000));
1449 BEAST_EXPECT(amm.expectBalances(
1450 gw["USD"](3000),
1451 gw2["USD"](4500),
1452 IOUAmount{3674234614174767, -12}));
1453
1454 // Issuer does not match with asset.
1455 env(amm::ammClawback(
1456 gw,
1457 alice,
1458 gw2["USD"],
1459 gw["USD"],
1460 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
1461 ter(temMALFORMED));
1462
1463 // gw2 clawback 500 gw2[USD] from alice.
1464 env(amm::ammClawback(
1465 gw2,
1466 alice,
1467 gw2["USD"],
1468 gw["USD"],
1469 STAmount{Issue{gw2["USD"].currency, gw2.id()}, 500}),
1470 ter(tesSUCCESS));
1471 env.close();
1472 BEAST_EXPECT(amm.expectBalances(
1473 STAmount{gw["USD"], UINT64_C(2666666666666667), -12},
1474 gw2["USD"](4000),
1475 IOUAmount{3265986323710904, -12}));
1476
1477 BEAST_EXPECT(
1478 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
1479 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2449489742783178, -12}));
1480 BEAST_EXPECT(
1481 env.balance(alice, gw["USD"]) ==
1482 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
1483 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
1484 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
1485 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](2000));
1486
1487 // gw clawback all gw["USD"] from bob.
1488 env(amm::ammClawback(gw, bob, gw["USD"], gw2["USD"], std::nullopt),
1489 ter(tesSUCCESS));
1490 env.close();
1491 BEAST_EXPECT(amm.expectBalances(
1492 STAmount{gw["USD"], UINT64_C(6666666666666670), -13},
1493 gw2["USD"](1000),
1494 IOUAmount{8164965809277260, -13}));
1495
1496 BEAST_EXPECT(
1497 amm.expectLPTokens(alice, IOUAmount{8164965809277260, -13}));
1498 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1499 BEAST_EXPECT(
1500 env.balance(alice, gw["USD"]) ==
1501 STAmount(gw["USD"], UINT64_C(7333333333333333), -12));
1502 BEAST_EXPECT(env.balance(alice, gw2["USD"]) == gw2["USD"](4500));
1503 BEAST_EXPECT(env.balance(bob, gw["USD"]) == gw["USD"](5000));
1504 // Bob gets 3000 gw2["USD"] back and now his balance is 5000.
1505 BEAST_EXPECT(env.balance(bob, gw2["USD"]) == gw2["USD"](5000));
1506 }
1507
1508 void
1510 {
1511 testcase("test AMMClawback when issuing token for each other");
1512 using namespace jtx;
1513
1514 // gw and gw2 issues token for each other. Test AMMClawback from
1515 // each other.
1516 Env env(*this, features);
1517 Account gw{"gateway"};
1518 Account gw2{"gateway2"};
1519 Account alice{"alice"};
1520 env.fund(XRP(1000000), gw, gw2, alice);
1521 env.close();
1522
1523 // gw sets asfAllowTrustLineClawback.
1525 env.close();
1527
1528 // gw2 sets asfAllowTrustLineClawback.
1530 env.close();
1532
1533 auto const USD = gw["USD"];
1534 env.trust(USD(100000), gw2);
1535 env(pay(gw, gw2, USD(5000)));
1536 env.trust(USD(100000), alice);
1537 env(pay(gw, alice, USD(5000)));
1538
1539 auto const EUR = gw2["EUR"];
1540 env.trust(EUR(100000), gw);
1541 env(pay(gw2, gw, EUR(6000)));
1542 env.trust(EUR(100000), alice);
1543 env(pay(gw2, alice, EUR(6000)));
1544 env.close();
1545
1546 AMM amm(env, gw, USD(1000), EUR(2000), ter(tesSUCCESS));
1547 env.close();
1548 BEAST_EXPECT(amm.expectBalances(
1549 USD(1000), EUR(2000), IOUAmount{1414213562373095, -12}));
1550
1551 amm.deposit(gw2, USD(2000), EUR(4000));
1552 BEAST_EXPECT(amm.expectBalances(
1553 USD(3000), EUR(6000), IOUAmount{4242640687119285, -12}));
1554
1555 amm.deposit(alice, USD(3000), EUR(6000));
1556 BEAST_EXPECT(amm.expectBalances(
1557 USD(6000), EUR(12000), IOUAmount{8485281374238570, -12}));
1558
1559 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
1560 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828427124746190, -12}));
1561 BEAST_EXPECT(
1562 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
1563
1564 // gw claws back 1000 USD from gw2.
1565 env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS));
1566 env.close();
1567 BEAST_EXPECT(amm.expectBalances(
1568 USD(5000), EUR(10000), IOUAmount{7071067811865475, -12}));
1569
1570 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12}));
1571 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
1572 BEAST_EXPECT(
1573 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
1574
1575 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1576 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
1577 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
1578 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
1579
1580 // gw2 claws back 1000 EUR from gw.
1581 env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS));
1582 env.close();
1583 if (!features[fixAMMv1_3])
1584 BEAST_EXPECT(amm.expectBalances(
1585 USD(4500),
1586 STAmount(EUR, UINT64_C(9000000000000001), -12),
1587 IOUAmount{6363961030678928, -12}));
1588 else
1589 BEAST_EXPECT(amm.expectBalances(
1590 USD(4500), EUR(9000), IOUAmount{6363961030678928, -12}));
1591
1592 if (!features[fixAMMv1_3])
1593 BEAST_EXPECT(
1594 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
1595 else
1596 BEAST_EXPECT(
1597 amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
1598 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
1599 BEAST_EXPECT(
1600 amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
1601
1602 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1603 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
1604 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
1605 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
1606
1607 // gw2 claws back 4000 EUR from alice.
1608 env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS));
1609 env.close();
1610 if (!features[fixAMMv1_3])
1611 BEAST_EXPECT(amm.expectBalances(
1612 USD(2500),
1613 STAmount(EUR, UINT64_C(5000000000000001), -12),
1614 IOUAmount{3535533905932738, -12}));
1615 else
1616 BEAST_EXPECT(amm.expectBalances(
1617 USD(2500), EUR(5000), IOUAmount{3535533905932738, -12}));
1618
1619 if (!features[fixAMMv1_3])
1620 BEAST_EXPECT(
1621 amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
1622 else
1623 BEAST_EXPECT(
1624 amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
1625 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
1626 BEAST_EXPECT(
1627 amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12}));
1628
1629 BEAST_EXPECT(env.balance(alice, USD) == USD(4000));
1630 BEAST_EXPECT(env.balance(alice, EUR) == EUR(0));
1631 BEAST_EXPECT(env.balance(gw, EUR) == EUR(4000));
1632 BEAST_EXPECT(env.balance(gw2, USD) == USD(3000));
1633 }
1634
1635 void
1637 {
1638 testcase(
1639 "test AMMClawback from account which does not own any lptoken in "
1640 "the pool");
1641 using namespace jtx;
1642
1643 Env env(*this, features);
1644 Account gw{"gateway"};
1645 Account alice{"alice"};
1646 env.fund(XRP(1000000), gw, alice);
1647 env.close();
1648
1649 // gw sets asfAllowTrustLineClawback.
1651 env.close();
1653
1654 auto const USD = gw["USD"];
1655 env.trust(USD(100000), alice);
1656 env(pay(gw, alice, USD(5000)));
1657
1658 AMM amm(env, gw, USD(1000), XRP(2000), ter(tesSUCCESS));
1659 env.close();
1660
1661 // Alice did not deposit in the amm pool. So AMMClawback from Alice
1662 // will fail.
1663 env(amm::ammClawback(gw, alice, USD, XRP, USD(1000)),
1665 }
1666
1667 void
1669 {
1670 testcase("test assets frozen");
1671 using namespace jtx;
1672
1673 // test individually frozen trustline.
1674 {
1675 Env env(*this, features);
1676 Account gw{"gateway"};
1677 Account gw2{"gateway2"};
1678 Account alice{"alice"};
1679 env.fund(XRP(1000000), gw, gw2, alice);
1680 env.close();
1681
1682 // gw sets asfAllowTrustLineClawback.
1684 env.close();
1686
1687 // gw issues 3000 USD to Alice.
1688 auto const USD = gw["USD"];
1689 env.trust(USD(100000), alice);
1690 env(pay(gw, alice, USD(3000)));
1691 env.close();
1692 env.require(balance(alice, gw["USD"](3000)));
1693
1694 // gw2 issues 3000 EUR to Alice.
1695 auto const EUR = gw2["EUR"];
1696 env.trust(EUR(100000), alice);
1697 env(pay(gw2, alice, EUR(3000)));
1698 env.close();
1699 env.require(balance(alice, gw2["EUR"](3000)));
1700
1701 // Alice creates AMM pool of EUR/USD.
1702 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
1703 env.close();
1704
1705 BEAST_EXPECT(amm.expectBalances(
1706 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
1707
1708 // freeze trustline
1709 env(trust(gw, alice["USD"](0), tfSetFreeze));
1710 env.close();
1711
1712 // gw clawback 1000 USD from the AMM pool.
1713 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1714 ter(tesSUCCESS));
1715 env.close();
1716
1717 env.require(balance(alice, gw["USD"](1000)));
1718 env.require(balance(alice, gw2["EUR"](2500)));
1719 BEAST_EXPECT(amm.expectBalances(
1720 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
1721
1722 // Alice has half of its initial lptokens Left.
1723 BEAST_EXPECT(
1724 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
1725
1726 // gw clawback another 1000 USD from the AMM pool. The AMM pool will
1727 // be empty and get deleted.
1728 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1729 ter(tesSUCCESS));
1730 env.close();
1731
1732 // Alice should still has 1000 USD because gw clawed back from the
1733 // AMM pool.
1734 env.require(balance(alice, gw["USD"](1000)));
1735 env.require(balance(alice, gw2["EUR"](3000)));
1736
1737 // amm is automatically deleted.
1738 BEAST_EXPECT(!amm.ammExists());
1739 }
1740
1741 // test individually frozen trustline of both USD and EUR currency.
1742 {
1743 Env env(*this, features);
1744 Account gw{"gateway"};
1745 Account gw2{"gateway2"};
1746 Account alice{"alice"};
1747 env.fund(XRP(1000000), gw, gw2, alice);
1748 env.close();
1749
1750 // gw sets asfAllowTrustLineClawback.
1752 env.close();
1754
1755 // gw issues 3000 USD to Alice.
1756 auto const USD = gw["USD"];
1757 env.trust(USD(100000), alice);
1758 env(pay(gw, alice, USD(3000)));
1759 env.close();
1760 env.require(balance(alice, gw["USD"](3000)));
1761
1762 // gw2 issues 3000 EUR to Alice.
1763 auto const EUR = gw2["EUR"];
1764 env.trust(EUR(100000), alice);
1765 env(pay(gw2, alice, EUR(3000)));
1766 env.close();
1767 env.require(balance(alice, gw2["EUR"](3000)));
1768
1769 // Alice creates AMM pool of EUR/USD.
1770 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
1771 env.close();
1772
1773 BEAST_EXPECT(amm.expectBalances(
1774 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
1775
1776 // freeze trustlines
1777 env(trust(gw, alice["USD"](0), tfSetFreeze));
1778 env(trust(gw2, alice["EUR"](0), tfSetFreeze));
1779 env.close();
1780
1781 // gw clawback 1000 USD from the AMM pool.
1782 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1783 ter(tesSUCCESS));
1784 env.close();
1785
1786 env.require(balance(alice, gw["USD"](1000)));
1787 env.require(balance(alice, gw2["EUR"](2500)));
1788 BEAST_EXPECT(amm.expectBalances(
1789 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
1790 BEAST_EXPECT(
1791 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
1792 }
1793
1794 // test gw global freeze.
1795 {
1796 Env env(*this, features);
1797 Account gw{"gateway"};
1798 Account gw2{"gateway2"};
1799 Account alice{"alice"};
1800 env.fund(XRP(1000000), gw, gw2, alice);
1801 env.close();
1802
1803 // gw sets asfAllowTrustLineClawback.
1805 env.close();
1807
1808 // gw issues 3000 USD to Alice.
1809 auto const USD = gw["USD"];
1810 env.trust(USD(100000), alice);
1811 env(pay(gw, alice, USD(3000)));
1812 env.close();
1813 env.require(balance(alice, gw["USD"](3000)));
1814
1815 // gw2 issues 3000 EUR to Alice.
1816 auto const EUR = gw2["EUR"];
1817 env.trust(EUR(100000), alice);
1818 env(pay(gw2, alice, EUR(3000)));
1819 env.close();
1820 env.require(balance(alice, gw2["EUR"](3000)));
1821
1822 // Alice creates AMM pool of EUR/USD.
1823 AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS));
1824 env.close();
1825
1826 BEAST_EXPECT(amm.expectBalances(
1827 USD(2000), EUR(1000), IOUAmount{1414213562373095, -12}));
1828
1829 // global freeze
1830 env(fset(gw, asfGlobalFreeze));
1831 env.close();
1832
1833 // gw clawback 1000 USD from the AMM pool.
1834 env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
1835 ter(tesSUCCESS));
1836 env.close();
1837
1838 env.require(balance(alice, gw["USD"](1000)));
1839 env.require(balance(alice, gw2["EUR"](2500)));
1840 BEAST_EXPECT(amm.expectBalances(
1841 USD(1000), EUR(500), IOUAmount{7071067811865475, -13}));
1842 BEAST_EXPECT(
1843 amm.expectLPTokens(alice, IOUAmount{7071067811865475, -13}));
1844 }
1845
1846 // Test both assets are issued by the same issuer. And issuer sets
1847 // global freeze.
1848 {
1849 Env env(*this, features);
1850 Account gw{"gateway"};
1851 Account alice{"alice"};
1852 Account bob{"bob"};
1853 Account carol{"carol"};
1854 env.fund(XRP(1000000), gw, alice, bob, carol);
1855 env.close();
1856
1857 // gw sets asfAllowTrustLineClawback.
1859 env.close();
1861
1862 auto const USD = gw["USD"];
1863 env.trust(USD(100000), alice);
1864 env(pay(gw, alice, USD(10000)));
1865 env.trust(USD(100000), bob);
1866 env(pay(gw, bob, USD(9000)));
1867 env.trust(USD(100000), carol);
1868 env(pay(gw, carol, USD(8000)));
1869 env.close();
1870
1871 auto const EUR = gw["EUR"];
1872 env.trust(EUR(100000), alice);
1873 env(pay(gw, alice, EUR(10000)));
1874 env.trust(EUR(100000), bob);
1875 env(pay(gw, bob, EUR(9000)));
1876 env.trust(EUR(100000), carol);
1877 env(pay(gw, carol, EUR(8000)));
1878 env.close();
1879
1880 AMM amm(env, alice, EUR(2000), USD(8000), ter(tesSUCCESS));
1881 env.close();
1882
1883 BEAST_EXPECT(
1884 amm.expectBalances(USD(8000), EUR(2000), IOUAmount(4000)));
1885 amm.deposit(bob, USD(4000), EUR(1000));
1886 BEAST_EXPECT(
1887 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
1888 if (!features[fixAMMv1_3])
1889 amm.deposit(carol, USD(2000), EUR(500));
1890 else
1891 amm.deposit(carol, USD(2000.25), EUR(500));
1892 BEAST_EXPECT(
1893 amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
1894
1895 // global freeze
1896 env(fset(gw, asfGlobalFreeze));
1897 env.close();
1898
1899 // gw clawback 1000 USD from carol.
1900 env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)),
1901 ter(tesSUCCESS));
1902 env.close();
1903 BEAST_EXPECT(
1904 amm.expectBalances(USD(13000), EUR(3250), IOUAmount(6500)));
1905
1906 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
1907 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2000)));
1908 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1909 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1910 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1911 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1912 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1913 if (!features[fixAMMv1_3])
1914 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1915 else
1916 BEAST_EXPECT(
1917 env.balance(carol, USD) ==
1918 STAmount(USD, UINT64_C(5999'999999999999), -12));
1919 // 250 EUR goes back to carol.
1920 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1921
1922 // gw clawback 1000 USD from bob with tfClawTwoAssets flag.
1923 // then the corresponding EUR will also be clawed back
1924 // by gw.
1925 env(amm::ammClawback(gw, bob, USD, EUR, USD(1000)),
1927 ter(tesSUCCESS));
1928 env.close();
1929 BEAST_EXPECT(
1930 amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
1931
1932 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(4000)));
1933 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
1934 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1935 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1936 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1937 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1938 // 250 EUR did not go back to bob because tfClawTwoAssets is set.
1939 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1940 if (!features[fixAMMv1_3])
1941 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1942 else
1943 BEAST_EXPECT(
1944 env.balance(carol, USD) ==
1945 STAmount(USD, UINT64_C(5999'999999999999), -12));
1946 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1947
1948 // gw clawback all USD from alice and set tfClawTwoAssets.
1949 env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt),
1951 ter(tesSUCCESS));
1952 env.close();
1953 BEAST_EXPECT(
1954 amm.expectBalances(USD(4000), EUR(1000), IOUAmount(2000)));
1955
1956 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1957 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(1500)));
1958 BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(500)));
1959 BEAST_EXPECT(env.balance(alice, USD) == USD(2000));
1960 BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
1961 BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
1962 BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
1963 if (!features[fixAMMv1_3])
1964 BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
1965 else
1966 BEAST_EXPECT(
1967 env.balance(carol, USD) ==
1968 STAmount(USD, UINT64_C(5999'999999999999), -12));
1969 BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
1970 }
1971 }
1972
1973 void
1975 {
1976 testcase("test single depoit and clawback");
1977 using namespace jtx;
1978
1979 // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back
1980 // to the holder.
1981 Env env(*this, features);
1982 Account gw{"gateway"};
1983 Account alice{"alice"};
1984 env.fund(XRP(1000000000), gw, alice);
1985 env.close();
1986
1987 // gw sets asfAllowTrustLineClawback.
1989 env.close();
1991
1992 // gw issues 1000 USD to Alice.
1993 auto const USD = gw["USD"];
1994 env.trust(USD(100000), alice);
1995 env(pay(gw, alice, USD(1000)));
1996 env.close();
1997 env.require(balance(alice, gw["USD"](1000)));
1998
1999 // gw creates AMM pool of XRP/USD.
2000 AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS));
2001 env.close();
2002
2003 BEAST_EXPECT(amm.expectBalances(USD(400), XRP(100), IOUAmount(200000)));
2004
2005 amm.deposit(alice, USD(400));
2006 env.close();
2007
2008 BEAST_EXPECT(amm.expectBalances(
2009 USD(800), XRP(100), IOUAmount{2828427124746190, -10}));
2010
2011 auto aliceXrpBalance = env.balance(alice, XRP);
2012
2013 env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS));
2014 env.close();
2015
2016 if (!features[fixAMMv1_3])
2017 BEAST_EXPECT(amm.expectBalances(
2018 STAmount(USD, UINT64_C(5656854249492380), -13),
2019 XRP(70.710678),
2020 IOUAmount(200000)));
2021 else
2022 BEAST_EXPECT(amm.expectBalances(
2023 STAmount(USD, UINT64_C(565'685424949238), -12),
2024 XRP(70.710679),
2025 IOUAmount(200000)));
2026 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
2027 if (!features[fixAMMv1_3])
2028 BEAST_EXPECT(expectLedgerEntryRoot(
2029 env, alice, aliceXrpBalance + XRP(29.289322)));
2030 else
2031 BEAST_EXPECT(expectLedgerEntryRoot(
2032 env, alice, aliceXrpBalance + XRP(29.289321)));
2033 }
2034
2035 void
2036 run() override
2037 {
2040 testFeatureDisabled(all - featureAMMClawback);
2043 testAMMClawbackExceedBalance(all - fixAMMv1_3);
2045 testAMMClawbackAll(all - fixAMMv1_3);
2052 testAssetFrozen(all - fixAMMv1_3);
2054 testSingleDepositAndClawback(all - fixAMMv1_3);
2055 }
2056};
2057BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple);
2058} // namespace test
2059} // namespace ripple
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 testInvalidRequest(FeatureBitset features)
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)
jtx::Account const alice
Definition: AMMTest.h:77
jtx::Account const gw
Definition: AMMTest.h:75
jtx::Account const bob
Definition: AMMTest.h:78
jtx::Account const carol
Definition: AMMTest.h:76
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
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
A transaction testing environment.
Definition: Env.h:121
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:544
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:306
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:275
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition: Env.cpp:179
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
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 pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
bool expectLedgerEntryRoot(Env &env, Account const &acct, STAmount const &expectedValue)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
FeatureBitset supported_amendments()
Definition: Env.h:74
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:226
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecAMM_BALANCE
Definition: TER.h:329
@ tesSUCCESS
Definition: TER.h:244
constexpr std::uint32_t tfTwoAssetIfEmpty
Definition: TxFlags.h:215
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