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 // Start with the featureDeletableAccounts amendment disabled.
425 // Then enable the amendment and delete an account.
426 using namespace jtx;
427
428 testcase("Amendment enable");
429
430 Env env{*this, testable_amendments() - featureDeletableAccounts};
431 Account const alice("alice");
432 Account const becky("becky");
433
434 env.fund(XRP(10000), alice, becky);
435 env.close();
436
437 // Close enough ledgers to be able to delete alice's account.
438 incLgrSeqForAccDel(env, alice);
439
440 // Verify that alice's account root is present.
441 Keylet const aliceAcctKey{keylet::account(alice.id())};
442 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
443
444 auto const alicePreDelBal{env.balance(alice)};
445 auto const beckyPreDelBal{env.balance(becky)};
446
447 auto const acctDelFee{drops(env.current()->fees().increment)};
448 env(acctdelete(alice, becky), fee(acctDelFee), ter(temDISABLED));
449 env.close();
450
451 // Verify that alice's account root is still present and alice and
452 // becky both have their XRP.
453 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
454 BEAST_EXPECT(env.balance(alice) == alicePreDelBal);
455 BEAST_EXPECT(env.balance(becky) == beckyPreDelBal);
456
457 // When the amendment is enabled the previous transaction is
458 // retried into the new open ledger and succeeds.
459 env.enableFeature(featureDeletableAccounts);
460 env.close();
461
462 // alice's account is still in the most recently closed ledger.
463 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
464
465 // Verify that alice's account root is gone from the current ledger
466 // and becky has alice's XRP.
467 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
468 BEAST_EXPECT(
469 env.balance(becky) == alicePreDelBal + beckyPreDelBal - acctDelFee);
470
471 env.close();
472 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
473 }
474
475 void
477 {
478 // Put enough offers in an account that we refuse to delete the account.
479 using namespace jtx;
480
481 testcase("Too many offers");
482
483 Env env{*this};
484 Account const alice("alice");
485 Account const gw("gw");
486
487 // Fund alice well so she can afford the reserve on the offers.
488 env.fund(XRP(10000000), alice, gw);
489 env.close();
490
491 // To increase the number of Books affected, change the currency of
492 // each offer.
493 std::string currency{"AAA"};
494
495 // Alice creates 1001 offers. This is one greater than the number of
496 // directory entries an AccountDelete will remove.
497 std::uint32_t const offerSeq0{env.seq(alice)};
498 constexpr int offerCount{1001};
499 for (int i{0}; i < offerCount; ++i)
500 {
501 env(offer(alice, gw[currency](1), XRP(1)));
502 env.close();
503
504 // Increment to next currency.
505 ++currency[0];
506 if (currency[0] > 'Z')
507 {
508 currency[0] = 'A';
509 ++currency[1];
510 }
511 if (currency[1] > 'Z')
512 {
513 currency[1] = 'A';
514 ++currency[2];
515 }
516 if (currency[2] > 'Z')
517 {
518 currency[0] = 'A';
519 currency[1] = 'A';
520 currency[2] = 'A';
521 }
522 }
523
524 // Close enough ledgers to be able to delete alice's account.
525 incLgrSeqForAccDel(env, alice);
526
527 // Verify the existence of the expected ledger entries.
528 Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
529 {
530 std::shared_ptr<ReadView const> closed{env.closed()};
531 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
532 BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
533
534 // alice's directory nodes.
535 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
536 BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
537
538 // alice's offers.
539 for (std::uint32_t i{0}; i < offerCount; ++i)
540 BEAST_EXPECT(
541 closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
542 }
543
544 // Delete alice's account. Should fail because she has too many
545 // offers in her directory.
546 auto const acctDelFee{drops(env.current()->fees().increment)};
547
548 env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
549
550 // Cancel one of alice's offers. Then the account delete can succeed.
551 env.require(offers(alice, offerCount));
552 env(offer_cancel(alice, offerSeq0));
553 env.close();
554 env.require(offers(alice, offerCount - 1));
555
556 // alice successfully deletes her account.
557 auto const alicePreDelBal{env.balance(alice)};
558 env(acctdelete(alice, gw), fee(acctDelFee));
559 verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
560 env.close();
561
562 // Verify that alice's account root is gone as well as her directory
563 // nodes and all of her offers.
564 {
565 std::shared_ptr<ReadView const> closed{env.closed()};
566 BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
567 BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
568
569 // alice's former directory nodes.
570 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
571 BEAST_EXPECT(
572 !closed->exists(keylet::page(aliceOwnerDirKey, i)));
573
574 // alice's former offers.
575 for (std::uint32_t i{0}; i < offerCount; ++i)
576 BEAST_EXPECT(
577 !closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
578 }
579 }
580
581 void
583 {
584 // Show that a trust line that is implicitly created by offer crossing
585 // prevents an account from being deleted.
586 using namespace jtx;
587
588 testcase("Implicitly created trust line");
589
590 Env env{*this};
591 Account const alice{"alice"};
592 Account const gw{"gw"};
593 auto const BUX{gw["BUX"]};
594
595 env.fund(XRP(10000), alice, gw);
596 env.close();
597
598 // alice creates an offer that, if crossed, will implicitly create
599 // a trust line.
600 env(offer(alice, BUX(30), XRP(30)));
601 env.close();
602
603 // gw crosses alice's offer. alice should end up with BUX(30).
604 env(offer(gw, XRP(30), BUX(30)));
605 env.close();
606 env.require(balance(alice, BUX(30)));
607
608 // Close enough ledgers to be able to delete alice's account.
609 incLgrSeqForAccDel(env, alice);
610
611 // alice and gw can't delete their accounts because of the implicitly
612 // created trust line.
613 auto const acctDelFee{drops(env.current()->fees().increment)};
614 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
615 env.close();
616
617 env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
618 env.close();
619 {
620 std::shared_ptr<ReadView const> closed{env.closed()};
621 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
622 BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
623 }
624 }
625
626 void
628 {
629 // See what happens when an account with a balance less than the
630 // incremental reserve tries to delete itself.
631 using namespace jtx;
632
633 testcase("Balance too small for fee");
634
635 Env env{*this};
636 Account const alice("alice");
637
638 // Note that the fee structure for unit tests does not match the fees
639 // on the production network (October 2019). Unit tests have a base
640 // reserve of 200 XRP.
641 env.fund(env.current()->fees().reserve, noripple(alice));
642 env.close();
643
644 // Burn a chunk of alice's funds so she only has 1 XRP remaining in
645 // her account.
646 env(noop(alice), fee(env.balance(alice) - XRP(1)));
647 env.close();
648
649 auto const acctDelFee{drops(env.current()->fees().increment)};
650 BEAST_EXPECT(acctDelFee > env.balance(alice));
651
652 // alice attempts to delete her account even though she can't pay
653 // the full fee. She specifies a fee that is larger than her balance.
654 //
655 // The balance of env.master should not change.
656 auto const masterBalance{env.balance(env.master)};
657 env(acctdelete(alice, env.master),
658 fee(acctDelFee),
660 env.close();
661 {
662 std::shared_ptr<ReadView const> const closed{env.closed()};
663 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
664 BEAST_EXPECT(env.balance(env.master) == masterBalance);
665 }
666
667 // alice again attempts to delete her account. This time she specifies
668 // her current balance in XRP. Again the transaction fails.
669 BEAST_EXPECT(env.balance(alice) == XRP(1));
670 env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
671 env.close();
672 {
673 std::shared_ptr<ReadView const> closed{env.closed()};
674 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
675 BEAST_EXPECT(env.balance(env.master) == masterBalance);
676 }
677 }
678
679 void
681 {
682 testcase("With Tickets");
683
684 using namespace test::jtx;
685
686 Account const alice{"alice"};
687 Account const bob{"bob"};
688
689 Env env{*this};
690 env.fund(XRP(100000), alice, bob);
691 env.close();
692
693 // bob grabs as many tickets as he is allowed to have.
694 std::uint32_t const ticketSeq{env.seq(bob) + 1};
695 env(ticket::create(bob, 250));
696 env.close();
697 env.require(owners(bob, 250));
698
699 {
700 std::shared_ptr<ReadView const> closed{env.closed()};
701 BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
702 for (std::uint32_t i = 0; i < 250; ++i)
703 {
704 BEAST_EXPECT(
705 closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
706 }
707 }
708
709 // Close enough ledgers to be able to delete bob's account.
710 incLgrSeqForAccDel(env, bob);
711
712 // bob deletes his account using a ticket. bob's account and all
713 // of his tickets should be removed from the ledger.
714 auto const acctDelFee{drops(env.current()->fees().increment)};
715 auto const bobOldBalance{env.balance(bob)};
716 env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
717 verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
718 env.close();
719 {
720 std::shared_ptr<ReadView const> closed{env.closed()};
721 BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
722 for (std::uint32_t i = 0; i < 250; ++i)
723 {
724 BEAST_EXPECT(
725 !closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
726 }
727 }
728 }
729
730 void
732 {
733 testcase("Destination Constraints");
734
735 using namespace test::jtx;
736
737 Account const alice{"alice"};
738 Account const becky{"becky"};
739 Account const carol{"carol"};
740 Account const daria{"daria"};
741
742 Env env{*this};
743 env.fund(XRP(100000), alice, becky, carol);
744 env.close();
745
746 // alice sets the lsfDepositAuth flag on her account. This should
747 // prevent becky from deleting her account while using alice as the
748 // destination.
749 env(fset(alice, asfDepositAuth));
750
751 // carol requires a destination tag.
752 env(fset(carol, asfRequireDest));
753 env.close();
754
755 // Close enough ledgers to be able to delete becky's account.
756 incLgrSeqForAccDel(env, becky);
757
758 // becky attempts to delete her account using daria as the destination.
759 // Since daria is not in the ledger the delete attempt fails.
760 auto const acctDelFee{drops(env.current()->fees().increment)};
761 env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
762 env.close();
763
764 // becky attempts to delete her account, but carol requires a
765 // destination tag which becky has omitted.
766 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
767 env.close();
768
769 // becky attempts to delete her account, but alice won't take her XRP,
770 // so the delete is blocked.
771 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
772 env.close();
773
774 // alice preauthorizes deposits from becky. Now becky can delete her
775 // account and forward the leftovers to alice.
776 env(deposit::auth(alice, becky));
777 env.close();
778
779 auto const beckyOldBalance{env.balance(becky)};
780 env(acctdelete(becky, alice), fee(acctDelFee));
781 verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
782 env.close();
783 }
784
785 void
787 {
788 {
789 testcase(
790 "Destination Constraints with DepositPreauth and Credentials");
791
792 using namespace test::jtx;
793
794 Account const alice{"alice"};
795 Account const becky{"becky"};
796 Account const carol{"carol"};
797 Account const daria{"daria"};
798
799 char const credType[] = "abcd";
800
801 Env env{*this};
802 env.fund(XRP(100000), alice, becky, carol, daria);
803 env.close();
804
805 // carol issue credentials for becky
806 env(credentials::create(becky, carol, credType));
807 env.close();
808
809 // get credentials index
810 auto const jv =
811 credentials::ledgerEntry(env, becky, carol, credType);
812 std::string const credIdx = jv[jss::result][jss::index].asString();
813
814 // Close enough ledgers to be able to delete becky's account.
815 incLgrSeqForAccDel(env, becky);
816
817 auto const acctDelFee{drops(env.current()->fees().increment)};
818
819 // becky use credentials but they aren't accepted
820 env(acctdelete(becky, alice),
821 credentials::ids({credIdx}),
822 fee(acctDelFee),
824 env.close();
825
826 {
827 // alice sets the lsfDepositAuth flag on her account. This
828 // should prevent becky from deleting her account while using
829 // alice as the destination.
830 env(fset(alice, asfDepositAuth));
831 env.close();
832 }
833
834 // Fail, credentials still not accepted
835 env(acctdelete(becky, alice),
836 credentials::ids({credIdx}),
837 fee(acctDelFee),
839 env.close();
840
841 // becky accept the credentials
842 env(credentials::accept(becky, carol, credType));
843 env.close();
844
845 // Fail, credentials doesn’t belong to carol
846 env(acctdelete(carol, alice),
847 credentials::ids({credIdx}),
848 fee(acctDelFee),
850
851 // Fail, no depositPreauth for provided credentials
852 env(acctdelete(becky, alice),
853 credentials::ids({credIdx}),
854 fee(acctDelFee),
856 env.close();
857
858 // alice create DepositPreauth Object
859 env(deposit::authCredentials(alice, {{carol, credType}}));
860 env.close();
861
862 // becky attempts to delete her account, but alice won't take her
863 // XRP, so the delete is blocked.
864 env(acctdelete(becky, alice),
865 fee(acctDelFee),
867
868 // becky use empty credentials and can't delete account
869 env(acctdelete(becky, alice),
870 fee(acctDelFee),
873
874 // becky use bad credentials and can't delete account
875 env(acctdelete(becky, alice),
877 {"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6E"
878 "A288BE4"}),
879 fee(acctDelFee),
881 env.close();
882
883 // becky use credentials and can delete account
884 env(acctdelete(becky, alice),
885 credentials::ids({credIdx}),
886 fee(acctDelFee));
887 env.close();
888
889 {
890 // check that credential object deleted too
891 auto const jNoCred =
892 credentials::ledgerEntry(env, becky, carol, credType);
893 BEAST_EXPECT(
894 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
895 jNoCred[jss::result].isMember(jss::error) &&
896 jNoCred[jss::result][jss::error] == "entryNotFound");
897 }
898
899 testcase("Credentials that aren't required");
900 { // carol issue credentials for daria
901 env(credentials::create(daria, carol, credType));
902 env.close();
903 env(credentials::accept(daria, carol, credType));
904 env.close();
905 std::string const credDaria =
907 env, daria, carol, credType)[jss::result][jss::index]
908 .asString();
909
910 // daria use valid credentials, which aren't required and can
911 // delete her account
912 env(acctdelete(daria, carol),
913 credentials::ids({credDaria}),
914 fee(acctDelFee));
915 env.close();
916
917 // check that credential object deleted too
918 auto const jNoCred =
919 credentials::ledgerEntry(env, daria, carol, credType);
920
921 BEAST_EXPECT(
922 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
923 jNoCred[jss::result].isMember(jss::error) &&
924 jNoCred[jss::result][jss::error] == "entryNotFound");
925 }
926
927 {
928 Account const eaton{"eaton"};
929 Account const fred{"fred"};
930
931 env.fund(XRP(5000), eaton, fred);
932
933 // carol issue credentials for eaton
934 env(credentials::create(eaton, carol, credType));
935 env.close();
936 env(credentials::accept(eaton, carol, credType));
937 env.close();
938 std::string const credEaton =
940 env, eaton, carol, credType)[jss::result][jss::index]
941 .asString();
942
943 // fred make preauthorization through authorized account
944 env(fset(fred, asfDepositAuth));
945 env.close();
946 env(deposit::auth(fred, eaton));
947 env.close();
948
949 // Close enough ledgers to be able to delete becky's account.
950 incLgrSeqForAccDel(env, eaton);
951 auto const acctDelFee{drops(env.current()->fees().increment)};
952
953 // eaton use valid credentials, but he already authorized
954 // through "Authorized" field.
955 env(acctdelete(eaton, fred),
956 credentials::ids({credEaton}),
957 fee(acctDelFee));
958 env.close();
959
960 // check that credential object deleted too
961 auto const jNoCred =
962 credentials::ledgerEntry(env, eaton, carol, credType);
963
964 BEAST_EXPECT(
965 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
966 jNoCred[jss::result].isMember(jss::error) &&
967 jNoCred[jss::result][jss::error] == "entryNotFound");
968 }
969
970 testcase("Expired credentials");
971 {
972 Account const john{"john"};
973
974 env.fund(XRP(10000), john);
975 env.close();
976
977 auto jv = credentials::create(john, carol, credType);
978 uint32_t const t = env.current()
979 ->info()
980 .parentCloseTime.time_since_epoch()
981 .count() +
982 20;
983 jv[sfExpiration.jsonName] = t;
984 env(jv);
985 env.close();
986 env(credentials::accept(john, carol, credType));
987 env.close();
988 jv = credentials::ledgerEntry(env, john, carol, credType);
989 std::string const credIdx =
990 jv[jss::result][jss::index].asString();
991
992 incLgrSeqForAccDel(env, john);
993
994 // credentials are expired
995 // john use credentials but can't delete account
996 env(acctdelete(john, alice),
997 credentials::ids({credIdx}),
998 fee(acctDelFee),
999 ter(tecEXPIRED));
1000 env.close();
1001
1002 {
1003 // check that expired credential object deleted
1004 auto jv =
1005 credentials::ledgerEntry(env, john, carol, credType);
1006 BEAST_EXPECT(
1007 jv.isObject() && jv.isMember(jss::result) &&
1008 jv[jss::result].isMember(jss::error) &&
1009 jv[jss::result][jss::error] == "entryNotFound");
1010 }
1011 }
1012 }
1013
1014 {
1015 testcase("Credentials feature disabled");
1016 using namespace test::jtx;
1017
1018 Account const alice{"alice"};
1019 Account const becky{"becky"};
1020 Account const carol{"carol"};
1021
1022 Env env{*this, testable_amendments() - featureCredentials};
1023 env.fund(XRP(100000), alice, becky, carol);
1024 env.close();
1025
1026 // alice sets the lsfDepositAuth flag on her account. This should
1027 // prevent becky from deleting her account while using alice as the
1028 // destination.
1029 env(fset(alice, asfDepositAuth));
1030 env.close();
1031
1032 // Close enough ledgers to be able to delete becky's account.
1033 incLgrSeqForAccDel(env, becky);
1034
1035 auto const acctDelFee{drops(env.current()->fees().increment)};
1036
1037 std::string const credIdx =
1038 "098B7F1B146470A1C5084DC7832C04A72939E3EBC58E68AB8B579BA072B0CE"
1039 "CB";
1040
1041 // and can't delete even with old DepositPreauth
1042 env(deposit::auth(alice, becky));
1043 env.close();
1044
1045 env(acctdelete(becky, alice),
1046 credentials::ids({credIdx}),
1047 fee(acctDelFee),
1048 ter(temDISABLED));
1049 env.close();
1050 }
1051 }
1052
1053 void
1055 {
1056 {
1057 testcase("Deleting Issuer deletes issued credentials");
1058
1059 using namespace test::jtx;
1060
1061 Account const alice{"alice"};
1062 Account const becky{"becky"};
1063 Account const carol{"carol"};
1064
1065 char const credType[] = "abcd";
1066
1067 Env env{*this};
1068 env.fund(XRP(100000), alice, becky, carol);
1069 env.close();
1070
1071 // carol issue credentials for becky
1072 env(credentials::create(becky, carol, credType));
1073 env.close();
1074 env(credentials::accept(becky, carol, credType));
1075 env.close();
1076
1077 // get credentials index
1078 auto const jv =
1079 credentials::ledgerEntry(env, becky, carol, credType);
1080 std::string const credIdx = jv[jss::result][jss::index].asString();
1081
1082 // Close enough ledgers to be able to delete carol's account.
1083 incLgrSeqForAccDel(env, carol);
1084
1085 auto const acctDelFee{drops(env.current()->fees().increment)};
1086 env(acctdelete(carol, alice), fee(acctDelFee));
1087 env.close();
1088
1089 { // check that credential object deleted too
1090 BEAST_EXPECT(!env.le(credIdx));
1091 auto const jv =
1092 credentials::ledgerEntry(env, becky, carol, credType);
1093 BEAST_EXPECT(
1094 jv.isObject() && jv.isMember(jss::result) &&
1095 jv[jss::result].isMember(jss::error) &&
1096 jv[jss::result][jss::error] == "entryNotFound");
1097 }
1098 }
1099
1100 {
1101 testcase("Deleting Subject deletes issued credentials");
1102
1103 using namespace test::jtx;
1104
1105 Account const alice{"alice"};
1106 Account const becky{"becky"};
1107 Account const carol{"carol"};
1108
1109 char const credType[] = "abcd";
1110
1111 Env env{*this};
1112 env.fund(XRP(100000), alice, becky, carol);
1113 env.close();
1114
1115 // carol issue credentials for becky
1116 env(credentials::create(becky, carol, credType));
1117 env.close();
1118 env(credentials::accept(becky, carol, credType));
1119 env.close();
1120
1121 // get credentials index
1122 auto const jv =
1123 credentials::ledgerEntry(env, becky, carol, credType);
1124 std::string const credIdx = jv[jss::result][jss::index].asString();
1125
1126 // Close enough ledgers to be able to delete carol's account.
1127 incLgrSeqForAccDel(env, becky);
1128
1129 auto const acctDelFee{drops(env.current()->fees().increment)};
1130 env(acctdelete(becky, alice), fee(acctDelFee));
1131 env.close();
1132
1133 { // check that credential object deleted too
1134 BEAST_EXPECT(!env.le(credIdx));
1135 auto const jv =
1136 credentials::ledgerEntry(env, becky, carol, credType);
1137 BEAST_EXPECT(
1138 jv.isObject() && jv.isMember(jss::result) &&
1139 jv[jss::result].isMember(jss::error) &&
1140 jv[jss::result][jss::error] == "entryNotFound");
1141 }
1142 }
1143 }
1144
1145 void
1160};
1161
1162BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2);
1163
1164} // namespace test
1165} // 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)