xrpld
Loading...
Searching...
No Matches
AMMClawbackMPT_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/mpt.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/ter.h>
12#include <test/jtx/trust.h>
13#include <test/jtx/txflags.h>
14
15#include <xrpl/beast/unit_test/suite.h>
16#include <xrpl/ledger/helpers/AMMHelpers.h>
17#include <xrpl/protocol/Feature.h>
18#include <xrpl/protocol/Indexes.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/TER.h>
21#include <xrpl/protocol/TxFlags.h>
22#include <xrpl/protocol/XRPAmount.h>
23#include <xrpl/protocol/jss.h>
24
25#include <cstdint>
26#include <memory>
27#include <optional>
28
29namespace xrpl::test {
31{
32 void
34 {
35 testcase("test invalid request");
36 using namespace jtx;
37
38 for (auto const& feature : {features, features - featureSingleAssetVault})
39 {
40 Env env(*this, feature);
41 Account const gw{"gateway"};
42 Account const alice{"alice"};
43 Account const bob{"bob"};
44 env.fund(XRP(100000), gw, alice, bob);
45 env.close();
46
47 env(fset(gw, asfAllowTrustLineClawback));
48 env.close();
49
50 MPT const btc = MPTTester(
51 {.env = env,
52 .issuer = gw,
53 .holders = {alice},
54 .pay = 40'000,
55 .flags = tfMPTCanClawback | kMptDexFlags});
56
57 auto const usd = gw["USD"];
58 env.trust(usd(10000), alice);
59 env(pay(gw, alice, usd(100)));
60 env.close();
61
62 AMM amm(env, gw, btc(100), usd(100));
63
64 // holder does not exist
65 env(amm::ammClawback(gw, Account("unknown"), usd, btc, std::nullopt),
67
68 // can not clawback from self.
69 env(amm::ammClawback(gw, gw, usd, btc, std::nullopt), Ter(temMALFORMED));
70
71 // provided Asset does not match issuer gw
72 {
74 gw, alice, Issue{gw["USD"].currency, alice.id()}, btc, std::nullopt),
76 env(amm::ammClawback(gw, alice, MPTIssue{makeMptID(1, alice)}, usd, std::nullopt),
78 }
79
80 // Amount does not match asset
81 {
83 gw, alice, usd, btc, STAmount{Issue{gw["USD"].currency, alice.id()}, 1}),
86 gw, alice, btc, usd, STAmount{MPTIssue{makeMptID(1, alice)}, 10}),
88 }
89
90 // Amount is not greater than 0
91 {
92 env(amm::ammClawback(gw, alice, btc, usd, btc(-1)), Ter(temBAD_AMOUNT));
93 env(amm::ammClawback(gw, alice, btc, usd, btc(0)), Ter(temBAD_AMOUNT));
94 }
95
96 // clawback from account not holding lptoken
97 env(amm::ammClawback(gw, bob, btc, usd, btc(1000)), Ter(tecAMM_BALANCE));
98
99 // can not perform regular claw from amm pool
100 {
101 Issue const ammUsd(usd.currency, amm.ammAccount());
102 auto amount = amountFromString(ammUsd, "10");
103 auto const err =
104 feature[featureSingleAssetVault] ? tecPSEUDO_ACCOUNT : tecAMM_ACCOUNT;
105 env(claw(gw, amount), Ter(err));
106 }
107
108 // AMM does not exist
109 {
110 // withdraw all tokens will delete the AMM
111 amm.withdrawAll(gw);
112 BEAST_EXPECT(!amm.ammExists());
113 env.close();
114 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt), Ter(terNO_AMM));
115 }
116 }
117
118 // tfMPTCanClawback is not enabled
119 {
120 Env env(*this, features);
121 Account const gw{"gateway"};
122 Account const alice{"alice"};
123 env.fund(XRP(100000), gw, alice);
124 env.close();
125
126 env(fset(gw, asfAllowTrustLineClawback));
127 env.close();
128
129 MPT const btc =
130 MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 40'000});
131
132 auto const usd = gw["USD"];
133 env.trust(usd(10000), alice);
134 env(pay(gw, alice, usd(10000)));
135 env.close();
136
137 AMM amm(env, gw, btc(100), usd(100));
138 env.close();
139 amm.deposit(alice, 1'000);
140 env.close();
141
142 // can not clawback when tfMPTCanClawback is not enabled
143 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(tecNO_PERMISSION));
144 }
145
146 // can not claw with tfClawTwoAssets if the assets are not issued by the
147 // same issuer
148 {
149 Env env(*this, features);
150 Account const gw{"gateway"};
151 Account const gw2{"gateway2"};
152 Account const alice{"alice"};
153 env.fund(XRP(100000), gw, gw2, alice);
154 env.close();
155
156 env(fset(gw, asfAllowTrustLineClawback));
157 env.close();
158
159 auto const usd = gw["USD"];
160 env.trust(usd(10000), alice);
161 env(pay(gw, alice, usd(10000)));
162 env.close();
163
164 // todo: check tfMPTCanTransfer in xrpl.org
165 MPT const btc = MPTTester(
166 {.env = env,
167 .issuer = gw2,
168 .holders = {alice},
169 .pay = 40'000,
170 .flags = tfMPTCanClawback | kMptDexFlags});
171
172 AMM const amm(env, alice, btc(100), usd(100));
173 env.close();
174
175 {
176 // Return temINVALID_FLAG because the issuer set
177 // tfClawTwoAssets, but the issuer only issues USD in the pool.
178 // The issuer is not allowed to set tfClawTwoAssets flag if he
179 // did not issue both assets in the pool.
180 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt),
181 Txflags(tfClawTwoAssets),
183 }
184 }
185
186 // Test if the issuer did not set asfAllowTrustLineClawback, but the MPT
187 // is set tfMPTCanClawback, the issuer can claw MPT.
188 {
189 Env env(*this, features);
190 Account const gw{"gateway"};
191 Account const alice{"alice"};
192 env.fund(XRP(10000), gw, alice);
193 env.close();
194
195 MPT const btc = MPTTester(
196 {.env = env,
197 .issuer = gw,
198 .holders = {alice},
199 .pay = 40'000,
200 .flags = tfMPTCanClawback | kMptDexFlags});
201
202 AMM const amm(env, alice, btc(100), XRP(100));
203 env.close();
204
205 // If asfAllowTrustLineClawback is not set, the issuer can
206 // still claw MPT because the MPT's tfMPTCanClawback is set.
207 env(amm::ammClawback(gw, alice, btc, XRP, std::nullopt));
208 }
209 }
210
211 void
213 {
214 testcase("test feature disabled.");
215 using namespace jtx;
216 Env env{*this, features};
217 Account const gw("gateway"), alice("alice");
218 env.fund(XRP(30'000), gw, alice);
219 env.close();
220 env(fset(gw, asfAllowTrustLineClawback));
221 env.close();
222
223 MPT const btc = MPTTester(
224 {.env = env,
225 .issuer = gw,
226 .holders = {alice},
227 .pay = 10'000,
228 .flags = tfMPTCanClawback | kMptDexFlags});
229
230 AMM const amm(env, alice, XRP(1'000), btc(1'000));
231
232 // disable featureAMMClawback
233 env.disableFeature(featureAMMClawback);
234 env(amm::ammClawback(gw, alice, btc, XRP, std::nullopt), Ter(temDISABLED));
235
236 // enable featureAMMClawback and disable featureMPTokensV2
237 env.enableFeature(featureAMMClawback);
238 env.disableFeature(featureMPTokensV2);
239 env(amm::ammClawback(gw, alice, btc, XRP, btc(100)), Ter(temDISABLED));
240
241 // enable featureMPTokensV2
242 env.enableFeature(featureMPTokensV2);
243 env(amm::ammClawback(gw, alice, btc, XRP, btc(200)));
244 }
245
246 void
248 {
249 testcase("test AMMClawback specific amount");
250 using namespace jtx;
251
252 // AMMClawback from MPT/IOU issued by different issuers
253 {
254 Env env(*this, features);
255 Account const gw{"gateway"};
256 Account const gw2{"gateway2"};
257 Account const alice{"alice"};
258 env.fund(XRP(100000), gw, gw2, alice);
259 env.close();
260
261 env(fset(gw, asfAllowTrustLineClawback));
262 env(fset(gw2, asfAllowTrustLineClawback));
263 env.close();
264
265 auto const usd = gw["USD"];
266 env.trust(usd(100000), alice);
267 env(pay(gw, alice, usd(50000)));
268 env.close();
269
270 MPT const btc = MPTTester(
271 {.env = env,
272 .issuer = gw2,
273 .holders = {alice},
274 .pay = 40'000'000000,
275 .flags = tfMPTCanClawback | kMptDexFlags});
276
277 AMM const amm(env, alice, btc(1000000000), usd(2000));
278 env.close();
279 BEAST_EXPECT(amm.expectBalances(
280 btc(1'000'000000), usd(2000), IOUAmount{1414'213'562373095, -9}));
281
282 // can not set tfClawTwoAssets because the assets are not issued by
283 // the same issuer.
284 env(amm::ammClawback(gw2, alice, btc, usd, btc(1000)),
285 Txflags(tfClawTwoAssets),
287
288 auto aliceUSD = env.balance(alice, usd);
289 auto aliceBTC = env.balance(alice, btc);
290 // gw clawback 1000 USD from alice
291 env(amm::ammClawback(gw, alice, usd, btc, usd(1000)));
292 env.close();
293
294 BEAST_EXPECT(
295 amm.expectBalances(btc(500'000000), usd(1000), IOUAmount{707'106'7811865475, -10}));
296 // USD is clawed back,
297 env.require(Balance(alice, aliceUSD));
298 // a proportional amount of BTC is returned to alice
299 env.require(Balance(alice, aliceBTC + btc(500'000000)));
300 aliceBTC = env.balance(alice, btc);
301
302 // gw2 clawback 250'000000 BTC from alice
303 env(amm::ammClawback(gw2, alice, btc, usd, btc(250'000000)));
304 env.close();
305 BEAST_EXPECT(
306 amm.expectBalances(btc(250'000000), usd(500), IOUAmount{353'553'3905932737, -10}));
307 env.require(Balance(alice, aliceUSD + usd(500)));
308 env.require(Balance(alice, aliceBTC));
309 aliceUSD = env.balance(alice, usd);
310
311 // gw2 clawback 500'000000 BTC which exceeds the balance,
312 // this will clawback all and the amm will be deleted.
313 env(amm::ammClawback(gw2, alice, btc, usd, btc(500'000000)));
314 env.close();
315 BEAST_EXPECT(!amm.ammExists());
316 env.require(Balance(alice, aliceUSD + usd(500)));
317 env.require(Balance(alice, aliceBTC));
318 }
319
320 // AMMClawback from MPT/XRP pool
321 {
322 Env env(*this, features);
323 Account const gw{"gateway"};
324 Account const alice{"alice"};
325 Account const bob{"bob"};
326 env.fund(XRP(100000), gw, alice, bob);
327 env.close();
328
329 env(fset(gw, asfAllowTrustLineClawback));
330 env.close();
331
332 MPT const btc = MPTTester(
333 {.env = env,
334 .issuer = gw,
335 .holders = {alice, bob},
336 .pay = 40'000'000000,
337 .flags = tfMPTCanClawback | kMptDexFlags});
338
339 AMM amm(env, alice, btc(1000000000), XRP(2000));
340 env.close();
341 BEAST_EXPECT(amm.expectBalances(
342 btc(1'000'000000), XRP(2000), IOUAmount{1'414'213'562'373095, -6}));
343
344 amm.deposit(bob, btc(2'000'000000), XRP(4000));
345 BEAST_EXPECT(amm.expectBalances(
346 btc(3'000'000000), XRP(6000), IOUAmount{4'242'640'687'119285, -6}));
347
348 auto aliceXRP = env.balance(alice, XRP);
349 auto aliceBTC = env.balance(alice, btc);
350 auto bobXRP = env.balance(bob, XRP);
351 auto bobBTC = env.balance(bob, btc);
352
353 // can not claw XRP
354 env(amm::ammClawback(gw, alice, XRP, btc, XRP(1000)), Ter(temMALFORMED));
355 // can not set tfClawTwoAssets
356 env(amm::ammClawback(gw, alice, btc, XRP, btc(1000)),
357 Txflags(tfClawTwoAssets),
359
360 // gw clawback 500 BTC from alice
361 env(amm::ammClawback(gw, alice, btc, XRP, btc(500)));
362 env.close();
363 BEAST_EXPECT(amm.expectBalances(
364 btc(2'999'999501),
365 STAmount{XRP, UINT64_C(5'999'999001)},
366 IOUAmount{4'242'639'980'012504, -6}));
367 env.require(Balance(alice, aliceXRP + drops(999)));
368 env.require(Balance(alice, aliceBTC));
369 env.require(Balance(bob, bobXRP));
370 env.require(Balance(bob, bobBTC));
371 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'414'212'855'266314, -6}));
372 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'828'427'124'74619, -5}));
373 aliceXRP = env.balance(alice, XRP);
374
375 // gw clawback 1000'000000 BTC from bob
376 env(amm::ammClawback(gw, bob, btc, XRP, btc(1'000'000000)));
377 env.close();
378 BEAST_EXPECT(amm.expectBalances(
379 btc(1'999'999501),
380 STAmount{XRP, UINT64_C(3'999'999002)},
381 IOUAmount{2828426418'110813, -6}));
382 env.require(Balance(alice, aliceXRP));
383 env.require(Balance(alice, aliceBTC));
384 env.require(Balance(bob, bobXRP + XRPAmount(1999999999)));
385 env.require(Balance(bob, bobBTC));
386 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'414'212'855'266314, -6}));
387 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{1'414'213'562'844499, -6}));
388 bobXRP = env.balance(bob, XRP);
389
390 // gw clawback 1000'000000 BTC from alice, which exceeds her balance
391 // will clawback all her balance
392 env(amm::ammClawback(gw, alice, btc, XRP, btc(1'000'000000)));
393 env.close();
394 BEAST_EXPECT(amm.expectBalances(
395 btc(1'000'000001), XRPAmount(2'000'000002), IOUAmount{1'414'213'562'844499, -6}));
396 env.require(Balance(alice, aliceXRP + STAmount{XRP, UINT64_C(1'999'999000)}));
397 env.require(Balance(alice, aliceBTC));
398 env.require(Balance(bob, bobXRP));
399 env.require(Balance(bob, bobBTC));
400 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
401 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{1'414'213'562'844499, -6}));
402 aliceXRP = env.balance(alice, XRP);
403
404 // gw clawback from bob, which exceeds his balance
405 env(amm::ammClawback(gw, bob, btc, XRP, btc(2'000'000000)));
406 env.close();
407 // amm is empty and deleted
408 BEAST_EXPECT(!amm.ammExists());
409 env.require(Balance(alice, aliceXRP));
410 env.require(Balance(alice, aliceBTC));
411 env.require(Balance(bob, bobXRP + XRPAmount(2000000002)));
412 env.require(Balance(bob, bobBTC));
413 }
414
415 // AMMClawback from MPT/MPT pool, different issuers
416 {
417 Env env(*this, features);
418 Account const gw{"gateway"};
419 Account const gw2{"gateway2"};
420 Account const alice{"alice"};
421 Account const bob{"bob"};
422 env.fund(XRP(100000), gw, gw2, alice, bob);
423 env.close();
424
425 env(fset(gw, asfAllowTrustLineClawback));
426 env(fset(gw2, asfAllowTrustLineClawback));
427 env.close();
428
429 MPT const btc = MPTTester(
430 {.env = env,
431 .issuer = gw,
432 .holders = {alice, bob},
433 .pay = 40'000'000000,
434 .flags = tfMPTCanClawback | kMptDexFlags});
435
436 MPT const eth = MPTTester(
437 {.env = env,
438 .issuer = gw2,
439 .holders = {alice, bob},
440 .pay = 30'000'000000,
441 .flags = tfMPTCanClawback | kMptDexFlags});
442
443 AMM amm(env, alice, btc(2'000'000000), eth(3'000'000000));
444 env.close();
445 BEAST_EXPECT(amm.expectBalances(
446 btc(2'000'000000), eth(3'000'000000), IOUAmount{2'449'489'742'783178, -6}));
447
448 amm.deposit(bob, btc(4'000'000000), eth(6'000'000000));
449 BEAST_EXPECT(amm.expectBalances(
450 btc(6'000'000000), eth(9'000'000000), IOUAmount{7'348'469'228'349534, -6}));
451
452 auto aliceBTC = env.balance(alice, btc);
453 auto aliceETH = env.balance(alice, eth);
454 auto bobBTC = env.balance(bob, btc);
455 auto bobETH = env.balance(bob, eth);
456
457 // gw clawback BTC from alice
458 env(amm::ammClawback(gw, alice, btc, eth, btc(1'000'000000)));
459 env.close();
460 BEAST_EXPECT(amm.expectBalances(
461 btc(5'000'000000), eth(7'500'000000), IOUAmount{6'123'724'356'957944, -6}));
462 env.require(Balance(alice, aliceBTC));
463 env.require(Balance(alice, aliceETH + eth(1'500'000000)));
464 env.require(Balance(bob, bobBTC));
465 env.require(Balance(bob, bobETH));
466 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
467 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{4'898'979'485'566356, -6}));
468 aliceETH = env.balance(alice, eth);
469
470 // gw2 clawback ETH from bob
471 env(amm::ammClawback(gw2, bob, eth, btc, eth(3'000'000000)));
472 env.close();
473 BEAST_EXPECT(amm.expectBalances(
474 btc(3'000'000000), eth(4'500'000000), IOUAmount{3'674'234'614'174766, -6}));
475 env.require(Balance(alice, aliceBTC));
476 env.require(Balance(alice, aliceETH));
477 env.require(Balance(bob, bobBTC + btc(2'000'000000)));
478 env.require(Balance(bob, bobETH));
479 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
480 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6}));
481 bobBTC = env.balance(bob, btc);
482
483 // gw2 clawback ETH from alice, which exceeds her balance
484 env(amm::ammClawback(gw2, alice, eth, btc, eth(4'000'000000)));
485 env.close();
486 BEAST_EXPECT(amm.expectBalances(
487 btc(2'000'000001), eth(3'000'000001), IOUAmount{2'449'489'742'783178, -6}));
488 env.require(Balance(alice, aliceBTC + btc(999'999999)));
489 env.require(Balance(alice, aliceETH));
490 env.require(Balance(bob, bobBTC));
491 env.require(Balance(bob, bobETH));
492 aliceBTC = env.balance(alice, btc);
493
494 // gw clawback BTC from bob, which exceeds his balance
495 env(amm::ammClawback(gw, bob, btc, eth, btc(3'000'000000)));
496 env.close();
497 // amm is empty and deleted
498 BEAST_EXPECT(!amm.ammExists());
499 env.require(Balance(alice, aliceBTC));
500 env.require(Balance(alice, aliceETH));
501 env.require(Balance(bob, bobBTC));
502 env.require(Balance(bob, bobETH + eth(3'000'000001)));
503 }
504 }
505
506 void
508 {
509 testcase("test AMMClawback all");
510 using namespace jtx;
511
512 // AMMClawback all from MPT/IOU issued by different issuers
513 {
514 Env env(*this, features);
515 Account const gw{"gateway"};
516 Account const gw2{"gateway2"};
517 Account const alice{"alice"};
518 Account const bob{"bob"};
519 env.fund(XRP(100000), gw, gw2, alice, bob);
520 env.close();
521
522 env(fset(gw, asfAllowTrustLineClawback));
523 env(fset(gw2, asfAllowTrustLineClawback));
524 env.close();
525
526 auto const usd = gw["USD"];
527 env.trust(usd(100000), alice);
528 env(pay(gw, alice, usd(50000)));
529 env.trust(usd(200000), bob);
530 env(pay(gw, bob, usd(60000)));
531 env.close();
532
533 MPT const btc = MPTTester(
534 {.env = env,
535 .issuer = gw2,
536 .holders = {alice, bob},
537 .pay = 40'000'000000,
538 .flags = tfMPTCanClawback | kMptDexFlags});
539
540 AMM amm(env, alice, btc(2000000000), usd(2000));
541 env.close();
542 BEAST_EXPECT(amm.expectBalances(btc(2'000'000000), usd(2000), IOUAmount(2000000)));
543
544 // gw clawback all BTC from alice
545 amm.deposit(bob, btc(1'000'000000), usd(2000));
546 env.close();
547 BEAST_EXPECT(amm.expectBalances(btc(3'000'000000), usd(3000), IOUAmount(3000000)));
548
549 auto aliceBTC = env.balance(alice, btc);
550 auto aliceUSD = env.balance(alice, usd);
551 auto bobBTC = env.balance(bob, btc);
552 auto bobUSD = env.balance(bob, usd);
553
554 // gw2 clawback all BTC from alice
555 env(amm::ammClawback(gw2, alice, btc, usd, std::nullopt));
556 env.close();
557 BEAST_EXPECT(amm.expectBalances(btc(1'000'000000), usd(1000), IOUAmount(1000000)));
558 env.require(Balance(alice, aliceBTC));
559 env.require(Balance(alice, aliceUSD + usd(2000)));
560 env.require(Balance(bob, bobBTC));
561 env.require(Balance(bob, bobUSD));
562 aliceUSD = env.balance(alice, usd);
563
564 // gw clawback all USD from bob
565 env(amm::ammClawback(gw, bob, usd, btc, std::nullopt));
566 env.close();
567 // amm is empty and deleted
568 BEAST_EXPECT(!amm.ammExists());
569 env.require(Balance(alice, aliceBTC));
570 env.require(Balance(alice, aliceUSD));
571 env.require(Balance(bob, bobBTC + btc(1'000'000000)));
572 env.require(Balance(bob, bobUSD));
573 }
574
575 // AMMClawback all from MPT/XRP pool
576 {
577 Env env(*this, features);
578 Account const gw{"gateway"};
579 Account const alice{"alice"};
580 Account const bob{"bob"};
581 env.fund(XRP(100000), gw, alice, bob);
582 env.close();
583
584 env(fset(gw, asfAllowTrustLineClawback));
585 env.close();
586
587 MPT const btc = MPTTester(
588 {.env = env,
589 .issuer = gw,
590 .holders = {alice, bob},
591 .pay = 40'000'000000,
592 .flags = tfMPTCanClawback | kMptDexFlags});
593
594 AMM amm(env, alice, btc(5000), XRP(10'000));
595 env.close();
596 BEAST_EXPECT(
597 amm.expectBalances(btc(5'000), XRP(10'000), IOUAmount{7'071'067'811865475, -9}));
598
599 amm.deposit(bob, btc(10'000), XRP(20'000));
600 BEAST_EXPECT(
601 amm.expectBalances(btc(15'000), XRP(30'000), IOUAmount{21'213'203'43559642, -8}));
602
603 auto aliceXRP = env.balance(alice, XRP);
604 auto aliceBTC = env.balance(alice, btc);
605 auto bobXRP = env.balance(bob, XRP);
606 auto bobBTC = env.balance(bob, btc);
607
608 // gw clawback all BTC from alice
609 env(amm::ammClawback(gw, alice, btc, XRP, std::nullopt));
610 env.close();
611 BEAST_EXPECT(
612 amm.expectBalances(btc(10'000), XRP(20'000), IOUAmount{14'142'135'62373094, -8}));
613 env.require(Balance(alice, aliceBTC));
614 env.require(Balance(alice, aliceXRP + XRP(10'000)));
615 env.require(Balance(bob, bobBTC));
616 env.require(Balance(bob, bobXRP));
617 aliceXRP = env.balance(alice, XRP);
618
619 // gw clawback all BTC from bob
620 env(amm::ammClawback(gw, bob, btc, XRP, std::nullopt));
621 env.close();
622 // amm is empty and deleted
623 BEAST_EXPECT(!amm.ammExists());
624 env.require(Balance(alice, aliceBTC));
625 env.require(Balance(alice, aliceXRP));
626 env.require(Balance(bob, bobBTC));
627 env.require(Balance(bob, bobXRP + XRP(20'000)));
628 }
629
630 // AMMClawback all from MPT/MPT pool, different issuers
631 {
632 Env env(*this, features);
633 Account const gw{"gateway"};
634 Account const gw2{"gateway2"};
635 Account const alice{"alice"};
636 Account const bob{"bob"};
637 env.fund(XRP(100000), gw, gw2, alice, bob);
638 env.close();
639
640 env(fset(gw, asfAllowTrustLineClawback));
641 env(fset(gw2, asfAllowTrustLineClawback));
642 env.close();
643
644 MPT const btc = MPTTester(
645 {.env = env,
646 .issuer = gw,
647 .holders = {alice, bob},
648 .pay = 40'000'000000,
649 .flags = tfMPTCanClawback | kMptDexFlags});
650
651 MPT const eth = MPTTester(
652 {.env = env,
653 .issuer = gw2,
654 .holders = {alice, bob},
655 .pay = 30'000'000000,
656 .flags = tfMPTCanClawback | kMptDexFlags});
657
658 AMM amm(env, alice, btc(20'000), eth(50'000));
659 env.close();
660 BEAST_EXPECT(
661 amm.expectBalances(btc(20'000), eth(50'000), IOUAmount{31'622'77660168379, -11}));
662
663 amm.deposit(bob, btc(40'000), eth(100'000));
664 BEAST_EXPECT(
665 amm.expectBalances(btc(60'000), eth(150'000), IOUAmount{94'868'32980505137, -11}));
666
667 auto aliceBTC = env.balance(alice, btc);
668 auto aliceETH = env.balance(alice, eth);
669 auto bobBTC = env.balance(bob, btc);
670 auto bobETH = env.balance(bob, eth);
671
672 // gw clawback all BTC from bob
673 env(amm::ammClawback(gw, bob, btc, eth, std::nullopt));
674 env.close();
675 BEAST_EXPECT(
676 amm.expectBalances(btc(20'000), eth(50'000), IOUAmount{31'622'77660168379, -11}));
677 env.require(Balance(alice, aliceBTC));
678 env.require(Balance(alice, aliceETH));
679 env.require(Balance(bob, bobBTC));
680 env.require(Balance(bob, bobETH + eth(100'000)));
681 bobETH = env.balance(bob, eth);
682
683 // gw2 clawback all ETH from alice
684 env(amm::ammClawback(gw2, alice, eth, btc, std::nullopt));
685 env.close();
686 // amm is empty and deleted
687 BEAST_EXPECT(!amm.ammExists());
688 env.require(Balance(alice, aliceBTC + btc(20'000)));
689 env.require(Balance(alice, aliceETH));
690 env.require(Balance(bob, bobBTC));
691 env.require(Balance(bob, bobETH));
692 }
693 }
694
695 void
697 {
698 testcase("test AMMClawback specific amount, assets have the same issuer");
699 using namespace jtx;
700
701 // AMMClawback from MPT/IOU issued by the same issuer
702 {
703 Env env(*this, features);
704 Account const gw{"gateway"};
705 Account const alice{"alice"};
706 Account const bob{"bob"};
707 env.fund(XRP(100000), gw, alice, bob);
708 env.close();
709
710 env(fset(gw, asfAllowTrustLineClawback));
711 env.close();
712
713 auto const usd = gw["USD"];
714 env.trust(usd(100000), alice);
715 env(pay(gw, alice, usd(50000)));
716 env.trust(usd(100000), bob);
717 env(pay(gw, bob, usd(40000)));
718 env.close();
719
720 MPT const btc = MPTTester(
721 {.env = env,
722 .issuer = gw,
723 .holders = {alice, bob},
724 .pay = 40'000'000000,
725 .flags = tfMPTCanClawback | kMptDexFlags});
726
727 AMM amm(env, alice, btc(1'000'000000), usd(2000));
728 env.close();
729 BEAST_EXPECT(amm.expectBalances(
730 btc(1'000'000000), usd(2000), IOUAmount{1414'213'562373095, -9}));
731
732 amm.deposit(bob, btc(500'000000), usd(1000));
733 BEAST_EXPECT(amm.expectBalances(
734 btc(1'500'000000),
735 STAmount{usd, UINT64_C(2'999'999999999999), -12},
736 IOUAmount{2'121'320'343559642, -9}));
737
738 auto aliceUSD = env.balance(alice, usd);
739 auto aliceBTC = env.balance(alice, btc);
740 auto bobUSD = env.balance(bob, usd);
741 auto bobBTC = env.balance(bob, btc);
742
743 // gw clawback 500 USD from alice.
744 env(amm::ammClawback(gw, alice, usd, btc, usd(500)));
745 env.close();
746 BEAST_EXPECT(amm.expectBalances(
747 btc(1250'000001), usd(2500), IOUAmount{1'767'766'952966369, -9}));
748 env.require(Balance(alice, aliceUSD));
749 env.require(Balance(alice, aliceBTC + btc(249'999999)));
750 env.require(Balance(bob, bobUSD));
751 env.require(Balance(bob, bobBTC));
752 aliceBTC = env.balance(alice, btc);
753 // gw clawback 250'000000 BTC and 500 USD from bob
754 // with tfClawTwoAssets
755 env(amm::ammClawback(gw, bob, btc, usd, btc(250'000000)), Txflags(tfClawTwoAssets));
756 env.close();
757 BEAST_EXPECT(amm.expectBalances(
758 btc(1'000'000002),
759 STAmount{usd, UINT64_C(2000'0000004), -7},
760 IOUAmount{1'414'213'562655938, -9}));
761 env.require(Balance(alice, aliceUSD));
762 env.require(Balance(alice, aliceBTC));
763 env.require(Balance(bob, bobUSD));
764 env.require(Balance(bob, bobBTC));
765 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'060'660'171779822, -9}));
766 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{353'553'390876116, -9}));
767
768 // gw clawback USD from alice exceeding her balance
769 env(amm::ammClawback(gw, alice, usd, btc, usd(5'000)));
770 env.close();
771 BEAST_EXPECT(amm.expectBalances(
772 btc(250'000001),
773 STAmount{usd, UINT64_C(500'0000004), -7},
774 IOUAmount{353'553'390876116, -9}));
775 env.require(Balance(alice, aliceUSD));
776 env.require(Balance(alice, aliceBTC + btc(750'000001)));
777 env.require(Balance(bob, bobUSD));
778 env.require(Balance(bob, bobBTC));
779 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
780 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{353'553'390876116, -9}));
781 aliceBTC = env.balance(alice, btc);
782
783 // gw clawback BTC from bob which exceeds his balance with
784 // tfClawTwoAssets
785 env(amm::ammClawback(gw, bob, btc, usd, btc(300'000000)), Txflags(tfClawTwoAssets));
786 env.close();
787 // amm is empty and deleted
788 BEAST_EXPECT(!amm.ammExists());
789 env.require(Balance(alice, aliceUSD));
790 env.require(Balance(alice, aliceBTC));
791 // USD is also clawed back from bob because of tfClawTwoAssets,
792 // bob's USD balance will not change
793 env.require(Balance(bob, bobUSD));
794 env.require(Balance(bob, bobBTC));
795 }
796
797 // AMMClawback from MPT/MPT issued by the same issuer
798 {
799 Env env(*this, features);
800 Account const gw{"gateway"};
801 Account const alice{"alice"};
802 Account const bob{"bob"};
803 env.fund(XRP(100000), gw, alice, bob);
804 env.close();
805
806 env(fset(gw, asfAllowTrustLineClawback));
807 env.close();
808
809 MPT const btc = MPTTester(
810 {.env = env,
811 .issuer = gw,
812 .holders = {alice, bob},
813 .pay = 40'000'000000,
814 .flags = tfMPTCanClawback | kMptDexFlags});
815
816 MPT const eth = MPTTester(
817 {.env = env,
818 .issuer = gw,
819 .holders = {alice, bob},
820 .pay = 30'000'000000,
821 .flags = tfMPTCanClawback | kMptDexFlags});
822
823 AMM amm(env, alice, btc(2'000'000000), eth(3'000'000000));
824 env.close();
825 BEAST_EXPECT(amm.expectBalances(
826 btc(2'000'000000), eth(3'000'000000), IOUAmount{2'449'489'742'783178, -6}));
827
828 amm.deposit(bob, btc(4'000'000000), eth(6'000'000000));
829 BEAST_EXPECT(amm.expectBalances(
830 btc(6'000'000000), eth(9'000'000000), IOUAmount{7'348'469'228'349534, -6}));
831
832 auto aliceBTC = env.balance(alice, btc);
833 auto aliceETH = env.balance(alice, eth);
834 auto bobBTC = env.balance(bob, btc);
835 auto bobETH = env.balance(bob, eth);
836
837 // gw clawback BTC from alice
838 env(amm::ammClawback(gw, alice, btc, eth, btc(1'000'000000)));
839 env.close();
840 BEAST_EXPECT(amm.expectBalances(
841 btc(5'000'000000), eth(7'500'000000), IOUAmount{6'123'724'356'957944, -6}));
842 env.require(Balance(alice, aliceBTC));
843 env.require(Balance(alice, aliceETH + eth(1'500'000000)));
844 env.require(Balance(bob, bobBTC));
845 env.require(Balance(bob, bobETH));
846 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
847 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{4'898'979'485'566356, -6}));
848 aliceETH = env.balance(alice, eth);
849
850 // gw clawback ETH and BTC from bob with tfClawTwoAssets
851 env(amm::ammClawback(gw, bob, eth, btc, eth(3'000'000000)), Txflags(tfClawTwoAssets));
852 env.close();
853 BEAST_EXPECT(amm.expectBalances(
854 btc(3'000'000000), eth(4'500'000000), IOUAmount{3'674'234'614'174766, -6}));
855 env.require(Balance(alice, aliceBTC));
856 env.require(Balance(alice, aliceETH));
857 env.require(Balance(bob, bobBTC));
858 env.require(Balance(bob, bobETH));
859 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
860 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6}));
861
862 // gw clawback BTC from alice, which exceeds her balance with
863 // tfClawTwoAssets
864 env(amm::ammClawback(gw, alice, btc, eth, btc(3'000'000000)), Txflags(tfClawTwoAssets));
865 env.close();
866 BEAST_EXPECT(amm.expectBalances(
867 btc(2'000'000001), eth(3'000'000001), IOUAmount{2'449'489'742'783178, -6}));
868 env.require(Balance(alice, aliceBTC));
869 env.require(Balance(alice, aliceETH));
870 env.require(Balance(bob, bobBTC));
871 env.require(Balance(bob, bobETH));
872 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
873 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6}));
874
875 // gw clawback ETH from bob, which is the same as his balance
876 env(amm::ammClawback(gw, bob, eth, btc, eth(3'000'000001)));
877 env.close();
878 // amm is empty and deleted
879 BEAST_EXPECT(!amm.ammExists());
880 env.require(Balance(alice, aliceBTC));
881 env.require(Balance(alice, aliceETH));
882 env.require(Balance(bob, bobBTC + btc(2'000'000001)));
883 env.require(Balance(bob, bobETH));
884 }
885 }
886
887 void
889 {
890 testcase("test AMMClawback all, assets have the same issuer");
891 using namespace jtx;
892
893 // AMMClawback all from MPT/IOU issued by the same issuer
894 {
895 Env env(*this, features);
896 Account const gw{"gateway"};
897 Account const alice{"alice"};
898 Account const bob{"bob"};
899 env.fund(XRP(100000), gw, alice, bob);
900 env.close();
901
902 env(fset(gw, asfAllowTrustLineClawback));
903 env.close();
904
905 auto const usd = gw["USD"];
906 env.trust(usd(100000), alice);
907 env(pay(gw, alice, usd(50000)));
908 env.trust(usd(200000), bob);
909 env(pay(gw, bob, usd(60000)));
910 env.close();
911
912 MPT const btc = MPTTester(
913 {.env = env,
914 .issuer = gw,
915 .holders = {alice, bob},
916 .pay = 40'000'000000,
917 .flags = tfMPTCanClawback | kMptDexFlags});
918
919 AMM amm(env, alice, btc(2'000'000000), usd(8'000));
920 env.close();
921 BEAST_EXPECT(amm.expectBalances(btc(2'000'000000), usd(8'000), IOUAmount(4'000'000)));
922
923 amm.deposit(bob, btc(1'000'000000), usd(4'000));
924 env.close();
925 BEAST_EXPECT(amm.expectBalances(btc(3'000'000000), usd(12'000), IOUAmount(6'000'000)));
926
927 auto aliceBTC = env.balance(alice, btc);
928 auto aliceUSD = env.balance(alice, usd);
929 auto bobBTC = env.balance(bob, btc);
930 auto bobUSD = env.balance(bob, usd);
931
932 // gw clawback all BTC and USD from alice
933 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Txflags(tfClawTwoAssets));
934 env.close();
935
936 BEAST_EXPECT(amm.expectBalances(btc(1'000'000000), usd(4'000), IOUAmount(2'000'000)));
937 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2'000'000)));
938 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
939 env.require(Balance(alice, aliceBTC));
940 env.require(Balance(alice, aliceUSD));
941 env.require(Balance(bob, bobBTC));
942 env.require(Balance(bob, bobUSD));
943
944 // gw clawback all USD from bob
945 env(amm::ammClawback(gw, bob, usd, btc, std::nullopt));
946 env.close();
947 // amm is empty and deleted
948 BEAST_EXPECT(!amm.ammExists());
949 env.require(Balance(alice, aliceBTC));
950 env.require(Balance(alice, aliceUSD));
951 env.require(Balance(bob, bobBTC + btc(1'000'000000)));
952 env.require(Balance(bob, bobUSD));
953 }
954
955 // AMMClawback all from MPT/MPT issued by the same issuer
956 {
957 Env env(*this, features);
958 Account const gw{"gateway"};
959 Account const alice{"alice"};
960 Account const bob{"bob"};
961 env.fund(XRP(100000), gw, alice, bob);
962 env.close();
963
964 env(fset(gw, asfAllowTrustLineClawback));
965 env.close();
966
967 MPT const btc = MPTTester(
968 {.env = env,
969 .issuer = gw,
970 .holders = {alice, bob},
971 .pay = 40'000'000000,
972 .flags = tfMPTCanClawback | kMptDexFlags});
973
974 MPT const eth = MPTTester(
975 {.env = env,
976 .issuer = gw,
977 .holders = {alice, bob},
978 .pay = 30'000'000000,
979 .flags = tfMPTCanClawback | kMptDexFlags});
980
981 AMM amm(env, alice, btc(20'000), eth(10'000));
982 env.close();
983 BEAST_EXPECT(
984 amm.expectBalances(btc(20'000), eth(10'000), IOUAmount{14'142'13562373095, -11}));
985
986 amm.deposit(bob, btc(40'000), eth(20'000));
987 BEAST_EXPECT(
988 amm.expectBalances(btc(60'000), eth(30'000), IOUAmount{42'426'40687119285, -11}));
989
990 auto aliceBTC = env.balance(alice, btc);
991 auto aliceETH = env.balance(alice, eth);
992 auto bobBTC = env.balance(bob, btc);
993 auto bobETH = env.balance(bob, eth);
994
995 // gw clawback all ETH from bob
996 env(amm::ammClawback(gw, bob, eth, btc, std::nullopt));
997 env.close();
998 BEAST_EXPECT(
999 amm.expectBalances(btc(20'000), eth(10'000), IOUAmount{14'142'13562373095, -11}));
1000 env.require(Balance(alice, aliceBTC));
1001 env.require(Balance(alice, aliceETH));
1002 env.require(Balance(bob, bobBTC + btc(40'000)));
1003 env.require(Balance(bob, bobETH));
1004 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{14'142'13562373095, -11}));
1005 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1006 bobBTC = env.balance(bob, btc);
1007
1008 // gw clawback all ETH and BTC from alice with tfClawTwoAssets
1009 env(amm::ammClawback(gw, alice, eth, btc, std::nullopt), Txflags(tfClawTwoAssets));
1010 env.close();
1011
1012 // amm is empty and deleted
1013 BEAST_EXPECT(!amm.ammExists());
1014 env.require(Balance(alice, aliceBTC));
1015 env.require(Balance(alice, aliceETH));
1016 env.require(Balance(bob, bobBTC));
1017 env.require(Balance(bob, bobETH));
1018 }
1019 }
1020
1021 void
1023 {
1024 testcase("test AMMClawback when issuing token for each other");
1025 using namespace jtx;
1026
1027 // AMMClawback from MPT/IOU issued by each other
1028 {
1029 Env env(*this, features);
1030 Account const gw{"gateway"};
1031 Account const gw2{"gateway2"};
1032 Account const alice{"alice"};
1033 env.fund(XRP(1000000), gw, gw2, alice);
1034 env.close();
1035
1036 env(fset(gw, asfAllowTrustLineClawback));
1037 env(fset(gw2, asfAllowTrustLineClawback));
1038 env.close();
1039
1040 auto const usd = gw["USD"];
1041 env.trust(usd(100000), gw2);
1042 env(pay(gw, gw2, usd(5000)));
1043 env.trust(usd(100000), alice);
1044 env(pay(gw, alice, usd(5000)));
1045
1046 MPT const btc = MPTTester(
1047 {.env = env,
1048 .issuer = gw2,
1049 .holders = {alice, gw},
1050 .pay = 40'000'000000,
1051 .flags = tfMPTCanClawback | kMptDexFlags});
1052
1053 AMM amm(env, gw, usd(1000), btc(2000));
1054 env.close();
1055 BEAST_EXPECT(
1056 amm.expectBalances(usd(1000), btc(2000), IOUAmount{1414'213562373095, -12}));
1057
1058 amm.deposit(gw2, usd(2000), btc(4000));
1059 BEAST_EXPECT(
1060 amm.expectBalances(usd(3000), btc(6000), IOUAmount{4242'640687119285, -12}));
1061
1062 amm.deposit(alice, usd(3000), btc(6000));
1063 BEAST_EXPECT(
1064 amm.expectBalances(usd(6000), btc(12000), IOUAmount{8485'281374238570, -12}));
1065
1066 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414'213562373095, -12}));
1067 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828'427124746190, -12}));
1068 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12}));
1069
1070 auto aliceBTC = env.balance(alice, btc);
1071 auto aliceUSD = env.balance(alice, usd);
1072 auto gwBTC = env.balance(gw, btc);
1073 auto gw2USD = env.balance(gw2, usd);
1074
1075 // gw claws back 1000 USD from gw2.
1076 env(amm::ammClawback(gw, gw2, usd, btc, usd(1000)));
1077 env.close();
1078 BEAST_EXPECT(
1079 amm.expectBalances(usd(5000), btc(10000), IOUAmount{7071'067811865474, -12}));
1080 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414'213562373095, -12}));
1081 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12}));
1082 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12}));
1083 env.require(Balance(alice, aliceBTC));
1084 env.require(Balance(alice, aliceUSD));
1085 env.require(Balance(gw, gwBTC));
1086 env.require(Balance(gw2, gw2USD));
1087
1088 // gw2 claws back 1000 BTC from gw.
1089 env(amm::ammClawback(gw2, gw, btc, usd, btc(1000)), Ter(tesSUCCESS));
1090 env.close();
1091 BEAST_EXPECT(
1092 amm.expectBalances(usd(4500), btc(9001), IOUAmount{6363'961030678927, -12}));
1093
1094 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{707'1067811865480, -13}));
1095 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12}));
1096 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12}));
1097 env.require(Balance(alice, aliceBTC));
1098 env.require(Balance(alice, aliceUSD));
1099 env.require(Balance(gw, gwBTC));
1100 env.require(Balance(gw2, gw2USD));
1101
1102 // gw2 claws back 4000 BTC from alice
1103 env(amm::ammClawback(gw2, alice, btc, usd, btc(4000)));
1104 env.close();
1105 BEAST_EXPECT(amm.expectBalances(
1106 STAmount{usd, UINT64_C(2500'222197533607), -12},
1107 btc(5001),
1108 IOUAmount{3535'84814069829, -11}));
1109
1110 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{707'1067811865480, -13}));
1111 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12}));
1112 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1414'527797138648, -12}));
1113 env.require(Balance(alice, aliceBTC));
1114 env.require(Balance(alice, aliceUSD + STAmount{usd, UINT64_C(1999'777802466393), -12}));
1115 env.require(Balance(gw, gwBTC));
1116 env.require(Balance(gw2, gw2USD));
1117 }
1118
1119 // AMMClawback from MPT/MPT issued by each other
1120 {
1121 Env env(*this, features);
1122 Account const gw{"gateway"};
1123 Account const gw2{"gateway2"};
1124 Account const alice{"alice"};
1125 env.fund(XRP(100000), gw, gw2, alice);
1126 env.close();
1127
1128 env(fset(gw, asfAllowTrustLineClawback));
1129 env(fset(gw2, asfAllowTrustLineClawback));
1130 env.close();
1131
1132 MPT const btc = MPTTester(
1133 {.env = env,
1134 .issuer = gw,
1135 .holders = {gw2, alice},
1136 .pay = 40'000'000000,
1137 .flags = tfMPTCanClawback | kMptDexFlags});
1138
1139 MPT const eth = MPTTester(
1140 {.env = env,
1141 .issuer = gw2,
1142 .holders = {gw, alice},
1143 .pay = 30'000'000000,
1144 .flags = tfMPTCanClawback | kMptDexFlags});
1145
1146 AMM amm(env, gw, btc(10'000), eth(50'000));
1147 env.close();
1148 BEAST_EXPECT(
1149 amm.expectBalances(btc(10'000), eth(50'000), IOUAmount{22'360'67977499789, -11}));
1150
1151 amm.deposit(gw2, btc(20'000), eth(100'000));
1152 BEAST_EXPECT(
1153 amm.expectBalances(btc(30'000), eth(150'000), IOUAmount{67'082'03932499367, -11}));
1154
1155 amm.deposit(alice, btc(40'000), eth(200'000));
1156 BEAST_EXPECT(
1157 amm.expectBalances(btc(70'000), eth(350'000), IOUAmount{156'524'7584249852, -10}));
1158
1159 auto aliceBTC = env.balance(alice, btc);
1160 auto aliceETH = env.balance(alice, eth);
1161 auto gw2BTC = env.balance(gw2, btc);
1162 auto gwETH = env.balance(gw, eth);
1163
1164 // gw claws back 1000 BTC from gw2.
1165 env(amm::ammClawback(gw, gw2, btc, eth, btc(1000)));
1166 env.close();
1167 BEAST_EXPECT(
1168 amm.expectBalances(btc(69'001), eth(345'001), IOUAmount{154'288'6904474855, -10}));
1169 env.require(Balance(alice, aliceBTC));
1170 env.require(Balance(alice, aliceETH));
1171 env.require(Balance(gw, gwETH));
1172 env.require(Balance(gw2, gw2BTC));
1173
1174 // gw2 claws back all ETH from gw
1175 env(amm::ammClawback(gw2, gw, eth, btc, std::nullopt));
1176 env.close();
1177 BEAST_EXPECT(
1178 amm.expectBalances(btc(59'001), eth(295'001), IOUAmount{131'928'0106724876, -10}));
1179 env.require(Balance(alice, aliceBTC));
1180 env.require(Balance(alice, aliceETH));
1181 env.require(Balance(gw, gwETH));
1182 env.require(Balance(gw2, gw2BTC));
1183
1184 // gw claws back all BTC from alice
1185 env(amm::ammClawback(gw, alice, btc, eth, std::nullopt));
1186 env.close();
1187 BEAST_EXPECT(
1188 amm.expectBalances(btc(19'001), eth(95'001), IOUAmount{42'485'29157249607, -11}));
1189 env.require(Balance(alice, aliceBTC));
1190 env.require(Balance(alice, aliceETH + eth(200'000)));
1191 env.require(Balance(gw, gwETH));
1192 env.require(Balance(gw2, gw2BTC));
1193 }
1194 }
1195
1196 void
1198 {
1199 testcase("test AMMClawback when asset is frozen or locked");
1200 using namespace jtx;
1201
1202 // test AMMClawback when MPT globally locked or IOU globally frozen
1203 {
1204 Env env{*this, features};
1205 Account const gw{"gateway"};
1206 Account const alice{"alice"};
1207 env.fund(XRP(1'000'000), gw, alice);
1208
1209 env(fset(gw, asfAllowTrustLineClawback));
1210 env.close();
1211 auto const usd = gw["USD"];
1212 env.trust(usd(1'000'000), alice);
1213 env(pay(gw, alice, usd(500'000)));
1214
1215 MPTTester btc(
1216 {.env = env,
1217 .issuer = gw,
1218 .holders = {alice},
1219 .pay = 30'000,
1220 .flags = tfMPTCanClawback | tfMPTCanLock | kMptDexFlags});
1221 AMM const ammAlice(env, alice, usd(10'000), btc(10'000));
1222 BEAST_EXPECT(ammAlice.expectBalances(usd(10'000), btc(10'000), IOUAmount(10'000)));
1223 env.close();
1224
1225 auto aliceBTC = env.balance(alice, MPT(btc));
1226 auto aliceUSD = env.balance(alice, usd);
1227
1228 // globally locked and claw back 1000 BTC.
1229 // this should be successful
1230 btc.set({.flags = tfMPTLock});
1231 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(1'000)));
1232 BEAST_EXPECT(ammAlice.expectBalances(usd(9'000), btc(9'000), IOUAmount(9'000)));
1233 env.require(Balance(alice, aliceBTC));
1234 env.require(Balance(alice, aliceUSD + usd(1'000)));
1235 aliceUSD = env.balance(alice, usd);
1236
1237 // unlock and claw back 2000 BTC
1238 btc.set({.flags = tfMPTUnlock});
1239 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(2'000)));
1240 BEAST_EXPECT(ammAlice.expectBalances(
1241 STAmount(usd, UINT64_C(7'000'000000000001), -12), btc(7'001), IOUAmount(7'000)));
1242 env.require(Balance(alice, aliceBTC));
1243 env.require(Balance(alice, aliceUSD + usd(2'000)));
1244 aliceUSD = env.balance(alice, usd);
1245
1246 // globally freeze trustline and claw back 1000 USD.
1247 // this should be successful
1248 env(trust(gw, alice["USD"](0), tfSetFreeze));
1249 env.close();
1250 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(1'000)));
1251 BEAST_EXPECT(ammAlice.expectBalances(
1252 STAmount(usd, UINT64_C(6000'000000000002), -12),
1253 btc(6'001),
1254 IOUAmount(6'000'000000000001, -12)));
1255 env.require(Balance(alice, aliceBTC + btc(1'000)));
1256 env.require(Balance(alice, aliceUSD));
1257 aliceBTC = env.balance(alice, MPT(btc));
1258
1259 // globally unfreeze trustline and claw back 2000 USD
1260 // and 2000 BTC with tfClawTwoAssets
1261 env(fset(gw, asfGlobalFreeze));
1262 env.close();
1263 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(2'000)), Txflags(tfClawTwoAssets));
1264 BEAST_EXPECT(ammAlice.expectBalances(
1265 STAmount(usd, UINT64_C(4'000'000000000002), -12),
1266 btc(4'001),
1267 IOUAmount(4'000'000000000001, -12)));
1268 env.require(Balance(alice, aliceBTC));
1269 env.require(Balance(alice, aliceUSD));
1270 }
1271
1272 // test AMMClawback when MPT individually locked or IOU individually
1273 // frozen
1274 {
1275 Env env{*this, features};
1276 Account const gw{"gateway"};
1277 Account const alice{"alice"};
1278 env.fund(XRP(1'000'000), gw, alice);
1279
1280 env(fset(gw, asfAllowTrustLineClawback));
1281 env.close();
1282 auto const usd = gw["USD"];
1283 env.trust(usd(1'000'000), alice);
1284 env(pay(gw, alice, usd(500'000)));
1285
1286 MPTTester btc(
1287 {.env = env,
1288 .issuer = gw,
1289 .holders = {alice},
1290 .pay = 30'000,
1291 .flags = tfMPTCanClawback | tfMPTCanLock | kMptDexFlags});
1292 AMM const ammAlice(env, alice, usd(10'000), btc(10'000));
1293 BEAST_EXPECT(ammAlice.expectBalances(usd(10'000), btc(10'000), IOUAmount(10'000)));
1294 env.close();
1295
1296 auto aliceBTC = env.balance(alice, MPT(btc));
1297 auto aliceUSD = env.balance(alice, usd);
1298
1299 // individually locked and claw back 2000 BTC from alice
1300 btc.set({.holder = alice, .flags = tfMPTLock});
1301 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(2'000)));
1302 BEAST_EXPECT(ammAlice.expectBalances(usd(8'000), btc(8'000), IOUAmount(8'000)));
1303 env.require(Balance(alice, aliceBTC));
1304 env.require(Balance(alice, aliceUSD + usd(2'000)));
1305 aliceUSD = env.balance(alice, usd);
1306
1307 // individually freeze trustline and claw back 1000 USD from alice
1308 env(trust(gw, alice["USD"](0), tfSetFreeze));
1309 env.close();
1310 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(1'000)));
1311 BEAST_EXPECT(ammAlice.expectBalances(usd(7'000), btc(7'000), IOUAmount(7'000)));
1312 env.require(Balance(alice, aliceBTC + btc(1'000)));
1313 env.require(Balance(alice, aliceUSD));
1314 aliceBTC = env.balance(alice, MPT(btc));
1315
1316 // unlock MPT and claw back 3000 BTC from alice
1317 btc.set({.holder = alice, .flags = tfMPTUnlock});
1318 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(3'000)));
1319 BEAST_EXPECT(ammAlice.expectBalances(
1320 STAmount{usd, UINT64_C(4000'000000000001), -12}, btc(4'001), IOUAmount(4'000)));
1321 env.require(Balance(alice, aliceBTC));
1322 env.require(Balance(alice, aliceUSD + usd(3'000)));
1323 aliceUSD = env.balance(alice, usd);
1324
1325 // unlock trustline and claw back 1000 USD from alice
1326 env(trust(gw, alice["USD"](0), tfClearFreeze));
1327 env.close();
1328 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(1'000)));
1329 BEAST_EXPECT(ammAlice.expectBalances(
1330 STAmount(usd, UINT64_C(3'000'000000000002), -12),
1331 btc(3'001),
1332 IOUAmount(3000'000000000001, -12)));
1333 env.require(Balance(alice, aliceBTC + btc(1'000)));
1334 env.require(Balance(alice, aliceUSD));
1335 }
1336 }
1337
1338 void
1340 {
1341 testcase("test single depoit and clawback");
1342 using namespace jtx;
1343
1344 // MPT/XRP
1345 {
1346 Env env(*this, features);
1347 Account const gw{"gateway"};
1348 Account const alice{"alice"};
1349 env.fund(XRP(1000000000), gw, alice);
1350 env.close();
1351
1352 MPT const btc = MPTTester(
1353 {.env = env,
1354 .issuer = gw,
1355 .holders = {alice},
1356 .pay = 40'000'000000,
1357 .flags = tfMPTCanClawback | kMptDexFlags});
1358
1359 // gw creates AMM pool of BTC/XRP.
1360 AMM amm(env, gw, XRP(100), btc(400), Ter(tesSUCCESS));
1361 env.close();
1362 BEAST_EXPECT(amm.expectBalances(XRP(100), btc(400), IOUAmount(200000)));
1363 amm.deposit(alice, btc(400));
1364 env.close();
1365 BEAST_EXPECT(amm.expectBalances(XRP(100), btc(800), IOUAmount{282842'712474619, -9}));
1366
1367 auto aliceBTC = env.balance(alice, MPT(btc));
1368 auto aliceXRP = env.balance(alice, XRP);
1369
1370 // gw clawback 100 BTC from alice
1371 env(amm::ammClawback(gw, alice, MPT(btc), XRP, btc(100)));
1372 BEAST_EXPECT(amm.expectBalances(
1373 XRPAmount(87500001), btc(701), IOUAmount{247'487'3734152917, -10}));
1374
1375 env.require(Balance(alice, aliceBTC));
1376 env.require(Balance(alice, aliceXRP + XRPAmount(12'499999)));
1377 }
1378
1379 // MPT/IOU
1380 {
1381 Env env(*this, features);
1382 Account const gw{"gateway"};
1383 Account const alice{"alice"};
1384 env.fund(XRP(1000000000), gw, alice);
1385 env.close();
1386
1387 // gw sets asfAllowTrustLineClawback.
1388 env(fset(gw, asfAllowTrustLineClawback));
1389 env.close();
1390 env.require(Flags(gw, asfAllowTrustLineClawback));
1391
1392 // gw issues 1000 USD to Alice.
1393 auto const usd = gw["USD"];
1394 env.trust(usd(100000), alice);
1395 env(pay(gw, alice, usd(1000)));
1396 env.close();
1397
1398 MPT const btc = MPTTester(
1399 {.env = env,
1400 .issuer = gw,
1401 .holders = {alice},
1402 .pay = 40'000'000000,
1403 .flags = tfMPTCanClawback | kMptDexFlags});
1404
1405 // gw creates AMM pool of BTC/USD.
1406 AMM amm(env, gw, usd(100), btc(400), Ter(tesSUCCESS));
1407 env.close();
1408 BEAST_EXPECT(amm.expectBalances(usd(100), btc(400), IOUAmount(200)));
1409 amm.deposit(alice, btc(400));
1410 env.close();
1411 BEAST_EXPECT(amm.expectBalances(usd(100), btc(800), IOUAmount{282'842712474619, -12}));
1412
1413 auto aliceBTC = env.balance(alice, MPT(btc));
1414 auto aliceUSD = env.balance(alice, usd);
1415
1416 // gw clawback 100 BTC from alice
1417 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(100)));
1418 BEAST_EXPECT(amm.expectBalances(
1419 STAmount{usd, UINT64_C(87'50000000000003), -14},
1420 btc(701),
1421 IOUAmount{247'4873734152917, -13}));
1422
1423 env.require(Balance(alice, aliceBTC));
1424 env.require(Balance(alice, aliceUSD + usd(12.5)));
1425 aliceUSD = env.balance(alice, usd);
1426
1427 // gw clawback 30 USD from alice with tfClawTwoAssets, which exceeds
1428 // her balance
1429 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(30)), Txflags(tfClawTwoAssets));
1430 BEAST_EXPECT(amm.expectBalances(
1431 STAmount{usd, UINT64_C(70'71067811865476), -14}, btc(567), IOUAmount(200)));
1432 env.require(Balance(alice, aliceBTC));
1433 env.require(Balance(alice, aliceUSD));
1434 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1435 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount(200)));
1436 }
1437
1438 // MPT/MPT
1439 {
1440 Env env(*this, features);
1441 Account const gw{"gateway"};
1442 Account const alice{"alice"};
1443 env.fund(XRP(1000000000), gw, alice);
1444 env.close();
1445
1446 MPT const usd = MPTTester(
1447 {.env = env,
1448 .issuer = gw,
1449 .holders = {alice},
1450 .pay = 40'000'000000,
1451 .flags = tfMPTCanClawback | kMptDexFlags});
1452
1453 MPT const btc = MPTTester(
1454 {.env = env,
1455 .issuer = gw,
1456 .holders = {alice},
1457 .pay = 40'000'000000,
1458 .flags = tfMPTCanClawback | kMptDexFlags});
1459
1460 // gw creates AMM pool of BTC/USD.
1461 AMM amm(env, gw, usd(100), btc(400), Ter(tesSUCCESS));
1462 env.close();
1463 BEAST_EXPECT(amm.expectBalances(usd(100), btc(400), IOUAmount(200)));
1464 amm.deposit(alice, btc(400));
1465 env.close();
1466 BEAST_EXPECT(amm.expectBalances(usd(100), btc(800), IOUAmount{282'842712474619, -12}));
1467
1468 auto aliceBTC = env.balance(alice, MPT(btc));
1469 auto aliceUSD = env.balance(alice, usd);
1470
1471 // gw clawback 100 BTC from alice
1472 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(100)));
1473 BEAST_EXPECT(amm.expectBalances(usd(88), btc(701), IOUAmount{247'4873734152917, -13}));
1474
1475 env.require(Balance(alice, aliceBTC));
1476 env.require(Balance(alice, aliceUSD + usd(12)));
1477 aliceUSD = env.balance(alice, usd);
1478
1479 // gw clawback 30 USD from alice with tfClawTwoAssets, which exceeds
1480 // her balance
1481 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(30)), Txflags(tfClawTwoAssets));
1482 BEAST_EXPECT(amm.expectBalances(usd(72), btc(567), IOUAmount(200)));
1483 env.require(Balance(alice, aliceBTC));
1484 env.require(Balance(alice, aliceUSD));
1485 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1486 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount(200)));
1487 }
1488 }
1489
1490 void
1492 {
1493 testcase(
1494 "test last holder's lptoken balance not equal to AMM's lptoken "
1495 "balance before clawback");
1496 using namespace jtx;
1497 std::string logs;
1498
1499 // MPT/IOU
1500 {
1501 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
1502 Account const gw{"gateway"}, alice{"alice"}, bob{"bob"};
1503 env.fund(XRP(100000), gw, alice, bob);
1504 env.close();
1505 env(fset(gw, asfAllowTrustLineClawback));
1506 env.close();
1507
1508 auto const usd = gw["USD"];
1509 env.trust(usd(100000), alice);
1510 env(pay(gw, alice, usd(50000)));
1511 env.trust(usd(100000), bob);
1512 env(pay(gw, bob, usd(40000)));
1513 env.close();
1514
1515 MPT const eur = MPTTester(
1516 {.env = env,
1517 .issuer = gw,
1518 .holders = {alice, bob},
1519 .pay = 40'000'000000,
1520 .flags = tfMPTCanClawback | kMptDexFlags});
1521
1522 AMM amm(env, alice, usd(2), eur(1));
1523 amm.deposit(alice, IOUAmount{1'576123487565916, -15});
1524 amm.deposit(bob, IOUAmount{1'000});
1525 amm.withdraw(alice, IOUAmount{1'576123487565916, -15});
1526 amm.withdrawAll(bob);
1527
1528 auto const lpToken =
1529 getAccountLines(env, alice, amm.lptIssue())[jss::lines][0u][jss::balance]
1530 .asString();
1531 auto const lpTokenBalance =
1532 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString();
1533 if (features[featureSingleAssetVault] || features[featureLendingProtocol])
1534 {
1535 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741");
1536 }
1537 else
1538 {
1539 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.414213562374");
1540 }
1541
1542 auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
1543 BEAST_EXPECT(res && res.value());
1544
1545 if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1546 {
1547 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1548 BEAST_EXPECT(!amm.ammExists());
1549 }
1550 else if (
1551 features[fixAMMv1_3] &&
1552 (features[featureSingleAssetVault] || features[featureLendingProtocol]))
1553 {
1554 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1555 // Without the Rounding feature and with new Number a dust pool
1556 // amount remains
1557 BEAST_EXPECT(amm.ammExists());
1558 }
1559 else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
1560 {
1561 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecINTERNAL));
1562 BEAST_EXPECT(amm.ammExists());
1563 }
1564 else
1565 {
1566 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecAMM_BALANCE));
1567 BEAST_EXPECT(amm.ammExists());
1568 }
1569 }
1570
1571 // MPT/MPT
1572 {
1573 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
1574 Account const gw{"gateway"}, alice{"alice"}, bob{"bob"};
1575 env.fund(XRP(100000), gw, alice, bob);
1576 env.close();
1577
1578 MPT const usd = MPTTester(
1579 {.env = env,
1580 .issuer = gw,
1581 .holders = {alice, bob},
1582 .pay = 40'000'000000,
1583 .flags = tfMPTCanClawback | kMptDexFlags});
1584
1585 MPT const eur = MPTTester(
1586 {.env = env,
1587 .issuer = gw,
1588 .holders = {alice, bob},
1589 .pay = 40'000'000000,
1590 .flags = tfMPTCanClawback | kMptDexFlags});
1591
1592 AMM amm(env, alice, usd(2), eur(1));
1593 amm.deposit(alice, IOUAmount{1'576123487565916, -15});
1594 amm.deposit(bob, IOUAmount{1'000});
1595 amm.withdraw(alice, IOUAmount{1'576123487565916, -15});
1596 amm.withdrawAll(bob);
1597
1598 auto const lpToken =
1599 getAccountLines(env, alice, amm.lptIssue())[jss::lines][0u][jss::balance]
1600 .asString();
1601 auto const lpTokenBalance =
1602 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString();
1603 if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
1604 {
1605 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.414213562374");
1606 }
1607 else
1608 {
1609 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741");
1610 }
1611
1612 auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
1613 BEAST_EXPECT(res && res.value());
1614
1615 if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1616 {
1617 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1618 BEAST_EXPECT(!amm.ammExists());
1619 }
1620 else if (
1621 features[fixAMMv1_3] &&
1622 (features[featureSingleAssetVault] || features[featureLendingProtocol]))
1623 {
1624 // Without the Rounding feature and with new Number a dust pool
1625 // amount remains
1626 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1627 BEAST_EXPECT(amm.ammExists());
1628 }
1629 else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
1630 {
1631 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecINTERNAL));
1632 BEAST_EXPECT(amm.ammExists());
1633 }
1634 else if (features[featureMPTokensV2])
1635 {
1636 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecAMM_BALANCE));
1637 BEAST_EXPECT(amm.ammExists());
1638 }
1639 }
1640 }
1641
1642 void
1644 {
1645 testcase("claw asset check for MPT and IOU");
1646 using namespace jtx;
1647
1648 // IOU/MPT, MPT not clawable
1649 {
1650 Env env(*this, features);
1651 Account const gw{"gateway"};
1652 Account const alice{"alice"};
1653 env.fund(XRP(100000), gw, alice);
1654 env.close();
1655
1656 env(fset(gw, asfAllowTrustLineClawback));
1657 env.close();
1658
1659 auto const usd = gw["USD"];
1660 env.trust(usd(100000), alice);
1661 env(pay(gw, alice, usd(1000)));
1662 env.close();
1663
1664 MPT const btc =
1665 MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 40'000});
1666
1667 AMM const amm(env, alice, usd(200), btc(100));
1668 // Asset BTC is not clawable without tfMPTCanClawback.
1669 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(tecNO_PERMISSION));
1670
1671 // Although USD is clawable with asfAllowTrustLineClawback.
1672 // When tfClawTwoAssets is set, we will claw Asser2 as well.
1673 // But Asset2 is not clawable. tfMPTCanClawback was not set for BTC.
1674 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt),
1675 Txflags(tfClawTwoAssets),
1677
1678 // Can only claw the other asset
1679 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt));
1680 }
1681
1682 // IOU/MPT, IOU not clawable
1683 {
1684 Env env(*this, features);
1685 Account const gw{"gateway"};
1686 Account const alice{"alice"};
1687 env.fund(XRP(100000), gw, alice);
1688 env.close();
1689
1690 auto const usd = gw["USD"];
1691 env.trust(usd(100000), alice);
1692 env(pay(gw, alice, usd(1000)));
1693 env.close();
1694
1695 MPT const btc = MPTTester(
1696 {.env = env,
1697 .issuer = gw,
1698 .holders = {alice},
1699 .pay = 40'000,
1700 .flags = tfMPTCanClawback | kMptDexFlags});
1701
1702 // Asset USD is not clawable without asfAllowTrustLineClawback.
1703 AMM const amm(env, alice, usd(200), btc(100));
1704 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt), Ter(tecNO_PERMISSION));
1705
1706 // Although BTC is clawable with tfMPTCanClawback.
1707 // When tfClawTwoAssets is set, we will claw Asset2 as well.
1708 // But Asset2 is not clawable. asfAllowTrustLineClawback was not set
1709 // by the issuer.
1710 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt),
1711 Txflags(tfClawTwoAssets),
1713
1714 // Can only claw the other asset
1715 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt));
1716 }
1717
1718 // IOU/MPT both clawable
1719 {
1720 Env env(*this, features);
1721 Account const gw{"gateway"};
1722 Account const gw2{"gateway2"};
1723 Account const alice{"alice"};
1724 env.fund(XRP(100000), gw, gw2, alice);
1725 env.close();
1726
1727 env(fset(gw, asfAllowTrustLineClawback));
1728 env.close();
1729
1730 auto const usd = gw["USD"];
1731 env.trust(usd(100000), alice);
1732 env(pay(gw, alice, usd(1000)));
1733 env.close();
1734
1735 MPT const btc = MPTTester(
1736 {.env = env,
1737 .issuer = gw2,
1738 .holders = {alice},
1739 .pay = 40'000,
1740 .flags = tfMPTCanClawback | kMptDexFlags});
1741
1742 AMM const amm(env, alice, usd(200), btc(100));
1743
1744 // the account trying to claw MPT is not its issuer
1745 // will return temMALFORMED in preflight.
1746 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(temMALFORMED));
1747 }
1748
1749 // only issuer can claw. IOU/MPT mix
1750 {
1751 auto test = [&](auto&& issue1, auto&& issue2) {
1752 Env env(*this);
1753 Account const gw("gateway"), alice("alice"), bob("bob");
1754 env.fund(XRP(30'000), alice, bob, gw);
1755 env.close();
1756 auto const usd = issue1(
1757 {.env = env,
1758 .token = "USD",
1759 .issuer = gw,
1760 .holders = {alice},
1761 .limit = 1'000'000});
1762 auto const btc = issue2(
1763 {.env = env,
1764 .token = "BTC",
1765 .issuer = bob,
1766 .holders = {alice},
1767 .limit = 1'000'000});
1768 env(pay(gw, alice, usd(50000)));
1769 env(pay(bob, alice, btc(50000)));
1770 env.close();
1771
1772 auto ammAlice = AMM(env, alice, usd(10000), btc(10100));
1773 // BTC's issuer is bob, alice can not clawback
1774 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(temMALFORMED));
1775 };
1777 }
1778
1779 // set tfClawTwoAssets, but the two assets are from different issuer.
1780 {
1781 auto test = [&](auto&& issue1, auto&& issue2) {
1782 Env env(*this);
1783 Account const gw("gateway"), alice("alice"), bob("bob");
1784 env.fund(XRP(30'000), alice, bob, gw);
1785 env.close();
1786 auto const usd = issue1(
1787 {.env = env,
1788 .token = "USD",
1789 .issuer = gw,
1790 .holders = {alice},
1791 .limit = 1'000'000});
1792 auto const btc = issue2(
1793 {.env = env,
1794 .token = "BTC",
1795 .issuer = bob,
1796 .holders = {alice},
1797 .limit = 1'000'000});
1798 env(pay(gw, alice, usd(50000)));
1799 env(pay(bob, alice, btc(50000)));
1800 env.close();
1801
1802 auto ammAlice = AMM(env, alice, usd(10000), btc(10100));
1803 // BTC's issuer is bob. But with tfClawTwoAssets, we will claw
1804 // both. It will fail because the other asset USD's issuer is
1805 // gw.
1806 env(amm::ammClawback(bob, alice, btc, usd, std::nullopt),
1807 Txflags(tfClawTwoAssets),
1809 };
1811 }
1812 }
1813
1814 void
1815 run() override
1816 {
1817 FeatureBitset const all{jtx::testableAmendments() | fixAMMClawbackRounding};
1818
1819 testInvalidRequest(all);
1822 testAMMClawbackAll(all);
1829 testLastHolderLPTokenBalance(all - fixAMMv1_3 - fixAMMClawbackRounding);
1831 all - fixAMMv1_3 - fixAMMClawbackRounding - featureSingleAssetVault -
1832 featureLendingProtocol);
1833 testLastHolderLPTokenBalance(all - fixAMMClawbackRounding);
1834 testClawAssetCheck(all);
1835 }
1836};
1837
1838BEAST_DEFINE_TESTSUITE(AMMClawbackMPT, app, xrpl);
1839
1840} // namespace xrpl::test
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
std::string asString() const
Returns the unquoted string value.
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:24
A currency issued by an account.
Definition Issue.h:13
void testAMMClawbackIssuesEachOther(FeatureBitset features)
void testAMMClawbackAmount(FeatureBitset features)
void testInvalidRequest(FeatureBitset features)
void testAssetFrozenOrLocked(FeatureBitset features)
void testAMMClawbackAllSameIssuer(FeatureBitset features)
void testAMMClawbackAmountSameIssuer(FeatureBitset features)
void testLastHolderLPTokenBalance(FeatureBitset features)
void testSingleDepositAndClawback(FeatureBitset features)
void testClawAssetCheck(FeatureBitset features)
void run() override
Runs the suite.
void testAMMClawbackAll(FeatureBitset features)
void testFeatureDisabled(FeatureBitset features)
Convenience class to test AMM functionality.
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
AccountID id() const
Returns the Account ID.
Definition jtx/Account.h:85
A transaction testing environment.
Definition Env.h:143
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:296
void enableFeature(uint256 const feature)
Definition Env.cpp:682
PrettyAmount limit(Account const &account, Issue const &issue) const
Returns the IOU limit on an account.
Definition Env.cpp:254
void disableFeature(uint256 const feature)
Definition Env.cpp:690
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:201
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:327
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:605
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:353
Match set account flags.
Definition flags.h:109
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:385
Converts to MPT Issue or STAmount.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:13
Set the flags on a JTx.
Definition txflags.h:9
T make_unique(T... args)
json::Value ammClawback(Account const &issuer, Account const &holder, Asset const &asset, Asset const &asset2, std::optional< STAmount > const &amount)
Definition AMM.cpp:920
auto const kMptDexFlags
Definition mpt.h:25
json::Value claw(Account const &account, STAmount const &amount, std::optional< Account > const &mptHolder)
Definition trust.cpp:51
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
void testHelper2TokensMix(TTester &&tester)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:76
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value getAccountLines(Env &env, AccountID const &acctId)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:48
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_AMM
Definition TER.h:219
@ terNO_ACCOUNT
Definition TER.h:209
std::expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
STAmount amountFromString(Asset const &asset, std::string const &amount)
Definition STAmount.cpp:907
@ temINVALID_FLAG
Definition TER.h:97
@ temMALFORMED
Definition TER.h:73
@ temDISABLED
Definition TER.h:100
@ temBAD_AMOUNT
Definition TER.h:75
@ tecPSEUDO_ACCOUNT
Definition TER.h:360
@ tecINTERNAL
Definition TER.h:308
@ tecAMM_BALANCE
Definition TER.h:327
@ tecAMM_ACCOUNT
Definition TER.h:332
@ tecNO_PERMISSION
Definition TER.h:303
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:172
@ tesSUCCESS
Definition TER.h:240