xrpld
Loading...
Searching...
No Matches
ClawbackMPT_test.cpp
1
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/mpt.h>
7#include <test/jtx/ter.h>
8#include <test/jtx/ticket.h>
9#include <test/jtx/trust.h>
10#include <test/jtx/txflags.h>
11
12#include <xrpl/beast/unit_test/suite.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/Indexes.h>
15#include <xrpl/protocol/SField.h>
16#include <xrpl/protocol/TER.h>
17#include <xrpl/protocol/TxFlags.h>
18
19#include <cstdint>
20
21namespace xrpl {
22
24{
25 static std::uint32_t
27 {
28 std::uint32_t ret{0};
29 if (auto const sleAcct = env.le(acct))
30 ret = sleAcct->at(~sfTicketCount).value_or(0);
31 return ret;
32 }
33
34 void
36 {
37 testcase("Validation");
38 using namespace test::jtx;
39
40 // MPT clawback fails when featureMPTokensV1 is disabled
41 {
42 Env env(*this, features - featureMPTokensV1);
43 Account const alice{"alice"};
44 Account const bob{"bob"};
45
46 env.fund(XRP(1000), alice, bob);
47 env.close();
48
49 auto const mpt = xrpl::test::jtx::MPT(alice.name(), makeMptID(env.seq(alice), alice));
50
51 env(claw(alice, mpt(5), bob), Ter(temDISABLED));
52 env.close();
53 }
54
55 // MPT clawback fails when tfMPTCanClawback is not set on the issuance
56 {
57 Env env(*this, features);
58 Account const alice{"alice"};
59 Account const bob{"bob"};
60
61 MPTTester mptAlice(env, alice, {.holders = {bob}});
62 mptAlice.create({.ownerCount = 1, .holderCount = 0});
63 mptAlice.authorize({.account = bob});
64 mptAlice.pay(alice, bob, 100);
65
66 mptAlice.claw(alice, bob, 10, tecNO_PERMISSION);
67 }
68
69 // Test preflight validation failures
70 {
71 Env env(*this, features);
72 Account const alice{"alice"};
73 Account const bob{"bob"};
74
75 env.fund(XRP(1000), alice, bob);
76 env.close();
77
78 auto const mpt = xrpl::test::jtx::MPT(alice.name(), makeMptID(env.seq(alice), alice));
79
80 // fails due to invalid flag
81 env(claw(alice, mpt(5), bob), Txflags(0x00008000), Ter(temINVALID_FLAG));
82 env.close();
83
84 // fails due to zero amount
85 env(claw(alice, mpt(0), bob), Ter(temBAD_AMOUNT));
86 env.close();
87
88 // fails due to negative amount
89 env(claw(alice, mpt(-1), bob), Ter(temBAD_AMOUNT));
90 env.close();
91
92 // fails when holder is not specified
93 env(claw(alice, mpt(5)), Ter(temMALFORMED));
94 env.close();
95
96 // fails when issuer and holder are the same account
97 env(claw(alice, mpt(5), alice), Ter(temMALFORMED));
98 env.close();
99 }
100
101 // Test preclaim failures
102 {
103 Env env(*this, features);
104 Account const alice{"alice"};
105 Account const bob{"bob"};
106
107 MPTTester mptAlice(env, alice, {.holders = {bob}});
108
109 auto const fakeMpt =
110 xrpl::test::jtx::MPT(alice.name(), makeMptID(env.seq(alice), alice));
111
112 // clawback fails when the issuance does not exist
113 env(claw(alice, fakeMpt(5), bob), Ter(tecOBJECT_NOT_FOUND));
114 env.close();
115
116 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
117
118 // clawback fails when bob has no MPToken
119 mptAlice.claw(alice, bob, 5, tecOBJECT_NOT_FOUND);
120
121 mptAlice.authorize({.account = bob});
122
123 // clawback fails because bob's balance is 0
124 mptAlice.claw(alice, bob, 5, tecINSUFFICIENT_FUNDS);
125 }
126 }
127
128 void
130 {
131 testcase("Permission");
132 using namespace test::jtx;
133
134 // Clawing back from a non-existent account fails
135 {
136 Env env(*this, features);
137 Account const alice{"alice"};
138 Account const bob{"bob"};
139
140 // bob is not funded and does not exist
141 MPTTester mptAlice(env, alice, MPTInit{});
142 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
143
144 mptAlice.claw(alice, bob, 5, terNO_ACCOUNT);
145 }
146
147 // A non-issuer cannot claw back MPT
148 {
149 Env env(*this, features);
150 Account const alice{"alice"};
151 Account const bob{"bob"};
152 Account const cindy{"cindy"};
153
154 MPTTester mptAlice(env, alice, {.holders = {bob}});
155 env.fund(XRP(1000), cindy);
156 env.close();
157
158 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
159 mptAlice.authorize({.account = bob});
160 mptAlice.pay(alice, bob, 1000);
161
162 // cindy fails to claw because she is not the issuer
163 mptAlice.claw(cindy, bob, 200, tecNO_PERMISSION);
164 }
165 }
166
167 void
169 {
170 testcase("Enable clawback");
171 using namespace test::jtx;
172
173 // Test that alice is able to successfully clawback MPT from bob
174 Env env(*this, features);
175 Account const alice{"alice"};
176 Account const bob{"bob"};
177
178 MPTTester mptAlice(env, alice, {.holders = {bob}});
179 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
180 mptAlice.authorize({.account = bob});
181 mptAlice.pay(alice, bob, 1000);
182
183 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1000));
184
185 // alice claws back 200 tokens from bob
186 mptAlice.claw(alice, bob, 200);
187 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 800));
188
189 // alice claws back remaining 800 tokens
190 mptAlice.claw(alice, bob, 800);
191 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 0));
192 }
193
194 void
196 {
197 testcase("Multi issuance");
198 using namespace test::jtx;
199
200 // Two issuers each issue their own MPT to cindy.
201 // Clawback from one does not affect the other.
202 {
203 Env env(*this, features);
204
205 Account const alice{"alice"};
206 Account const bob{"bob"};
207 Account const cindy{"cindy"};
208
209 MPTTester mptAlice(env, alice, {.holders = {cindy}});
210 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
211 mptAlice.authorize({.account = cindy});
212 mptAlice.pay(alice, cindy, 1000);
213
214 MPTTester mptBob(env, bob, MPTInit{}); // cindy already funded by mptAlice
215 mptBob.create({.ownerCount = 1, .flags = tfMPTCanClawback});
216 mptBob.authorize({.account = cindy});
217 mptBob.pay(bob, cindy, 1000);
218
219 BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 1000));
220 BEAST_EXPECT(mptBob.checkMPTokenAmount(cindy, 1000));
221
222 // alice claws back 200 from cindy, bob's issuance is unaffected
223 mptAlice.claw(alice, cindy, 200);
224 BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 800));
225 BEAST_EXPECT(mptBob.checkMPTokenAmount(cindy, 1000));
226
227 // bob claws back 600 from cindy, alice's issuance is unaffected
228 mptBob.claw(bob, cindy, 600);
229 BEAST_EXPECT(mptBob.checkMPTokenAmount(cindy, 400));
230 BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 800));
231 }
232
233 // One issuer issues MPT to two different holders.
234 // Clawback from one holder does not affect the other.
235 {
236 Env env(*this, features);
237
238 Account const alice{"alice"};
239 Account const bob{"bob"};
240 Account const cindy{"cindy"};
241
242 MPTTester mptAlice(env, alice, {.holders = {bob, cindy}});
243 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
244 mptAlice.authorize({.account = bob});
245 mptAlice.authorize({.account = cindy});
246 mptAlice.pay(alice, bob, 600);
247 mptAlice.pay(alice, cindy, 1000);
248
249 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 600));
250 BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 1000));
251
252 // alice claws back 500 from bob, cindy's balance is unchanged
253 mptAlice.claw(alice, bob, 500);
254 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 100));
255 BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 1000));
256
257 // alice claws back 300 from cindy, bob's balance is unchanged
258 mptAlice.claw(alice, cindy, 300);
259 BEAST_EXPECT(mptAlice.checkMPTokenAmount(cindy, 700));
260 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 100));
261 }
262 }
263
264 void
266 {
267 testcase("Zero balance after clawback");
268 using namespace test::jtx;
269
270 // After clawback reduces balance to zero, the MPToken object
271 // still exists (unlike IOU trustlines which are deleted).
272 Env env(*this, features);
273 Account const alice{"alice"};
274 Account const bob{"bob"};
275
276 MPTTester mptAlice(env, alice, {.holders = {bob}});
277 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
278 mptAlice.authorize({.account = bob});
279 mptAlice.pay(alice, bob, 1000);
280
281 BEAST_EXPECT(ownerCount(env, bob) == 1);
282 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1000));
283
284 // alice claws back the full amount
285 mptAlice.claw(alice, bob, 1000);
286 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 0));
287
288 // bob still holds the MPToken object (balance 0, not deleted)
289 BEAST_EXPECT(ownerCount(env, bob) == 1);
290 }
291
292 void
294 {
295 testcase("Locked MPT");
296 using namespace test::jtx;
297
298 // Test that globally locked MPT can still be clawed back
299 {
300 Env env(*this, features);
301 Account const alice{"alice"};
302 Account const bob{"bob"};
303
304 MPTTester mptAlice(env, alice, {.holders = {bob}});
305 mptAlice.create(
306 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock | tfMPTCanClawback});
307 mptAlice.authorize({.account = bob});
308 mptAlice.pay(alice, bob, 1000);
309
310 // globally lock the issuance
311 mptAlice.set({.account = alice, .flags = tfMPTLock});
312
313 // clawback succeeds despite global lock
314 mptAlice.claw(alice, bob, 200);
315 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 800));
316 }
317
318 // Test that individually locked MPT can still be clawed back
319 {
320 Env env(*this, features);
321 Account const alice{"alice"};
322 Account const bob{"bob"};
323
324 MPTTester mptAlice(env, alice, {.holders = {bob}});
325 mptAlice.create(
326 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock | tfMPTCanClawback});
327 mptAlice.authorize({.account = bob});
328 mptAlice.pay(alice, bob, 1000);
329
330 // individually lock bob's MPToken
331 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
332
333 // clawback succeeds despite individual lock
334 mptAlice.claw(alice, bob, 200);
335 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 800));
336 }
337 }
338
339 void
341 {
342 testcase("Amount exceeds available");
343 using namespace test::jtx;
344
345 // When alice tries to claw back more than bob holds,
346 // only the available balance is clawed back
347 Env env(*this, features);
348 Account const alice{"alice"};
349 Account const bob{"bob"};
350
351 MPTTester mptAlice(env, alice, {.holders = {bob}});
352 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
353 mptAlice.authorize({.account = bob});
354 mptAlice.pay(alice, bob, 1000);
355
356 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1000));
357
358 // alice tries to claw back 2000, but bob only has 1000
359 mptAlice.claw(alice, bob, 2000);
360 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 0));
361 BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(0));
362
363 // MPToken object still exists with 0 balance
364 BEAST_EXPECT(ownerCount(env, bob) == 1);
365 }
366
367 void
369 {
370 testcase("Tickets");
371 using namespace test::jtx;
372
373 // Tests MPT clawback using tickets
374 Env env(*this, features);
375 Account const alice{"alice"};
376 Account const bob{"bob"};
377
378 MPTTester mptAlice(env, alice, {.holders = {bob}});
379 mptAlice.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
380 mptAlice.authorize({.account = bob});
381 mptAlice.pay(alice, bob, 100);
382
383 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 100));
384
385 // alice creates 10 tickets
386 std::uint32_t ticketCnt = 10;
387 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
388 env(ticket::create(alice, ticketCnt));
389 env.close();
390 std::uint32_t const aliceSeq{env.seq(alice)};
391 BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
392 BEAST_EXPECT(ownerCount(env, alice) == ticketCnt + 1); // tickets + issuance
393
394 while (ticketCnt > 0)
395 {
396 // alice claws back 5 tokens using a ticket
397 env(claw(alice, mptAlice.mpt(5), bob), ticket::Use(aliceTicketSeq++));
398 env.close();
399
400 ticketCnt--;
401 BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
402 }
403
404 // alice clawed back 50 tokens total, 50 remain
405 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 50));
406
407 // account sequence numbers did not advance
408 BEAST_EXPECT(env.seq(alice) == aliceSeq);
409 }
410
411 void
413 {
414 testValidation(features);
415 testPermission(features);
416 testEnabled(features);
417 testMultiIssuance(features);
419 testLockedMPT(features);
421 testTickets(features);
422 }
423
424public:
425 void
426 run() override
427 {
428 using namespace test::jtx;
429 FeatureBitset const all{testableAmendments()};
430 testWithFeats(all);
431 }
432};
433
434BEAST_DEFINE_TESTSUITE(ClawbackMPT, app, xrpl);
435} // namespace xrpl
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
void testPermission(FeatureBitset features)
void run() override
Runs the suite.
void testEnabled(FeatureBitset features)
void testTickets(FeatureBitset features)
void testAmountExceedsAvailable(FeatureBitset features)
static std::uint32_t ticketCount(test::jtx::Env const &env, test::jtx::Account const &acct)
void testWithFeats(FeatureBitset features)
void testLockedMPT(FeatureBitset features)
void testValidation(FeatureBitset features)
void testMultiIssuance(FeatureBitset features)
void testZeroBalanceAfterClawback(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
A transaction testing environment.
Definition Env.h:143
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:284
Converts to MPT Issue or STAmount.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_ACCOUNT
Definition TER.h:209
@ temINVALID_FLAG
Definition TER.h:97
@ temMALFORMED
Definition TER.h:73
@ temDISABLED
Definition TER.h:100
@ temBAD_AMOUNT
Definition TER.h:75
@ tecOBJECT_NOT_FOUND
Definition TER.h:324
@ tecINSUFFICIENT_FUNDS
Definition TER.h:323
@ tecNO_PERMISSION
Definition TER.h:303
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, xrpl)
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:172