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