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