rippled
Loading...
Searching...
No Matches
Clawback_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/trust.h>
22#include <xrpl/protocol/Feature.h>
23
24namespace ripple {
25
27{
28 template <class T>
29 static std::string
30 to_string(T const& t)
31 {
32 return boost::lexical_cast<std::string>(t);
33 }
34
35 // Helper function that returns the number of tickets held by an account.
36 static std::uint32_t
38 {
39 std::uint32_t ret{0};
40 if (auto const sleAcct = env.le(acct))
41 ret = sleAcct->at(~sfTicketCount).value_or(0);
42 return ret;
43 }
44
45 // Helper function that returns the freeze status of a trustline
46 static bool
48 test::jtx::Env const& env,
49 test::jtx::Account const& src,
50 test::jtx::Account const& dst,
51 Currency const& cur)
52 {
53 if (auto sle = env.le(keylet::line(src, dst, cur)))
54 {
55 auto const useHigh = src.id() > dst.id();
56 return sle->isFlag(useHigh ? lsfHighFreeze : lsfLowFreeze);
57 }
58 Throw<std::runtime_error>("No line in getLineFreezeFlag");
59 return false; // silence warning
60 }
61
62 void
64 {
65 testcase("Enable AllowTrustLineClawback flag");
66 using namespace test::jtx;
67
68 // Test that one can successfully set asfAllowTrustLineClawback flag.
69 // If successful, asfNoFreeze can no longer be set.
70 // Also, asfAllowTrustLineClawback cannot be cleared.
71 {
72 Env env(*this, features);
73 Account alice{"alice"};
74
75 env.fund(XRP(1000), alice);
76 env.close();
77
78 // set asfAllowTrustLineClawback
79 env(fset(alice, asfAllowTrustLineClawback));
80 env.close();
81 env.require(flags(alice, asfAllowTrustLineClawback));
82
83 // clear asfAllowTrustLineClawback does nothing
84 env(fclear(alice, asfAllowTrustLineClawback));
85 env.close();
86 env.require(flags(alice, asfAllowTrustLineClawback));
87
88 // asfNoFreeze cannot be set when asfAllowTrustLineClawback is set
89 env.require(nflags(alice, asfNoFreeze));
90 env(fset(alice, asfNoFreeze), ter(tecNO_PERMISSION));
91 env.close();
92 }
93
94 // Test that asfAllowTrustLineClawback cannot be set when
95 // asfNoFreeze has been set
96 {
97 Env env(*this, features);
98 Account alice{"alice"};
99
100 env.fund(XRP(1000), alice);
101 env.close();
102
103 env.require(nflags(alice, asfNoFreeze));
104
105 // set asfNoFreeze
106 env(fset(alice, asfNoFreeze));
107 env.close();
108
109 // NoFreeze is set
110 env.require(flags(alice, asfNoFreeze));
111
112 // asfAllowTrustLineClawback cannot be set if asfNoFreeze is set
113 env(fset(alice, asfAllowTrustLineClawback), ter(tecNO_PERMISSION));
114 env.close();
115
116 env.require(nflags(alice, asfAllowTrustLineClawback));
117 }
118
119 // Test that asfAllowTrustLineClawback is not allowed when owner dir is
120 // non-empty
121 {
122 Env env(*this, features);
123
124 Account alice{"alice"};
125 Account bob{"bob"};
126
127 env.fund(XRP(1000), alice, bob);
128 env.close();
129
130 auto const USD = alice["USD"];
131 env.require(nflags(alice, asfAllowTrustLineClawback));
132
133 // alice issues 10 USD to bob
134 env.trust(USD(1000), bob);
135 env(pay(alice, bob, USD(10)));
136 env.close();
137
138 BEAST_EXPECT(ownerCount(env, alice) == 0);
139 BEAST_EXPECT(ownerCount(env, bob) == 1);
140
141 // alice fails to enable clawback because she has trustline with bob
142 env(fset(alice, asfAllowTrustLineClawback), ter(tecOWNERS));
143 env.close();
144
145 // bob sets trustline to default limit and pays alice back to delete
146 // the trustline
147 env(trust(bob, USD(0), 0));
148 env(pay(bob, alice, USD(10)));
149
150 BEAST_EXPECT(ownerCount(env, alice) == 0);
151 BEAST_EXPECT(ownerCount(env, bob) == 0);
152
153 // alice now is able to set asfAllowTrustLineClawback
154 env(fset(alice, asfAllowTrustLineClawback));
155 env.close();
156 env.require(flags(alice, asfAllowTrustLineClawback));
157
158 BEAST_EXPECT(ownerCount(env, alice) == 0);
159 BEAST_EXPECT(ownerCount(env, bob) == 0);
160 }
161
162 // Test that one cannot enable asfAllowTrustLineClawback when
163 // featureClawback amendment is disabled
164 {
165 Env env(*this, features - featureClawback);
166
167 Account alice{"alice"};
168
169 env.fund(XRP(1000), alice);
170 env.close();
171
172 env.require(nflags(alice, asfAllowTrustLineClawback));
173
174 // alice attempts to set asfAllowTrustLineClawback flag while
175 // amendment is disabled. no error is returned, but the flag remains
176 // to be unset.
177 env(fset(alice, asfAllowTrustLineClawback));
178 env.close();
179 env.require(nflags(alice, asfAllowTrustLineClawback));
180
181 // now enable clawback amendment
182 env.enableFeature(featureClawback);
183 env.close();
184
185 // asfAllowTrustLineClawback can be set
186 env(fset(alice, asfAllowTrustLineClawback));
187 env.close();
188 env.require(flags(alice, asfAllowTrustLineClawback));
189 }
190 }
191
192 void
194 {
195 testcase("Validation");
196 using namespace test::jtx;
197
198 // Test that Clawback tx fails for the following:
199 // 1. when amendment is disabled
200 // 2. when asfAllowTrustLineClawback flag has not been set
201 {
202 Env env(*this, features - featureClawback);
203
204 Account alice{"alice"};
205 Account bob{"bob"};
206
207 env.fund(XRP(1000), alice, bob);
208 env.close();
209
210 env.require(nflags(alice, asfAllowTrustLineClawback));
211
212 auto const USD = alice["USD"];
213
214 // alice issues 10 USD to bob
215 env.trust(USD(1000), bob);
216 env(pay(alice, bob, USD(10)));
217 env.close();
218
219 env.require(balance(bob, alice["USD"](10)));
220 env.require(balance(alice, bob["USD"](-10)));
221
222 // clawback fails because amendment is disabled
223 env(claw(alice, bob["USD"](5)), ter(temDISABLED));
224 env.close();
225
226 // now enable clawback amendment
227 env.enableFeature(featureClawback);
228 env.close();
229
230 // clawback fails because asfAllowTrustLineClawback has not been set
231 env(claw(alice, bob["USD"](5)), ter(tecNO_PERMISSION));
232 env.close();
233
234 env.require(balance(bob, alice["USD"](10)));
235 env.require(balance(alice, bob["USD"](-10)));
236 }
237
238 // Test that Clawback tx fails for the following:
239 // 1. invalid flag
240 // 2. negative STAmount
241 // 3. zero STAmount
242 // 4. XRP amount
243 // 5. `account` and `issuer` fields are same account
244 // 6. trustline has a balance of 0
245 // 7. trustline does not exist
246 {
247 Env env(*this, features);
248
249 Account alice{"alice"};
250 Account bob{"bob"};
251
252 env.fund(XRP(1000), alice, bob);
253 env.close();
254
255 // alice sets asfAllowTrustLineClawback
256 env(fset(alice, asfAllowTrustLineClawback));
257 env.close();
258 env.require(flags(alice, asfAllowTrustLineClawback));
259
260 auto const USD = alice["USD"];
261
262 // alice issues 10 USD to bob
263 env.trust(USD(1000), bob);
264 env(pay(alice, bob, USD(10)));
265 env.close();
266
267 env.require(balance(bob, alice["USD"](10)));
268 env.require(balance(alice, bob["USD"](-10)));
269
270 // fails due to invalid flag
271 env(claw(alice, bob["USD"](5)),
272 txflags(0x00008000),
273 ter(temINVALID_FLAG));
274 env.close();
275
276 // fails due to negative amount
277 env(claw(alice, bob["USD"](-5)), ter(temBAD_AMOUNT));
278 env.close();
279
280 // fails due to zero amount
281 env(claw(alice, bob["USD"](0)), ter(temBAD_AMOUNT));
282 env.close();
283
284 // fails because amount is in XRP
285 env(claw(alice, XRP(10)), ter(temBAD_AMOUNT));
286 env.close();
287
288 // fails when `issuer` field in `amount` is not token holder
289 // NOTE: we are using the `issuer` field for the token holder
290 env(claw(alice, alice["USD"](5)), ter(temBAD_AMOUNT));
291 env.close();
292
293 // bob pays alice back, trustline has a balance of 0
294 env(pay(bob, alice, USD(10)));
295 env.close();
296
297 // bob still owns the trustline that has 0 balance
298 BEAST_EXPECT(ownerCount(env, alice) == 0);
299 BEAST_EXPECT(ownerCount(env, bob) == 1);
300 env.require(balance(bob, alice["USD"](0)));
301 env.require(balance(alice, bob["USD"](0)));
302
303 // clawback fails because because balance is 0
304 env(claw(alice, bob["USD"](5)), ter(tecINSUFFICIENT_FUNDS));
305 env.close();
306
307 // set the limit to default, which should delete the trustline
308 env(trust(bob, USD(0), 0));
309 env.close();
310
311 // bob no longer owns the trustline
312 BEAST_EXPECT(ownerCount(env, alice) == 0);
313 BEAST_EXPECT(ownerCount(env, bob) == 0);
314
315 // clawback fails because trustline does not exist
316 env(claw(alice, bob["USD"](5)), ter(tecNO_LINE));
317 env.close();
318 }
319 }
320
321 void
323 {
324 // Checks the tx submitter has the permission to clawback.
325 // Exercises preclaim code
326 testcase("Permission");
327 using namespace test::jtx;
328
329 // Clawing back from an non-existent account returns error
330 {
331 Env env(*this, features);
332
333 Account alice{"alice"};
334 Account bob{"bob"};
335
336 // bob's account is not funded and does not exist
337 env.fund(XRP(1000), alice);
338 env.close();
339
340 // alice sets asfAllowTrustLineClawback
341 env(fset(alice, asfAllowTrustLineClawback));
342 env.close();
343 env.require(flags(alice, asfAllowTrustLineClawback));
344
345 // bob, the token holder, does not exist
346 env(claw(alice, bob["USD"](5)), ter(terNO_ACCOUNT));
347 env.close();
348 }
349
350 // Test that trustline cannot be clawed by someone who is
351 // not the issuer of the currency
352 {
353 Env env(*this, features);
354
355 Account alice{"alice"};
356 Account bob{"bob"};
357 Account cindy{"cindy"};
358
359 env.fund(XRP(1000), alice, bob, cindy);
360 env.close();
361
362 auto const USD = alice["USD"];
363
364 // alice sets asfAllowTrustLineClawback
365 env(fset(alice, asfAllowTrustLineClawback));
366 env.close();
367 env.require(flags(alice, asfAllowTrustLineClawback));
368
369 // cindy sets asfAllowTrustLineClawback
370 env(fset(cindy, asfAllowTrustLineClawback));
371 env.close();
372 env.require(flags(cindy, asfAllowTrustLineClawback));
373
374 // alice issues 1000 USD to bob
375 env.trust(USD(1000), bob);
376 env(pay(alice, bob, USD(1000)));
377 env.close();
378
379 env.require(balance(bob, alice["USD"](1000)));
380 env.require(balance(alice, bob["USD"](-1000)));
381
382 // cindy tries to claw from bob, and fails because trustline does
383 // not exist
384 env(claw(cindy, bob["USD"](200)), ter(tecNO_LINE));
385 env.close();
386 }
387
388 // When a trustline is created between issuer and holder,
389 // we must make sure the holder is unable to claw back from
390 // the issuer by impersonating the issuer account.
391 //
392 // This must be tested bidirectionally for both accounts because the
393 // issuer could be either the low or high account in the trustline
394 // object
395 {
396 Env env(*this, features);
397
398 Account alice{"alice"};
399 Account bob{"bob"};
400
401 env.fund(XRP(1000), alice, bob);
402 env.close();
403
404 auto const USD = alice["USD"];
405 auto const CAD = bob["CAD"];
406
407 // alice sets asfAllowTrustLineClawback
408 env(fset(alice, asfAllowTrustLineClawback));
409 env.close();
410 env.require(flags(alice, asfAllowTrustLineClawback));
411
412 // bob sets asfAllowTrustLineClawback
413 env(fset(bob, asfAllowTrustLineClawback));
414 env.close();
415 env.require(flags(bob, asfAllowTrustLineClawback));
416
417 // alice issues 10 USD to bob.
418 // bob then attempts to submit a clawback tx to claw USD from alice.
419 // this must FAIL, because bob is not the issuer for this
420 // trustline!!!
421 {
422 // bob creates a trustline with alice, and alice sends 10 USD to
423 // bob
424 env.trust(USD(1000), bob);
425 env(pay(alice, bob, USD(10)));
426 env.close();
427
428 env.require(balance(bob, alice["USD"](10)));
429 env.require(balance(alice, bob["USD"](-10)));
430
431 // bob cannot claw back USD from alice because he's not the
432 // issuer
433 env(claw(bob, alice["USD"](5)), ter(tecNO_PERMISSION));
434 env.close();
435 }
436
437 // bob issues 10 CAD to alice.
438 // alice then attempts to submit a clawback tx to claw CAD from bob.
439 // this must FAIL, because alice is not the issuer for this
440 // trustline!!!
441 {
442 // alice creates a trustline with bob, and bob sends 10 CAD to
443 // alice
444 env.trust(CAD(1000), alice);
445 env(pay(bob, alice, CAD(10)));
446 env.close();
447
448 env.require(balance(bob, alice["CAD"](-10)));
449 env.require(balance(alice, bob["CAD"](10)));
450
451 // alice cannot claw back CAD from bob because she's not the
452 // issuer
453 env(claw(alice, bob["CAD"](5)), ter(tecNO_PERMISSION));
454 env.close();
455 }
456 }
457 }
458
459 void
461 {
462 testcase("Enable clawback");
463 using namespace test::jtx;
464
465 // Test that alice is able to successfully clawback tokens from bob
466 Env env(*this, features);
467
468 Account alice{"alice"};
469 Account bob{"bob"};
470
471 env.fund(XRP(1000), alice, bob);
472 env.close();
473
474 auto const USD = alice["USD"];
475
476 // alice sets asfAllowTrustLineClawback
477 env(fset(alice, asfAllowTrustLineClawback));
478 env.close();
479 env.require(flags(alice, asfAllowTrustLineClawback));
480
481 // alice issues 1000 USD to bob
482 env.trust(USD(1000), bob);
483 env(pay(alice, bob, USD(1000)));
484 env.close();
485
486 env.require(balance(bob, alice["USD"](1000)));
487 env.require(balance(alice, bob["USD"](-1000)));
488
489 // alice claws back 200 USD from bob
490 env(claw(alice, bob["USD"](200)));
491 env.close();
492
493 // bob should have 800 USD left
494 env.require(balance(bob, alice["USD"](800)));
495 env.require(balance(alice, bob["USD"](-800)));
496
497 // alice claws back 800 USD from bob again
498 env(claw(alice, bob["USD"](800)));
499 env.close();
500
501 // trustline has a balance of 0
502 env.require(balance(bob, alice["USD"](0)));
503 env.require(balance(alice, bob["USD"](0)));
504 }
505
506 void
508 {
509 // Test scenarios where multiple trustlines are involved
510 testcase("Multi line");
511 using namespace test::jtx;
512
513 // Both alice and bob issues their own "USD" to cindy.
514 // When alice and bob tries to claw back, they will only
515 // claw back from their respective trustline.
516 {
517 Env env(*this, features);
518
519 Account alice{"alice"};
520 Account bob{"bob"};
521 Account cindy{"cindy"};
522
523 env.fund(XRP(1000), alice, bob, cindy);
524 env.close();
525
526 // alice sets asfAllowTrustLineClawback
527 env(fset(alice, asfAllowTrustLineClawback));
528 env.close();
529 env.require(flags(alice, asfAllowTrustLineClawback));
530
531 // bob sets asfAllowTrustLineClawback
532 env(fset(bob, asfAllowTrustLineClawback));
533 env.close();
534 env.require(flags(bob, asfAllowTrustLineClawback));
535
536 // alice sends 1000 USD to cindy
537 env.trust(alice["USD"](1000), cindy);
538 env(pay(alice, cindy, alice["USD"](1000)));
539 env.close();
540
541 // bob sends 1000 USD to cindy
542 env.trust(bob["USD"](1000), cindy);
543 env(pay(bob, cindy, bob["USD"](1000)));
544 env.close();
545
546 // alice claws back 200 USD from cindy
547 env(claw(alice, cindy["USD"](200)));
548 env.close();
549
550 // cindy has 800 USD left in alice's trustline after clawed by alice
551 env.require(balance(cindy, alice["USD"](800)));
552 env.require(balance(alice, cindy["USD"](-800)));
553
554 // cindy still has 1000 USD in bob's trustline
555 env.require(balance(cindy, bob["USD"](1000)));
556 env.require(balance(bob, cindy["USD"](-1000)));
557
558 // bob claws back 600 USD from cindy
559 env(claw(bob, cindy["USD"](600)));
560 env.close();
561
562 // cindy has 400 USD left in bob's trustline after clawed by bob
563 env.require(balance(cindy, bob["USD"](400)));
564 env.require(balance(bob, cindy["USD"](-400)));
565
566 // cindy still has 800 USD in alice's trustline
567 env.require(balance(cindy, alice["USD"](800)));
568 env.require(balance(alice, cindy["USD"](-800)));
569 }
570
571 // alice issues USD to both bob and cindy.
572 // when alice claws back from bob, only bob's USD balance is
573 // affected, and cindy's balance remains unchanged, and vice versa.
574 {
575 Env env(*this, features);
576
577 Account alice{"alice"};
578 Account bob{"bob"};
579 Account cindy{"cindy"};
580
581 env.fund(XRP(1000), alice, bob, cindy);
582 env.close();
583
584 auto const USD = alice["USD"];
585
586 // alice sets asfAllowTrustLineClawback
587 env(fset(alice, asfAllowTrustLineClawback));
588 env.close();
589 env.require(flags(alice, asfAllowTrustLineClawback));
590
591 // alice sends 600 USD to bob
592 env.trust(USD(1000), bob);
593 env(pay(alice, bob, USD(600)));
594 env.close();
595
596 env.require(balance(alice, bob["USD"](-600)));
597 env.require(balance(bob, alice["USD"](600)));
598
599 // alice sends 1000 USD to cindy
600 env.trust(USD(1000), cindy);
601 env(pay(alice, cindy, USD(1000)));
602 env.close();
603
604 env.require(balance(alice, cindy["USD"](-1000)));
605 env.require(balance(cindy, alice["USD"](1000)));
606
607 // alice claws back 500 USD from bob
608 env(claw(alice, bob["USD"](500)));
609 env.close();
610
611 // bob's balance is reduced
612 env.require(balance(alice, bob["USD"](-100)));
613 env.require(balance(bob, alice["USD"](100)));
614
615 // cindy's balance is unchanged
616 env.require(balance(alice, cindy["USD"](-1000)));
617 env.require(balance(cindy, alice["USD"](1000)));
618
619 // alice claws back 300 USD from cindy
620 env(claw(alice, cindy["USD"](300)));
621 env.close();
622
623 // bob's balance is unchanged
624 env.require(balance(alice, bob["USD"](-100)));
625 env.require(balance(bob, alice["USD"](100)));
626
627 // cindy's balance is reduced
628 env.require(balance(alice, cindy["USD"](-700)));
629 env.require(balance(cindy, alice["USD"](700)));
630 }
631 }
632
633 void
635 {
636 testcase("Bidirectional line");
637 using namespace test::jtx;
638
639 // Test when both alice and bob issues USD to each other.
640 // This scenario creates only one trustline.
641 // In this case, both alice and bob can be seen as the "issuer"
642 // and they can send however many USDs to each other.
643 // We test that only the person who has a negative balance from their
644 // perspective is allowed to clawback
645 Env env(*this, features);
646
647 Account alice{"alice"};
648 Account bob{"bob"};
649
650 env.fund(XRP(1000), alice, bob);
651 env.close();
652
653 // alice sets asfAllowTrustLineClawback
654 env(fset(alice, asfAllowTrustLineClawback));
655 env.close();
656 env.require(flags(alice, asfAllowTrustLineClawback));
657
658 // bob sets asfAllowTrustLineClawback
659 env(fset(bob, asfAllowTrustLineClawback));
660 env.close();
661 env.require(flags(bob, asfAllowTrustLineClawback));
662
663 // alice issues 1000 USD to bob
664 env.trust(alice["USD"](1000), bob);
665 env(pay(alice, bob, alice["USD"](1000)));
666 env.close();
667
668 BEAST_EXPECT(ownerCount(env, alice) == 0);
669 BEAST_EXPECT(ownerCount(env, bob) == 1);
670
671 // bob is the holder, and alice is the issuer
672 env.require(balance(bob, alice["USD"](1000)));
673 env.require(balance(alice, bob["USD"](-1000)));
674
675 // bob issues 1500 USD to alice
676 env.trust(bob["USD"](1500), alice);
677 env(pay(bob, alice, bob["USD"](1500)));
678 env.close();
679
680 BEAST_EXPECT(ownerCount(env, alice) == 1);
681 BEAST_EXPECT(ownerCount(env, bob) == 1);
682
683 // bob has negative 500 USD because bob issued 500 USD more than alice
684 // bob can now been seen as the issuer, while alice is the holder
685 env.require(balance(bob, alice["USD"](-500)));
686 env.require(balance(alice, bob["USD"](500)));
687
688 // At this point, both alice and bob are the issuers of USD
689 // and can send USD to each other through one trustline
690
691 // alice fails to clawback. Even though she is also an issuer,
692 // the trustline balance is positive from her perspective
693 env(claw(alice, bob["USD"](200)), ter(tecNO_PERMISSION));
694 env.close();
695
696 // bob is able to successfully clawback from alice because
697 // the trustline balance is negative from his perspective
698 env(claw(bob, alice["USD"](200)));
699 env.close();
700
701 env.require(balance(bob, alice["USD"](-300)));
702 env.require(balance(alice, bob["USD"](300)));
703
704 // alice pays bob 1000 USD
705 env(pay(alice, bob, alice["USD"](1000)));
706 env.close();
707
708 // bob's balance becomes positive from his perspective because
709 // alice issued more USD than the balance
710 env.require(balance(bob, alice["USD"](700)));
711 env.require(balance(alice, bob["USD"](-700)));
712
713 // bob is now the holder and fails to clawback
714 env(claw(bob, alice["USD"](200)), ter(tecNO_PERMISSION));
715 env.close();
716
717 // alice successfully claws back
718 env(claw(alice, bob["USD"](200)));
719 env.close();
720
721 env.require(balance(bob, alice["USD"](500)));
722 env.require(balance(alice, bob["USD"](-500)));
723 }
724
725 void
727 {
728 testcase("Delete default trustline");
729 using namespace test::jtx;
730
731 // If clawback results the trustline to be default,
732 // trustline should be automatically deleted
733 Env env(*this, features);
734 Account alice{"alice"};
735 Account bob{"bob"};
736
737 env.fund(XRP(1000), alice, bob);
738 env.close();
739
740 auto const USD = alice["USD"];
741
742 // alice sets asfAllowTrustLineClawback
743 env(fset(alice, asfAllowTrustLineClawback));
744 env.close();
745 env.require(flags(alice, asfAllowTrustLineClawback));
746
747 // alice issues 1000 USD to bob
748 env.trust(USD(1000), bob);
749 env(pay(alice, bob, USD(1000)));
750 env.close();
751
752 BEAST_EXPECT(ownerCount(env, alice) == 0);
753 BEAST_EXPECT(ownerCount(env, bob) == 1);
754
755 env.require(balance(bob, alice["USD"](1000)));
756 env.require(balance(alice, bob["USD"](-1000)));
757
758 // set limit to default,
759 env(trust(bob, USD(0), 0));
760 env.close();
761
762 BEAST_EXPECT(ownerCount(env, alice) == 0);
763 BEAST_EXPECT(ownerCount(env, bob) == 1);
764
765 // alice claws back full amount from bob, and should also delete
766 // trustline
767 env(claw(alice, bob["USD"](1000)));
768 env.close();
769
770 // bob no longer owns the trustline because it was deleted
771 BEAST_EXPECT(ownerCount(env, alice) == 0);
772 BEAST_EXPECT(ownerCount(env, bob) == 0);
773 }
774
775 void
777 {
778 testcase("Frozen trustline");
779 using namespace test::jtx;
780
781 // Claws back from frozen trustline
782 // and the trustline should remain frozen
783 Env env(*this, features);
784 Account alice{"alice"};
785 Account bob{"bob"};
786
787 env.fund(XRP(1000), alice, bob);
788 env.close();
789
790 auto const USD = alice["USD"];
791
792 // alice sets asfAllowTrustLineClawback
793 env(fset(alice, asfAllowTrustLineClawback));
794 env.close();
795 env.require(flags(alice, asfAllowTrustLineClawback));
796
797 // alice issues 1000 USD to bob
798 env.trust(USD(1000), bob);
799 env(pay(alice, bob, USD(1000)));
800 env.close();
801
802 env.require(balance(bob, alice["USD"](1000)));
803 env.require(balance(alice, bob["USD"](-1000)));
804
805 // freeze trustline
806 env(trust(alice, bob["USD"](0), tfSetFreeze));
807 env.close();
808
809 // alice claws back 200 USD from bob
810 env(claw(alice, bob["USD"](200)));
811 env.close();
812
813 // bob should have 800 USD left
814 env.require(balance(bob, alice["USD"](800)));
815 env.require(balance(alice, bob["USD"](-800)));
816
817 // trustline remains frozen
818 BEAST_EXPECT(getLineFreezeFlag(env, alice, bob, USD.currency));
819 }
820
821 void
823 {
824 testcase("Amount exceeds available");
825 using namespace test::jtx;
826
827 // When alice tries to claw back an amount that is greater
828 // than what bob holds, only the max available balance is clawed
829 Env env(*this, features);
830 Account alice{"alice"};
831 Account bob{"bob"};
832
833 env.fund(XRP(1000), alice, bob);
834 env.close();
835
836 auto const USD = alice["USD"];
837
838 // alice sets asfAllowTrustLineClawback
839 env(fset(alice, asfAllowTrustLineClawback));
840 env.close();
841 env.require(flags(alice, asfAllowTrustLineClawback));
842
843 // alice issues 1000 USD to bob
844 env.trust(USD(1000), bob);
845 env(pay(alice, bob, USD(1000)));
846 env.close();
847
848 env.require(balance(bob, alice["USD"](1000)));
849 env.require(balance(alice, bob["USD"](-1000)));
850
851 // alice tries to claw back 2000 USD
852 env(claw(alice, bob["USD"](2000)));
853 env.close();
854
855 // check alice and bob's balance.
856 // alice was only able to claw back 1000 USD at maximum
857 env.require(balance(bob, alice["USD"](0)));
858 env.require(balance(alice, bob["USD"](0)));
859
860 // bob still owns the trustline because trustline is not in default
861 // state
862 BEAST_EXPECT(ownerCount(env, alice) == 0);
863 BEAST_EXPECT(ownerCount(env, bob) == 1);
864
865 // set limit to default,
866 env(trust(bob, USD(0), 0));
867 env.close();
868
869 // verify that bob's trustline was deleted
870 BEAST_EXPECT(ownerCount(env, alice) == 0);
871 BEAST_EXPECT(ownerCount(env, bob) == 0);
872 }
873
874 void
876 {
877 testcase("Tickets");
878 using namespace test::jtx;
879
880 // Tests clawback with tickets
881 Env env(*this, features);
882 Account alice{"alice"};
883 Account bob{"bob"};
884
885 env.fund(XRP(1000), alice, bob);
886 env.close();
887
888 auto const USD = alice["USD"];
889
890 // alice sets asfAllowTrustLineClawback
891 env(fset(alice, asfAllowTrustLineClawback));
892 env.close();
893 env.require(flags(alice, asfAllowTrustLineClawback));
894
895 // alice issues 100 USD to bob
896 env.trust(USD(1000), bob);
897 env(pay(alice, bob, USD(100)));
898 env.close();
899
900 env.require(balance(bob, alice["USD"](100)));
901 env.require(balance(alice, bob["USD"](-100)));
902
903 // alice creates 10 tickets
904 std::uint32_t ticketCnt = 10;
905 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
906 env(ticket::create(alice, ticketCnt));
907 env.close();
908 std::uint32_t const aliceSeq{env.seq(alice)};
909 BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
910 BEAST_EXPECT(ownerCount(env, alice) == ticketCnt);
911
912 while (ticketCnt > 0)
913 {
914 // alice claws back 5 USD using a ticket
915 env(claw(alice, bob["USD"](5)), ticket::use(aliceTicketSeq++));
916 env.close();
917
918 ticketCnt--;
919 BEAST_EXPECT(ticketCount(env, alice) == ticketCnt);
920 BEAST_EXPECT(ownerCount(env, alice) == ticketCnt);
921 }
922
923 // alice clawed back 50 USD total, trustline has 50 USD remaining
924 env.require(balance(bob, alice["USD"](50)));
925 env.require(balance(alice, bob["USD"](-50)));
926
927 // Verify that the account sequence numbers did not advance.
928 BEAST_EXPECT(env.seq(alice) == aliceSeq);
929 }
930
931 void
933 {
935 testValidation(features);
936 testPermission(features);
937 testEnabled(features);
938 testMultiLine(features);
939 testBidirectionalLine(features);
940 testDeleteDefaultLine(features);
941 testFrozenLine(features);
943 testTickets(features);
944 }
945
946public:
947 void
948 run() override
949 {
950 using namespace test::jtx;
951 FeatureBitset const all{supported_amendments()};
952
953 testWithFeats(all - featureMPTokensV1);
955 }
956};
957
958BEAST_DEFINE_TESTSUITE(Clawback, app, ripple);
959} // namespace ripple
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void testDeleteDefaultLine(FeatureBitset features)
void testPermission(FeatureBitset features)
void testBidirectionalLine(FeatureBitset features)
void testAmountExceedsAvailable(FeatureBitset features)
void run() override
Runs the suite.
static std::uint32_t ticketCount(test::jtx::Env const &env, test::jtx::Account const &acct)
static bool getLineFreezeFlag(test::jtx::Env const &env, test::jtx::Account const &src, test::jtx::Account const &dst, Currency const &cur)
void testTickets(FeatureBitset features)
void testWithFeats(FeatureBitset features)
static std::string to_string(T const &t)
void testMultiLine(FeatureBitset features)
void testFrozenLine(FeatureBitset features)
void testEnabled(FeatureBitset features)
void testAllowTrustLineClawbackFlag(FeatureBitset features)
void testValidation(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition: Account.h:39
AccountID id() const
Returns the Account ID.
Definition: Account.h:107
A transaction testing environment.
Definition: Env.h:118
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:219
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition: Indexes.cpp:235
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
@ lsfHighFreeze
@ lsfLowFreeze
constexpr std::uint32_t asfNoFreeze
Definition: TxFlags.h:81
@ tecOWNERS
Definition: TER.h:285
@ tecINSUFFICIENT_FUNDS
Definition: TER.h:312
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecNO_LINE
Definition: TER.h:288
constexpr std::uint32_t asfAllowTrustLineClawback
Definition: TxFlags.h:93
@ terNO_ACCOUNT
Definition: TER.h:217
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:115
@ temBAD_AMOUNT
Definition: TER.h:89
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114