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/LedgerFormats.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STAmount.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/TxFlags.h>
24#include <xrpl/protocol/XRPAmount.h>
25#include <xrpl/protocol/jss.h>
26
27#include <cstdint>
28#include <memory>
29#include <optional>
30
31namespace xrpl::test {
33{
34 void
36 {
37 testcase("test invalid request");
38 using namespace jtx;
39
40 for (auto const& feature : {features, features - featureSingleAssetVault})
41 {
42 Env env(*this, feature);
43 Account const gw{"gateway"};
44 Account const alice{"alice"};
45 Account const bob{"bob"};
46 env.fund(XRP(100000), gw, alice, bob);
47 env.close();
48
49 env(fset(gw, asfAllowTrustLineClawback));
50 env.close();
51
52 MPT const btc = MPTTester(
53 {.env = env,
54 .issuer = gw,
55 .holders = {alice},
56 .pay = 40'000,
57 .flags = tfMPTCanClawback | kMptDexFlags});
58
59 auto const usd = gw["USD"];
60 env.trust(usd(10000), alice);
61 env(pay(gw, alice, usd(100)));
62 env.close();
63
64 AMM amm(env, gw, btc(100), usd(100));
65
66 // holder does not exist
67 env(amm::ammClawback(gw, Account("unknown"), usd, btc, std::nullopt),
69
70 // can not clawback from self.
71 env(amm::ammClawback(gw, gw, usd, btc, std::nullopt), Ter(temMALFORMED));
72
73 // provided Asset does not match issuer gw
74 {
76 gw, alice, Issue{gw["USD"].currency, alice.id()}, btc, std::nullopt),
78 env(amm::ammClawback(gw, alice, MPTIssue{makeMptID(1, alice)}, usd, std::nullopt),
80 }
81
82 // Amount does not match asset
83 {
85 gw, alice, usd, btc, STAmount{Issue{gw["USD"].currency, alice.id()}, 1}),
88 gw, alice, btc, usd, STAmount{MPTIssue{makeMptID(1, alice)}, 10}),
90 }
91
92 // Amount is not greater than 0
93 {
94 env(amm::ammClawback(gw, alice, btc, usd, btc(-1)), Ter(temBAD_AMOUNT));
95 env(amm::ammClawback(gw, alice, btc, usd, btc(0)), Ter(temBAD_AMOUNT));
96 }
97
98 // clawback from account not holding lptoken
99 env(amm::ammClawback(gw, bob, btc, usd, btc(1000)), Ter(tecAMM_BALANCE));
100
101 // can not perform regular claw from amm pool
102 {
103 Issue const ammUsd(usd.currency, amm.ammAccount());
104 auto amount = amountFromString(ammUsd, "10");
105 auto const err =
106 feature[featureSingleAssetVault] ? tecPSEUDO_ACCOUNT : tecAMM_ACCOUNT;
107 env(claw(gw, amount), Ter(err));
108 }
109
110 // AMM does not exist
111 {
112 // withdraw all tokens will delete the AMM
113 amm.withdrawAll(gw);
114 BEAST_EXPECT(!amm.ammExists());
115 env.close();
116 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt), Ter(terNO_AMM));
117 }
118 }
119
120 // tfMPTCanClawback is not enabled
121 {
122 Env env(*this, features);
123 Account const gw{"gateway"};
124 Account const alice{"alice"};
125 env.fund(XRP(100000), gw, alice);
126 env.close();
127
128 env(fset(gw, asfAllowTrustLineClawback));
129 env.close();
130
131 MPT const btc =
132 MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 40'000});
133
134 auto const usd = gw["USD"];
135 env.trust(usd(10000), alice);
136 env(pay(gw, alice, usd(10000)));
137 env.close();
138
139 AMM amm(env, gw, btc(100), usd(100));
140 env.close();
141 amm.deposit(alice, 1'000);
142
143 // can not clawback when tfMPTCanClawback is not enabled
144 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(tecNO_PERMISSION));
145 }
146
147 // can not claw with tfClawTwoAssets if the assets are not issued by the
148 // same issuer
149 {
150 Env env(*this, features);
151 Account const gw{"gateway"};
152 Account const gw2{"gateway2"};
153 Account const alice{"alice"};
154 env.fund(XRP(100000), gw, gw2, alice);
155 env.close();
156
157 env(fset(gw, asfAllowTrustLineClawback));
158 env.close();
159
160 auto const usd = gw["USD"];
161 env.trust(usd(10000), alice);
162 env(pay(gw, alice, usd(10000)));
163 env.close();
164
165 // todo: check tfMPTCanTransfer in xrpl.org
166 MPT const btc = MPTTester(
167 {.env = env,
168 .issuer = gw2,
169 .holders = {alice},
170 .pay = 40'000,
171 .flags = tfMPTCanClawback | kMptDexFlags});
172
173 AMM const amm(env, alice, btc(100), usd(100));
174 env.close();
175
176 {
177 // Return temINVALID_FLAG because the issuer set
178 // tfClawTwoAssets, but the issuer only issues USD in the pool.
179 // The issuer is not allowed to set tfClawTwoAssets flag if he
180 // did not issue both assets in the pool.
181 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt),
182 Txflags(tfClawTwoAssets),
184 }
185 }
186
187 // Test if the issuer did not set asfAllowTrustLineClawback, but the MPT
188 // is set tfMPTCanClawback, the issuer can claw MPT.
189 {
190 Env env(*this, features);
191 Account const gw{"gateway"};
192 Account const alice{"alice"};
193 env.fund(XRP(10000), gw, alice);
194 env.close();
195
196 MPT const btc = MPTTester(
197 {.env = env,
198 .issuer = gw,
199 .holders = {alice},
200 .pay = 40'000,
201 .flags = tfMPTCanClawback | kMptDexFlags});
202
203 AMM const amm(env, alice, btc(100), XRP(100));
204 env.close();
205
206 // If asfAllowTrustLineClawback is not set, the issuer can
207 // still claw MPT because the MPT's tfMPTCanClawback is set.
208 env(amm::ammClawback(gw, alice, btc, XRP, std::nullopt));
209 }
210 }
211
212 void
214 {
215 testcase("test feature disabled.");
216 using namespace jtx;
217 Env env{*this, features};
218 Account const gw("gateway"), alice("alice");
219 env.fund(XRP(30'000), gw, alice);
220 env.close();
221 env(fset(gw, asfAllowTrustLineClawback));
222 env.close();
223
224 MPT const btc = MPTTester(
225 {.env = env,
226 .issuer = gw,
227 .holders = {alice},
228 .pay = 10'000,
229 .flags = tfMPTCanClawback | kMptDexFlags});
230
231 AMM const amm(env, alice, XRP(1'000), btc(1'000));
232
233 // disable featureAMMClawback
234 env.disableFeature(featureAMMClawback);
235 env(amm::ammClawback(gw, alice, btc, XRP, std::nullopt), Ter(temDISABLED));
236
237 // enable featureAMMClawback and disable featureMPTokensV2
238 env.enableFeature(featureAMMClawback);
239 env.disableFeature(featureMPTokensV2);
240 env(amm::ammClawback(gw, alice, btc, XRP, btc(100)), Ter(temDISABLED));
241
242 // enable featureMPTokensV2
243 env.enableFeature(featureMPTokensV2);
244 env(amm::ammClawback(gw, alice, btc, XRP, btc(200)));
245 }
246
247 void
249 {
250 testcase("test AMMClawback specific amount");
251 using namespace jtx;
252
253 // AMMClawback from MPT/IOU issued by different issuers
254 {
255 Env env(*this, features);
256 Account const gw{"gateway"};
257 Account const gw2{"gateway2"};
258 Account const alice{"alice"};
259 env.fund(XRP(100000), gw, gw2, alice);
260 env.close();
261
262 env(fset(gw, asfAllowTrustLineClawback));
263 env(fset(gw2, asfAllowTrustLineClawback));
264 env.close();
265
266 auto const usd = gw["USD"];
267 env.trust(usd(100000), alice);
268 env(pay(gw, alice, usd(50000)));
269 env.close();
270
271 MPT const btc = MPTTester(
272 {.env = env,
273 .issuer = gw2,
274 .holders = {alice},
275 .pay = 40'000'000000,
276 .flags = tfMPTCanClawback | kMptDexFlags});
277
278 AMM const amm(env, alice, btc(1000000000), usd(2000));
279 env.close();
280 BEAST_EXPECT(amm.expectBalances(
281 btc(1'000'000000), usd(2000), IOUAmount{1414'213'562373095, -9}));
282
283 // can not set tfClawTwoAssets because the assets are not issued by
284 // the same issuer.
285 env(amm::ammClawback(gw2, alice, btc, usd, btc(1000)),
286 Txflags(tfClawTwoAssets),
288
289 auto aliceUSD = env.balance(alice, usd);
290 auto aliceBTC = env.balance(alice, btc);
291 // gw clawback 1000 USD from alice
292 env(amm::ammClawback(gw, alice, usd, btc, usd(1000)));
293 env.close();
294
295 BEAST_EXPECT(
296 amm.expectBalances(btc(500'000000), usd(1000), IOUAmount{707'106'7811865475, -10}));
297 // USD is clawed back,
298 env.require(Balance(alice, aliceUSD));
299 // a proportional amount of BTC is returned to alice
300 env.require(Balance(alice, aliceBTC + btc(500'000000)));
301 aliceBTC = env.balance(alice, btc);
302
303 // gw2 clawback 250'000000 BTC from alice
304 env(amm::ammClawback(gw2, alice, btc, usd, btc(250'000000)));
305 env.close();
306 BEAST_EXPECT(
307 amm.expectBalances(btc(250'000000), usd(500), IOUAmount{353'553'3905932737, -10}));
308 env.require(Balance(alice, aliceUSD + usd(500)));
309 env.require(Balance(alice, aliceBTC));
310 aliceUSD = env.balance(alice, usd);
311
312 // gw2 clawback 500'000000 BTC which exceeds the balance,
313 // this will clawback all and the amm will be deleted.
314 env(amm::ammClawback(gw2, alice, btc, usd, btc(500'000000)));
315 env.close();
316 BEAST_EXPECT(!amm.ammExists());
317 env.require(Balance(alice, aliceUSD + usd(500)));
318 env.require(Balance(alice, aliceBTC));
319 }
320
321 // AMMClawback from MPT/XRP pool
322 {
323 Env env(*this, features);
324 Account const gw{"gateway"};
325 Account const alice{"alice"};
326 Account const bob{"bob"};
327 env.fund(XRP(100000), gw, alice, bob);
328 env.close();
329
330 env(fset(gw, asfAllowTrustLineClawback));
331 env.close();
332
333 MPT const btc = MPTTester(
334 {.env = env,
335 .issuer = gw,
336 .holders = {alice, bob},
337 .pay = 40'000'000000,
338 .flags = tfMPTCanClawback | kMptDexFlags});
339
340 AMM amm(env, alice, btc(1000000000), XRP(2000));
341 env.close();
342 BEAST_EXPECT(amm.expectBalances(
343 btc(1'000'000000), XRP(2000), IOUAmount{1'414'213'562'373095, -6}));
344
345 amm.deposit(bob, btc(2'000'000000), XRP(4000));
346 BEAST_EXPECT(amm.expectBalances(
347 btc(3'000'000000), XRP(6000), IOUAmount{4'242'640'687'119285, -6}));
348
349 auto aliceXRP = env.balance(alice, XRP);
350 auto aliceBTC = env.balance(alice, btc);
351 auto bobXRP = env.balance(bob, XRP);
352 auto bobBTC = env.balance(bob, btc);
353
354 // can not claw XRP
355 env(amm::ammClawback(gw, alice, XRP, btc, XRP(1000)), Ter(temMALFORMED));
356 // can not set tfClawTwoAssets
357 env(amm::ammClawback(gw, alice, btc, XRP, btc(1000)),
358 Txflags(tfClawTwoAssets),
360
361 // gw clawback 500 BTC from alice
362 env(amm::ammClawback(gw, alice, btc, XRP, btc(500)));
363 env.close();
364 BEAST_EXPECT(amm.expectBalances(
365 btc(2'999'999501),
366 STAmount{XRP, UINT64_C(5'999'999001)},
367 IOUAmount{4'242'639'980'012504, -6}));
368 env.require(Balance(alice, aliceXRP + drops(999)));
369 env.require(Balance(alice, aliceBTC));
370 env.require(Balance(bob, bobXRP));
371 env.require(Balance(bob, bobBTC));
372 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'414'212'855'266314, -6}));
373 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'828'427'124'74619, -5}));
374 aliceXRP = env.balance(alice, XRP);
375
376 // gw clawback 1000'000000 BTC from bob
377 env(amm::ammClawback(gw, bob, btc, XRP, btc(1'000'000000)));
378 env.close();
379 BEAST_EXPECT(amm.expectBalances(
380 btc(1'999'999501),
381 STAmount{XRP, UINT64_C(3'999'999002)},
382 IOUAmount{2828426418'110813, -6}));
383 env.require(Balance(alice, aliceXRP));
384 env.require(Balance(alice, aliceBTC));
385 env.require(Balance(bob, bobXRP + XRPAmount(1999999999)));
386 env.require(Balance(bob, bobBTC));
387 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'414'212'855'266314, -6}));
388 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{1'414'213'562'844499, -6}));
389 bobXRP = env.balance(bob, XRP);
390
391 // gw clawback 1000'000000 BTC from alice, which exceeds her balance
392 // will clawback all her balance
393 env(amm::ammClawback(gw, alice, btc, XRP, btc(1'000'000000)));
394 env.close();
395 BEAST_EXPECT(amm.expectBalances(
396 btc(1'000'000001), XRPAmount(2'000'000002), IOUAmount{1'414'213'562'844499, -6}));
397 env.require(Balance(alice, aliceXRP + STAmount{XRP, UINT64_C(1'999'999000)}));
398 env.require(Balance(alice, aliceBTC));
399 env.require(Balance(bob, bobXRP));
400 env.require(Balance(bob, bobBTC));
401 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
402 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{1'414'213'562'844499, -6}));
403 aliceXRP = env.balance(alice, XRP);
404
405 // gw clawback from bob, which exceeds his balance
406 env(amm::ammClawback(gw, bob, btc, XRP, btc(2'000'000000)));
407 env.close();
408 // amm is empty and deleted
409 BEAST_EXPECT(!amm.ammExists());
410 env.require(Balance(alice, aliceXRP));
411 env.require(Balance(alice, aliceBTC));
412 env.require(Balance(bob, bobXRP + XRPAmount(2000000002)));
413 env.require(Balance(bob, bobBTC));
414 }
415
416 // AMMClawback from MPT/MPT pool, different issuers
417 {
418 Env env(*this, features);
419 Account const gw{"gateway"};
420 Account const gw2{"gateway2"};
421 Account const alice{"alice"};
422 Account const bob{"bob"};
423 env.fund(XRP(100000), gw, gw2, alice, bob);
424 env.close();
425
426 env(fset(gw, asfAllowTrustLineClawback));
427 env(fset(gw2, asfAllowTrustLineClawback));
428 env.close();
429
430 MPT const btc = MPTTester(
431 {.env = env,
432 .issuer = gw,
433 .holders = {alice, bob},
434 .pay = 40'000'000000,
435 .flags = tfMPTCanClawback | kMptDexFlags});
436
437 MPT const eth = MPTTester(
438 {.env = env,
439 .issuer = gw2,
440 .holders = {alice, bob},
441 .pay = 30'000'000000,
442 .flags = tfMPTCanClawback | kMptDexFlags});
443
444 AMM amm(env, alice, btc(2'000'000000), eth(3'000'000000));
445 env.close();
446 BEAST_EXPECT(amm.expectBalances(
447 btc(2'000'000000), eth(3'000'000000), IOUAmount{2'449'489'742'783178, -6}));
448
449 amm.deposit(bob, btc(4'000'000000), eth(6'000'000000));
450 BEAST_EXPECT(amm.expectBalances(
451 btc(6'000'000000), eth(9'000'000000), IOUAmount{7'348'469'228'349534, -6}));
452
453 auto aliceBTC = env.balance(alice, btc);
454 auto aliceETH = env.balance(alice, eth);
455 auto bobBTC = env.balance(bob, btc);
456 auto bobETH = env.balance(bob, eth);
457
458 // gw clawback BTC from alice
459 env(amm::ammClawback(gw, alice, btc, eth, btc(1'000'000000)));
460 env.close();
461 BEAST_EXPECT(amm.expectBalances(
462 btc(5'000'000000), eth(7'500'000000), IOUAmount{6'123'724'356'957944, -6}));
463 env.require(Balance(alice, aliceBTC));
464 env.require(Balance(alice, aliceETH + eth(1'500'000000)));
465 env.require(Balance(bob, bobBTC));
466 env.require(Balance(bob, bobETH));
467 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
468 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{4'898'979'485'566356, -6}));
469 aliceETH = env.balance(alice, eth);
470
471 // gw2 clawback ETH from bob
472 env(amm::ammClawback(gw2, bob, eth, btc, eth(3'000'000000)));
473 env.close();
474 BEAST_EXPECT(amm.expectBalances(
475 btc(3'000'000000), eth(4'500'000000), IOUAmount{3'674'234'614'174766, -6}));
476 env.require(Balance(alice, aliceBTC));
477 env.require(Balance(alice, aliceETH));
478 env.require(Balance(bob, bobBTC + btc(2'000'000000)));
479 env.require(Balance(bob, bobETH));
480 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
481 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6}));
482 bobBTC = env.balance(bob, btc);
483
484 // gw2 clawback ETH from alice, which exceeds her balance
485 env(amm::ammClawback(gw2, alice, eth, btc, eth(4'000'000000)));
486 env.close();
487 BEAST_EXPECT(amm.expectBalances(
488 btc(2'000'000001), eth(3'000'000001), IOUAmount{2'449'489'742'783178, -6}));
489 env.require(Balance(alice, aliceBTC + btc(999'999999)));
490 env.require(Balance(alice, aliceETH));
491 env.require(Balance(bob, bobBTC));
492 env.require(Balance(bob, bobETH));
493 aliceBTC = env.balance(alice, btc);
494
495 // gw clawback BTC from bob, which exceeds his balance
496 env(amm::ammClawback(gw, bob, btc, eth, btc(3'000'000000)));
497 env.close();
498 // amm is empty and deleted
499 BEAST_EXPECT(!amm.ammExists());
500 env.require(Balance(alice, aliceBTC));
501 env.require(Balance(alice, aliceETH));
502 env.require(Balance(bob, bobBTC));
503 env.require(Balance(bob, bobETH + eth(3'000'000001)));
504 }
505 }
506
507 void
509 {
510 // Ensure a clawback that rounds down to zero MPT fails with
511 // tecAMM_FAILED instead of silently burning the holder's LP.
512 testcase("test AMMClawback amount that rounds down to zero");
513 using namespace jtx;
514
515 Env env(*this, features);
516 Account const gw{"gateway"};
517 Account const alice{"alice"};
518 Account const bob{"bob"};
519 env.fund(XRP(10'000'000), gw, alice, bob);
520 env.close();
521
522 env(fset(gw, asfAllowTrustLineClawback));
523 env.close();
524
525 // The clawed asset (amountRounded) rounds to zero while its XRP
526 // counterpart is always large.
527 {
528 MPTTester const mptBtc(
529 {.env = env,
530 .issuer = gw,
531 .holders = {alice, bob},
532 .pay = 1'000,
533 .flags = tfMPTCanClawback | kMptDexFlags});
534 MPT const btc = mptBtc;
535
536 AMM amm(env, alice, btc(3), XRP(333'000));
537 amm.deposit(bob, btc(3), XRP(333'000));
538
539 [[maybe_unused]] auto const [poolBtcBefore, poolXrpBefore, lptBefore] = amm.balances();
540 BEAST_EXPECT(poolBtcBefore == btc(6));
541
542 auto const issuerOABefore = mptBtc.getBalance(gw);
543 auto const aliceLpBefore = amm.getLPTokensBalance(alice.id());
544 auto const bobLpBefore = amm.getLPTokensBalance(bob.id());
545
546 // Attempt to clawback 1/6th of the BTC pool. When the zero-rounding
547 // guard is active (gated by fixCleanup3_4_0) the rounded amount
548 // drops to 0 and should trigger tecAMM_FAILED.
549 env(amm::ammClawback(gw, alice, btc, XRP, btc(1)),
550 Ter(features[fixCleanup3_4_0] ? TER{tecAMM_FAILED} : TER{tesSUCCESS}));
551 env.close();
552
553 [[maybe_unused]] auto const [poolBtcAfter, poolXrpAfter, lptAfter] = amm.balances();
554 auto const issuerOAAfter = mptBtc.getBalance(gw);
555 auto const aliceLpAfter = amm.getLPTokensBalance(alice.id());
556 auto const bobLpAfter = amm.getLPTokensBalance(bob.id());
557
558 if (features[fixCleanup3_4_0])
559 {
560 // Post-fixCleanup3_4_0: Clawback fails because the BTC balance
561 // would round to zero. All balances must remain untouched.
562 BEAST_EXPECT(poolBtcAfter == poolBtcBefore);
563 BEAST_EXPECT(poolXrpAfter == poolXrpBefore);
564 BEAST_EXPECT(issuerOAAfter == issuerOABefore);
565 BEAST_EXPECT(aliceLpAfter == aliceLpBefore);
566 BEAST_EXPECT(bobLpAfter == bobLpBefore);
567 }
568 else
569 {
570 // Pre-fixCleanup3_4_0: BTC rounds to zero and the clawback
571 // silently burns alice's LP without clawing back any BTC.
572 BEAST_EXPECT(poolBtcAfter == poolBtcBefore);
573 BEAST_EXPECT(poolXrpAfter < poolXrpBefore);
574 BEAST_EXPECT(issuerOAAfter == issuerOABefore);
575 BEAST_EXPECT(aliceLpAfter < aliceLpBefore);
576 BEAST_EXPECT(bobLpAfter == bobLpBefore);
577 }
578 }
579
580 // The pool above only ever rounds the clawed asset (amountRounded) to
581 // zero; its XRP counterpart is always large. Exercise the other operand
582 // of the guard (amount2Rounded == 0) with an MPT/MPT pool where the
583 // *paired* asset is the tiny integer that floors to zero while the
584 // clawed asset still rounds non-zero.
585 {
586 Account const carol{"carol"};
587 Account const dan{"dan"};
588 env.fund(XRP(10'000'000), carol, dan);
589 env.close();
590
591 MPTTester const mptBtc(
592 {.env = env,
593 .issuer = gw,
594 .holders = {carol, dan},
595 .pay = 100'000,
596 .flags = tfMPTCanClawback | kMptDexFlags});
597 MPT const btc = mptBtc;
598
599 MPTTester const mptEth(
600 {.env = env,
601 .issuer = gw,
602 .holders = {carol, dan},
603 .pay = 1'000,
604 .flags = tfMPTCanClawback | kMptDexFlags});
605 MPT const eth = mptEth;
606
607 // btc pool dwarfs the eth pool, so a ~1/12th claw withdraws a
608 // non-zero btc amount while the eth counterpart rounds to zero.
609 AMM amm(env, carol, btc(3'000), eth(3));
610 amm.deposit(dan, btc(3'000), eth(3));
611
612 [[maybe_unused]] auto const [poolBtcBefore, poolEthBefore, lptBefore] = amm.balances();
613 BEAST_EXPECT(poolBtcBefore == btc(6'000));
614 BEAST_EXPECT(poolEthBefore == eth(6));
615
616 auto const carolLpBefore = amm.getLPTokensBalance(carol.id());
617 auto const danLpBefore = amm.getLPTokensBalance(dan.id());
618
619 env(amm::ammClawback(gw, carol, btc, eth, btc(500)),
620 Ter(features[fixCleanup3_4_0] ? TER{tecAMM_FAILED} : TER{tesSUCCESS}));
621 env.close();
622
623 [[maybe_unused]] auto const [poolBtcAfter, poolEthAfter, lptAfter] = amm.balances();
624 auto const carolLpAfter = amm.getLPTokensBalance(carol.id());
625 auto const danLpAfter = amm.getLPTokensBalance(dan.id());
626
627 if (features[fixCleanup3_4_0])
628 {
629 // Post-fixCleanup3_4_0: clawback fails because the ETH (Asset2)
630 // balance would round to zero (guard fires via
631 // amount2Rounded == 0). All balances must remain untouched.
632 BEAST_EXPECT(poolBtcAfter == poolBtcBefore);
633 BEAST_EXPECT(poolEthAfter == poolEthBefore);
634 BEAST_EXPECT(carolLpAfter == carolLpBefore);
635 BEAST_EXPECT(danLpAfter == danLpBefore);
636 }
637 else
638 {
639 // Pre-fixCleanup3_4_0: the asymmetric round-off goes through.
640 // btc is clawed (non-zero) but eth rounds to zero, so the eth
641 // pool is untouched while carol's LP is burned. This asymmetry
642 // proves amount2Rounded == 0 is the trigger.
643 BEAST_EXPECT(poolBtcAfter < poolBtcBefore);
644 BEAST_EXPECT(poolEthAfter == poolEthBefore);
645 BEAST_EXPECT(carolLpAfter < carolLpBefore);
646 BEAST_EXPECT(danLpAfter == danLpBefore);
647 }
648 }
649 }
650
651 void
653 {
654 testcase("test AMMClawback all");
655 using namespace jtx;
656
657 // AMMClawback all from MPT/IOU issued by different issuers
658 {
659 Env env(*this, features);
660 Account const gw{"gateway"};
661 Account const gw2{"gateway2"};
662 Account const alice{"alice"};
663 Account const bob{"bob"};
664 env.fund(XRP(100000), gw, gw2, alice, bob);
665 env.close();
666
667 env(fset(gw, asfAllowTrustLineClawback));
668 env(fset(gw2, asfAllowTrustLineClawback));
669 env.close();
670
671 auto const usd = gw["USD"];
672 env.trust(usd(100000), alice);
673 env(pay(gw, alice, usd(50000)));
674 env.trust(usd(200000), bob);
675 env(pay(gw, bob, usd(60000)));
676 env.close();
677
678 MPT const btc = MPTTester(
679 {.env = env,
680 .issuer = gw2,
681 .holders = {alice, bob},
682 .pay = 40'000'000000,
683 .flags = tfMPTCanClawback | kMptDexFlags});
684
685 AMM amm(env, alice, btc(2000000000), usd(2000));
686 env.close();
687 BEAST_EXPECT(amm.expectBalances(btc(2'000'000000), usd(2000), IOUAmount(2000000)));
688
689 // gw clawback all BTC from alice
690 amm.deposit(bob, btc(1'000'000000), usd(2000));
691 BEAST_EXPECT(amm.expectBalances(btc(3'000'000000), usd(3000), IOUAmount(3000000)));
692
693 auto aliceBTC = env.balance(alice, btc);
694 auto aliceUSD = env.balance(alice, usd);
695 auto bobBTC = env.balance(bob, btc);
696 auto bobUSD = env.balance(bob, usd);
697
698 // gw2 clawback all BTC from alice
699 env(amm::ammClawback(gw2, alice, btc, usd, std::nullopt));
700 env.close();
701 BEAST_EXPECT(amm.expectBalances(btc(1'000'000000), usd(1000), IOUAmount(1000000)));
702 env.require(Balance(alice, aliceBTC));
703 env.require(Balance(alice, aliceUSD + usd(2000)));
704 env.require(Balance(bob, bobBTC));
705 env.require(Balance(bob, bobUSD));
706 aliceUSD = env.balance(alice, usd);
707
708 // gw clawback all USD from bob
709 env(amm::ammClawback(gw, bob, usd, btc, std::nullopt));
710 env.close();
711 // amm is empty and deleted
712 BEAST_EXPECT(!amm.ammExists());
713 env.require(Balance(alice, aliceBTC));
714 env.require(Balance(alice, aliceUSD));
715 env.require(Balance(bob, bobBTC + btc(1'000'000000)));
716 env.require(Balance(bob, bobUSD));
717 }
718
719 // AMMClawback all from MPT/XRP pool
720 {
721 Env env(*this, features);
722 Account const gw{"gateway"};
723 Account const alice{"alice"};
724 Account const bob{"bob"};
725 env.fund(XRP(100000), gw, alice, bob);
726 env.close();
727
728 env(fset(gw, asfAllowTrustLineClawback));
729 env.close();
730
731 MPT const btc = MPTTester(
732 {.env = env,
733 .issuer = gw,
734 .holders = {alice, bob},
735 .pay = 40'000'000000,
736 .flags = tfMPTCanClawback | kMptDexFlags});
737
738 AMM amm(env, alice, btc(5000), XRP(10'000));
739 env.close();
740 BEAST_EXPECT(
741 amm.expectBalances(btc(5'000), XRP(10'000), IOUAmount{7'071'067'811865475, -9}));
742
743 amm.deposit(bob, btc(10'000), XRP(20'000));
744 BEAST_EXPECT(
745 amm.expectBalances(btc(15'000), XRP(30'000), IOUAmount{21'213'203'43559642, -8}));
746
747 auto aliceXRP = env.balance(alice, XRP);
748 auto aliceBTC = env.balance(alice, btc);
749 auto bobXRP = env.balance(bob, XRP);
750 auto bobBTC = env.balance(bob, btc);
751
752 // gw clawback all BTC from alice
753 env(amm::ammClawback(gw, alice, btc, XRP, std::nullopt));
754 env.close();
755 BEAST_EXPECT(
756 amm.expectBalances(btc(10'000), XRP(20'000), IOUAmount{14'142'135'62373094, -8}));
757 env.require(Balance(alice, aliceBTC));
758 env.require(Balance(alice, aliceXRP + XRP(10'000)));
759 env.require(Balance(bob, bobBTC));
760 env.require(Balance(bob, bobXRP));
761 aliceXRP = env.balance(alice, XRP);
762
763 // gw clawback all BTC from bob
764 env(amm::ammClawback(gw, bob, btc, XRP, std::nullopt));
765 env.close();
766 // amm is empty and deleted
767 BEAST_EXPECT(!amm.ammExists());
768 env.require(Balance(alice, aliceBTC));
769 env.require(Balance(alice, aliceXRP));
770 env.require(Balance(bob, bobBTC));
771 env.require(Balance(bob, bobXRP + XRP(20'000)));
772 }
773
774 // AMMClawback all from MPT/MPT pool, different issuers
775 {
776 Env env(*this, features);
777 Account const gw{"gateway"};
778 Account const gw2{"gateway2"};
779 Account const alice{"alice"};
780 Account const bob{"bob"};
781 env.fund(XRP(100000), gw, gw2, alice, bob);
782 env.close();
783
784 env(fset(gw, asfAllowTrustLineClawback));
785 env(fset(gw2, asfAllowTrustLineClawback));
786 env.close();
787
788 MPT const btc = MPTTester(
789 {.env = env,
790 .issuer = gw,
791 .holders = {alice, bob},
792 .pay = 40'000'000000,
793 .flags = tfMPTCanClawback | kMptDexFlags});
794
795 MPT const eth = MPTTester(
796 {.env = env,
797 .issuer = gw2,
798 .holders = {alice, bob},
799 .pay = 30'000'000000,
800 .flags = tfMPTCanClawback | kMptDexFlags});
801
802 AMM amm(env, alice, btc(20'000), eth(50'000));
803 env.close();
804 BEAST_EXPECT(
805 amm.expectBalances(btc(20'000), eth(50'000), IOUAmount{31'622'77660168379, -11}));
806
807 amm.deposit(bob, btc(40'000), eth(100'000));
808 BEAST_EXPECT(
809 amm.expectBalances(btc(60'000), eth(150'000), IOUAmount{94'868'32980505137, -11}));
810
811 auto aliceBTC = env.balance(alice, btc);
812 auto aliceETH = env.balance(alice, eth);
813 auto bobBTC = env.balance(bob, btc);
814 auto bobETH = env.balance(bob, eth);
815
816 // gw clawback all BTC from bob
817 env(amm::ammClawback(gw, bob, btc, eth, std::nullopt));
818 env.close();
819 BEAST_EXPECT(
820 amm.expectBalances(btc(20'000), eth(50'000), IOUAmount{31'622'77660168379, -11}));
821 env.require(Balance(alice, aliceBTC));
822 env.require(Balance(alice, aliceETH));
823 env.require(Balance(bob, bobBTC));
824 env.require(Balance(bob, bobETH + eth(100'000)));
825 bobETH = env.balance(bob, eth);
826
827 // gw2 clawback all ETH from alice
828 env(amm::ammClawback(gw2, alice, eth, btc, std::nullopt));
829 env.close();
830 // amm is empty and deleted
831 BEAST_EXPECT(!amm.ammExists());
832 env.require(Balance(alice, aliceBTC + btc(20'000)));
833 env.require(Balance(alice, aliceETH));
834 env.require(Balance(bob, bobBTC));
835 env.require(Balance(bob, bobETH));
836 }
837 }
838
839 void
841 {
842 testcase("test AMMClawback specific amount, assets have the same issuer");
843 using namespace jtx;
844
845 // AMMClawback from MPT/IOU issued by the same issuer
846 {
847 Env env(*this, features);
848 Account const gw{"gateway"};
849 Account const alice{"alice"};
850 Account const bob{"bob"};
851 env.fund(XRP(100000), gw, alice, bob);
852 env.close();
853
854 env(fset(gw, asfAllowTrustLineClawback));
855 env.close();
856
857 auto const usd = gw["USD"];
858 env.trust(usd(100000), alice);
859 env(pay(gw, alice, usd(50000)));
860 env.trust(usd(100000), bob);
861 env(pay(gw, bob, usd(40000)));
862 env.close();
863
864 MPT const btc = MPTTester(
865 {.env = env,
866 .issuer = gw,
867 .holders = {alice, bob},
868 .pay = 40'000'000000,
869 .flags = tfMPTCanClawback | kMptDexFlags});
870
871 AMM amm(env, alice, btc(1'000'000000), usd(2000));
872 env.close();
873 BEAST_EXPECT(amm.expectBalances(
874 btc(1'000'000000), usd(2000), IOUAmount{1414'213'562373095, -9}));
875
876 amm.deposit(bob, btc(500'000000), usd(1000));
877 BEAST_EXPECT(amm.expectBalances(
878 btc(1'500'000000),
879 STAmount{usd, UINT64_C(2'999'999999999999), -12},
880 IOUAmount{2'121'320'343559642, -9}));
881
882 auto aliceUSD = env.balance(alice, usd);
883 auto aliceBTC = env.balance(alice, btc);
884 auto bobUSD = env.balance(bob, usd);
885 auto bobBTC = env.balance(bob, btc);
886
887 // gw clawback 500 USD from alice.
888 env(amm::ammClawback(gw, alice, usd, btc, usd(500)));
889 env.close();
890 BEAST_EXPECT(amm.expectBalances(
891 btc(1250'000001), usd(2500), IOUAmount{1'767'766'952966369, -9}));
892 env.require(Balance(alice, aliceUSD));
893 env.require(Balance(alice, aliceBTC + btc(249'999999)));
894 env.require(Balance(bob, bobUSD));
895 env.require(Balance(bob, bobBTC));
896 aliceBTC = env.balance(alice, btc);
897 // gw clawback 250'000000 BTC and 500 USD from bob
898 // with tfClawTwoAssets
899 env(amm::ammClawback(gw, bob, btc, usd, btc(250'000000)), Txflags(tfClawTwoAssets));
900 env.close();
901 BEAST_EXPECT(amm.expectBalances(
902 btc(1'000'000002),
903 STAmount{usd, UINT64_C(2000'0000004), -7},
904 IOUAmount{1'414'213'562655938, -9}));
905 env.require(Balance(alice, aliceUSD));
906 env.require(Balance(alice, aliceBTC));
907 env.require(Balance(bob, bobUSD));
908 env.require(Balance(bob, bobBTC));
909 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'060'660'171779822, -9}));
910 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{353'553'390876116, -9}));
911
912 // gw clawback USD from alice exceeding her balance
913 env(amm::ammClawback(gw, alice, usd, btc, usd(5'000)));
914 env.close();
915 BEAST_EXPECT(amm.expectBalances(
916 btc(250'000001),
917 STAmount{usd, UINT64_C(500'0000004), -7},
918 IOUAmount{353'553'390876116, -9}));
919 env.require(Balance(alice, aliceUSD));
920 env.require(Balance(alice, aliceBTC + btc(750'000001)));
921 env.require(Balance(bob, bobUSD));
922 env.require(Balance(bob, bobBTC));
923 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
924 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{353'553'390876116, -9}));
925 aliceBTC = env.balance(alice, btc);
926
927 // gw clawback BTC from bob which exceeds his balance with
928 // tfClawTwoAssets
929 env(amm::ammClawback(gw, bob, btc, usd, btc(300'000000)), Txflags(tfClawTwoAssets));
930 env.close();
931 // amm is empty and deleted
932 BEAST_EXPECT(!amm.ammExists());
933 env.require(Balance(alice, aliceUSD));
934 env.require(Balance(alice, aliceBTC));
935 // USD is also clawed back from bob because of tfClawTwoAssets,
936 // bob's USD balance will not change
937 env.require(Balance(bob, bobUSD));
938 env.require(Balance(bob, bobBTC));
939 }
940
941 // AMMClawback from MPT/MPT issued by the same issuer
942 {
943 Env env(*this, features);
944 Account const gw{"gateway"};
945 Account const alice{"alice"};
946 Account const bob{"bob"};
947 env.fund(XRP(100000), gw, alice, bob);
948 env.close();
949
950 env(fset(gw, asfAllowTrustLineClawback));
951 env.close();
952
953 MPT const btc = MPTTester(
954 {.env = env,
955 .issuer = gw,
956 .holders = {alice, bob},
957 .pay = 40'000'000000,
958 .flags = tfMPTCanClawback | kMptDexFlags});
959
960 MPT const eth = MPTTester(
961 {.env = env,
962 .issuer = gw,
963 .holders = {alice, bob},
964 .pay = 30'000'000000,
965 .flags = tfMPTCanClawback | kMptDexFlags});
966
967 AMM amm(env, alice, btc(2'000'000000), eth(3'000'000000));
968 env.close();
969 BEAST_EXPECT(amm.expectBalances(
970 btc(2'000'000000), eth(3'000'000000), IOUAmount{2'449'489'742'783178, -6}));
971
972 amm.deposit(bob, btc(4'000'000000), eth(6'000'000000));
973 BEAST_EXPECT(amm.expectBalances(
974 btc(6'000'000000), eth(9'000'000000), IOUAmount{7'348'469'228'349534, -6}));
975
976 auto aliceBTC = env.balance(alice, btc);
977 auto aliceETH = env.balance(alice, eth);
978 auto bobBTC = env.balance(bob, btc);
979 auto bobETH = env.balance(bob, eth);
980
981 // gw clawback BTC from alice
982 env(amm::ammClawback(gw, alice, btc, eth, btc(1'000'000000)));
983 env.close();
984 BEAST_EXPECT(amm.expectBalances(
985 btc(5'000'000000), eth(7'500'000000), IOUAmount{6'123'724'356'957944, -6}));
986 env.require(Balance(alice, aliceBTC));
987 env.require(Balance(alice, aliceETH + eth(1'500'000000)));
988 env.require(Balance(bob, bobBTC));
989 env.require(Balance(bob, bobETH));
990 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
991 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{4'898'979'485'566356, -6}));
992 aliceETH = env.balance(alice, eth);
993
994 // gw clawback ETH and BTC from bob with tfClawTwoAssets
995 env(amm::ammClawback(gw, bob, eth, btc, eth(3'000'000000)), Txflags(tfClawTwoAssets));
996 env.close();
997 BEAST_EXPECT(amm.expectBalances(
998 btc(3'000'000000), eth(4'500'000000), IOUAmount{3'674'234'614'174766, -6}));
999 env.require(Balance(alice, aliceBTC));
1000 env.require(Balance(alice, aliceETH));
1001 env.require(Balance(bob, bobBTC));
1002 env.require(Balance(bob, bobETH));
1003 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6}));
1004 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6}));
1005
1006 // gw clawback BTC from alice, which exceeds her balance with
1007 // tfClawTwoAssets
1008 env(amm::ammClawback(gw, alice, btc, eth, btc(3'000'000000)), Txflags(tfClawTwoAssets));
1009 env.close();
1010 BEAST_EXPECT(amm.expectBalances(
1011 btc(2'000'000001), eth(3'000'000001), IOUAmount{2'449'489'742'783178, -6}));
1012 env.require(Balance(alice, aliceBTC));
1013 env.require(Balance(alice, aliceETH));
1014 env.require(Balance(bob, bobBTC));
1015 env.require(Balance(bob, bobETH));
1016 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1017 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6}));
1018
1019 // gw clawback ETH from bob, which is the same as his balance
1020 env(amm::ammClawback(gw, bob, eth, btc, eth(3'000'000001)));
1021 env.close();
1022 // amm is empty and deleted
1023 BEAST_EXPECT(!amm.ammExists());
1024 env.require(Balance(alice, aliceBTC));
1025 env.require(Balance(alice, aliceETH));
1026 env.require(Balance(bob, bobBTC + btc(2'000'000001)));
1027 env.require(Balance(bob, bobETH));
1028 }
1029 }
1030
1031 void
1033 {
1034 testcase("test AMMClawback all, assets have the same issuer");
1035 using namespace jtx;
1036
1037 // AMMClawback all from MPT/IOU issued by the same issuer
1038 {
1039 Env env(*this, features);
1040 Account const gw{"gateway"};
1041 Account const alice{"alice"};
1042 Account const bob{"bob"};
1043 env.fund(XRP(100000), gw, alice, bob);
1044 env.close();
1045
1046 env(fset(gw, asfAllowTrustLineClawback));
1047 env.close();
1048
1049 auto const usd = gw["USD"];
1050 env.trust(usd(100000), alice);
1051 env(pay(gw, alice, usd(50000)));
1052 env.trust(usd(200000), bob);
1053 env(pay(gw, bob, usd(60000)));
1054 env.close();
1055
1056 MPT const btc = MPTTester(
1057 {.env = env,
1058 .issuer = gw,
1059 .holders = {alice, bob},
1060 .pay = 40'000'000000,
1061 .flags = tfMPTCanClawback | kMptDexFlags});
1062
1063 AMM amm(env, alice, btc(2'000'000000), usd(8'000));
1064 env.close();
1065 BEAST_EXPECT(amm.expectBalances(btc(2'000'000000), usd(8'000), IOUAmount(4'000'000)));
1066
1067 amm.deposit(bob, btc(1'000'000000), usd(4'000));
1068 BEAST_EXPECT(amm.expectBalances(btc(3'000'000000), usd(12'000), IOUAmount(6'000'000)));
1069
1070 auto aliceBTC = env.balance(alice, btc);
1071 auto aliceUSD = env.balance(alice, usd);
1072 auto bobBTC = env.balance(bob, btc);
1073 auto bobUSD = env.balance(bob, usd);
1074
1075 // gw clawback all BTC and USD from alice
1076 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Txflags(tfClawTwoAssets));
1077 env.close();
1078
1079 BEAST_EXPECT(amm.expectBalances(btc(1'000'000000), usd(4'000), IOUAmount(2'000'000)));
1080 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2'000'000)));
1081 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1082 env.require(Balance(alice, aliceBTC));
1083 env.require(Balance(alice, aliceUSD));
1084 env.require(Balance(bob, bobBTC));
1085 env.require(Balance(bob, bobUSD));
1086
1087 // gw clawback all USD from bob
1088 env(amm::ammClawback(gw, bob, usd, btc, std::nullopt));
1089 env.close();
1090 // amm is empty and deleted
1091 BEAST_EXPECT(!amm.ammExists());
1092 env.require(Balance(alice, aliceBTC));
1093 env.require(Balance(alice, aliceUSD));
1094 env.require(Balance(bob, bobBTC + btc(1'000'000000)));
1095 env.require(Balance(bob, bobUSD));
1096 }
1097
1098 // AMMClawback all from MPT/MPT issued by the same issuer
1099 {
1100 Env env(*this, features);
1101 Account const gw{"gateway"};
1102 Account const alice{"alice"};
1103 Account const bob{"bob"};
1104 env.fund(XRP(100000), gw, alice, bob);
1105 env.close();
1106
1107 env(fset(gw, asfAllowTrustLineClawback));
1108 env.close();
1109
1110 MPT const btc = MPTTester(
1111 {.env = env,
1112 .issuer = gw,
1113 .holders = {alice, bob},
1114 .pay = 40'000'000000,
1115 .flags = tfMPTCanClawback | kMptDexFlags});
1116
1117 MPT const eth = MPTTester(
1118 {.env = env,
1119 .issuer = gw,
1120 .holders = {alice, bob},
1121 .pay = 30'000'000000,
1122 .flags = tfMPTCanClawback | kMptDexFlags});
1123
1124 AMM amm(env, alice, btc(20'000), eth(10'000));
1125 env.close();
1126 BEAST_EXPECT(
1127 amm.expectBalances(btc(20'000), eth(10'000), IOUAmount{14'142'13562373095, -11}));
1128
1129 amm.deposit(bob, btc(40'000), eth(20'000));
1130 BEAST_EXPECT(
1131 amm.expectBalances(btc(60'000), eth(30'000), IOUAmount{42'426'40687119285, -11}));
1132
1133 auto aliceBTC = env.balance(alice, btc);
1134 auto aliceETH = env.balance(alice, eth);
1135 auto bobBTC = env.balance(bob, btc);
1136 auto bobETH = env.balance(bob, eth);
1137
1138 // gw clawback all ETH from bob
1139 env(amm::ammClawback(gw, bob, eth, btc, std::nullopt));
1140 env.close();
1141 BEAST_EXPECT(
1142 amm.expectBalances(btc(20'000), eth(10'000), IOUAmount{14'142'13562373095, -11}));
1143 env.require(Balance(alice, aliceBTC));
1144 env.require(Balance(alice, aliceETH));
1145 env.require(Balance(bob, bobBTC + btc(40'000)));
1146 env.require(Balance(bob, bobETH));
1147 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{14'142'13562373095, -11}));
1148 BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
1149 bobBTC = env.balance(bob, btc);
1150
1151 // gw clawback all ETH and BTC from alice with tfClawTwoAssets
1152 env(amm::ammClawback(gw, alice, eth, btc, std::nullopt), Txflags(tfClawTwoAssets));
1153 env.close();
1154
1155 // amm is empty and deleted
1156 BEAST_EXPECT(!amm.ammExists());
1157 env.require(Balance(alice, aliceBTC));
1158 env.require(Balance(alice, aliceETH));
1159 env.require(Balance(bob, bobBTC));
1160 env.require(Balance(bob, bobETH));
1161 }
1162 }
1163
1164 void
1166 {
1167 testcase("test AMMClawback when issuing token for each other");
1168 using namespace jtx;
1169
1170 // AMMClawback from MPT/IOU issued by each other
1171 {
1172 Env env(*this, features);
1173 Account const gw{"gateway"};
1174 Account const gw2{"gateway2"};
1175 Account const alice{"alice"};
1176 env.fund(XRP(1000000), gw, gw2, alice);
1177 env.close();
1178
1179 env(fset(gw, asfAllowTrustLineClawback));
1180 env(fset(gw2, asfAllowTrustLineClawback));
1181 env.close();
1182
1183 auto const usd = gw["USD"];
1184 env.trust(usd(100000), gw2);
1185 env(pay(gw, gw2, usd(5000)));
1186 env.trust(usd(100000), alice);
1187 env(pay(gw, alice, usd(5000)));
1188
1189 MPT const btc = MPTTester(
1190 {.env = env,
1191 .issuer = gw2,
1192 .holders = {alice, gw},
1193 .pay = 40'000'000000,
1194 .flags = tfMPTCanClawback | kMptDexFlags});
1195
1196 AMM amm(env, gw, usd(1000), btc(2000));
1197 env.close();
1198 BEAST_EXPECT(
1199 amm.expectBalances(usd(1000), btc(2000), IOUAmount{1414'213562373095, -12}));
1200
1201 amm.deposit(gw2, usd(2000), btc(4000));
1202 BEAST_EXPECT(
1203 amm.expectBalances(usd(3000), btc(6000), IOUAmount{4242'640687119285, -12}));
1204
1205 amm.deposit(alice, usd(3000), btc(6000));
1206 BEAST_EXPECT(
1207 amm.expectBalances(usd(6000), btc(12000), IOUAmount{8485'281374238570, -12}));
1208
1209 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414'213562373095, -12}));
1210 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828'427124746190, -12}));
1211 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12}));
1212
1213 auto aliceBTC = env.balance(alice, btc);
1214 auto aliceUSD = env.balance(alice, usd);
1215 auto gwBTC = env.balance(gw, btc);
1216 auto gw2USD = env.balance(gw2, usd);
1217
1218 // gw claws back 1000 USD from gw2.
1219 env(amm::ammClawback(gw, gw2, usd, btc, usd(1000)));
1220 env.close();
1221 BEAST_EXPECT(
1222 amm.expectBalances(usd(5000), btc(10000), IOUAmount{7071'067811865474, -12}));
1223 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414'213562373095, -12}));
1224 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12}));
1225 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12}));
1226 env.require(Balance(alice, aliceBTC));
1227 env.require(Balance(alice, aliceUSD));
1228 env.require(Balance(gw, gwBTC));
1229 env.require(Balance(gw2, gw2USD));
1230
1231 // gw2 claws back 1000 BTC from gw.
1232 env(amm::ammClawback(gw2, gw, btc, usd, btc(1000)), Ter(tesSUCCESS));
1233 env.close();
1234 BEAST_EXPECT(
1235 amm.expectBalances(usd(4500), btc(9001), IOUAmount{6363'961030678927, -12}));
1236
1237 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{707'1067811865480, -13}));
1238 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12}));
1239 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12}));
1240 env.require(Balance(alice, aliceBTC));
1241 env.require(Balance(alice, aliceUSD));
1242 env.require(Balance(gw, gwBTC));
1243 env.require(Balance(gw2, gw2USD));
1244
1245 // gw2 claws back 4000 BTC from alice
1246 env(amm::ammClawback(gw2, alice, btc, usd, btc(4000)));
1247 env.close();
1248 BEAST_EXPECT(amm.expectBalances(
1249 STAmount{usd, UINT64_C(2500'222197533607), -12},
1250 btc(5001),
1251 IOUAmount{3535'84814069829, -11}));
1252
1253 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{707'1067811865480, -13}));
1254 BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12}));
1255 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1414'527797138648, -12}));
1256 env.require(Balance(alice, aliceBTC));
1257 env.require(Balance(alice, aliceUSD + STAmount{usd, UINT64_C(1999'777802466393), -12}));
1258 env.require(Balance(gw, gwBTC));
1259 env.require(Balance(gw2, gw2USD));
1260 }
1261
1262 // AMMClawback from MPT/MPT issued by each other
1263 {
1264 Env env(*this, features);
1265 Account const gw{"gateway"};
1266 Account const gw2{"gateway2"};
1267 Account const alice{"alice"};
1268 env.fund(XRP(100000), gw, gw2, alice);
1269 env.close();
1270
1271 env(fset(gw, asfAllowTrustLineClawback));
1272 env(fset(gw2, asfAllowTrustLineClawback));
1273 env.close();
1274
1275 MPT const btc = MPTTester(
1276 {.env = env,
1277 .issuer = gw,
1278 .holders = {gw2, alice},
1279 .pay = 40'000'000000,
1280 .flags = tfMPTCanClawback | kMptDexFlags});
1281
1282 MPT const eth = MPTTester(
1283 {.env = env,
1284 .issuer = gw2,
1285 .holders = {gw, alice},
1286 .pay = 30'000'000000,
1287 .flags = tfMPTCanClawback | kMptDexFlags});
1288
1289 AMM amm(env, gw, btc(10'000), eth(50'000));
1290 env.close();
1291 BEAST_EXPECT(
1292 amm.expectBalances(btc(10'000), eth(50'000), IOUAmount{22'360'67977499789, -11}));
1293
1294 amm.deposit(gw2, btc(20'000), eth(100'000));
1295 BEAST_EXPECT(
1296 amm.expectBalances(btc(30'000), eth(150'000), IOUAmount{67'082'03932499367, -11}));
1297
1298 amm.deposit(alice, btc(40'000), eth(200'000));
1299 BEAST_EXPECT(
1300 amm.expectBalances(btc(70'000), eth(350'000), IOUAmount{156'524'7584249852, -10}));
1301
1302 auto aliceBTC = env.balance(alice, btc);
1303 auto aliceETH = env.balance(alice, eth);
1304 auto gw2BTC = env.balance(gw2, btc);
1305 auto gwETH = env.balance(gw, eth);
1306
1307 // gw claws back 1000 BTC from gw2.
1308 env(amm::ammClawback(gw, gw2, btc, eth, btc(1000)));
1309 env.close();
1310 BEAST_EXPECT(
1311 amm.expectBalances(btc(69'001), eth(345'001), IOUAmount{154'288'6904474855, -10}));
1312 env.require(Balance(alice, aliceBTC));
1313 env.require(Balance(alice, aliceETH));
1314 env.require(Balance(gw, gwETH));
1315 env.require(Balance(gw2, gw2BTC));
1316
1317 // gw2 claws back all ETH from gw
1318 env(amm::ammClawback(gw2, gw, eth, btc, std::nullopt));
1319 env.close();
1320 BEAST_EXPECT(
1321 amm.expectBalances(btc(59'001), eth(295'001), IOUAmount{131'928'0106724876, -10}));
1322 env.require(Balance(alice, aliceBTC));
1323 env.require(Balance(alice, aliceETH));
1324 env.require(Balance(gw, gwETH));
1325 env.require(Balance(gw2, gw2BTC));
1326
1327 // gw claws back all BTC from alice
1328 env(amm::ammClawback(gw, alice, btc, eth, std::nullopt));
1329 env.close();
1330 BEAST_EXPECT(
1331 amm.expectBalances(btc(19'001), eth(95'001), IOUAmount{42'485'29157249607, -11}));
1332 env.require(Balance(alice, aliceBTC));
1333 env.require(Balance(alice, aliceETH + eth(200'000)));
1334 env.require(Balance(gw, gwETH));
1335 env.require(Balance(gw2, gw2BTC));
1336 }
1337 }
1338
1339 void
1341 {
1342 testcase("test AMMClawback when asset is frozen or locked");
1343 using namespace jtx;
1344
1345 // test AMMClawback when MPT globally locked or IOU globally frozen
1346 {
1347 Env env{*this, features};
1348 Account const gw{"gateway"};
1349 Account const alice{"alice"};
1350 env.fund(XRP(1'000'000), gw, alice);
1351
1352 env(fset(gw, asfAllowTrustLineClawback));
1353 env.close();
1354 auto const usd = gw["USD"];
1355 env.trust(usd(1'000'000), alice);
1356 env(pay(gw, alice, usd(500'000)));
1357
1358 MPTTester btc(
1359 {.env = env,
1360 .issuer = gw,
1361 .holders = {alice},
1362 .pay = 30'000,
1363 .flags = tfMPTCanClawback | tfMPTCanLock | kMptDexFlags});
1364 AMM const ammAlice(env, alice, usd(10'000), btc(10'000));
1365 BEAST_EXPECT(ammAlice.expectBalances(usd(10'000), btc(10'000), IOUAmount(10'000)));
1366 env.close();
1367
1368 auto aliceBTC = env.balance(alice, MPT(btc));
1369 auto aliceUSD = env.balance(alice, usd);
1370
1371 // globally locked and claw back 1000 BTC.
1372 // this should be successful
1373 btc.set({.flags = tfMPTLock});
1374 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(1'000)));
1375 BEAST_EXPECT(ammAlice.expectBalances(usd(9'000), btc(9'000), IOUAmount(9'000)));
1376 env.require(Balance(alice, aliceBTC));
1377 env.require(Balance(alice, aliceUSD + usd(1'000)));
1378 aliceUSD = env.balance(alice, usd);
1379
1380 // unlock and claw back 2000 BTC
1381 btc.set({.flags = tfMPTUnlock});
1382 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(2'000)));
1383 BEAST_EXPECT(ammAlice.expectBalances(
1384 STAmount(usd, UINT64_C(7'000'000000000001), -12), btc(7'001), IOUAmount(7'000)));
1385 env.require(Balance(alice, aliceBTC));
1386 env.require(Balance(alice, aliceUSD + usd(2'000)));
1387 aliceUSD = env.balance(alice, usd);
1388
1389 // globally freeze trustline and claw back 1000 USD.
1390 // this should be successful
1391 env(trust(gw, alice["USD"](0), tfSetFreeze));
1392 env.close();
1393 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(1'000)));
1394 BEAST_EXPECT(ammAlice.expectBalances(
1395 STAmount(usd, UINT64_C(6000'000000000002), -12),
1396 btc(6'001),
1397 IOUAmount(6'000'000000000001, -12)));
1398 env.require(Balance(alice, aliceBTC + btc(1'000)));
1399 env.require(Balance(alice, aliceUSD));
1400 aliceBTC = env.balance(alice, MPT(btc));
1401
1402 // globally unfreeze trustline and claw back 2000 USD
1403 // and 2000 BTC with tfClawTwoAssets
1404 env(fset(gw, asfGlobalFreeze));
1405 env.close();
1406 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(2'000)), Txflags(tfClawTwoAssets));
1407 BEAST_EXPECT(ammAlice.expectBalances(
1408 STAmount(usd, UINT64_C(4'000'000000000002), -12),
1409 btc(4'001),
1410 IOUAmount(4'000'000000000001, -12)));
1411 env.require(Balance(alice, aliceBTC));
1412 env.require(Balance(alice, aliceUSD));
1413 }
1414
1415 // test AMMClawback when MPT individually locked or IOU individually
1416 // frozen
1417 {
1418 Env env{*this, features};
1419 Account const gw{"gateway"};
1420 Account const alice{"alice"};
1421 env.fund(XRP(1'000'000), gw, alice);
1422
1423 env(fset(gw, asfAllowTrustLineClawback));
1424 env.close();
1425 auto const usd = gw["USD"];
1426 env.trust(usd(1'000'000), alice);
1427 env(pay(gw, alice, usd(500'000)));
1428
1429 MPTTester btc(
1430 {.env = env,
1431 .issuer = gw,
1432 .holders = {alice},
1433 .pay = 30'000,
1434 .flags = tfMPTCanClawback | tfMPTCanLock | kMptDexFlags});
1435 AMM const ammAlice(env, alice, usd(10'000), btc(10'000));
1436 BEAST_EXPECT(ammAlice.expectBalances(usd(10'000), btc(10'000), IOUAmount(10'000)));
1437 env.close();
1438
1439 auto aliceBTC = env.balance(alice, MPT(btc));
1440 auto aliceUSD = env.balance(alice, usd);
1441
1442 // individually locked and claw back 2000 BTC from alice
1443 btc.set({.holder = alice, .flags = tfMPTLock});
1444 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(2'000)));
1445 BEAST_EXPECT(ammAlice.expectBalances(usd(8'000), btc(8'000), IOUAmount(8'000)));
1446 env.require(Balance(alice, aliceBTC));
1447 env.require(Balance(alice, aliceUSD + usd(2'000)));
1448 aliceUSD = env.balance(alice, usd);
1449
1450 // individually freeze trustline and claw back 1000 USD from alice
1451 env(trust(gw, alice["USD"](0), tfSetFreeze));
1452 env.close();
1453 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(1'000)));
1454 BEAST_EXPECT(ammAlice.expectBalances(usd(7'000), btc(7'000), IOUAmount(7'000)));
1455 env.require(Balance(alice, aliceBTC + btc(1'000)));
1456 env.require(Balance(alice, aliceUSD));
1457 aliceBTC = env.balance(alice, MPT(btc));
1458
1459 // unlock MPT and claw back 3000 BTC from alice
1460 btc.set({.holder = alice, .flags = tfMPTUnlock});
1461 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(3'000)));
1462 BEAST_EXPECT(ammAlice.expectBalances(
1463 STAmount{usd, UINT64_C(4000'000000000001), -12}, btc(4'001), IOUAmount(4'000)));
1464 env.require(Balance(alice, aliceBTC));
1465 env.require(Balance(alice, aliceUSD + usd(3'000)));
1466 aliceUSD = env.balance(alice, usd);
1467
1468 // unlock trustline and claw back 1000 USD from alice
1469 env(trust(gw, alice["USD"](0), tfClearFreeze));
1470 env.close();
1471 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(1'000)));
1472 BEAST_EXPECT(ammAlice.expectBalances(
1473 STAmount(usd, UINT64_C(3'000'000000000002), -12),
1474 btc(3'001),
1475 IOUAmount(3000'000000000001, -12)));
1476 env.require(Balance(alice, aliceBTC + btc(1'000)));
1477 env.require(Balance(alice, aliceUSD));
1478 }
1479 }
1480
1481 void
1483 {
1484 testcase("test AMMClawback creates missing MPToken");
1485 using namespace jtx;
1486
1487 auto test = [&](std::optional<std::uint64_t> const clawAmount) {
1488 Env env{*this, features};
1489 Account const gw{"gateway"};
1490 Account const alice{"alice"};
1491 env.fund(XRP(1'000'000), gw, alice);
1492 env.close();
1493
1495 {.env = env,
1496 .issuer = gw,
1497 .holders = {alice},
1498 .pay = 1'000,
1499 .flags = tfMPTCanClawback | tfMPTRequireAuth | kMptDexFlags,
1500 .authHolder = true});
1501
1502 AMM ammAlice(env, alice, token(1'000), XRP(1'000));
1503 env.close();
1504 BEAST_EXPECT(env.balance(alice, token) == token(0));
1505
1506 // The holder can delete the zero-balance MPToken while still
1507 // holding LP tokens. A regular AMMWithdraw remains subject to
1508 // RequireAuth and cannot recreate the missing token.
1509 token.authorize({.account = alice, .flags = tfMPTUnauthorize});
1510 env.close();
1511 BEAST_EXPECT(!env.le(keylet::mptoken(token.issuanceID(), alice.id())));
1512 ammAlice.withdrawAll(alice, std::nullopt, Ter(tecNO_AUTH));
1513 env.close();
1514 BEAST_EXPECT(!env.le(keylet::mptoken(token.issuanceID(), alice.id())));
1515
1516 // AMMClawback ignores authorization and must be able to recreate
1517 // the holder MPToken so the issuer can recover MPT from the pool.
1519 if (clawAmount)
1520 amount = token(*clawAmount);
1521 env(amm::ammClawback(gw, alice, token, XRP, amount));
1522 env.close();
1523
1524 auto const sleMpt = env.le(keylet::mptoken(token.issuanceID(), alice.id()));
1525 BEAST_EXPECT(sleMpt && sleMpt->isFlag(lsfMPTAuthorized));
1526 env.require(Balance(alice, token(0)));
1527
1528 BEAST_EXPECT(clawAmount ? ammAlice.ammExists() : !ammAlice.ammExists());
1529 };
1530
1531 test(std::nullopt);
1532 test(400);
1533 }
1534
1535 void
1537 {
1538 testcase("test single depoit and clawback");
1539 using namespace jtx;
1540
1541 // MPT/XRP
1542 {
1543 Env env(*this, features);
1544 Account const gw{"gateway"};
1545 Account const alice{"alice"};
1546 env.fund(XRP(1000000000), gw, alice);
1547 env.close();
1548
1549 MPT const btc = MPTTester(
1550 {.env = env,
1551 .issuer = gw,
1552 .holders = {alice},
1553 .pay = 40'000'000000,
1554 .flags = tfMPTCanClawback | kMptDexFlags});
1555
1556 // gw creates AMM pool of BTC/XRP.
1557 AMM amm(env, gw, XRP(100), btc(400), Ter(tesSUCCESS));
1558 env.close();
1559 BEAST_EXPECT(amm.expectBalances(XRP(100), btc(400), IOUAmount(200000)));
1560 amm.deposit(alice, btc(400));
1561 BEAST_EXPECT(amm.expectBalances(XRP(100), btc(800), IOUAmount{282842'712474619, -9}));
1562
1563 auto aliceBTC = env.balance(alice, MPT(btc));
1564 auto aliceXRP = env.balance(alice, XRP);
1565
1566 // gw clawback 100 BTC from alice
1567 env(amm::ammClawback(gw, alice, MPT(btc), XRP, btc(100)));
1568 BEAST_EXPECT(amm.expectBalances(
1569 XRPAmount(87500001), btc(701), IOUAmount{247'487'3734152917, -10}));
1570
1571 env.require(Balance(alice, aliceBTC));
1572 env.require(Balance(alice, aliceXRP + XRPAmount(12'499999)));
1573 }
1574
1575 // MPT/IOU
1576 {
1577 Env env(*this, features);
1578 Account const gw{"gateway"};
1579 Account const alice{"alice"};
1580 env.fund(XRP(1000000000), gw, alice);
1581 env.close();
1582
1583 // gw sets asfAllowTrustLineClawback.
1584 env(fset(gw, asfAllowTrustLineClawback));
1585 env.close();
1586 env.require(Flags(gw, asfAllowTrustLineClawback));
1587
1588 // gw issues 1000 USD to Alice.
1589 auto const usd = gw["USD"];
1590 env.trust(usd(100000), alice);
1591 env(pay(gw, alice, usd(1000)));
1592 env.close();
1593
1594 MPT const btc = MPTTester(
1595 {.env = env,
1596 .issuer = gw,
1597 .holders = {alice},
1598 .pay = 40'000'000000,
1599 .flags = tfMPTCanClawback | kMptDexFlags});
1600
1601 // gw creates AMM pool of BTC/USD.
1602 AMM amm(env, gw, usd(100), btc(400), Ter(tesSUCCESS));
1603 env.close();
1604 BEAST_EXPECT(amm.expectBalances(usd(100), btc(400), IOUAmount(200)));
1605 amm.deposit(alice, btc(400));
1606 BEAST_EXPECT(amm.expectBalances(usd(100), btc(800), IOUAmount{282'842712474619, -12}));
1607
1608 auto aliceBTC = env.balance(alice, MPT(btc));
1609 auto aliceUSD = env.balance(alice, usd);
1610
1611 // gw clawback 100 BTC from alice
1612 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(100)));
1613 BEAST_EXPECT(amm.expectBalances(
1614 STAmount{usd, UINT64_C(87'50000000000003), -14},
1615 btc(701),
1616 IOUAmount{247'4873734152917, -13}));
1617
1618 env.require(Balance(alice, aliceBTC));
1619 env.require(Balance(alice, aliceUSD + usd(12.5)));
1620 aliceUSD = env.balance(alice, usd);
1621
1622 // gw clawback 30 USD from alice with tfClawTwoAssets, which exceeds
1623 // her balance
1624 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(30)), Txflags(tfClawTwoAssets));
1625 BEAST_EXPECT(amm.expectBalances(
1626 STAmount{usd, UINT64_C(70'71067811865476), -14}, btc(567), IOUAmount(200)));
1627 env.require(Balance(alice, aliceBTC));
1628 env.require(Balance(alice, aliceUSD));
1629 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1630 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount(200)));
1631 }
1632
1633 // MPT/MPT
1634 {
1635 Env env(*this, features);
1636 Account const gw{"gateway"};
1637 Account const alice{"alice"};
1638 env.fund(XRP(1000000000), gw, alice);
1639 env.close();
1640
1641 MPT const usd = MPTTester(
1642 {.env = env,
1643 .issuer = gw,
1644 .holders = {alice},
1645 .pay = 40'000'000000,
1646 .flags = tfMPTCanClawback | kMptDexFlags});
1647
1648 MPT const btc = MPTTester(
1649 {.env = env,
1650 .issuer = gw,
1651 .holders = {alice},
1652 .pay = 40'000'000000,
1653 .flags = tfMPTCanClawback | kMptDexFlags});
1654
1655 // gw creates AMM pool of BTC/USD.
1656 AMM amm(env, gw, usd(100), btc(400), Ter(tesSUCCESS));
1657 env.close();
1658 BEAST_EXPECT(amm.expectBalances(usd(100), btc(400), IOUAmount(200)));
1659 amm.deposit(alice, btc(400));
1660 BEAST_EXPECT(amm.expectBalances(usd(100), btc(800), IOUAmount{282'842712474619, -12}));
1661
1662 auto aliceBTC = env.balance(alice, MPT(btc));
1663 auto aliceUSD = env.balance(alice, usd);
1664
1665 // gw clawback 100 BTC from alice
1666 env(amm::ammClawback(gw, alice, MPT(btc), usd, btc(100)));
1667 BEAST_EXPECT(amm.expectBalances(usd(88), btc(701), IOUAmount{247'4873734152917, -13}));
1668
1669 env.require(Balance(alice, aliceBTC));
1670 env.require(Balance(alice, aliceUSD + usd(12)));
1671 aliceUSD = env.balance(alice, usd);
1672
1673 // gw clawback 30 USD from alice with tfClawTwoAssets, which exceeds
1674 // her balance
1675 env(amm::ammClawback(gw, alice, usd, MPT(btc), usd(30)), Txflags(tfClawTwoAssets));
1676 BEAST_EXPECT(amm.expectBalances(usd(72), btc(567), IOUAmount(200)));
1677 env.require(Balance(alice, aliceBTC));
1678 env.require(Balance(alice, aliceUSD));
1679 BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
1680 BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount(200)));
1681 }
1682 }
1683
1684 void
1686 {
1687 testcase(
1688 "test last holder's lptoken balance not equal to AMM's lptoken "
1689 "balance before clawback");
1690 using namespace jtx;
1691 std::string logs;
1692
1693 // MPT/IOU
1694 {
1695 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
1696 Account const gw{"gateway"}, alice{"alice"}, bob{"bob"};
1697 env.fund(XRP(100000), gw, alice, bob);
1698 env.close();
1699 env(fset(gw, asfAllowTrustLineClawback));
1700 env.close();
1701
1702 auto const usd = gw["USD"];
1703 env.trust(usd(100000), alice);
1704 env(pay(gw, alice, usd(50000)));
1705 env.trust(usd(100000), bob);
1706 env(pay(gw, bob, usd(40000)));
1707 env.close();
1708
1709 MPT const eur = MPTTester(
1710 {.env = env,
1711 .issuer = gw,
1712 .holders = {alice, bob},
1713 .pay = 40'000'000000,
1714 .flags = tfMPTCanClawback | kMptDexFlags});
1715
1716 AMM amm(env, alice, usd(2), eur(1));
1717 amm.deposit(alice, IOUAmount{1'576123487565916, -15});
1718 amm.deposit(bob, IOUAmount{1'000});
1719 amm.withdraw(alice, IOUAmount{1'576123487565916, -15});
1720 amm.withdrawAll(bob);
1721
1722 auto const lpToken =
1723 getAccountLines(env, alice, amm.lptIssue())[jss::lines][0u][jss::balance]
1724 .asString();
1725 auto const lpTokenBalance =
1726 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString();
1727 if (features[featureSingleAssetVault] || features[featureLendingProtocol])
1728 {
1729 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741");
1730 }
1731 else
1732 {
1733 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.414213562374");
1734 }
1735
1736 auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
1737 BEAST_EXPECT(res && res.value());
1738
1739 if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1740 {
1741 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1742 BEAST_EXPECT(!amm.ammExists());
1743 }
1744 else if (
1745 features[fixAMMv1_3] &&
1746 (features[featureSingleAssetVault] || features[featureLendingProtocol]))
1747 {
1748 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1749 // Without the Rounding feature and with new Number a dust pool
1750 // amount remains
1751 BEAST_EXPECT(amm.ammExists());
1752 }
1753 else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
1754 {
1755 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecINTERNAL));
1756 BEAST_EXPECT(amm.ammExists());
1757 }
1758 else
1759 {
1760 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecAMM_BALANCE));
1761 BEAST_EXPECT(amm.ammExists());
1762 }
1763 }
1764
1765 // MPT/MPT
1766 {
1767 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
1768 Account const gw{"gateway"}, alice{"alice"}, bob{"bob"};
1769 env.fund(XRP(100000), gw, alice, bob);
1770 env.close();
1771
1772 MPT const usd = MPTTester(
1773 {.env = env,
1774 .issuer = gw,
1775 .holders = {alice, bob},
1776 .pay = 40'000'000000,
1777 .flags = tfMPTCanClawback | kMptDexFlags});
1778
1779 MPT const eur = MPTTester(
1780 {.env = env,
1781 .issuer = gw,
1782 .holders = {alice, bob},
1783 .pay = 40'000'000000,
1784 .flags = tfMPTCanClawback | kMptDexFlags});
1785
1786 AMM amm(env, alice, usd(2), eur(1));
1787 amm.deposit(alice, IOUAmount{1'576123487565916, -15});
1788 amm.deposit(bob, IOUAmount{1'000});
1789 amm.withdraw(alice, IOUAmount{1'576123487565916, -15});
1790 amm.withdrawAll(bob);
1791
1792 auto const lpToken =
1793 getAccountLines(env, alice, amm.lptIssue())[jss::lines][0u][jss::balance]
1794 .asString();
1795 auto const lpTokenBalance =
1796 amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString();
1797 if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
1798 {
1799 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.414213562374");
1800 }
1801 else
1802 {
1803 BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741");
1804 }
1805
1806 auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice);
1807 BEAST_EXPECT(res && res.value());
1808
1809 if (features[fixAMMv1_3] && features[fixAMMClawbackRounding])
1810 {
1811 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1812 BEAST_EXPECT(!amm.ammExists());
1813 }
1814 else if (
1815 features[fixAMMv1_3] &&
1816 (features[featureSingleAssetVault] || features[featureLendingProtocol]))
1817 {
1818 // Without the Rounding feature and with new Number a dust pool
1819 // amount remains
1820 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt));
1821 BEAST_EXPECT(amm.ammExists());
1822 }
1823 else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol])
1824 {
1825 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecINTERNAL));
1826 BEAST_EXPECT(amm.ammExists());
1827 }
1828 else if (features[featureMPTokensV2])
1829 {
1830 env(amm::ammClawback(gw, alice, usd, eur, std::nullopt), Ter(tecAMM_BALANCE));
1831 BEAST_EXPECT(amm.ammExists());
1832 }
1833 }
1834 }
1835
1836 void
1838 {
1839 testcase("claw asset check for MPT and IOU");
1840 using namespace jtx;
1841
1842 // IOU/MPT, MPT not clawable
1843 {
1844 Env env(*this, features);
1845 Account const gw{"gateway"};
1846 Account const alice{"alice"};
1847 env.fund(XRP(100000), gw, alice);
1848 env.close();
1849
1850 env(fset(gw, asfAllowTrustLineClawback));
1851 env.close();
1852
1853 auto const usd = gw["USD"];
1854 env.trust(usd(100000), alice);
1855 env(pay(gw, alice, usd(1000)));
1856 env.close();
1857
1858 MPT const btc =
1859 MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 40'000});
1860
1861 AMM const amm(env, alice, usd(200), btc(100));
1862 // Asset BTC is not clawable without tfMPTCanClawback.
1863 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(tecNO_PERMISSION));
1864
1865 // Although USD is clawable with asfAllowTrustLineClawback.
1866 // When tfClawTwoAssets is set, we will claw Asset2 as well.
1867 // But Asset2 is not clawable. tfMPTCanClawback was not set for BTC.
1868 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt),
1869 Txflags(tfClawTwoAssets),
1871
1872 // Can only claw the other asset
1873 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt));
1874 }
1875
1876 // IOU/MPT, IOU not clawable
1877 {
1878 Env env(*this, features);
1879 Account const gw{"gateway"};
1880 Account const alice{"alice"};
1881 env.fund(XRP(100000), gw, alice);
1882 env.close();
1883
1884 auto const usd = gw["USD"];
1885 env.trust(usd(100000), alice);
1886 env(pay(gw, alice, usd(1000)));
1887 env.close();
1888
1889 MPT const btc = MPTTester(
1890 {.env = env,
1891 .issuer = gw,
1892 .holders = {alice},
1893 .pay = 40'000,
1894 .flags = tfMPTCanClawback | kMptDexFlags});
1895
1896 // Asset USD is not clawable without asfAllowTrustLineClawback.
1897 AMM const amm(env, alice, usd(200), btc(100));
1898 env(amm::ammClawback(gw, alice, usd, btc, std::nullopt), Ter(tecNO_PERMISSION));
1899
1900 // Although BTC is clawable with tfMPTCanClawback.
1901 // When tfClawTwoAssets is set, we will claw Asset2 as well.
1902 // But Asset2 is not clawable. asfAllowTrustLineClawback was not set
1903 // by the issuer.
1904 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt),
1905 Txflags(tfClawTwoAssets),
1907
1908 // Can only claw the other asset
1909 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt));
1910 }
1911
1912 // IOU/MPT both clawable
1913 {
1914 Env env(*this, features);
1915 Account const gw{"gateway"};
1916 Account const gw2{"gateway2"};
1917 Account const alice{"alice"};
1918 env.fund(XRP(100000), gw, gw2, alice);
1919 env.close();
1920
1921 env(fset(gw, asfAllowTrustLineClawback));
1922 env.close();
1923
1924 auto const usd = gw["USD"];
1925 env.trust(usd(100000), alice);
1926 env(pay(gw, alice, usd(1000)));
1927 env.close();
1928
1929 MPT const btc = MPTTester(
1930 {.env = env,
1931 .issuer = gw2,
1932 .holders = {alice},
1933 .pay = 40'000,
1934 .flags = tfMPTCanClawback | kMptDexFlags});
1935
1936 AMM const amm(env, alice, usd(200), btc(100));
1937
1938 // the account trying to claw MPT is not its issuer
1939 // will return temMALFORMED in preflight.
1940 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(temMALFORMED));
1941 }
1942
1943 // only issuer can claw. IOU/MPT mix
1944 {
1945 auto test = [&](auto&& issue1, auto&& issue2) {
1946 Env env(*this);
1947 Account const gw("gateway"), alice("alice"), bob("bob");
1948 env.fund(XRP(30'000), alice, bob, gw);
1949 env.close();
1950 auto const usd = issue1(
1951 {.env = env,
1952 .token = "USD",
1953 .issuer = gw,
1954 .holders = {alice},
1955 .limit = 1'000'000});
1956 auto const btc = issue2(
1957 {.env = env,
1958 .token = "BTC",
1959 .issuer = bob,
1960 .holders = {alice},
1961 .limit = 1'000'000});
1962 env(pay(gw, alice, usd(50000)));
1963 env(pay(bob, alice, btc(50000)));
1964 env.close();
1965
1966 auto ammAlice = AMM(env, alice, usd(10000), btc(10100));
1967 // BTC's issuer is bob, alice can not clawback
1968 env(amm::ammClawback(gw, alice, btc, usd, std::nullopt), Ter(temMALFORMED));
1969 };
1971 }
1972
1973 // set tfClawTwoAssets, but the two assets are from different issuer.
1974 {
1975 auto test = [&](auto&& issue1, auto&& issue2) {
1976 Env env(*this);
1977 Account const gw("gateway"), alice("alice"), bob("bob");
1978 env.fund(XRP(30'000), alice, bob, gw);
1979 env.close();
1980 auto const usd = issue1(
1981 {.env = env,
1982 .token = "USD",
1983 .issuer = gw,
1984 .holders = {alice},
1985 .limit = 1'000'000});
1986 auto const btc = issue2(
1987 {.env = env,
1988 .token = "BTC",
1989 .issuer = bob,
1990 .holders = {alice},
1991 .limit = 1'000'000});
1992 env(pay(gw, alice, usd(50000)));
1993 env(pay(bob, alice, btc(50000)));
1994 env.close();
1995
1996 auto ammAlice = AMM(env, alice, usd(10000), btc(10100));
1997 // BTC's issuer is bob. But with tfClawTwoAssets, we will claw
1998 // both. It will fail because the other asset USD's issuer is
1999 // gw.
2000 env(amm::ammClawback(bob, alice, btc, usd, std::nullopt),
2001 Txflags(tfClawTwoAssets),
2003 };
2005 }
2006 }
2007
2008 // Test that AMMClawback succeeds when the LP has previously deleted both
2009 // zero-balance MPToken objects in an MPT/MPT pool. The fix changes the
2010 // ValidMPTIssuance invariant threshold from > 1 to > 2 so that the two
2011 // MPToken creations triggered by the internal AMMWithdraw are permitted.
2012 void
2014 {
2015 testcase("test AMMClawback after holder deletes zero-balance MPTokens");
2016 using namespace jtx;
2017
2018 // Partial clawback (one asset): verify both MPTokens are recreated and
2019 // the non-claw asset is returned to alice.
2020 {
2021 Env env(*this, features);
2022 Account const gw{"gateway"};
2023 Account const alice{"alice"};
2024 env.fund(XRP(100'000), gw, alice);
2025 env.close();
2026
2027 MPTTester btc(
2028 {.env = env,
2029 .issuer = gw,
2030 .holders = {alice},
2031 .pay = 10'000,
2032 .flags = tfMPTCanClawback | kMptDexFlags});
2033
2034 MPTTester eth(
2035 {.env = env,
2036 .issuer = gw,
2037 .holders = {alice},
2038 .pay = 10'000,
2039 .flags = tfMPTCanClawback | kMptDexFlags});
2040
2041 // Alice deposits everything into the MPT/MPT pool; her MPT
2042 // balances drop to zero.
2043 AMM const amm(env, alice, btc(10'000), eth(10'000));
2044 env.close();
2045 BEAST_EXPECT(amm.expectBalances(btc(10'000), eth(10'000), IOUAmount{10'000}));
2046
2047 auto aliceBTC = env.balance(alice, btc);
2048 auto aliceETH = env.balance(alice, eth);
2049 BEAST_EXPECT(aliceBTC == btc(0));
2050 BEAST_EXPECT(aliceETH == eth(0));
2051
2052 // Alice deletes both zero-balance MPTokens to reclaim reserves.
2053 btc.authorize({.account = alice, .flags = tfMPTUnauthorize});
2054 eth.authorize({.account = alice, .flags = tfMPTUnauthorize});
2055 BEAST_EXPECT(!env.le(keylet::mptoken(btc.issuanceID(), alice.id())));
2056 BEAST_EXPECT(!env.le(keylet::mptoken(eth.issuanceID(), alice.id())));
2057
2058 // gw claws back some BTC from alice's share in the pool.
2059 // AMMWithdraw internally creates both missing MPTokens
2060 // (mptokensCreated_ == 2); the invariant (> 2) allows this.
2061 env(amm::ammClawback(gw, alice, btc, eth, btc(1'000)));
2062 env.close();
2063
2064 // Both MPToken objects must have been recreated.
2065 BEAST_EXPECT(env.le(keylet::mptoken(btc.issuanceID(), alice.id())));
2066 BEAST_EXPECT(env.le(keylet::mptoken(eth.issuanceID(), alice.id())));
2067
2068 // The non-claw asset (eth) was returned to alice.
2069 BEAST_EXPECT(env.balance(alice, eth) > aliceETH);
2070 // The claw asset (btc) was burned; alice's btc balance stays 0.
2071 env.require(Balance(alice, aliceBTC));
2072 BEAST_EXPECT(amm.ammExists());
2073 }
2074
2075 // Full clawback (two assets, tfClawTwoAssets): verify both MPTokens
2076 // are recreated and the AMM is deleted when fully drained.
2077 {
2078 Env env(*this, features);
2079 Account const gw{"gateway"};
2080 Account const alice{"alice"};
2081 env.fund(XRP(100'000), gw, alice);
2082 env.close();
2083
2084 MPTTester btc(
2085 {.env = env,
2086 .issuer = gw,
2087 .holders = {alice},
2088 .pay = 10'000,
2089 .flags = tfMPTCanClawback | kMptDexFlags});
2090
2091 MPTTester eth(
2092 {.env = env,
2093 .issuer = gw,
2094 .holders = {alice},
2095 .pay = 10'000,
2096 .flags = tfMPTCanClawback | kMptDexFlags});
2097
2098 AMM const amm(env, alice, btc(10'000), eth(10'000));
2099 env.close();
2100
2101 auto aliceBTC = env.balance(alice, btc);
2102 auto aliceETH = env.balance(alice, eth);
2103
2104 btc.authorize({.account = alice, .flags = tfMPTUnauthorize});
2105 eth.authorize({.account = alice, .flags = tfMPTUnauthorize});
2106 BEAST_EXPECT(!env.le(keylet::mptoken(btc.issuanceID(), alice.id())));
2107 BEAST_EXPECT(!env.le(keylet::mptoken(eth.issuanceID(), alice.id())));
2108
2109 // Full two-asset clawback: both assets are clawed and alice
2110 // receives nothing back. The AMM should be empty and deleted.
2111 env(amm::ammClawback(gw, alice, btc, eth, std::nullopt), Txflags(tfClawTwoAssets));
2112 env.close();
2113
2114 BEAST_EXPECT(!amm.ammExists());
2115 // Both assets were clawed; alice's balances remain at zero.
2116 env.require(Balance(alice, aliceBTC));
2117 env.require(Balance(alice, aliceETH));
2118 }
2119 }
2120
2121 void
2123 {
2124 testcase("test AMMClawback recreates paired-issuer MPToken unauthorized");
2125 using namespace jtx;
2126
2127 // Cross-issuer MPT/MPT pool: btc is issued by gw, eth by gw2, and both
2128 // require authorization. Alice deposits her entire balance of both and
2129 // deletes the resulting zero-balance MPTokens. When gw claws back its
2130 // own asset (btc), the two-asset withdrawal must recreate both of
2131 // Alice's MPTokens so the pool can pay her the paired asset. The
2132 // recreated MPToken may only be auto-authorized for the clawback
2133 // issuer's own asset (btc); the paired asset's issuer (gw2) never
2134 // consented, so eth must be recreated *unauthorized*, leaving gw2 in
2135 // control of its own token and preserving its RequireAuth guarantee.
2136 Env env(*this, features);
2137 Account const gw{"gateway"};
2138 Account const gw2{"gateway2"};
2139 Account const alice{"alice"};
2140 env.fund(XRP(100'000), gw, gw2, alice);
2141 env.close();
2142
2143 MPTTester btc(
2144 {.env = env,
2145 .issuer = gw,
2146 .holders = {alice},
2147 .pay = 10'000,
2148 .flags = tfMPTCanClawback | tfMPTRequireAuth | kMptDexFlags,
2149 .authHolder = true});
2150
2151 MPTTester eth(
2152 {.env = env,
2153 .issuer = gw2,
2154 .holders = {alice},
2155 .pay = 10'000,
2156 .flags = tfMPTCanClawback | tfMPTRequireAuth | kMptDexFlags,
2157 .authHolder = true});
2158
2159 // Alice deposits everything into the pool; her MPT balances drop to 0.
2160 AMM const amm(env, alice, btc(10'000), eth(10'000));
2161 env.close();
2162 BEAST_EXPECT(amm.expectBalances(btc(10'000), eth(10'000), IOUAmount{10'000}));
2163 BEAST_EXPECT(env.balance(alice, btc) == btc(0));
2164 BEAST_EXPECT(env.balance(alice, eth) == eth(0));
2165
2166 // Alice deletes both zero-balance MPTokens to reclaim reserves.
2167 btc.authorize({.account = alice, .flags = tfMPTUnauthorize});
2168 eth.authorize({.account = alice, .flags = tfMPTUnauthorize});
2169 BEAST_EXPECT(!env.le(keylet::mptoken(btc.issuanceID(), alice.id())));
2170 BEAST_EXPECT(!env.le(keylet::mptoken(eth.issuanceID(), alice.id())));
2171
2172 // gw (issuer of btc) claws back part of Alice's btc. This is a
2173 // cross-issuer pool, so tfClawTwoAssets is not permitted: only btc is
2174 // clawed back, while the paired eth is returned to Alice.
2175 env(amm::ammClawback(gw, alice, btc, eth, btc(1'000)));
2176 env.close();
2177
2178 // Both MPTokens were recreated so the withdrawal could pay Alice.
2179 auto const sleBtc = env.le(keylet::mptoken(btc.issuanceID(), alice.id()));
2180 auto const sleEth = env.le(keylet::mptoken(eth.issuanceID(), alice.id()));
2181 BEAST_EXPECT(sleBtc);
2182 BEAST_EXPECT(sleEth);
2183
2184 // The clawback issuer's own asset (btc) may be recreated authorized:
2185 // gw has authority over its own token.
2186 BEAST_EXPECT(sleBtc && sleBtc->isFlag(lsfMPTAuthorized));
2187
2188 // The paired asset (eth) is issued by gw2, who did not sign this
2189 // transaction. It must be recreated *unauthorized* so gw2's RequireAuth
2190 // is not bypassed. This is the core assertion for the cross-issuer fix.
2191 BEAST_EXPECT(sleEth && !sleEth->isFlag(lsfMPTAuthorized));
2192
2193 // The clawback still completed: btc was clawed back (Alice keeps a zero
2194 // btc balance) and the paired eth was delivered into Alice's now
2195 // unauthorized, gw2-gated MPToken (non-zero raw balance).
2196 BEAST_EXPECT(sleBtc && sleBtc->getFieldU64(sfMPTAmount) == 0);
2197 BEAST_EXPECT(sleEth && sleEth->getFieldU64(sfMPTAmount) > 0);
2198 BEAST_EXPECT(amm.ammExists());
2199 }
2200
2201 void
2202 run() override
2203 {
2204 FeatureBitset const all{jtx::testableAmendments() | fixAMMClawbackRounding};
2205
2206 testInvalidRequest(all);
2209 testAMMClawbackAmount(all - fixCleanup3_4_0);
2211 testAMMClawbackAmountRoundsToZero(all - fixCleanup3_4_0);
2212 testAMMClawbackAll(all);
2222 testLastHolderLPTokenBalance(all - fixAMMv1_3 - fixAMMClawbackRounding);
2224 all - fixAMMv1_3 - fixAMMClawbackRounding - featureSingleAssetVault -
2225 featureLendingProtocol);
2226 testLastHolderLPTokenBalance(all - fixAMMClawbackRounding);
2227 testClawAssetCheck(all);
2228 }
2229};
2230
2231BEAST_DEFINE_TESTSUITE(AMMClawbackMPT, app, xrpl);
2232
2233} // 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 testAMMClawbackIssuesEachOther(FeatureBitset features)
void testAMMClawbackAmount(FeatureBitset features)
void testAMMClawbackAmountRoundsToZero(FeatureBitset features)
void testClawbackAfterDeletingMPTokens(FeatureBitset features)
void testClawbackCreatesMissingMPToken(FeatureBitset features)
void testInvalidRequest(FeatureBitset features)
void testAssetFrozenOrLocked(FeatureBitset features)
void testAMMClawbackAllSameIssuer(FeatureBitset features)
void testClawbackCrossIssuerPairedAssetAuth(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:21
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:311
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
void enableFeature(uint256 const feature)
Definition Env.cpp:709
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:717
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
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:447
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:18
Set the flags on a JTx.
Definition txflags.h:14
T make_unique(T... args)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:543
json::Value ammClawback(Account const &issuer, Account const &holder, Asset const &asset, Asset const &asset2, std::optional< STAmount > const &amount)
Definition AMM.cpp:911
auto const kMptDexFlags
Definition mpt.h:45
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:92
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: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.
STAmount amountFromString(Asset const &asset, std::string const &amount)
Definition STAmount.cpp:907
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:184
@ temINVALID_FLAG
Definition TER.h:99
@ temMALFORMED
Definition TER.h:75
@ temDISABLED
Definition TER.h:102
@ temBAD_AMOUNT
Definition TER.h:77
TERSubset< CanCvtToTER > TER
Definition TER.h:647
@ tecPSEUDO_ACCOUNT
Definition TER.h:365
@ tecAMM_FAILED
Definition TER.h:333
@ tecNO_AUTH
Definition TER.h:303
@ tecINTERNAL
Definition TER.h:313
@ tecAMM_BALANCE
Definition TER.h:332
@ tecAMM_ACCOUNT
Definition TER.h:337
@ tecNO_PERMISSION
Definition TER.h:308
@ tesSUCCESS
Definition TER.h:245