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,
519  becky,
520  drops(env.current()->fees().accountReserve(0)) - XRP(1)),
522  env.close();
523 
524  // Actually resurrect becky's account.
525  env(pay(alice, becky, XRP(10)));
526  env.close();
527 
528  // becky's account root should be back.
529  BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
530  BEAST_EXPECT(env.balance(becky) == XRP(10));
531 
532  // becky's resurrected account can be the destination of alice's
533  // PayChannel.
534  auto payChanClaim = [&]() {
535  Json::Value jv;
536  jv[jss::TransactionType] = jss::PaymentChannelClaim;
537  jv[jss::Flags] = tfUniversal;
538  jv[jss::Account] = alice.human();
539  jv[sfChannel.jsonName] = to_string(payChanKey.key);
540  jv[sfBalance.jsonName] =
541  payChanXRP.value().getJson(JsonOptions::none);
542  return jv;
543  };
544  env(payChanClaim());
545  env.close();
546 
547  BEAST_EXPECT(env.balance(becky) == XRP(10) + payChanXRP);
548  }
549 
550  void
552  {
553  // Start with the featureDeletableAccounts amendment disabled.
554  // Then enable the amendment and delete an account.
555  using namespace jtx;
556 
557  testcase("Amendment enable");
558 
560  Account const alice("alice");
561  Account const becky("becky");
562 
563  env.fund(XRP(10000), alice, becky);
564  env.close();
565 
566  // Close enough ledgers to be able to delete alice's account.
567  incLgrSeqForAccDel(env, alice);
568 
569  // Verify that alice's account root is present.
570  Keylet const aliceAcctKey{keylet::account(alice.id())};
571  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
572 
573  auto const alicePreDelBal{env.balance(alice)};
574  auto const beckyPreDelBal{env.balance(becky)};
575 
576  auto const acctDelFee{drops(env.current()->fees().increment)};
577  env(acctdelete(alice, becky), fee(acctDelFee), ter(temDISABLED));
578  env.close();
579 
580  // Verify that alice's account root is still present and alice and
581  // becky both have their XRP.
582  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
583  BEAST_EXPECT(env.balance(alice) == alicePreDelBal);
584  BEAST_EXPECT(env.balance(becky) == beckyPreDelBal);
585 
586  // When the amendment is enabled the previous transaction is
587  // retried into the new open ledger and succeeds.
588  env.enableFeature(featureDeletableAccounts);
589  env.close();
590 
591  // alice's account is still in the most recently closed ledger.
592  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
593 
594  // Verify that alice's account root is gone from the current ledger
595  // and becky has alice's XRP.
596  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
597  BEAST_EXPECT(
598  env.balance(becky) == alicePreDelBal + beckyPreDelBal - acctDelFee);
599 
600  env.close();
601  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
602  }
603 
604  void
606  {
607  // Put enough offers in an account that we refuse to delete the account.
608  using namespace jtx;
609 
610  testcase("Too many offers");
611 
612  Env env{*this};
613  Account const alice("alice");
614  Account const gw("gw");
615 
616  // Fund alice well so she can afford the reserve on the offers.
617  env.fund(XRP(10000000), alice, gw);
618  env.close();
619 
620  // To increase the number of Books affected, change the currency of
621  // each offer.
622  std::string currency{"AAA"};
623 
624  // Alice creates 1001 offers. This is one greater than the number of
625  // directory entries an AccountDelete will remove.
626  std::uint32_t const offerSeq0{env.seq(alice)};
627  constexpr int offerCount{1001};
628  for (int i{0}; i < offerCount; ++i)
629  {
630  env(offer(alice, gw[currency](1), XRP(1)));
631  env.close();
632 
633  // Increment to next currency.
634  ++currency[0];
635  if (currency[0] > 'Z')
636  {
637  currency[0] = 'A';
638  ++currency[1];
639  }
640  if (currency[1] > 'Z')
641  {
642  currency[1] = 'A';
643  ++currency[2];
644  }
645  if (currency[2] > 'Z')
646  {
647  currency[0] = 'A';
648  currency[1] = 'A';
649  currency[2] = 'A';
650  }
651  }
652 
653  // Close enough ledgers to be able to delete alice's account.
654  incLgrSeqForAccDel(env, alice);
655 
656  // Verify the existence of the expected ledger entries.
657  Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
658  {
659  std::shared_ptr<ReadView const> closed{env.closed()};
660  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
661  BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
662 
663  // alice's directory nodes.
664  for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
665  BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
666 
667  // alice's offers.
668  for (std::uint32_t i{0}; i < offerCount; ++i)
669  BEAST_EXPECT(
670  closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
671  }
672 
673  // Delete alice's account. Should fail because she has too many
674  // offers in her directory.
675  auto const acctDelFee{drops(env.current()->fees().increment)};
676 
677  env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
678 
679  // Cancel one of alice's offers. Then the account delete can succeed.
680  env.require(offers(alice, offerCount));
681  env(offer_cancel(alice, offerSeq0));
682  env.close();
683  env.require(offers(alice, offerCount - 1));
684 
685  // alice successfully deletes her account.
686  auto const alicePreDelBal{env.balance(alice)};
687  env(acctdelete(alice, gw), fee(acctDelFee));
688  verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
689  env.close();
690 
691  // Verify that alice's account root is gone as well as her directory
692  // nodes and all of her offers.
693  {
694  std::shared_ptr<ReadView const> closed{env.closed()};
695  BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
696  BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
697 
698  // alice's former directory nodes.
699  for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
700  BEAST_EXPECT(
701  !closed->exists(keylet::page(aliceOwnerDirKey, i)));
702 
703  // alice's former offers.
704  for (std::uint32_t i{0}; i < offerCount; ++i)
705  BEAST_EXPECT(
706  !closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
707  }
708  }
709 
710  void
712  {
713  // Show that a trust line that is implicitly created by offer crossing
714  // prevents an account from being deleted.
715  using namespace jtx;
716 
717  testcase("Implicitly created trust line");
718 
719  Env env{*this};
720  Account const alice{"alice"};
721  Account const gw{"gw"};
722  auto const BUX{gw["BUX"]};
723 
724  env.fund(XRP(10000), alice, gw);
725  env.close();
726 
727  // alice creates an offer that, if crossed, will implicitly create
728  // a trust line.
729  env(offer(alice, BUX(30), XRP(30)));
730  env.close();
731 
732  // gw crosses alice's offer. alice should end up with BUX(30).
733  env(offer(gw, XRP(30), BUX(30)));
734  env.close();
735  env.require(balance(alice, BUX(30)));
736 
737  // Close enough ledgers to be able to delete alice's account.
738  incLgrSeqForAccDel(env, alice);
739 
740  // alice and gw can't delete their accounts because of the implicitly
741  // created trust line.
742  auto const acctDelFee{drops(env.current()->fees().increment)};
743  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
744  env.close();
745 
746  env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
747  env.close();
748  {
749  std::shared_ptr<ReadView const> closed{env.closed()};
750  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
751  BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
752  }
753  }
754 
755  void
757  {
758  // See what happens when an account with a balance less than the
759  // incremental reserve tries to delete itself.
760  using namespace jtx;
761 
762  testcase("Balance too small for fee");
763 
764  Env env{*this};
765  Account const alice("alice");
766 
767  // Note that the fee structure for unit tests does not match the fees
768  // on the production network (October 2019). Unit tests have a base
769  // reserve of 200 XRP.
770  env.fund(env.current()->fees().accountReserve(0), noripple(alice));
771  env.close();
772 
773  // Burn a chunk of alice's funds so she only has 1 XRP remaining in
774  // her account.
775  env(noop(alice), fee(env.balance(alice) - XRP(1)));
776  env.close();
777 
778  auto const acctDelFee{drops(env.current()->fees().increment)};
779  BEAST_EXPECT(acctDelFee > env.balance(alice));
780 
781  // alice attempts to delete her account even though she can't pay
782  // the full fee. She specifies a fee that is larger than her balance.
783  //
784  // The balance of env.master should not change.
785  auto const masterBalance{env.balance(env.master)};
786  env(acctdelete(alice, env.master),
787  fee(acctDelFee),
789  env.close();
790  {
791  std::shared_ptr<ReadView const> const closed{env.closed()};
792  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
793  BEAST_EXPECT(env.balance(env.master) == masterBalance);
794  }
795 
796  // alice again attempts to delete her account. This time she specifies
797  // her current balance in XRP. Again the transaction fails.
798  BEAST_EXPECT(env.balance(alice) == XRP(1));
799  env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
800  env.close();
801  {
802  std::shared_ptr<ReadView const> closed{env.closed()};
803  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
804  BEAST_EXPECT(env.balance(env.master) == masterBalance);
805  }
806  }
807 
808  void
810  {
811  testcase("With Tickets");
812 
813  using namespace test::jtx;
814 
815  Account const alice{"alice"};
816  Account const bob{"bob"};
817 
818  Env env{*this};
819  env.fund(XRP(100000), alice, bob);
820  env.close();
821 
822  // bob grabs as many tickets as he is allowed to have.
823  std::uint32_t const ticketSeq{env.seq(bob) + 1};
824  env(ticket::create(bob, 250));
825  env.close();
826  env.require(owners(bob, 250));
827 
828  {
829  std::shared_ptr<ReadView const> closed{env.closed()};
830  BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
831  for (std::uint32_t i = 0; i < 250; ++i)
832  {
833  BEAST_EXPECT(
834  closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
835  }
836  }
837 
838  // Close enough ledgers to be able to delete bob's account.
839  incLgrSeqForAccDel(env, bob);
840 
841  // bob deletes his account using a ticket. bob's account and all
842  // of his tickets should be removed from the ledger.
843  auto const acctDelFee{drops(env.current()->fees().increment)};
844  auto const bobOldBalance{env.balance(bob)};
845  env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
846  verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
847  env.close();
848  {
849  std::shared_ptr<ReadView const> closed{env.closed()};
850  BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
851  for (std::uint32_t i = 0; i < 250; ++i)
852  {
853  BEAST_EXPECT(
854  !closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
855  }
856  }
857  }
858 
859  void
861  {
862  testcase("Destination Constraints");
863 
864  using namespace test::jtx;
865 
866  Account const alice{"alice"};
867  Account const becky{"becky"};
868  Account const carol{"carol"};
869  Account const daria{"daria"};
870 
871  Env env{*this};
872  env.fund(XRP(100000), alice, becky, carol);
873  env.close();
874 
875  // alice sets the lsfDepositAuth flag on her account. This should
876  // prevent becky from deleting her account while using alice as the
877  // destination.
878  env(fset(alice, asfDepositAuth));
879 
880  // carol requires a destination tag.
881  env(fset(carol, asfRequireDest));
882  env.close();
883 
884  // Close enough ledgers to be able to delete becky's account.
885  incLgrSeqForAccDel(env, becky);
886 
887  // becky attempts to delete her account using daria as the destination.
888  // Since daria is not in the ledger the delete attempt fails.
889  auto const acctDelFee{drops(env.current()->fees().increment)};
890  env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
891  env.close();
892 
893  // becky attempts to delete her account, but carol requires a
894  // destination tag which becky has omitted.
895  env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
896  env.close();
897 
898  // becky attempts to delete her account, but alice won't take her XRP,
899  // so the delete is blocked.
900  env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
901  env.close();
902 
903  // alice preauthorizes deposits from becky. Now becky can delete her
904  // account and forward the leftovers to alice.
905  env(deposit::auth(alice, becky));
906  env.close();
907 
908  auto const beckyOldBalance{env.balance(becky)};
909  env(acctdelete(becky, alice), fee(acctDelFee));
910  verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
911  env.close();
912  }
913 
914  void
915  run() override
916  {
917  testBasics();
918  testDirectories();
919  testOwnedTypes();
925  testWithTickets();
926  testDest();
927  }
928 };
929 
930 BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2);
931 
932 } // namespace test
933 } // 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:303
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
ripple::asfDepositAuth
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:82
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:197
ripple::test::jtx::Env::tx
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:382
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:860
ripple::test::AccountDelete_test::testImplicitlyCreatedTrustline
void testImplicitlyCreatedTrustline()
Definition: AccountDelete_test.cpp:711
ripple::sfOwner
const SF_ACCOUNT sfOwner
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:809
ripple::STAmount::getJson
Json::Value getJson(JsonOptions) const override
Definition: STAmount.cpp:655
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:276
ripple::test::AccountDelete_test::testAmendmentEnable
void testAmendmentEnable()
Definition: AccountDelete_test.cpp:551
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:222
ripple::tfClose
constexpr std::uint32_t tfClose
Definition: TxFlags.h:123
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:113
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::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:605
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:136
ripple::test::jtx::msig
Set a multisignature on a JTx.
Definition: multisign.h:63
ripple::temDST_IS_SRC
@ temDST_IS_SRC
Definition: TER.h:106
ripple::test::BEAST_DEFINE_TESTSUITE_PRIO
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2)
ripple::featureDeletableAccounts
const uint256 featureDeletableAccounts
ripple::test::jtx::Account::id
AccountID id() const
Returns the Account ID.
Definition: Account.h:106
ripple::tecNO_DST_INSUF_XRP
@ tecNO_DST_INSUF_XRP
Definition: TER.h:258
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:109
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:756
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
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:309
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:45
std::chrono::time_point
ripple::fixPayChanRecipientOwnerDir
const uint256 fixPayChanRecipientOwnerDir
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::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:207
ripple::temBAD_FEE
@ temBAD_FEE
Definition: TER.h:90
ripple::test::jtx::fee
Set the fee on a JTx.
Definition: fee.h:35
ripple::tecTOO_SOON
@ tecTOO_SOON
Definition: TER.h:285
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:915
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:324
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
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:228
ripple::tefTOO_BIG
@ tefTOO_BIG
Definition: TER.h:166
ripple::asfRequireDest
constexpr std::uint32_t asfRequireDest
Definition: TxFlags.h:74
ripple::tecHAS_OBLIGATIONS
@ tecHAS_OBLIGATIONS
Definition: TER.h:284
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:272
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:41
ripple::sfFinishAfter
const SF_UINT32 sfFinishAfter
ripple::sfChannel
const SF_UINT256 sfChannel
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
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::tfUniversal
constexpr std::uint32_t tfUniversal
Definition: TxFlags.h:59
ripple::keylet::signers
static Keylet signers(AccountID const &account, std::uint32_t page) noexcept
Definition: Indexes.cpp:268
ripple::keylet::check
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:281
ripple::keylet::depositPreauth
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:287
ripple::sfPublicKey
const SF_VL sfPublicKey
ripple::test::jtx::Account::pk
PublicKey const & pk() const
Return the public key.
Definition: Account.h:89
ripple::tfImmediateOrCancel
constexpr std::uint32_t tfImmediateOrCancel
Definition: TxFlags.h:94
ripple::test::jtx::Env::current
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:300
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::tecNO_DST
@ tecNO_DST
Definition: TER.h:257
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:687
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::jtx::owner_count
Definition: owners.h:49