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