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