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  // Invalid flags.
126  env(acctdelete(alice, becky),
129 
130  // Account deletion has a high fee. Make sure the fee requirement
131  // behaves as we expect.
132  auto const acctDelFee{drops(env.current()->fees().increment)};
133  env(acctdelete(alice, becky), ter(telINSUF_FEE_P));
134 
135  // Try a fee one drop less than the required amount.
136  env(acctdelete(alice, becky),
137  fee(acctDelFee - drops(1)),
139 
140  // alice's account is created too recently to be deleted.
141  env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
142 
143  // Give becky a trustline. She is no longer deletable.
144  env(trust(becky, gw["USD"](1000)));
145  env.close();
146 
147  // Give carol a deposit preauthorization, an offer, a ticket,
148  // and a signer list. Even with all that she's still deletable.
149  env(deposit::auth(carol, becky));
150  std::uint32_t const carolOfferSeq{env.seq(carol)};
151  env(offer(carol, gw["USD"](51), XRP(51)));
152  std::uint32_t const carolTicketSeq{env.seq(carol) + 1};
153  env(ticket::create(carol, 1));
154  env(signers(carol, 1, {{alice, 1}, {becky, 1}}));
155 
156  // Deleting should fail with TOO_SOON, which is a relatively
157  // cheap check compared to validating the contents of her directory.
158  env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
159 
160  // Close enough ledgers to almost be able to delete alice's account.
161  incLgrSeqForAccDel(env, alice, 1);
162 
163  // alice's account is still created too recently to be deleted.
164  env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
165 
166  // The most recent delete attempt advanced alice's sequence. So
167  // close two ledgers and her account should be deletable.
168  env.close();
169  env.close();
170 
171  {
172  auto const aliceOldBalance{env.balance(alice)};
173  auto const beckyOldBalance{env.balance(becky)};
174 
175  // Verify that alice's account exists but she has no directory.
176  BEAST_EXPECT(env.closed()->exists(keylet::account(alice.id())));
177  BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
178 
179  env(acctdelete(alice, becky), fee(acctDelFee));
180  verifyDeliveredAmount(env, aliceOldBalance - acctDelFee);
181  env.close();
182 
183  // Verify that alice's account and directory are actually gone.
184  BEAST_EXPECT(!env.closed()->exists(keylet::account(alice.id())));
185  BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
186 
187  // Verify that alice's XRP, minus the fee, was transferred to becky.
188  BEAST_EXPECT(
189  env.balance(becky) ==
190  aliceOldBalance + beckyOldBalance - acctDelFee);
191  }
192 
193  // Attempt to delete becky's account but get stopped by the trust line.
194  env(acctdelete(becky, carol), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
195  env.close();
196 
197  // Verify that becky's account is still there.
198  env(noop(becky));
199 
200  {
201  auto const beckyOldBalance{env.balance(becky)};
202  auto const carolOldBalance{env.balance(carol)};
203 
204  // Verify that Carol's account, directory, deposit
205  // preauthorization, offer, ticket, and signer list exist.
206  BEAST_EXPECT(env.closed()->exists(keylet::account(carol.id())));
207  BEAST_EXPECT(env.closed()->exists(keylet::ownerDir(carol.id())));
208  BEAST_EXPECT(env.closed()->exists(
209  keylet::depositPreauth(carol.id(), becky.id())));
210  BEAST_EXPECT(
211  env.closed()->exists(keylet::offer(carol.id(), carolOfferSeq)));
212  BEAST_EXPECT(env.closed()->exists(
213  keylet::ticket(carol.id(), carolTicketSeq)));
214  BEAST_EXPECT(env.closed()->exists(keylet::signers(carol.id())));
215 
216  // Delete carol's account even with stuff in her directory. Show
217  // that multisigning for the delete does not increase carol's fee.
218  env(acctdelete(carol, becky), fee(acctDelFee), msig(alice));
219  verifyDeliveredAmount(env, carolOldBalance - acctDelFee);
220  env.close();
221 
222  // Verify that Carol's account, directory, and other stuff are gone.
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(!env.closed()->exists(
228  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  // Verify that Carol's XRP, minus the fee, was transferred to becky.
234  BEAST_EXPECT(
235  env.balance(becky) ==
236  carolOldBalance + beckyOldBalance - acctDelFee);
237  }
238  }
239 
240  void
242  {
243  // The code that deletes consecutive directory entries uses a
244  // peculiarity of the implementation. Make sure that peculiarity
245  // behaves as expected across owner directory pages.
246  using namespace jtx;
247 
248  testcase("Directories");
249 
250  Env env{*this};
251  Account const alice("alice");
252  Account const gw("gw");
253 
254  env.fund(XRP(10000), alice, gw);
255  env.close();
256 
257  // Alice creates enough offers to require two owner directories.
258  for (int i{0}; i < 45; ++i)
259  {
260  env(offer(alice, gw["USD"](1), XRP(1)));
261  env.close();
262  }
263  env.require(offers(alice, 45));
264 
265  // Close enough ledgers to be able to delete alice's account.
266  incLgrSeqForAccDel(env, alice);
267 
268  // Verify that both directory nodes exist.
269  Keylet const aliceRootKey{keylet::ownerDir(alice.id())};
270  Keylet const alicePageKey{keylet::page(aliceRootKey, 1)};
271  BEAST_EXPECT(env.closed()->exists(aliceRootKey));
272  BEAST_EXPECT(env.closed()->exists(alicePageKey));
273 
274  // Delete alice's account.
275  auto const acctDelFee{drops(env.current()->fees().increment)};
276  auto const aliceBalance{env.balance(alice)};
277  env(acctdelete(alice, gw), fee(acctDelFee));
278  verifyDeliveredAmount(env, aliceBalance - acctDelFee);
279  env.close();
280 
281  // Both of alice's directory nodes should be gone.
282  BEAST_EXPECT(!env.closed()->exists(aliceRootKey));
283  BEAST_EXPECT(!env.closed()->exists(alicePageKey));
284  }
285 
286  void
288  {
289  using namespace jtx;
290 
291  testcase("Owned types");
292 
293  // We want to test both...
294  // o Old-style PayChannels without a recipient backlink as well as
295  // o New-styled PayChannels with the backlink.
296  // So we start the test using old-style PayChannels. Then we pass
297  // the amendment to get new-style PayChannels.
299  Account const alice("alice");
300  Account const becky("becky");
301  Account const gw("gw");
302 
303  env.fund(XRP(100000), alice, becky, gw);
304  env.close();
305 
306  // Give alice and becky a bunch of offers that we have to search
307  // through before we figure out that there's a non-deletable
308  // entry in their directory.
309  for (int i{0}; i < 200; ++i)
310  {
311  env(offer(alice, gw["USD"](1), XRP(1)));
312  env(offer(becky, gw["USD"](1), XRP(1)));
313  env.close();
314  }
315  env.require(offers(alice, 200));
316  env.require(offers(becky, 200));
317 
318  // Close enough ledgers to be able to delete alice's and becky's
319  // accounts.
320  incLgrSeqForAccDel(env, alice);
321  incLgrSeqForAccDel(env, becky);
322 
323  // alice writes a check to becky. Until that check is cashed or
324  // canceled it will prevent alice's and becky's accounts from being
325  // deleted.
326  uint256 const checkId = keylet::check(alice, env.seq(alice)).key;
327  env(check::create(alice, becky, XRP(1)));
328  env.close();
329 
330  auto const acctDelFee{drops(env.current()->fees().increment)};
331  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
332  env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
333  env.close();
334 
335  // Cancel the check, but add an escrow. Again, with the escrow
336  // on board, alice and becky should not be able to delete their
337  // accounts.
338  env(check::cancel(becky, checkId));
339  env.close();
340 
341  // Lambda to create an escrow.
342  auto escrowCreate = [](jtx::Account const& account,
343  jtx::Account const& to,
344  STAmount const& amount,
345  NetClock::time_point const& cancelAfter) {
346  Json::Value jv;
347  jv[jss::TransactionType] = jss::EscrowCreate;
348  jv[jss::Flags] = tfUniversal;
349  jv[jss::Account] = account.human();
350  jv[jss::Destination] = to.human();
351  jv[jss::Amount] = amount.getJson(JsonOptions::none);
352  jv[sfFinishAfter.jsonName] =
353  cancelAfter.time_since_epoch().count() + 1;
354  jv[sfCancelAfter.jsonName] =
355  cancelAfter.time_since_epoch().count() + 2;
356  return jv;
357  };
358 
359  using namespace std::chrono_literals;
360  std::uint32_t const escrowSeq{env.seq(alice)};
361  env(escrowCreate(alice, becky, XRP(333), env.now() + 2s));
362  env.close();
363 
364  // alice and becky should be unable to delete their accounts because
365  // of the escrow.
366  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
367  env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
368  env.close();
369 
370  // Now cancel the escrow, but create a payment channel between
371  // alice and becky.
372 
373  // Lambda to cancel an escrow.
374  auto escrowCancel =
375  [](Account const& account, Account const& from, std::uint32_t seq) {
376  Json::Value jv;
377  jv[jss::TransactionType] = jss::EscrowCancel;
378  jv[jss::Flags] = tfUniversal;
379  jv[jss::Account] = account.human();
380  jv[sfOwner.jsonName] = from.human();
382  return jv;
383  };
384  env(escrowCancel(becky, alice, escrowSeq));
385  env.close();
386 
387  Keylet const alicePayChanKey{
388  keylet::payChan(alice, becky, env.seq(alice))};
389 
390  env(payChanCreate(
391  alice, becky, XRP(57), 4s, env.now() + 2s, alice.pk()));
392  env.close();
393 
394  // An old-style PayChannel does not add a back link from the
395  // destination. So with the PayChannel in place becky should be
396  // able to delete her account, but alice should not.
397  auto const beckyBalance{env.balance(becky)};
398  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
399  env(acctdelete(becky, gw), fee(acctDelFee));
400  verifyDeliveredAmount(env, beckyBalance - acctDelFee);
401  env.close();
402 
403  // Alice cancels her PayChannel which will leave her with only offers
404  // in her directory.
405 
406  // Lambda to close a PayChannel.
407  auto payChanClose = [](jtx::Account const& account,
408  Keylet const& payChanKeylet,
409  PublicKey const& pk) {
410  Json::Value jv;
411  jv[jss::TransactionType] = jss::PaymentChannelClaim;
412  jv[jss::Flags] = tfClose;
413  jv[jss::Account] = account.human();
414  jv[sfChannel.jsonName] = to_string(payChanKeylet.key);
415  jv[sfPublicKey.jsonName] = strHex(pk.slice());
416  return jv;
417  };
418  env(payChanClose(alice, alicePayChanKey, alice.pk()));
419  env.close();
420 
421  // Now enable the amendment so PayChannels add a backlink from the
422  // destination.
423  env.enableFeature(fixPayChanRecipientOwnerDir);
424  env.close();
425 
426  // gw creates a PayChannel with alice as the destination. With the
427  // amendment passed this should prevent alice from deleting her
428  // account.
429  Keylet const gwPayChanKey{keylet::payChan(gw, alice, env.seq(gw))};
430 
431  env(payChanCreate(gw, alice, XRP(68), 4s, env.now() + 2s, alice.pk()));
432  env.close();
433 
434  // alice can't delete her account because of the PayChannel.
435  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
436  env.close();
437 
438  // alice closes the PayChannel which should (finally) allow her to
439  // delete her account.
440  env(payChanClose(alice, gwPayChanKey, alice.pk()));
441  env.close();
442 
443  // Now alice can successfully delete her account.
444  auto const aliceBalance{env.balance(alice)};
445  env(acctdelete(alice, gw), fee(acctDelFee));
446  verifyDeliveredAmount(env, aliceBalance - acctDelFee);
447  env.close();
448  }
449 
450  void
452  {
453  // Create an account with an old-style PayChannel. Delete the
454  // destination of the PayChannel then resurrect the destination.
455  // The PayChannel should still work.
456  using namespace jtx;
457 
458  testcase("Resurrection");
459 
460  // We need an old-style PayChannel that doesn't provide a backlink
461  // from the destination. So don't enable the amendment with that fix.
463  Account const alice("alice");
464  Account const becky("becky");
465 
466  env.fund(XRP(10000), alice, becky);
467  env.close();
468 
469  // Verify that becky's account root is present.
470  Keylet const beckyAcctKey{keylet::account(becky.id())};
471  BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
472 
473  using namespace std::chrono_literals;
474  Keylet const payChanKey{keylet::payChan(alice, becky, env.seq(alice))};
475  auto const payChanXRP = XRP(37);
476 
477  env(payChanCreate(
478  alice, becky, payChanXRP, 4s, env.now() + 1h, alice.pk()));
479  env.close();
480  BEAST_EXPECT(env.closed()->exists(payChanKey));
481 
482  // Close enough ledgers to be able to delete becky's account.
483  incLgrSeqForAccDel(env, becky);
484 
485  auto const beckyPreDelBalance{env.balance(becky)};
486 
487  auto const acctDelFee{drops(env.current()->fees().increment)};
488  env(acctdelete(becky, alice), fee(acctDelFee));
489  verifyDeliveredAmount(env, beckyPreDelBalance - acctDelFee);
490  env.close();
491 
492  // Verify that becky's account root is gone.
493  BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
494 
495  // All it takes is a large enough XRP payment to resurrect
496  // becky's account. Try too small a payment.
497  env(pay(alice, becky, XRP(19)), ter(tecNO_DST_INSUF_XRP));
498  env.close();
499 
500  // Actually resurrect becky's account.
501  env(pay(alice, becky, XRP(20)));
502  env.close();
503 
504  // becky's account root should be back.
505  BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
506  BEAST_EXPECT(env.balance(becky) == XRP(20));
507 
508  // becky's resurrected account can be the destination of alice's
509  // PayChannel.
510  auto payChanClaim = [&]() {
511  Json::Value jv;
512  jv[jss::TransactionType] = jss::PaymentChannelClaim;
513  jv[jss::Flags] = tfUniversal;
514  jv[jss::Account] = alice.human();
515  jv[sfChannel.jsonName] = to_string(payChanKey.key);
516  jv[sfBalance.jsonName] =
517  payChanXRP.value().getJson(JsonOptions::none);
518  return jv;
519  };
520  env(payChanClaim());
521  env.close();
522 
523  BEAST_EXPECT(env.balance(becky) == XRP(20) + payChanXRP);
524  }
525 
526  void
528  {
529  // Start with the featureDeletableAccounts amendment disabled.
530  // Then enable the amendment and delete an account.
531  using namespace jtx;
532 
533  testcase("Amendment enable");
534 
536  Account const alice("alice");
537  Account const becky("becky");
538 
539  env.fund(XRP(10000), alice, becky);
540  env.close();
541 
542  // Close enough ledgers to be able to delete alice's account.
543  incLgrSeqForAccDel(env, alice);
544 
545  // Verify that alice's account root is present.
546  Keylet const aliceAcctKey{keylet::account(alice.id())};
547  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
548 
549  auto const alicePreDelBal{env.balance(alice)};
550  auto const beckyPreDelBal{env.balance(becky)};
551 
552  auto const acctDelFee{drops(env.current()->fees().increment)};
553  env(acctdelete(alice, becky), fee(acctDelFee), ter(temDISABLED));
554  env.close();
555 
556  // Verify that alice's account root is still present and alice and
557  // becky both have their XRP.
558  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
559  BEAST_EXPECT(env.balance(alice) == alicePreDelBal);
560  BEAST_EXPECT(env.balance(becky) == beckyPreDelBal);
561 
562  // When the amendment is enabled the previous transaction is
563  // retried into the new open ledger and succeeds.
564  env.enableFeature(featureDeletableAccounts);
565  env.close();
566 
567  // alice's account is still in the most recently closed ledger.
568  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
569 
570  // Verify that alice's account root is gone from the current ledger
571  // and becky has alice's XRP.
572  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
573  BEAST_EXPECT(
574  env.balance(becky) == alicePreDelBal + beckyPreDelBal - acctDelFee);
575 
576  env.close();
577  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
578  }
579 
580  void
582  {
583  // Put enough offers in an account that we refuse to delete the account.
584  using namespace jtx;
585 
586  testcase("Too many offers");
587 
588  Env env{*this};
589  Account const alice("alice");
590  Account const gw("gw");
591 
592  // Fund alice well so she can afford the reserve on the offers.
593  env.fund(XRP(10000000), alice, gw);
594  env.close();
595 
596  // To increase the number of Books affected, change the currency of
597  // each offer.
598  std::string currency{"AAA"};
599 
600  // Alice creates 1001 offers. This is one greater than the number of
601  // directory entries an AccountDelete will remove.
602  std::uint32_t const offerSeq0{env.seq(alice)};
603  constexpr int offerCount{1001};
604  for (int i{0}; i < offerCount; ++i)
605  {
606  env(offer(alice, gw[currency](1), XRP(1)));
607  env.close();
608 
609  // Increment to next currency.
610  ++currency[0];
611  if (currency[0] > 'Z')
612  {
613  currency[0] = 'A';
614  ++currency[1];
615  }
616  if (currency[1] > 'Z')
617  {
618  currency[1] = 'A';
619  ++currency[2];
620  }
621  if (currency[2] > 'Z')
622  {
623  currency[0] = 'A';
624  currency[1] = 'A';
625  currency[2] = 'A';
626  }
627  }
628 
629  // Close enough ledgers to be able to delete alice's account.
630  incLgrSeqForAccDel(env, alice);
631 
632  // Verify the existence of the expected ledger entries.
633  Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
634  {
635  std::shared_ptr<ReadView const> closed{env.closed()};
636  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
637  BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
638 
639  // alice's directory nodes.
640  for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
641  BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
642 
643  // alice's offers.
644  for (std::uint32_t i{0}; i < offerCount; ++i)
645  BEAST_EXPECT(
646  closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
647  }
648 
649  // Delete alice's account. Should fail because she has too many
650  // offers in her directory.
651  auto const acctDelFee{drops(env.current()->fees().increment)};
652 
653  env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
654 
655  // Cancel one of alice's offers. Then the account delete can succeed.
656  env.require(offers(alice, offerCount));
657  env(offer_cancel(alice, offerSeq0));
658  env.close();
659  env.require(offers(alice, offerCount - 1));
660 
661  // alice successfully deletes her account.
662  auto const alicePreDelBal{env.balance(alice)};
663  env(acctdelete(alice, gw), fee(acctDelFee));
664  verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
665  env.close();
666 
667  // Verify that alice's account root is gone as well as her directory
668  // nodes and all of her offers.
669  {
670  std::shared_ptr<ReadView const> closed{env.closed()};
671  BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
672  BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
673 
674  // alice's former directory nodes.
675  for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
676  BEAST_EXPECT(
677  !closed->exists(keylet::page(aliceOwnerDirKey, i)));
678 
679  // alice's former offers.
680  for (std::uint32_t i{0}; i < offerCount; ++i)
681  BEAST_EXPECT(
682  !closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
683  }
684  }
685 
686  void
688  {
689  // Show that a trust line that is implicitly created by offer crossing
690  // prevents an account from being deleted.
691  using namespace jtx;
692 
693  testcase("Implicitly created trust line");
694 
695  Env env{*this};
696  Account const alice{"alice"};
697  Account const gw{"gw"};
698  auto const BUX{gw["BUX"]};
699 
700  env.fund(XRP(10000), alice, gw);
701  env.close();
702 
703  // alice creates an offer that, if crossed, will implicitly create
704  // a trust line.
705  env(offer(alice, BUX(30), XRP(30)));
706  env.close();
707 
708  // gw crosses alice's offer. alice should end up with BUX(30).
709  env(offer(gw, XRP(30), BUX(30)));
710  env.close();
711  env.require(balance(alice, BUX(30)));
712 
713  // Close enough ledgers to be able to delete alice's account.
714  incLgrSeqForAccDel(env, alice);
715 
716  // alice and gw can't delete their accounts because of the implicitly
717  // created trust line.
718  auto const acctDelFee{drops(env.current()->fees().increment)};
719  env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
720  env.close();
721 
722  env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
723  env.close();
724  {
725  std::shared_ptr<ReadView const> closed{env.closed()};
726  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
727  BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
728  }
729  }
730 
731  void
733  {
734  // See what happens when an account with a balance less than the
735  // incremental reserve tries to delete itself.
736  using namespace jtx;
737 
738  testcase("Balance too small for fee");
739 
740  Env env{*this};
741  Account const alice("alice");
742 
743  // Note that the fee structure for unit tests does not match the fees
744  // on the production network (October 2019). Unit tests have a base
745  // reserve of 200 XRP.
746  env.fund(env.current()->fees().accountReserve(0), noripple(alice));
747  env.close();
748 
749  // Burn a chunk of alice's funds so she only has 1 XRP remaining in
750  // her account.
751  env(noop(alice), fee(env.balance(alice) - XRP(1)));
752  env.close();
753 
754  auto const acctDelFee{drops(env.current()->fees().increment)};
755  BEAST_EXPECT(acctDelFee > env.balance(alice));
756 
757  // alice attempts to delete her account even though she can't pay
758  // the full fee. She specifies a fee that is larger than her balance.
759  //
760  // The balance of env.master should not change.
761  auto const masterBalance{env.balance(env.master)};
762  env(acctdelete(alice, env.master),
763  fee(acctDelFee),
765  env.close();
766  {
767  std::shared_ptr<ReadView const> const closed{env.closed()};
768  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
769  BEAST_EXPECT(env.balance(env.master) == masterBalance);
770  }
771 
772  // alice again attempts to delete her account. This time she specifies
773  // her current balance in XRP. Again the transaction fails.
774  BEAST_EXPECT(env.balance(alice) == XRP(1));
775  env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
776  env.close();
777  {
778  std::shared_ptr<ReadView const> closed{env.closed()};
779  BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
780  BEAST_EXPECT(env.balance(env.master) == masterBalance);
781  }
782  }
783 
784  void
786  {
787  testcase("With Tickets");
788 
789  using namespace test::jtx;
790 
791  Account const alice{"alice"};
792  Account const bob{"bob"};
793 
794  Env env{*this};
795  env.fund(XRP(100000), alice, bob);
796  env.close();
797 
798  // bob grabs as many tickets as he is allowed to have.
799  std::uint32_t const ticketSeq{env.seq(bob) + 1};
800  env(ticket::create(bob, 250));
801  env.close();
802  env.require(owners(bob, 250));
803 
804  {
805  std::shared_ptr<ReadView const> closed{env.closed()};
806  BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
807  for (std::uint32_t i = 0; i < 250; ++i)
808  {
809  BEAST_EXPECT(
810  closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
811  }
812  }
813 
814  // Close enough ledgers to be able to delete bob's account.
815  incLgrSeqForAccDel(env, bob);
816 
817  // bob deletes his account using a ticket. bob's account and all
818  // of his tickets should be removed from the ledger.
819  auto const acctDelFee{drops(env.current()->fees().increment)};
820  auto const bobOldBalance{env.balance(bob)};
821  env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
822  verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
823  env.close();
824  {
825  std::shared_ptr<ReadView const> closed{env.closed()};
826  BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
827  for (std::uint32_t i = 0; i < 250; ++i)
828  {
829  BEAST_EXPECT(
830  !closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
831  }
832  }
833  }
834 
835  void
836  run() override
837  {
838  testBasics();
839  testDirectories();
840  testOwnedTypes();
846  testWithTickets();
847  }
848 };
849 
850 BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple);
851 
852 } // namespace test
853 } // 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:304
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::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
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:191
ripple::test::jtx::Env::tx
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:372
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::testImplicitlyCreatedTrustline
void testImplicitlyCreatedTrustline()
Definition: AccountDelete_test.cpp:687
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:785
ripple::STAmount::getJson
Json::Value getJson(JsonOptions) const override
Definition: STAmount.cpp:594
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::test::AccountDelete_test::testAmendmentEnable
void testAmendmentEnable()
Definition: AccountDelete_test.cpp:527
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:223
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:241
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:451
ripple::test::AccountDelete_test::openLedgerSeq
std::uint32_t openLedgerSeq(jtx::Env &env)
Definition: AccountDelete_test.cpp:31
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:45
ripple::test::AccountDelete_test::testTooManyOffers
void testTooManyOffers()
Definition: AccountDelete_test.cpp:581
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::featureDeletableAccounts
const uint256 featureDeletableAccounts
Definition: Feature.cpp:182
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:252
ripple::Keylet::key
uint256 key
Definition: Keylet.h:41
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:287
ripple::sfDeliveredAmount
const SF_AMOUNT sfDeliveredAmount
ripple::sfSettleDelay
const SF_UINT32 sfSettleDelay
ripple::test::AccountDelete_test::testBalanceTooSmallForFee
void testBalanceTooSmallForFee()
Definition: AccountDelete_test.cpp:732
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:134
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:310
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::STAmount
Definition: STAmount.h:42
std::chrono::time_point
ripple::fixPayChanRecipientOwnerDir
const uint256 fixPayChanRecipientOwnerDir
Definition: Feature.cpp:181
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::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:198
ripple::tfUniversal
const std::uint32_t tfUniversal
Definition: TxFlags.h:49
ripple::sfChannel
const SF_HASH256 sfChannel
ripple::test::jtx::fee
Set the fee on a JTx.
Definition: fee.h:34
ripple::tecTOO_SOON
@ tecTOO_SOON
Definition: TER.h:279
ripple::test::jtx::seq
Set the sequence number on a JTx.
Definition: seq.h:32
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:836
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:325
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:109
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, boost::optional< std::chrono::milliseconds > consensusDelay=boost::none)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:219
ripple::tefTOO_BIG
@ tefTOO_BIG
Definition: TER.h:161
ripple::tecHAS_OBLIGATIONS
@ tecHAS_OBLIGATIONS
Definition: TER.h:278
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::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:269
ripple::keylet::check
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:282
ripple::keylet::depositPreauth
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:288
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::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:684
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::jtx::owner_count
Definition: owners.h:49