rippled
AccountDelete_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2019 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 <ripple/protocol/Feature.h>
21 #include <ripple/protocol/jss.h>
22 #include <test/jtx.h>
23 
24 namespace ripple {
25 namespace test {
26 
27 class AccountDelete_test : public beast::unit_test::suite
28 {
29 private:
32  {
33  return env.current()->seq();
34  }
35 
36  // Helper function that verifies the expected DeliveredAmount is present.
37  //
38  // NOTE: the function _infers_ the transaction to operate on by calling
39  // env.tx(), which returns the result from the most recent transaction.
40  void
42  {
43  // Get the hash for the most recent transaction.
44  std::string const txHash{
45  env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
46 
47  // Verify DeliveredAmount and delivered_amount metadata are correct.
48  // We can't use env.meta() here, because meta() doesn't include
49  // delivered_amount.
50  env.close();
51  Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
52 
53  // Expect there to be a DeliveredAmount field.
54  if (!BEAST_EXPECT(meta.isMember(sfDeliveredAmount.jsonName)))
55  return;
56 
57  // DeliveredAmount and delivered_amount should both be present and
58  // equal amount.
59  Json::Value const jsonExpect{amount.getJson(JsonOptions::none)};
60  BEAST_EXPECT(meta[sfDeliveredAmount.jsonName] == jsonExpect);
61  BEAST_EXPECT(meta[jss::delivered_amount] == jsonExpect);
62  }
63 
64  // Helper function to create a payment channel.
65  static Json::Value
67  jtx::Account const& account,
68  jtx::Account const& to,
69  STAmount const& amount,
70  NetClock::duration const& settleDelay,
71  NetClock::time_point const& cancelAfter,
72  PublicKey const& pk)
73  {
74  Json::Value jv;
75  jv[jss::TransactionType] = jss::PaymentChannelCreate;
76  jv[jss::Account] = account.human();
77  jv[jss::Destination] = to.human();
78  jv[jss::Amount] = amount.getJson(JsonOptions::none);
79  jv[sfSettleDelay.jsonName] = settleDelay.count();
80  jv[sfCancelAfter.jsonName] = cancelAfter.time_since_epoch().count() + 2;
81  jv[sfPublicKey.jsonName] = strHex(pk.slice());
82  return jv;
83  };
84 
85  // Close the ledger until the ledger sequence is large enough to close
86  // the account. If margin is specified, close the ledger so `margin`
87  // more closes are needed
88  void
90  jtx::Env& env,
91  jtx::Account const& acc,
92  std::uint32_t margin = 0)
93  {
94  int const delta = [&]() -> int {
95  if (env.seq(acc) + 255 > openLedgerSeq(env))
96  return env.seq(acc) - openLedgerSeq(env) + 255 - margin;
97  return 0;
98  }();
99  BEAST_EXPECT(margin == 0 || delta >= 0);
100  for (int i = 0; i < delta; ++i)
101  env.close();
102  BEAST_EXPECT(openLedgerSeq(env) == env.seq(acc) + 255 - margin);
103  }
104 
105 public:
106  void
108  {
109  using namespace jtx;
110 
111  testcase("Basics");
112 
113  Env env{*this};
114  Account const alice("alice");
115  Account const becky("becky");
116  Account const carol("carol");
117  Account const gw("gw");
118 
119  env.fund(XRP(10000), alice, becky, carol, gw);
120  env.close();
121 
122  // Alice can't delete her account and then give herself the XRP.
123  env(acctdelete(alice, alice), ter(temDST_IS_SRC));
124 
125  // alice can't delete her account with a negative fee.
126  env(acctdelete(alice, becky), fee(drops(-1)), ter(temBAD_FEE));
127 
128  // Invalid flags.
129  env(acctdelete(alice, becky),
132 
133  // Account deletion has a high fee. Make sure the fee requirement
134  // behaves as we expect.
135  auto const acctDelFee{drops(env.current()->fees().increment)};
136  env(acctdelete(alice, becky), ter(telINSUF_FEE_P));
137 
138  // Try a fee one drop less than the required amount.
139  env(acctdelete(alice, becky),
140  fee(acctDelFee - drops(1)),
142 
143  // alice's account is created too recently to be deleted.
144  env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
145 
146  // Give becky a trustline. She is no longer deletable.
147  env(trust(becky, gw["USD"](1000)));
148  env.close();
149 
150  // Give carol a deposit preauthorization, an offer, a ticket,
151  // and a signer list. Even with all that she's still deletable.
152  env(deposit::auth(carol, becky));
153  std::uint32_t const carolOfferSeq{env.seq(carol)};
154  env(offer(carol, gw["USD"](51), XRP(51)));
155  std::uint32_t const carolTicketSeq{env.seq(carol) + 1};
156  env(ticket::create(carol, 1));
157  env(signers(carol, 1, {{alice, 1}, {becky, 1}}));
158 
159  // Deleting should fail with TOO_SOON, which is a relatively
160  // cheap check compared to validating the contents of her directory.
161  env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
162 
163  // Close enough ledgers to almost be able to delete alice's account.
164  incLgrSeqForAccDel(env, alice, 1);
165 
166  // alice's account is still created too recently to be deleted.
167  env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
168 
169  // The most recent delete attempt advanced alice's sequence. So
170  // close two ledgers and her account should be deletable.
171  env.close();
172  env.close();
173 
174  {
175  auto const aliceOldBalance{env.balance(alice)};
176  auto const beckyOldBalance{env.balance(becky)};
177 
178  // Verify that alice's account exists but she has no directory.
179  BEAST_EXPECT(env.closed()->exists(keylet::account(alice.id())));
180  BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
181 
182  env(acctdelete(alice, becky), fee(acctDelFee));
183  verifyDeliveredAmount(env, aliceOldBalance - acctDelFee);
184  env.close();
185 
186  // Verify that alice's account and directory are actually gone.
187  BEAST_EXPECT(!env.closed()->exists(keylet::account(alice.id())));
188  BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
189 
190  // Verify that alice's XRP, minus the fee, was transferred to becky.
191  BEAST_EXPECT(
192  env.balance(becky) ==
193  aliceOldBalance + beckyOldBalance - acctDelFee);
194  }
195 
196  // Attempt to delete becky's account but get stopped by the trust line.
197  env(acctdelete(becky, carol), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
198  env.close();
199 
200  // Verify that becky's account is still there by giving her a regular
201  // key. This has the side effect of setting the lsfPasswordSpent bit
202  // on her account root.
203  Account const beck("beck");
204  env(regkey(becky, beck), fee(drops(0)));
205  env.close();
206 
207  // Show that the lsfPasswordSpent bit is set by attempting to change
208  // becky's regular key for free again. That fails.
209  Account const reb("reb");
210  env(regkey(becky, reb), sig(becky), fee(drops(0)), ter(telINSUF_FEE_P));
211 
212  // Close enough ledgers that becky's failing regkey transaction is
213  // no longer retried.
214  for (int i = 0; i < 8; ++i)
215  env.close();
216 
217  {
218  auto const beckyOldBalance{env.balance(becky)};
219  auto const carolOldBalance{env.balance(carol)};
220 
221  // Verify that Carol's account, directory, deposit
222  // preauthorization, offer, ticket, and signer list exist.
223  BEAST_EXPECT(env.closed()->exists(keylet::account(carol.id())));
224  BEAST_EXPECT(env.closed()->exists(keylet::ownerDir(carol.id())));
225  BEAST_EXPECT(env.closed()->exists(
226  keylet::depositPreauth(carol.id(), becky.id())));
227  BEAST_EXPECT(
228  env.closed()->exists(keylet::offer(carol.id(), carolOfferSeq)));
229  BEAST_EXPECT(env.closed()->exists(
230  keylet::ticket(carol.id(), carolTicketSeq)));
231  BEAST_EXPECT(env.closed()->exists(keylet::signers(carol.id())));
232 
233  // Delete carol's account even with stuff in her directory. Show
234  // that multisigning for the delete does not increase carol's fee.
235  env(acctdelete(carol, becky), fee(acctDelFee), msig(alice));
236  verifyDeliveredAmount(env, carolOldBalance - acctDelFee);
237  env.close();
238 
239  // Verify that Carol's account, directory, and other stuff are gone.
240  BEAST_EXPECT(!env.closed()->exists(keylet::account(carol.id())));
241  BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(carol.id())));
242  BEAST_EXPECT(!env.closed()->exists(
243  keylet::depositPreauth(carol.id(), becky.id())));
244  BEAST_EXPECT(!env.closed()->exists(
245  keylet::offer(carol.id(), carolOfferSeq)));
246  BEAST_EXPECT(!env.closed()->exists(
247  keylet::ticket(carol.id(), carolTicketSeq)));
248  BEAST_EXPECT(!env.closed()->exists(keylet::signers(carol.id())));
249 
250  // Verify that Carol's XRP, minus the fee, was transferred to becky.
251  BEAST_EXPECT(
252  env.balance(becky) ==
253  carolOldBalance + beckyOldBalance - acctDelFee);
254 
255  // Since becky received an influx of XRP, her lsfPasswordSpent bit
256  // is cleared and she can change her regular key for free again.
257  env(regkey(becky, reb), sig(becky), fee(drops(0)));
258  }
259  }
260 
261  void
263  {
264  // The code that deletes consecutive directory entries uses a
265  // peculiarity of the implementation. Make sure that peculiarity
266  // behaves as expected across owner directory pages.
267  using namespace jtx;
268 
269  testcase("Directories");
270 
271  Env env{*this};
272  Account const alice("alice");
273  Account const gw("gw");
274 
275  env.fund(XRP(10000), alice, gw);
276  env.close();
277 
278  // Alice creates enough offers to require two owner directories.
279  for (int i{0}; i < 45; ++i)
280  {
281  env(offer(alice, gw["USD"](1), XRP(1)));
282  env.close();
283  }
284  env.require(offers(alice, 45));
285 
286  // Close enough ledgers to be able to delete alice's account.
287  incLgrSeqForAccDel(env, alice);
288 
289  // Verify that both directory nodes exist.
290  Keylet const aliceRootKey{keylet::ownerDir(alice.id())};
291  Keylet const alicePageKey{keylet::page(aliceRootKey, 1)};
292  BEAST_EXPECT(env.closed()->exists(aliceRootKey));
293  BEAST_EXPECT(env.closed()->exists(alicePageKey));
294 
295  // Delete alice's account.
296  auto const acctDelFee{drops(env.current()->fees().increment)};
297  auto const aliceBalance{env.balance(alice)};
298  env(acctdelete(alice, gw), fee(acctDelFee));
299  verifyDeliveredAmount(env, aliceBalance - acctDelFee);
300  env.close();
301 
302  // Both of alice's directory nodes should be gone.
303  BEAST_EXPECT(!env.closed()->exists(aliceRootKey));
304  BEAST_EXPECT(!env.closed()->exists(alicePageKey));
305  }
306 
307  void
309  {
310  using namespace jtx;
311 
312  testcase("Owned types");
313 
314  // We want to test both...
315  // o Old-style PayChannels without a recipient backlink as well as
316  // o New-styled PayChannels with the backlink.
317  // So we start the test using old-style PayChannels. Then we pass
318  // the amendment to get new-style PayChannels.
320  Account const alice("alice");
321  Account const becky("becky");
322  Account const gw("gw");
323 
324  env.fund(XRP(100000), alice, becky, gw);
325  env.close();
326 
327  // Give alice and becky a bunch of offers that we have to search
328  // through before we figure out that there's a non-deletable
329  // entry in their directory.
330  for (int i{0}; i < 200; ++i)
331  {
332  env(offer(alice, gw["USD"](1), XRP(1)));
333  env(offer(becky, gw["USD"](1), XRP(1)));
334  env.close();
335  }
336  env.require(offers(alice, 200));
337  env.require(offers(becky, 200));
338 
339  // Close enough ledgers to be able to delete alice's and becky's
340  // accounts.
341  incLgrSeqForAccDel(env, alice);
342  incLgrSeqForAccDel(env, becky);
343 
344  // alice writes a check to becky. Until that check is cashed or
345  // canceled it will prevent alice's and becky's accounts from being
346  // deleted.
347  uint256 const checkId = keylet::check(alice, env.seq(alice)).key;
348  env(check::create(alice, becky, XRP(1)));
349  env.close();
350 
351  auto const acctDelFee{drops(env.current()->fees().increment)};
352  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
353  env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
354  env.close();
355 
356  // Cancel the check, but add an escrow. Again, with the escrow
357  // on board, alice and becky should not be able to delete their
358  // accounts.
359  env(check::cancel(becky, checkId));
360  env.close();
361 
362  // Lambda to create an escrow.
363  auto escrowCreate = [](jtx::Account const& account,
364  jtx::Account const& to,
365  STAmount const& amount,
366  NetClock::time_point const& cancelAfter) {
367  Json::Value jv;
368  jv[jss::TransactionType] = jss::EscrowCreate;
369  jv[jss::Flags] = tfUniversal;
370  jv[jss::Account] = account.human();
371  jv[jss::Destination] = to.human();
372  jv[jss::Amount] = amount.getJson(JsonOptions::none);
373  jv[sfFinishAfter.jsonName] =
374  cancelAfter.time_since_epoch().count() + 1;
375  jv[sfCancelAfter.jsonName] =
376  cancelAfter.time_since_epoch().count() + 2;
377  return jv;
378  };
379 
380  using namespace std::chrono_literals;
381  std::uint32_t const escrowSeq{env.seq(alice)};
382  env(escrowCreate(alice, becky, XRP(333), env.now() + 2s));
383  env.close();
384 
385  // alice and becky should be unable to delete their accounts because
386  // of the escrow.
387  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
388  env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
389  env.close();
390 
391  // Now cancel the escrow, but create a payment channel between
392  // alice and becky.
393 
394  // Lambda to cancel an escrow.
395  auto escrowCancel =
396  [](Account const& account, Account const& from, std::uint32_t seq) {
397  Json::Value jv;
398  jv[jss::TransactionType] = jss::EscrowCancel;
399  jv[jss::Flags] = tfUniversal;
400  jv[jss::Account] = account.human();
401  jv[sfOwner.jsonName] = from.human();
403  return jv;
404  };
405  env(escrowCancel(becky, alice, escrowSeq));
406  env.close();
407 
408  Keylet const alicePayChanKey{
409  keylet::payChan(alice, becky, env.seq(alice))};
410 
411  env(payChanCreate(
412  alice, becky, XRP(57), 4s, env.now() + 2s, alice.pk()));
413  env.close();
414 
415  // An old-style PayChannel does not add a back link from the
416  // destination. So with the PayChannel in place becky should be
417  // able to delete her account, but alice should not.
418  auto const beckyBalance{env.balance(becky)};
419  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
420  env(acctdelete(becky, gw), fee(acctDelFee));
421  verifyDeliveredAmount(env, beckyBalance - acctDelFee);
422  env.close();
423 
424  // Alice cancels her PayChannel which will leave her with only offers
425  // in her directory.
426 
427  // Lambda to close a PayChannel.
428  auto payChanClose = [](jtx::Account const& account,
429  Keylet const& payChanKeylet,
430  PublicKey const& pk) {
431  Json::Value jv;
432  jv[jss::TransactionType] = jss::PaymentChannelClaim;
433  jv[jss::Flags] = tfClose;
434  jv[jss::Account] = account.human();
435  jv[sfChannel.jsonName] = to_string(payChanKeylet.key);
436  jv[sfPublicKey.jsonName] = strHex(pk.slice());
437  return jv;
438  };
439  env(payChanClose(alice, alicePayChanKey, alice.pk()));
440  env.close();
441 
442  // Now enable the amendment so PayChannels add a backlink from the
443  // destination.
444  env.enableFeature(fixPayChanRecipientOwnerDir);
445  env.close();
446 
447  // gw creates a PayChannel with alice as the destination. With the
448  // amendment passed this should prevent alice from deleting her
449  // account.
450  Keylet const gwPayChanKey{keylet::payChan(gw, alice, env.seq(gw))};
451 
452  env(payChanCreate(gw, alice, XRP(68), 4s, env.now() + 2s, alice.pk()));
453  env.close();
454 
455  // alice can't delete her account because of the PayChannel.
456  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
457  env.close();
458 
459  // alice closes the PayChannel which should (finally) allow her to
460  // delete her account.
461  env(payChanClose(alice, gwPayChanKey, alice.pk()));
462  env.close();
463 
464  // Now alice can successfully delete her account.
465  auto const aliceBalance{env.balance(alice)};
466  env(acctdelete(alice, gw), fee(acctDelFee));
467  verifyDeliveredAmount(env, aliceBalance - acctDelFee);
468  env.close();
469  }
470 
471  void
473  {
474  // Create an account with an old-style PayChannel. Delete the
475  // destination of the PayChannel then resurrect the destination.
476  // The PayChannel should still work.
477  using namespace jtx;
478 
479  testcase("Resurrection");
480 
481  // We need an old-style PayChannel that doesn't provide a backlink
482  // from the destination. So don't enable the amendment with that fix.
484  Account const alice("alice");
485  Account const becky("becky");
486 
487  env.fund(XRP(10000), alice, becky);
488  env.close();
489 
490  // Verify that becky's account root is present.
491  Keylet const beckyAcctKey{keylet::account(becky.id())};
492  BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
493 
494  using namespace std::chrono_literals;
495  Keylet const payChanKey{keylet::payChan(alice, becky, env.seq(alice))};
496  auto const payChanXRP = XRP(37);
497 
498  env(payChanCreate(
499  alice, becky, payChanXRP, 4s, env.now() + 1h, alice.pk()));
500  env.close();
501  BEAST_EXPECT(env.closed()->exists(payChanKey));
502 
503  // Close enough ledgers to be able to delete becky's account.
504  incLgrSeqForAccDel(env, becky);
505 
506  auto const beckyPreDelBalance{env.balance(becky)};
507 
508  auto const acctDelFee{drops(env.current()->fees().increment)};
509  env(acctdelete(becky, alice), fee(acctDelFee));
510  verifyDeliveredAmount(env, beckyPreDelBalance - acctDelFee);
511  env.close();
512 
513  // Verify that becky's account root is gone.
514  BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
515 
516  // All it takes is a large enough XRP payment to resurrect
517  // becky's account. Try too small a payment.
518  env(pay(alice, becky, XRP(19)), ter(tecNO_DST_INSUF_XRP));
519  env.close();
520 
521  // Actually resurrect becky's account.
522  env(pay(alice, becky, XRP(20)));
523  env.close();
524 
525  // becky's account root should be back.
526  BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
527  BEAST_EXPECT(env.balance(becky) == XRP(20));
528 
529  // becky's resurrected account can be the destination of alice's
530  // PayChannel.
531  auto payChanClaim = [&]() {
532  Json::Value jv;
533  jv[jss::TransactionType] = jss::PaymentChannelClaim;
534  jv[jss::Flags] = tfUniversal;
535  jv[jss::Account] = alice.human();
536  jv[sfChannel.jsonName] = to_string(payChanKey.key);
537  jv[sfBalance.jsonName] =
538  payChanXRP.value().getJson(JsonOptions::none);
539  return jv;
540  };
541  env(payChanClaim());
542  env.close();
543 
544  BEAST_EXPECT(env.balance(becky) == XRP(20) + payChanXRP);
545  }
546 
547  void
549  {
550  // Start with the featureDeletableAccounts amendment disabled.
551  // Then enable the amendment and delete an account.
552  using namespace jtx;
553 
554  testcase("Amendment enable");
555 
557  Account const alice("alice");
558  Account const becky("becky");
559 
560  env.fund(XRP(10000), alice, becky);
561  env.close();
562 
563  // Close enough ledgers to be able to delete alice's account.
564  incLgrSeqForAccDel(env, alice);
565 
566  // Verify that alice's account root is present.
567  Keylet const aliceAcctKey{keylet::account(alice.id())};
568  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
569 
570  auto const alicePreDelBal{env.balance(alice)};
571  auto const beckyPreDelBal{env.balance(becky)};
572 
573  auto const acctDelFee{drops(env.current()->fees().increment)};
574  env(acctdelete(alice, becky), fee(acctDelFee), ter(temDISABLED));
575  env.close();
576 
577  // Verify that alice's account root is still present and alice and
578  // becky both have their XRP.
579  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
580  BEAST_EXPECT(env.balance(alice) == alicePreDelBal);
581  BEAST_EXPECT(env.balance(becky) == beckyPreDelBal);
582 
583  // When the amendment is enabled the previous transaction is
584  // retried into the new open ledger and succeeds.
585  env.enableFeature(featureDeletableAccounts);
586  env.close();
587 
588  // alice's account is still in the most recently closed ledger.
589  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
590 
591  // Verify that alice's account root is gone from the current ledger
592  // and becky has alice's XRP.
593  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
594  BEAST_EXPECT(
595  env.balance(becky) == alicePreDelBal + beckyPreDelBal - acctDelFee);
596 
597  env.close();
598  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
599  }
600 
601  void
603  {
604  // Put enough offers in an account that we refuse to delete the account.
605  using namespace jtx;
606 
607  testcase("Too many offers");
608 
609  Env env{*this};
610  Account const alice("alice");
611  Account const gw("gw");
612 
613  // Fund alice well so she can afford the reserve on the offers.
614  env.fund(XRP(10000000), alice, gw);
615  env.close();
616 
617  // To increase the number of Books affected, change the currency of
618  // each offer.
619  std::string currency{"AAA"};
620 
621  // Alice creates 1001 offers. This is one greater than the number of
622  // directory entries an AccountDelete will remove.
623  std::uint32_t const offerSeq0{env.seq(alice)};
624  constexpr int offerCount{1001};
625  for (int i{0}; i < offerCount; ++i)
626  {
627  env(offer(alice, gw[currency](1), XRP(1)));
628  env.close();
629 
630  // Increment to next currency.
631  ++currency[0];
632  if (currency[0] > 'Z')
633  {
634  currency[0] = 'A';
635  ++currency[1];
636  }
637  if (currency[1] > 'Z')
638  {
639  currency[1] = 'A';
640  ++currency[2];
641  }
642  if (currency[2] > 'Z')
643  {
644  currency[0] = 'A';
645  currency[1] = 'A';
646  currency[2] = 'A';
647  }
648  }
649 
650  // Close enough ledgers to be able to delete alice's account.
651  incLgrSeqForAccDel(env, alice);
652 
653  // Verify the existence of the expected ledger entries.
654  Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
655  {
656  std::shared_ptr<ReadView const> closed{env.closed()};
657  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
658  BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
659 
660  // alice's directory nodes.
661  for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
662  BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
663 
664  // alice's offers.
665  for (std::uint32_t i{0}; i < offerCount; ++i)
666  BEAST_EXPECT(
667  closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
668  }
669 
670  // Delete alice's account. Should fail because she has too many
671  // offers in her directory.
672  auto const acctDelFee{drops(env.current()->fees().increment)};
673 
674  env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
675 
676  // Cancel one of alice's offers. Then the account delete can succeed.
677  env.require(offers(alice, offerCount));
678  env(offer_cancel(alice, offerSeq0));
679  env.close();
680  env.require(offers(alice, offerCount - 1));
681 
682  // alice successfully deletes her account.
683  auto const alicePreDelBal{env.balance(alice)};
684  env(acctdelete(alice, gw), fee(acctDelFee));
685  verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
686  env.close();
687 
688  // Verify that alice's account root is gone as well as her directory
689  // nodes and all of her offers.
690  {
691  std::shared_ptr<ReadView const> closed{env.closed()};
692  BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
693  BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
694 
695  // alice's former directory nodes.
696  for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
697  BEAST_EXPECT(
698  !closed->exists(keylet::page(aliceOwnerDirKey, i)));
699 
700  // alice's former offers.
701  for (std::uint32_t i{0}; i < offerCount; ++i)
702  BEAST_EXPECT(
703  !closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
704  }
705  }
706 
707  void
709  {
710  // Show that a trust line that is implicitly created by offer crossing
711  // prevents an account from being deleted.
712  using namespace jtx;
713 
714  testcase("Implicitly created trust line");
715 
716  Env env{*this};
717  Account const alice{"alice"};
718  Account const gw{"gw"};
719  auto const BUX{gw["BUX"]};
720 
721  env.fund(XRP(10000), alice, gw);
722  env.close();
723 
724  // alice creates an offer that, if crossed, will implicitly create
725  // a trust line.
726  env(offer(alice, BUX(30), XRP(30)));
727  env.close();
728 
729  // gw crosses alice's offer. alice should end up with BUX(30).
730  env(offer(gw, XRP(30), BUX(30)));
731  env.close();
732  env.require(balance(alice, BUX(30)));
733 
734  // Close enough ledgers to be able to delete alice's account.
735  incLgrSeqForAccDel(env, alice);
736 
737  // alice and gw can't delete their accounts because of the implicitly
738  // created trust line.
739  auto const acctDelFee{drops(env.current()->fees().increment)};
740  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
741  env.close();
742 
743  env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
744  env.close();
745  {
746  std::shared_ptr<ReadView const> closed{env.closed()};
747  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
748  BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
749  }
750  }
751 
752  void
754  {
755  // See what happens when an account with a balance less than the
756  // incremental reserve tries to delete itself.
757  using namespace jtx;
758 
759  testcase("Balance too small for fee");
760 
761  Env env{*this};
762  Account const alice("alice");
763 
764  // Note that the fee structure for unit tests does not match the fees
765  // on the production network (October 2019). Unit tests have a base
766  // reserve of 200 XRP.
767  env.fund(env.current()->fees().accountReserve(0), noripple(alice));
768  env.close();
769 
770  // Burn a chunk of alice's funds so she only has 1 XRP remaining in
771  // her account.
772  env(noop(alice), fee(env.balance(alice) - XRP(1)));
773  env.close();
774 
775  auto const acctDelFee{drops(env.current()->fees().increment)};
776  BEAST_EXPECT(acctDelFee > env.balance(alice));
777 
778  // alice attempts to delete her account even though she can't pay
779  // the full fee. She specifies a fee that is larger than her balance.
780  //
781  // The balance of env.master should not change.
782  auto const masterBalance{env.balance(env.master)};
783  env(acctdelete(alice, env.master),
784  fee(acctDelFee),
786  env.close();
787  {
788  std::shared_ptr<ReadView const> const closed{env.closed()};
789  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
790  BEAST_EXPECT(env.balance(env.master) == masterBalance);
791  }
792 
793  // alice again attempts to delete her account. This time she specifies
794  // her current balance in XRP. Again the transaction fails.
795  BEAST_EXPECT(env.balance(alice) == XRP(1));
796  env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
797  env.close();
798  {
799  std::shared_ptr<ReadView const> closed{env.closed()};
800  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
801  BEAST_EXPECT(env.balance(env.master) == masterBalance);
802  }
803  }
804 
805  void
807  {
808  testcase("With Tickets");
809 
810  using namespace test::jtx;
811 
812  Account const alice{"alice"};
813  Account const bob{"bob"};
814 
815  Env env{*this};
816  env.fund(XRP(100000), alice, bob);
817  env.close();
818 
819  // bob grabs as many tickets as he is allowed to have.
820  std::uint32_t const ticketSeq{env.seq(bob) + 1};
821  env(ticket::create(bob, 250));
822  env.close();
823  env.require(owners(bob, 250));
824 
825  {
826  std::shared_ptr<ReadView const> closed{env.closed()};
827  BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
828  for (std::uint32_t i = 0; i < 250; ++i)
829  {
830  BEAST_EXPECT(
831  closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
832  }
833  }
834 
835  // Close enough ledgers to be able to delete bob's account.
836  incLgrSeqForAccDel(env, bob);
837 
838  // bob deletes his account using a ticket. bob's account and all
839  // of his tickets should be removed from the ledger.
840  auto const acctDelFee{drops(env.current()->fees().increment)};
841  auto const bobOldBalance{env.balance(bob)};
842  env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
843  verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
844  env.close();
845  {
846  std::shared_ptr<ReadView const> closed{env.closed()};
847  BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
848  for (std::uint32_t i = 0; i < 250; ++i)
849  {
850  BEAST_EXPECT(
851  !closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
852  }
853  }
854  }
855 
856  void
858  {
859  testcase("Destination Constraints");
860 
861  using namespace test::jtx;
862 
863  Account const alice{"alice"};
864  Account const becky{"becky"};
865  Account const carol{"carol"};
866  Account const daria{"daria"};
867 
868  Env env{*this};
869  env.fund(XRP(100000), alice, becky, carol);
870  env.close();
871 
872  // alice sets the lsfDepositAuth flag on her account. This should
873  // prevent becky from deleting her account while using alice as the
874  // destination.
875  env(fset(alice, asfDepositAuth));
876 
877  // carol requires a destination tag.
878  env(fset(carol, asfRequireDest));
879  env.close();
880 
881  // Close enough ledgers to be able to delete becky's account.
882  incLgrSeqForAccDel(env, becky);
883 
884  // becky attempts to delete her account using daria as the destination.
885  // Since daria is not in the ledger the delete attempt fails.
886  auto const acctDelFee{drops(env.current()->fees().increment)};
887  env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
888  env.close();
889 
890  // becky attempts to delete her account, but carol requires a
891  // destination tag which becky has omitted.
892  env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
893  env.close();
894 
895  // becky attempts to delete her account, but alice won't take her XRP,
896  // so the delete is blocked.
897  env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
898  env.close();
899 
900  // alice preauthorizes deposits from becky. Now becky can delete her
901  // account and forward the leftovers to alice.
902  env(deposit::auth(alice, becky));
903  env.close();
904 
905  auto const beckyOldBalance{env.balance(becky)};
906  env(acctdelete(becky, alice), fee(acctDelFee));
907  verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
908  env.close();
909  }
910 
911  void
912  run() override
913  {
914  testBasics();
915  testDirectories();
916  testOwnedTypes();
922  testWithTickets();
923  testDest();
924  }
925 };
926 
927 BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2);
928 
929 } // namespace test
930 } // namespace ripple
ripple::sfOfferSequence
const SF_UINT32 sfOfferSequence
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:300
ripple::test::jtx::noop
Json::Value noop(Account const &account)
The null transaction.
Definition: noop.h:31
ripple::test::AccountDelete_test::testBasics
void testBasics()
Definition: AccountDelete_test.cpp:107
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::Keylet
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:38
std::string
STL class.
std::shared_ptr
STL class.
ripple::test::jtx::drops
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Definition: amount.h:241
ripple::terINSUF_FEE_B
@ terINSUF_FEE_B
Definition: TER.h:192
ripple::test::jtx::Env::tx
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:379
ripple::test::jtx::ter
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:33
ripple::test::jtx::owners
Match the number of items in the account's owner directory.
Definition: owners.h:69
ripple::test::AccountDelete_test::testDest
void testDest()
Definition: AccountDelete_test.cpp:857
ripple::test::AccountDelete_test::testImplicitlyCreatedTrustline
void testImplicitlyCreatedTrustline()
Definition: AccountDelete_test.cpp:708
ripple::sfOwner
const SF_ACCOUNT sfOwner
ripple::tfClose
const std::uint32_t tfClose
Definition: TxFlags.h:106
ripple::test::jtx::balance
A balance matches.
Definition: balance.h:38
ripple::test::jtx::trust
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:30
ripple::test::AccountDelete_test::testWithTickets
void testWithTickets()
Definition: AccountDelete_test.cpp:806
ripple::STAmount::getJson
Json::Value getJson(JsonOptions) const override
Definition: STAmount.cpp:596
std::chrono::duration
ripple::test::jtx::offer_cancel
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition: offer.cpp:45
ripple::tecDST_TAG_NEEDED
@ tecDST_TAG_NEEDED
Definition: TER.h:271
ripple::test::AccountDelete_test::testAmendmentEnable
void testAmendmentEnable()
Definition: AccountDelete_test.cpp:548
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:219
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:109
ripple::test::AccountDelete_test::testDirectories
void testDirectories()
Definition: AccountDelete_test.cpp:262
ripple::test::AccountDelete_test::verifyDeliveredAmount
void verifyDeliveredAmount(jtx::Env &env, STAmount const &amount)
Definition: AccountDelete_test.cpp:41
ripple::PublicKey::slice
Slice slice() const noexcept
Definition: PublicKey.h:123
ripple::asfDepositAuth
const std::uint32_t asfDepositAuth
Definition: TxFlags.h:73
ripple::keylet::ticket
static const ticket_t ticket
Definition: Indexes.h:167
ripple::test::AccountDelete_test::testResurrection
void testResurrection()
Definition: AccountDelete_test.cpp:472
ripple::test::AccountDelete_test::openLedgerSeq
std::uint32_t openLedgerSeq(jtx::Env &env)
Definition: AccountDelete_test.cpp:31
ripple::test::AccountDelete_test::testTooManyOffers
void testTooManyOffers()
Definition: AccountDelete_test.cpp:602
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:133
ripple::test::jtx::msig
Set a multisignature on a JTx.
Definition: multisign.h:58
ripple::temDST_IS_SRC
@ temDST_IS_SRC
Definition: TER.h:103
ripple::test::BEAST_DEFINE_TESTSUITE_PRIO
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2)
ripple::featureDeletableAccounts
const uint256 featureDeletableAccounts
Definition: Feature.cpp:185
ripple::test::jtx::Account::id
AccountID id() const
Returns the Account ID.
Definition: Account.h:102
ripple::tecNO_DST_INSUF_XRP
@ tecNO_DST_INSUF_XRP
Definition: TER.h:253
ripple::Keylet::key
uint256 key
Definition: Keylet.h:40
ripple::base_uint< 256 >
ripple::test::jtx::ticket::use
Set a ticket sequence on a JTx.
Definition: ticket.h:47
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:106
std::chrono::time_point::time_since_epoch
T time_since_epoch(T... args)
ripple::test::AccountDelete_test::testOwnedTypes
void testOwnedTypes()
Definition: AccountDelete_test.cpp:308
ripple::sfDeliveredAmount
const SF_AMOUNT sfDeliveredAmount
ripple::sfSettleDelay
const SF_UINT32 sfSettleDelay
ripple::test::AccountDelete_test::testBalanceTooSmallForFee
void testBalanceTooSmallForFee()
Definition: AccountDelete_test.cpp:753
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:130
ripple::JsonOptions::none
@ none
ripple::test::AccountDelete_test::incLgrSeqForAccDel
void incLgrSeqForAccDel(jtx::Env &env, jtx::Account const &acc, std::uint32_t margin=0)
Definition: AccountDelete_test.cpp:89
ripple::keylet::page
Keylet page(uint256 const &key, std::uint64_t index) noexcept
A page in a directory.
Definition: Indexes.cpp:306
ripple::test::jtx::fset
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:28
ripple::telINSUF_FEE_P
@ telINSUF_FEE_P
Definition: TER.h:56
ripple::test::jtx::txflags
Set the flags on a JTx.
Definition: txflags.h:30
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::STAmount
Definition: STAmount.h:43
std::chrono::time_point
ripple::fixPayChanRecipientOwnerDir
const uint256 fixPayChanRecipientOwnerDir
Definition: Feature.cpp:184
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
ripple::test::jtx::supported_amendments
FeatureBitset supported_amendments()
Definition: Env.h:70
std::uint32_t
ripple::test::jtx::sig
Set the regular signature on a JTx.
Definition: sig.h:34
ripple::tfImmediateOrCancel
const std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:77
ripple::test::jtx::Account::master
static const Account master
The master account.
Definition: Account.h:47
ripple::test::jtx::Env::seq
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:204
ripple::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:87
ripple::tfUniversal
const std::uint32_t tfUniversal
Definition: TxFlags.h:49
ripple::sfChannel
const SF_HASH256 sfChannel
ripple::asfRequireDest
const std::uint32_t asfRequireDest
Definition: TxFlags.h:65
ripple::test::jtx::fee
Set the fee on a JTx.
Definition: fee.h:35
ripple::tecTOO_SOON
@ tecTOO_SOON
Definition: TER.h:280
ripple::test::jtx::seq
Set the sequence number on a JTx.
Definition: seq.h:33
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::test::AccountDelete_test::run
void run() override
Definition: AccountDelete_test.cpp:912
ripple::test::AccountDelete_test
Definition: AccountDelete_test.cpp:27
ripple::test::jtx::noripple
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
Definition: Env.h:64
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
ripple::keylet::payChan
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:321
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:109
ripple::test::jtx::regkey
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:28
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:225
ripple::tefTOO_BIG
@ tefTOO_BIG
Definition: TER.h:162
ripple::tecHAS_OBLIGATIONS
@ tecHAS_OBLIGATIONS
Definition: TER.h:279
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:267
ripple::sfBalance
const SF_AMOUNT sfBalance
ripple::test::jtx::acctdelete
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
Definition: acctdelete.cpp:29
std::chrono::duration::count
T count(T... args)
ripple::sfCancelAfter
const SF_UINT32 sfCancelAfter
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:38
ripple::sfFinishAfter
const SF_UINT32 sfFinishAfter
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:45
ripple::test::AccountDelete_test::payChanCreate
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)
Definition: AccountDelete_test.cpp:66
ripple::keylet::signers
static Keylet signers(AccountID const &account, std::uint32_t page) noexcept
Definition: Indexes.cpp:265
ripple::keylet::check
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:278
ripple::keylet::depositPreauth
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:284
ripple::sfPublicKey
const SF_VL sfPublicKey
ripple::test::jtx::Account::pk
PublicKey const & pk() const
Return the public key.
Definition: Account.h:85
ripple::test::jtx::Env::current
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:299
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:115
ripple::tecNO_DST
@ tecNO_DST
Definition: TER.h:252
ripple::test::jtx::Env::rpc
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:683
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::jtx::owner_count
Definition: owners.h:49