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