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