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