rippled
Loading...
Searching...
No Matches
AccountDelete_test.cpp
1#include <test/jtx.h>
2
3#include <xrpl/protocol/Feature.h>
4#include <xrpl/protocol/jss.h>
5
6namespace ripple {
7namespace test {
8
10{
11private:
12 // Helper function that verifies the expected DeliveredAmount is present.
13 //
14 // NOTE: the function _infers_ the transaction to operate on by calling
15 // env.tx(), which returns the result from the most recent transaction.
16 void
18 {
19 // Get the hash for the most recent transaction.
20 std::string const txHash{
21 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
22
23 // Verify DeliveredAmount and delivered_amount metadata are correct.
24 // We can't use env.meta() here, because meta() doesn't include
25 // delivered_amount.
26 env.close();
27 Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
28
29 // Expect there to be a DeliveredAmount field.
30 if (!BEAST_EXPECT(meta.isMember(sfDeliveredAmount.jsonName)))
31 return;
32
33 // DeliveredAmount and delivered_amount should both be present and
34 // equal amount.
35 Json::Value const jsonExpect{amount.getJson(JsonOptions::none)};
36 BEAST_EXPECT(meta[sfDeliveredAmount.jsonName] == jsonExpect);
37 BEAST_EXPECT(meta[jss::delivered_amount] == jsonExpect);
38 }
39
40 // Helper function to create a payment channel.
41 static Json::Value
43 jtx::Account const& account,
44 jtx::Account const& to,
45 STAmount const& amount,
46 NetClock::duration const& settleDelay,
47 NetClock::time_point const& cancelAfter,
48 PublicKey const& pk)
49 {
50 Json::Value jv;
51 jv[jss::TransactionType] = jss::PaymentChannelCreate;
52 jv[jss::Account] = account.human();
53 jv[jss::Destination] = to.human();
54 jv[jss::Amount] = amount.getJson(JsonOptions::none);
55 jv[sfSettleDelay.jsonName] = settleDelay.count();
56 jv[sfCancelAfter.jsonName] = cancelAfter.time_since_epoch().count() + 2;
57 jv[sfPublicKey.jsonName] = strHex(pk.slice());
58 return jv;
59 };
60
61public:
62 void
64 {
65 using namespace jtx;
66
67 testcase("Basics");
68
69 Env env{*this};
70 Account const alice("alice");
71 Account const becky("becky");
72 Account const carol("carol");
73 Account const gw("gw");
74
75 env.fund(XRP(10000), alice, becky, carol, gw);
76 env.close();
77
78 // Alice can't delete her account and then give herself the XRP.
79 env(acctdelete(alice, alice), ter(temDST_IS_SRC));
80
81 // alice can't delete her account with a negative fee.
82 env(acctdelete(alice, becky), fee(drops(-1)), ter(temBAD_FEE));
83
84 // Invalid flags.
85 env(acctdelete(alice, becky),
88
89 // Account deletion has a high fee. Make sure the fee requirement
90 // behaves as we expect.
91 auto const acctDelFee{drops(env.current()->fees().increment)};
92 env(acctdelete(alice, becky), ter(telINSUF_FEE_P));
93
94 // Try a fee one drop less than the required amount.
95 env(acctdelete(alice, becky),
96 fee(acctDelFee - drops(1)),
98
99 // alice's account is created too recently to be deleted.
100 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
101
102 // Give becky a trustline. She is no longer deletable.
103 env(trust(becky, gw["USD"](1000)));
104 env.close();
105
106 // Give carol a deposit preauthorization, an offer, a ticket,
107 // a signer list, and a DID. Even with all that she's still deletable.
108 env(deposit::auth(carol, becky));
109 std::uint32_t const carolOfferSeq{env.seq(carol)};
110 env(offer(carol, gw["USD"](51), XRP(51)));
111 std::uint32_t const carolTicketSeq{env.seq(carol) + 1};
112 env(ticket::create(carol, 1));
113 env(signers(carol, 1, {{alice, 1}, {becky, 1}}));
114 env(did::setValid(carol));
115
116 // Deleting should fail with TOO_SOON, which is a relatively
117 // cheap check compared to validating the contents of her directory.
118 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
119
120 // Close enough ledgers to almost be able to delete alice's account.
121 incLgrSeqForAccDel(env, alice, 1);
122
123 // alice's account is still created too recently to be deleted.
124 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
125
126 // The most recent delete attempt advanced alice's sequence. So
127 // close two ledgers and her account should be deletable.
128 env.close();
129 env.close();
130
131 {
132 auto const aliceOldBalance{env.balance(alice)};
133 auto const beckyOldBalance{env.balance(becky)};
134
135 // Verify that alice's account exists but she has no directory.
136 BEAST_EXPECT(env.closed()->exists(keylet::account(alice.id())));
137 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
138
139 env(acctdelete(alice, becky), fee(acctDelFee));
140 verifyDeliveredAmount(env, aliceOldBalance - acctDelFee);
141 env.close();
142
143 // Verify that alice's account and directory are actually gone.
144 BEAST_EXPECT(!env.closed()->exists(keylet::account(alice.id())));
145 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
146
147 // Verify that alice's XRP, minus the fee, was transferred to becky.
148 BEAST_EXPECT(
149 env.balance(becky) ==
150 aliceOldBalance + beckyOldBalance - acctDelFee);
151 }
152
153 // Attempt to delete becky's account but get stopped by the trust line.
154 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
155 env.close();
156
157 // Verify that becky's account is still there by giving her a regular
158 // key. This has the side effect of setting the lsfPasswordSpent bit
159 // on her account root.
160 Account const beck("beck");
161 env(regkey(becky, beck), fee(drops(0)));
162 env.close();
163
164 // Show that the lsfPasswordSpent bit is set by attempting to change
165 // becky's regular key for free again. That fails.
166 Account const reb("reb");
167 env(regkey(becky, reb), sig(becky), fee(drops(0)), ter(telINSUF_FEE_P));
168
169 // Close enough ledgers that becky's failing regkey transaction is
170 // no longer retried.
171 for (int i = 0; i < 8; ++i)
172 env.close();
173
174 {
175 auto const beckyOldBalance{env.balance(becky)};
176 auto const carolOldBalance{env.balance(carol)};
177
178 // Verify that Carol's account, directory, deposit
179 // preauthorization, offer, ticket, and signer list exist.
180 BEAST_EXPECT(env.closed()->exists(keylet::account(carol.id())));
181 BEAST_EXPECT(env.closed()->exists(keylet::ownerDir(carol.id())));
182 BEAST_EXPECT(env.closed()->exists(
183 keylet::depositPreauth(carol.id(), becky.id())));
184 BEAST_EXPECT(
185 env.closed()->exists(keylet::offer(carol.id(), carolOfferSeq)));
186 BEAST_EXPECT(env.closed()->exists(
187 keylet::ticket(carol.id(), carolTicketSeq)));
188 BEAST_EXPECT(env.closed()->exists(keylet::signers(carol.id())));
189
190 // Delete carol's account even with stuff in her directory. Show
191 // that multisigning for the delete does not increase carol's fee.
192 env(acctdelete(carol, becky), fee(acctDelFee), msig(alice));
193 verifyDeliveredAmount(env, carolOldBalance - acctDelFee);
194 env.close();
195
196 // Verify that Carol's account, directory, and other stuff are gone.
197 BEAST_EXPECT(!env.closed()->exists(keylet::account(carol.id())));
198 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(carol.id())));
199 BEAST_EXPECT(!env.closed()->exists(
200 keylet::depositPreauth(carol.id(), becky.id())));
201 BEAST_EXPECT(!env.closed()->exists(
202 keylet::offer(carol.id(), carolOfferSeq)));
203 BEAST_EXPECT(!env.closed()->exists(
204 keylet::ticket(carol.id(), carolTicketSeq)));
205 BEAST_EXPECT(!env.closed()->exists(keylet::signers(carol.id())));
206
207 // Verify that Carol's XRP, minus the fee, was transferred to becky.
208 BEAST_EXPECT(
209 env.balance(becky) ==
210 carolOldBalance + beckyOldBalance - acctDelFee);
211
212 // Since becky received an influx of XRP, her lsfPasswordSpent bit
213 // is cleared and she can change her regular key for free again.
214 env(regkey(becky, reb), sig(becky), fee(drops(0)));
215 }
216 }
217
218 void
220 {
221 // The code that deletes consecutive directory entries uses a
222 // peculiarity of the implementation. Make sure that peculiarity
223 // behaves as expected across owner directory pages.
224 using namespace jtx;
225
226 testcase("Directories");
227
228 Env env{*this};
229 Account const alice("alice");
230 Account const gw("gw");
231
232 env.fund(XRP(10000), alice, gw);
233 env.close();
234
235 // Alice creates enough offers to require two owner directories.
236 for (int i{0}; i < 45; ++i)
237 {
238 env(offer(alice, gw["USD"](1), XRP(1)));
239 env.close();
240 }
241 env.require(offers(alice, 45));
242
243 // Close enough ledgers to be able to delete alice's account.
244 incLgrSeqForAccDel(env, alice);
245
246 // Verify that both directory nodes exist.
247 Keylet const aliceRootKey{keylet::ownerDir(alice.id())};
248 Keylet const alicePageKey{keylet::page(aliceRootKey, 1)};
249 BEAST_EXPECT(env.closed()->exists(aliceRootKey));
250 BEAST_EXPECT(env.closed()->exists(alicePageKey));
251
252 // Delete alice's account.
253 auto const acctDelFee{drops(env.current()->fees().increment)};
254 auto const aliceBalance{env.balance(alice)};
255 env(acctdelete(alice, gw), fee(acctDelFee));
256 verifyDeliveredAmount(env, aliceBalance - acctDelFee);
257 env.close();
258
259 // Both of alice's directory nodes should be gone.
260 BEAST_EXPECT(!env.closed()->exists(aliceRootKey));
261 BEAST_EXPECT(!env.closed()->exists(alicePageKey));
262 }
263
264 void
266 {
267 using namespace jtx;
268
269 testcase("Owned types");
270
271 // We want to test PayChannels with the backlink.
272 Env env{*this, testable_amendments()};
273 Account const alice("alice");
274 Account const becky("becky");
275 Account const gw("gw");
276
277 env.fund(XRP(100000), alice, becky, gw);
278 env.close();
279
280 // Give alice and becky a bunch of offers that we have to search
281 // through before we figure out that there's a non-deletable
282 // entry in their directory.
283 for (int i{0}; i < 200; ++i)
284 {
285 env(offer(alice, gw["USD"](1), XRP(1)));
286 env(offer(becky, gw["USD"](1), XRP(1)));
287 env.close();
288 }
289 env.require(offers(alice, 200));
290 env.require(offers(becky, 200));
291
292 // Close enough ledgers to be able to delete alice's and becky's
293 // accounts.
294 incLgrSeqForAccDel(env, alice);
295 incLgrSeqForAccDel(env, becky);
296
297 // alice writes a check to becky. Until that check is cashed or
298 // canceled it will prevent alice's and becky's accounts from being
299 // deleted.
300 uint256 const checkId = keylet::check(alice, env.seq(alice)).key;
301 env(check::create(alice, becky, XRP(1)));
302 env.close();
303
304 auto const acctDelFee{drops(env.current()->fees().increment)};
305 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
306 env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
307 env.close();
308
309 // Cancel the check, but add an escrow. Again, with the escrow
310 // on board, alice and becky should not be able to delete their
311 // accounts.
312 env(check::cancel(becky, checkId));
313 env.close();
314
315 using namespace std::chrono_literals;
316 std::uint32_t const escrowSeq{env.seq(alice)};
317 env(escrow::create(alice, becky, XRP(333)),
318 escrow::finish_time(env.now() + 3s),
319 escrow::cancel_time(env.now() + 4s));
320 env.close();
321
322 // alice and becky should be unable to delete their accounts because
323 // of the escrow.
324 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
325 env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
326 env.close();
327
328 // Now cancel the escrow, but create a payment channel between
329 // alice and becky.
330
331 bool const withTokenEscrow =
332 env.current()->rules().enabled(featureTokenEscrow);
333 if (withTokenEscrow)
334 {
335 Account const gw1("gw1");
336 Account const carol("carol");
337 auto const USD = gw1["USD"];
338 env.fund(XRP(100000), carol, gw1);
340 env.close();
341 env.trust(USD(10000), carol);
342 env.close();
343 env(pay(gw1, carol, USD(100)));
344 env.close();
345
346 std::uint32_t const escrowSeq{env.seq(carol)};
347 env(escrow::create(carol, becky, USD(1)),
348 escrow::finish_time(env.now() + 3s),
349 escrow::cancel_time(env.now() + 4s));
350 env.close();
351
352 incLgrSeqForAccDel(env, gw1);
353
354 env(acctdelete(gw1, becky),
355 fee(acctDelFee),
357 env.close();
358
359 env(escrow::cancel(becky, carol, escrowSeq));
360 env.close();
361 }
362
363 env(escrow::cancel(becky, alice, escrowSeq));
364 env.close();
365
366 Keylet const alicePayChanKey{
367 keylet::payChan(alice, becky, env.seq(alice))};
368
369 env(payChanCreate(
370 alice, becky, XRP(57), 4s, env.now() + 2s, alice.pk()));
371 env.close();
372
373 // With the PayChannel in place becky and alice should not be
374 // able to delete her account
375 auto const beckyBalance{env.balance(becky)};
376 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
377 env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
378 env.close();
379
380 // Alice cancels her PayChannel, which will leave her with only offers
381 // in her directory.
382
383 // Lambda to close a PayChannel.
384 auto payChanClose = [](jtx::Account const& account,
385 Keylet const& payChanKeylet,
386 PublicKey const& pk) {
387 Json::Value jv;
388 jv[jss::TransactionType] = jss::PaymentChannelClaim;
389 jv[jss::Flags] = tfClose;
390 jv[jss::Account] = account.human();
391 jv[sfChannel.jsonName] = to_string(payChanKeylet.key);
392 jv[sfPublicKey.jsonName] = strHex(pk.slice());
393 return jv;
394 };
395 env(payChanClose(alice, alicePayChanKey, alice.pk()));
396 env.close();
397
398 // gw creates a PayChannel with alice as the destination, this should
399 // prevent alice from deleting her account.
400 Keylet const gwPayChanKey{keylet::payChan(gw, alice, env.seq(gw))};
401
402 env(payChanCreate(gw, alice, XRP(68), 4s, env.now() + 2s, alice.pk()));
403 env.close();
404
405 // alice can't delete her account because of the PayChannel.
406 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
407 env.close();
408
409 // alice closes the PayChannel which should (finally) allow her to
410 // delete her account.
411 env(payChanClose(alice, gwPayChanKey, alice.pk()));
412 env.close();
413
414 // Now alice can successfully delete her account.
415 auto const aliceBalance{env.balance(alice)};
416 env(acctdelete(alice, gw), fee(acctDelFee));
417 verifyDeliveredAmount(env, aliceBalance - acctDelFee);
418 env.close();
419 }
420
421 void
423 {
424 // Put enough offers in an account that we refuse to delete the account.
425 using namespace jtx;
426
427 testcase("Too many offers");
428
429 Env env{*this};
430 Account const alice("alice");
431 Account const gw("gw");
432
433 // Fund alice well so she can afford the reserve on the offers.
434 env.fund(XRP(10000000), alice, gw);
435 env.close();
436
437 // To increase the number of Books affected, change the currency of
438 // each offer.
439 std::string currency{"AAA"};
440
441 // Alice creates 1001 offers. This is one greater than the number of
442 // directory entries an AccountDelete will remove.
443 std::uint32_t const offerSeq0{env.seq(alice)};
444 constexpr int offerCount{1001};
445 for (int i{0}; i < offerCount; ++i)
446 {
447 env(offer(alice, gw[currency](1), XRP(1)));
448 env.close();
449
450 // Increment to next currency.
451 ++currency[0];
452 if (currency[0] > 'Z')
453 {
454 currency[0] = 'A';
455 ++currency[1];
456 }
457 if (currency[1] > 'Z')
458 {
459 currency[1] = 'A';
460 ++currency[2];
461 }
462 if (currency[2] > 'Z')
463 {
464 currency[0] = 'A';
465 currency[1] = 'A';
466 currency[2] = 'A';
467 }
468 }
469
470 // Close enough ledgers to be able to delete alice's account.
471 incLgrSeqForAccDel(env, alice);
472
473 // Verify the existence of the expected ledger entries.
474 Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
475 {
476 std::shared_ptr<ReadView const> closed{env.closed()};
477 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
478 BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
479
480 // alice's directory nodes.
481 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
482 BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
483
484 // alice's offers.
485 for (std::uint32_t i{0}; i < offerCount; ++i)
486 BEAST_EXPECT(
487 closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
488 }
489
490 // Delete alice's account. Should fail because she has too many
491 // offers in her directory.
492 auto const acctDelFee{drops(env.current()->fees().increment)};
493
494 env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
495
496 // Cancel one of alice's offers. Then the account delete can succeed.
497 env.require(offers(alice, offerCount));
498 env(offer_cancel(alice, offerSeq0));
499 env.close();
500 env.require(offers(alice, offerCount - 1));
501
502 // alice successfully deletes her account.
503 auto const alicePreDelBal{env.balance(alice)};
504 env(acctdelete(alice, gw), fee(acctDelFee));
505 verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
506 env.close();
507
508 // Verify that alice's account root is gone as well as her directory
509 // nodes and all of her offers.
510 {
511 std::shared_ptr<ReadView const> closed{env.closed()};
512 BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
513 BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
514
515 // alice's former directory nodes.
516 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
517 BEAST_EXPECT(
518 !closed->exists(keylet::page(aliceOwnerDirKey, i)));
519
520 // alice's former offers.
521 for (std::uint32_t i{0}; i < offerCount; ++i)
522 BEAST_EXPECT(
523 !closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
524 }
525 }
526
527 void
529 {
530 // Show that a trust line that is implicitly created by offer crossing
531 // prevents an account from being deleted.
532 using namespace jtx;
533
534 testcase("Implicitly created trust line");
535
536 Env env{*this};
537 Account const alice{"alice"};
538 Account const gw{"gw"};
539 auto const BUX{gw["BUX"]};
540
541 env.fund(XRP(10000), alice, gw);
542 env.close();
543
544 // alice creates an offer that, if crossed, will implicitly create
545 // a trust line.
546 env(offer(alice, BUX(30), XRP(30)));
547 env.close();
548
549 // gw crosses alice's offer. alice should end up with BUX(30).
550 env(offer(gw, XRP(30), BUX(30)));
551 env.close();
552 env.require(balance(alice, BUX(30)));
553
554 // Close enough ledgers to be able to delete alice's account.
555 incLgrSeqForAccDel(env, alice);
556
557 // alice and gw can't delete their accounts because of the implicitly
558 // created trust line.
559 auto const acctDelFee{drops(env.current()->fees().increment)};
560 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
561 env.close();
562
563 env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
564 env.close();
565 {
566 std::shared_ptr<ReadView const> closed{env.closed()};
567 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
568 BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
569 }
570 }
571
572 void
574 {
575 // See what happens when an account with a balance less than the
576 // incremental reserve tries to delete itself.
577 using namespace jtx;
578
579 testcase("Balance too small for fee");
580
581 Env env{*this};
582 Account const alice("alice");
583
584 // Note that the fee structure for unit tests does not match the fees
585 // on the production network (October 2019). Unit tests have a base
586 // reserve of 200 XRP.
587 env.fund(env.current()->fees().reserve, noripple(alice));
588 env.close();
589
590 // Burn a chunk of alice's funds so she only has 1 XRP remaining in
591 // her account.
592 env(noop(alice), fee(env.balance(alice) - XRP(1)));
593 env.close();
594
595 auto const acctDelFee{drops(env.current()->fees().increment)};
596 BEAST_EXPECT(acctDelFee > env.balance(alice));
597
598 // alice attempts to delete her account even though she can't pay
599 // the full fee. She specifies a fee that is larger than her balance.
600 //
601 // The balance of env.master should not change.
602 auto const masterBalance{env.balance(env.master)};
603 env(acctdelete(alice, env.master),
604 fee(acctDelFee),
606 env.close();
607 {
608 std::shared_ptr<ReadView const> const closed{env.closed()};
609 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
610 BEAST_EXPECT(env.balance(env.master) == masterBalance);
611 }
612
613 // alice again attempts to delete her account. This time she specifies
614 // her current balance in XRP. Again the transaction fails.
615 BEAST_EXPECT(env.balance(alice) == XRP(1));
616 env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
617 env.close();
618 {
619 std::shared_ptr<ReadView const> closed{env.closed()};
620 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
621 BEAST_EXPECT(env.balance(env.master) == masterBalance);
622 }
623 }
624
625 void
627 {
628 testcase("With Tickets");
629
630 using namespace test::jtx;
631
632 Account const alice{"alice"};
633 Account const bob{"bob"};
634
635 Env env{*this};
636 env.fund(XRP(100000), alice, bob);
637 env.close();
638
639 // bob grabs as many tickets as he is allowed to have.
640 std::uint32_t const ticketSeq{env.seq(bob) + 1};
641 env(ticket::create(bob, 250));
642 env.close();
643 env.require(owners(bob, 250));
644
645 {
646 std::shared_ptr<ReadView const> closed{env.closed()};
647 BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
648 for (std::uint32_t i = 0; i < 250; ++i)
649 {
650 BEAST_EXPECT(
651 closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
652 }
653 }
654
655 // Close enough ledgers to be able to delete bob's account.
656 incLgrSeqForAccDel(env, bob);
657
658 // bob deletes his account using a ticket. bob's account and all
659 // of his tickets should be removed from the ledger.
660 auto const acctDelFee{drops(env.current()->fees().increment)};
661 auto const bobOldBalance{env.balance(bob)};
662 env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
663 verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
664 env.close();
665 {
666 std::shared_ptr<ReadView const> closed{env.closed()};
667 BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
668 for (std::uint32_t i = 0; i < 250; ++i)
669 {
670 BEAST_EXPECT(
671 !closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
672 }
673 }
674 }
675
676 void
678 {
679 testcase("Destination Constraints");
680
681 using namespace test::jtx;
682
683 Account const alice{"alice"};
684 Account const becky{"becky"};
685 Account const carol{"carol"};
686 Account const daria{"daria"};
687
688 Env env{*this};
689 env.fund(XRP(100000), alice, becky, carol);
690 env.close();
691
692 // alice sets the lsfDepositAuth flag on her account. This should
693 // prevent becky from deleting her account while using alice as the
694 // destination.
695 env(fset(alice, asfDepositAuth));
696
697 // carol requires a destination tag.
698 env(fset(carol, asfRequireDest));
699 env.close();
700
701 // Close enough ledgers to be able to delete becky's account.
702 incLgrSeqForAccDel(env, becky);
703
704 // becky attempts to delete her account using daria as the destination.
705 // Since daria is not in the ledger the delete attempt fails.
706 auto const acctDelFee{drops(env.current()->fees().increment)};
707 env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
708 env.close();
709
710 // becky attempts to delete her account, but carol requires a
711 // destination tag which becky has omitted.
712 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
713 env.close();
714
715 // becky attempts to delete her account, but alice won't take her XRP,
716 // so the delete is blocked.
717 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
718 env.close();
719
720 // alice preauthorizes deposits from becky. Now becky can delete her
721 // account and forward the leftovers to alice.
722 env(deposit::auth(alice, becky));
723 env.close();
724
725 auto const beckyOldBalance{env.balance(becky)};
726 env(acctdelete(becky, alice), fee(acctDelFee));
727 verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
728 env.close();
729 }
730
731 void
733 {
734 {
735 testcase(
736 "Destination Constraints with DepositPreauth and Credentials");
737
738 using namespace test::jtx;
739
740 Account const alice{"alice"};
741 Account const becky{"becky"};
742 Account const carol{"carol"};
743 Account const daria{"daria"};
744
745 char const credType[] = "abcd";
746
747 Env env{*this};
748 env.fund(XRP(100000), alice, becky, carol, daria);
749 env.close();
750
751 // carol issue credentials for becky
752 env(credentials::create(becky, carol, credType));
753 env.close();
754
755 // get credentials index
756 auto const jv =
757 credentials::ledgerEntry(env, becky, carol, credType);
758 std::string const credIdx = jv[jss::result][jss::index].asString();
759
760 // Close enough ledgers to be able to delete becky's account.
761 incLgrSeqForAccDel(env, becky);
762
763 auto const acctDelFee{drops(env.current()->fees().increment)};
764
765 // becky use credentials but they aren't accepted
766 env(acctdelete(becky, alice),
767 credentials::ids({credIdx}),
768 fee(acctDelFee),
770 env.close();
771
772 {
773 // alice sets the lsfDepositAuth flag on her account. This
774 // should prevent becky from deleting her account while using
775 // alice as the destination.
776 env(fset(alice, asfDepositAuth));
777 env.close();
778 }
779
780 // Fail, credentials still not accepted
781 env(acctdelete(becky, alice),
782 credentials::ids({credIdx}),
783 fee(acctDelFee),
785 env.close();
786
787 // becky accept the credentials
788 env(credentials::accept(becky, carol, credType));
789 env.close();
790
791 // Fail, credentials doesn’t belong to carol
792 env(acctdelete(carol, alice),
793 credentials::ids({credIdx}),
794 fee(acctDelFee),
796
797 // Fail, no depositPreauth for provided credentials
798 env(acctdelete(becky, alice),
799 credentials::ids({credIdx}),
800 fee(acctDelFee),
802 env.close();
803
804 // alice create DepositPreauth Object
805 env(deposit::authCredentials(alice, {{carol, credType}}));
806 env.close();
807
808 // becky attempts to delete her account, but alice won't take her
809 // XRP, so the delete is blocked.
810 env(acctdelete(becky, alice),
811 fee(acctDelFee),
813
814 // becky use empty credentials and can't delete account
815 env(acctdelete(becky, alice),
816 fee(acctDelFee),
819
820 // becky use bad credentials and can't delete account
821 env(acctdelete(becky, alice),
823 {"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6E"
824 "A288BE4"}),
825 fee(acctDelFee),
827 env.close();
828
829 // becky use credentials and can delete account
830 env(acctdelete(becky, alice),
831 credentials::ids({credIdx}),
832 fee(acctDelFee));
833 env.close();
834
835 {
836 // check that credential object deleted too
837 auto const jNoCred =
838 credentials::ledgerEntry(env, becky, carol, credType);
839 BEAST_EXPECT(
840 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
841 jNoCred[jss::result].isMember(jss::error) &&
842 jNoCred[jss::result][jss::error] == "entryNotFound");
843 }
844
845 testcase("Credentials that aren't required");
846 { // carol issue credentials for daria
847 env(credentials::create(daria, carol, credType));
848 env.close();
849 env(credentials::accept(daria, carol, credType));
850 env.close();
851 std::string const credDaria =
853 env, daria, carol, credType)[jss::result][jss::index]
854 .asString();
855
856 // daria use valid credentials, which aren't required and can
857 // delete her account
858 env(acctdelete(daria, carol),
859 credentials::ids({credDaria}),
860 fee(acctDelFee));
861 env.close();
862
863 // check that credential object deleted too
864 auto const jNoCred =
865 credentials::ledgerEntry(env, daria, carol, credType);
866
867 BEAST_EXPECT(
868 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
869 jNoCred[jss::result].isMember(jss::error) &&
870 jNoCred[jss::result][jss::error] == "entryNotFound");
871 }
872
873 {
874 Account const eaton{"eaton"};
875 Account const fred{"fred"};
876
877 env.fund(XRP(5000), eaton, fred);
878
879 // carol issue credentials for eaton
880 env(credentials::create(eaton, carol, credType));
881 env.close();
882 env(credentials::accept(eaton, carol, credType));
883 env.close();
884 std::string const credEaton =
886 env, eaton, carol, credType)[jss::result][jss::index]
887 .asString();
888
889 // fred make preauthorization through authorized account
890 env(fset(fred, asfDepositAuth));
891 env.close();
892 env(deposit::auth(fred, eaton));
893 env.close();
894
895 // Close enough ledgers to be able to delete becky's account.
896 incLgrSeqForAccDel(env, eaton);
897 auto const acctDelFee{drops(env.current()->fees().increment)};
898
899 // eaton use valid credentials, but he already authorized
900 // through "Authorized" field.
901 env(acctdelete(eaton, fred),
902 credentials::ids({credEaton}),
903 fee(acctDelFee));
904 env.close();
905
906 // check that credential object deleted too
907 auto const jNoCred =
908 credentials::ledgerEntry(env, eaton, carol, credType);
909
910 BEAST_EXPECT(
911 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
912 jNoCred[jss::result].isMember(jss::error) &&
913 jNoCred[jss::result][jss::error] == "entryNotFound");
914 }
915
916 testcase("Expired credentials");
917 {
918 Account const john{"john"};
919
920 env.fund(XRP(10000), john);
921 env.close();
922
923 auto jv = credentials::create(john, carol, credType);
924 uint32_t const t = env.current()
925 ->info()
926 .parentCloseTime.time_since_epoch()
927 .count() +
928 20;
929 jv[sfExpiration.jsonName] = t;
930 env(jv);
931 env.close();
932 env(credentials::accept(john, carol, credType));
933 env.close();
934 jv = credentials::ledgerEntry(env, john, carol, credType);
935 std::string const credIdx =
936 jv[jss::result][jss::index].asString();
937
938 incLgrSeqForAccDel(env, john);
939
940 // credentials are expired
941 // john use credentials but can't delete account
942 env(acctdelete(john, alice),
943 credentials::ids({credIdx}),
944 fee(acctDelFee),
945 ter(tecEXPIRED));
946 env.close();
947
948 {
949 // check that expired credential object deleted
950 auto jv =
951 credentials::ledgerEntry(env, john, carol, credType);
952 BEAST_EXPECT(
953 jv.isObject() && jv.isMember(jss::result) &&
954 jv[jss::result].isMember(jss::error) &&
955 jv[jss::result][jss::error] == "entryNotFound");
956 }
957 }
958 }
959
960 {
961 testcase("Credentials feature disabled");
962 using namespace test::jtx;
963
964 Account const alice{"alice"};
965 Account const becky{"becky"};
966 Account const carol{"carol"};
967
968 Env env{*this, testable_amendments() - featureCredentials};
969 env.fund(XRP(100000), alice, becky, carol);
970 env.close();
971
972 // alice sets the lsfDepositAuth flag on her account. This should
973 // prevent becky from deleting her account while using alice as the
974 // destination.
975 env(fset(alice, asfDepositAuth));
976 env.close();
977
978 // Close enough ledgers to be able to delete becky's account.
979 incLgrSeqForAccDel(env, becky);
980
981 auto const acctDelFee{drops(env.current()->fees().increment)};
982
983 std::string const credIdx =
984 "098B7F1B146470A1C5084DC7832C04A72939E3EBC58E68AB8B579BA072B0CE"
985 "CB";
986
987 // and can't delete even with old DepositPreauth
988 env(deposit::auth(alice, becky));
989 env.close();
990
991 env(acctdelete(becky, alice),
992 credentials::ids({credIdx}),
993 fee(acctDelFee),
995 env.close();
996 }
997 }
998
999 void
1001 {
1002 {
1003 testcase("Deleting Issuer deletes issued credentials");
1004
1005 using namespace test::jtx;
1006
1007 Account const alice{"alice"};
1008 Account const becky{"becky"};
1009 Account const carol{"carol"};
1010
1011 char const credType[] = "abcd";
1012
1013 Env env{*this};
1014 env.fund(XRP(100000), alice, becky, carol);
1015 env.close();
1016
1017 // carol issue credentials for becky
1018 env(credentials::create(becky, carol, credType));
1019 env.close();
1020 env(credentials::accept(becky, carol, credType));
1021 env.close();
1022
1023 // get credentials index
1024 auto const jv =
1025 credentials::ledgerEntry(env, becky, carol, credType);
1026 std::string const credIdx = jv[jss::result][jss::index].asString();
1027
1028 // Close enough ledgers to be able to delete carol's account.
1029 incLgrSeqForAccDel(env, carol);
1030
1031 auto const acctDelFee{drops(env.current()->fees().increment)};
1032 env(acctdelete(carol, alice), fee(acctDelFee));
1033 env.close();
1034
1035 { // check that credential object deleted too
1036 BEAST_EXPECT(!env.le(credIdx));
1037 auto const jv =
1038 credentials::ledgerEntry(env, becky, carol, credType);
1039 BEAST_EXPECT(
1040 jv.isObject() && jv.isMember(jss::result) &&
1041 jv[jss::result].isMember(jss::error) &&
1042 jv[jss::result][jss::error] == "entryNotFound");
1043 }
1044 }
1045
1046 {
1047 testcase("Deleting Subject deletes issued credentials");
1048
1049 using namespace test::jtx;
1050
1051 Account const alice{"alice"};
1052 Account const becky{"becky"};
1053 Account const carol{"carol"};
1054
1055 char const credType[] = "abcd";
1056
1057 Env env{*this};
1058 env.fund(XRP(100000), alice, becky, carol);
1059 env.close();
1060
1061 // carol issue credentials for becky
1062 env(credentials::create(becky, carol, credType));
1063 env.close();
1064 env(credentials::accept(becky, carol, credType));
1065 env.close();
1066
1067 // get credentials index
1068 auto const jv =
1069 credentials::ledgerEntry(env, becky, carol, credType);
1070 std::string const credIdx = jv[jss::result][jss::index].asString();
1071
1072 // Close enough ledgers to be able to delete carol's account.
1073 incLgrSeqForAccDel(env, becky);
1074
1075 auto const acctDelFee{drops(env.current()->fees().increment)};
1076 env(acctdelete(becky, alice), fee(acctDelFee));
1077 env.close();
1078
1079 { // check that credential object deleted too
1080 BEAST_EXPECT(!env.le(credIdx));
1081 auto const jv =
1082 credentials::ledgerEntry(env, becky, carol, credType);
1083 BEAST_EXPECT(
1084 jv.isObject() && jv.isMember(jss::result) &&
1085 jv[jss::result].isMember(jss::error) &&
1086 jv[jss::result][jss::error] == "entryNotFound");
1087 }
1088 }
1089 }
1090
1091 void
1105};
1106
1107BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2);
1108
1109} // namespace test
1110} // namespace ripple
Represents a JSON value.
Definition json_value.h:131
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
A public key.
Definition PublicKey.h:43
Slice slice() const noexcept
Definition PublicKey.h:104
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:753
void verifyDeliveredAmount(jtx::Env &env, STAmount const &amount)
void run() override
Runs the suite.
static Json::Value payChanCreate(jtx::Account const &account, jtx::Account const &to, STAmount const &amount, NetClock::duration const &settleDelay, NetClock::time_point const &cancelAfter, PublicKey const &pk)
Immutable cryptographic account descriptor.
Definition Account.h:20
PublicKey const & pk() const
Return the public key.
Definition Account.h:75
AccountID id() const
Returns the Account ID.
Definition Account.h:92
static Account const master
The master account.
Definition Account.h:29
std::string const & human() const
Returns the human readable public key.
Definition Account.h:99
A transaction testing environment.
Definition Env.h:102
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:507
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:103
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:772
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
A balance matches.
Definition balance.h:20
Set the fee on a JTx.
Definition fee.h:18
Set a multisignature on a JTx.
Definition multisign.h:48
Match the number of items in the account's owner directory.
Definition owners.h:54
Set the regular signature on a JTx.
Definition sig.h:16
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:16
Set a ticket sequence on a JTx.
Definition ticket.h:29
Set the flags on a JTx.
Definition txflags.h:12
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:361
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:355
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:311
static ticket_t const ticket
Definition Indexes.h:152
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:317
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:255
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:323
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:376
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Definition check.cpp:41
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:13
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:29
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:59
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:35
Json::Value setValid(jtx::Account const &account)
Definition dids.cpp:23
Json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount)
Definition escrow.cpp:14
auto const finish_time
Set the "FinishAfter" time tag on a JTx.
Definition escrow.h:79
Json::Value cancel(AccountID const &account, Account const &from, std::uint32_t seq)
Definition escrow.cpp:38
auto const cancel_time
Set the "CancelAfter" time tag on a JTx.
Definition escrow.h:82
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition regkey.cpp:10
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition multisign.cpp:15
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition owners.h:73
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
FeatureBitset testable_amendments()
Definition Env.h:55
void incLgrSeqForAccDel(jtx::Env &env, jtx::Account const &acc, std::uint32_t margin=0)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:27
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
@ telINSUF_FEE_P
Definition TER.h:38
constexpr std::uint32_t asfAllowTrustLineLocking
Definition TxFlags.h:76
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
constexpr std::uint32_t tfImmediateOrCancel
Definition TxFlags.h:80
@ tefTOO_BIG
Definition TER.h:165
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
@ tecNO_DST
Definition TER.h:272
@ tecTOO_SOON
Definition TER.h:300
@ tecBAD_CREDENTIALS
Definition TER.h:341
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecEXPIRED
Definition TER.h:296
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
constexpr std::uint32_t tfClose
Definition TxFlags.h:116
@ terINSUF_FEE_B
Definition TER.h:197
@ temBAD_FEE
Definition TER.h:73
@ temMALFORMED
Definition TER.h:68
@ temINVALID_FLAG
Definition TER.h:92
@ temDISABLED
Definition TER.h:95
@ temDST_IS_SRC
Definition TER.h:89
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
T time_since_epoch(T... args)