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