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:
31  {
32  return env.current()->seq();
33  }
34 
35  // Helper function that verifies the expected DeliveredAmount is present.
36  //
37  // NOTE: the function _infers_ the transaction to operate on by calling
38  // env.tx(), which returns the result from the most recent transaction.
39  void verifyDeliveredAmount (jtx::Env& env, STAmount const& amount)
40  {
41  // Get the hash for the most recent transaction.
42  std::string const txHash {
43  env.tx()->getJson (JsonOptions::none)[jss::hash].asString()};
44 
45  // Verify DeliveredAmount and delivered_amount metadata are correct.
46  // We can't use env.meta() here, because meta() doesn't include
47  // delivered_amount.
48  env.close();
49  Json::Value const meta = env.rpc ("tx", txHash)[jss::result][jss::meta];
50 
51  // Expect there to be a DeliveredAmount field.
52  if (! BEAST_EXPECT(meta.isMember (sfDeliveredAmount.jsonName)))
53  return;
54 
55  // DeliveredAmount and delivered_amount should both be present and
56  // equal amount.
57  Json::Value const jsonExpect {amount.getJson (JsonOptions::none)};
58  BEAST_EXPECT (meta[sfDeliveredAmount.jsonName] == jsonExpect);
59  BEAST_EXPECT (meta[jss::delivered_amount] == jsonExpect);
60  }
61 
62  // Helper function to create a payment channel.
64  jtx::Account const& account,
65  jtx::Account const& to,
66  STAmount const& amount,
67  NetClock::duration const& settleDelay,
68  NetClock::time_point const& cancelAfter,
69  PublicKey const& pk)
70  {
71  Json::Value jv;
72  jv[jss::TransactionType] = jss::PaymentChannelCreate;
73  jv[jss::Account] = account.human();
74  jv[jss::Destination] = to.human();
75  jv[jss::Amount] = amount.getJson (JsonOptions::none);
76  jv[sfSettleDelay.jsonName] = settleDelay.count();
78  cancelAfter.time_since_epoch().count() + 2;
79  jv[sfPublicKey.jsonName] = strHex (pk.slice());
80  return jv;
81  };
82 
83  // Close the ledger until the ledger sequence is large enough to close
84  // the account. If margin is specified, close the ledger so `margin`
85  // more closes are needed
86  void
88  jtx::Env& env,
89  jtx::Account const& acc,
90  std::uint32_t margin = 0)
91  {
92  int const delta = [&]()->int{
93  if (env.seq(acc) + 255 > openLedgerSeq(env))
94  return env.seq(acc) - openLedgerSeq(env) + 255 - margin;
95  return 0;
96  }();
97  BEAST_EXPECT(margin==0 || delta >= 0);
98  for (int i = 0; i < delta; ++i)
99  env.close();
100  BEAST_EXPECT(openLedgerSeq(env) == env.seq(acc) + 255 - margin);
101  }
102 
103 public:
104  void testBasics()
105  {
106  using namespace jtx;
107 
108  testcase("Basics");
109 
110  Env env {*this};
111  Account const alice("alice");
112  Account const becky("becky");
113  Account const carol("carol");
114  Account const gw("gw");
115 
116  env.fund(XRP(10000), alice, becky, carol, gw);
117  env.close();
118 
119  // Alice can't delete her account and then give herself the XRP.
120  env (acctdelete (alice, alice), ter (temDST_IS_SRC));
121 
122  // Invalid flags.
123  env (acctdelete (alice, becky),
125 
126  // Account deletion has a high fee. Make sure the fee requirement
127  // behaves as we expect.
128  auto const acctDelFee {drops (env.current()->fees().increment)};
129  env (acctdelete (alice, becky), ter (telINSUF_FEE_P));
130 
131  // Try a fee one drop less than the required amount.
132  env (acctdelete (alice, becky),
133  fee (acctDelFee - drops(1)), ter (telINSUF_FEE_P));
134 
135  // alice's account is created too recently to be deleted.
136  env (acctdelete (alice, becky), fee (acctDelFee), ter (tecTOO_SOON));
137 
138  // Give becky a trustline. She is no longer deletable.
139  env (trust (becky, gw["USD"](1000)));
140  env.close();
141 
142  // Give carol a deposit preauthorization, an offer, and a signer list.
143  // Even with all that she's still deletable.
144  env (deposit::auth (carol, becky));
145  std::uint32_t const carolOfferSeq {env.seq (carol)};
146  env (offer (carol, gw["USD"](51), XRP (51)));
147  env (signers (carol, 1, {{alice, 1}, {becky, 1}}));
148 
149  // Deleting should fail with TOO_SOON, which is a relatively
150  // cheap check compared to validating the contents of her directory.
151  env (acctdelete (alice, becky), fee (acctDelFee), ter (tecTOO_SOON));
152 
153  // Close enough ledgers to almost be able to delete alice's account.
154  incLgrSeqForAccDel(env, alice, 1);
155 
156  // alice's account is still created too recently to be deleted.
157  env (acctdelete (alice, becky), fee (acctDelFee), ter (tecTOO_SOON));
158 
159  // The most recent delete attempt advanced alice's sequence. So
160  // close two ledgers and her account should be deletable.
161  env.close();
162  env.close();
163 
164  {
165  auto const aliceOldBalance {env.balance (alice)};
166  auto const beckyOldBalance {env.balance (becky)};
167 
168  // Verify that alice's account exists but she has no directory.
169  BEAST_EXPECT (env.closed()->exists(keylet::account (alice.id())));
170  BEAST_EXPECT (! env.closed()->exists(keylet::ownerDir(alice.id())));
171 
172  env (acctdelete (alice, becky), fee (acctDelFee));
173  verifyDeliveredAmount (env, aliceOldBalance - acctDelFee);
174  env.close();
175 
176  // Verify that alice's account and directory are actually gone.
177  BEAST_EXPECT (! env.closed()->exists(keylet::account (alice.id())));
178  BEAST_EXPECT (! env.closed()->exists(keylet::ownerDir(alice.id())));
179 
180  // Verify that alice's XRP, minus the fee, was transferred to becky.
181  BEAST_EXPECT (env.balance (becky) ==
182  aliceOldBalance + beckyOldBalance - acctDelFee);
183  }
184 
185  // Attempt to delete becky's account but get stopped by the trust line.
186  env (acctdelete (becky, carol),
187  fee (acctDelFee), ter (tecHAS_OBLIGATIONS));
188  env.close();
189 
190  // Verify that becky's account is still there.
191  env (noop (becky));
192 
193  {
194  auto const beckyOldBalance {env.balance (becky)};
195  auto const carolOldBalance {env.balance (carol)};
196 
197  // Verify that Carol's account, directory, deposit
198  // preauthorization, offer, and signer list exist.
199  BEAST_EXPECT (env.closed()->exists (keylet::account (carol.id())));
200  BEAST_EXPECT (env.closed()->exists (keylet::ownerDir(carol.id())));
201  BEAST_EXPECT (env.closed()->exists (keylet::depositPreauth(
202  carol.id(), becky.id())));
203  BEAST_EXPECT (env.closed()->exists (keylet::offer(
204  carol.id(), carolOfferSeq)));
205  BEAST_EXPECT (env.closed()->exists (keylet::signers (carol.id())));
206 
207  // Delete carol's account even with stuff in her directory. Show
208  // that multisigning for the delete does not increase carol's fee.
209  env (acctdelete (carol, becky), fee (acctDelFee), msig (alice));
210  verifyDeliveredAmount (env, carolOldBalance - acctDelFee);
211  env.close();
212 
213  // Verify that Carol's account, directory, and other stuff are gone.
214  BEAST_EXPECT (! env.closed()->exists(keylet::account (carol.id())));
215  BEAST_EXPECT (! env.closed()->exists(keylet::ownerDir(carol.id())));
216  BEAST_EXPECT (! env.closed()->exists(keylet::depositPreauth (
217  carol.id(), becky.id())));
218  BEAST_EXPECT (! env.closed()->exists(keylet::offer (
219  carol.id(), carolOfferSeq)));
220  BEAST_EXPECT (! env.closed()->exists(keylet::signers (carol.id())));
221 
222  // Verify that Carol's XRP, minus the fee, was transferred to becky.
223  BEAST_EXPECT (env.balance (becky) ==
224  carolOldBalance + beckyOldBalance - acctDelFee);
225  }
226  }
227 
229  {
230  // The code that deletes consecutive directory entries uses a
231  // peculiarity of the implementation. Make sure that peculiarity
232  // behaves as expected across owner directory pages.
233  using namespace jtx;
234 
235  testcase("Directories");
236 
237  Env env {*this};
238  Account const alice("alice");
239  Account const gw("gw");
240 
241  env.fund(XRP(10000), alice, gw);
242  env.close();
243 
244  // Alice creates enough offers to require two owner directories.
245  for (int i {0}; i < 45; ++i)
246  {
247  env (offer (alice, gw["USD"](1), XRP (1)));
248  env.close();
249  }
250  env.require (offers (alice, 45));
251 
252  // Close enough ledgers to be able to delete alice's account.
253  incLgrSeqForAccDel(env, alice);
254 
255  // Verify that both directory nodes exist.
256  Keylet const aliceRootKey {keylet::ownerDir (alice.id())};
257  Keylet const alicePageKey {keylet::page (aliceRootKey, 1)};
258  BEAST_EXPECT (env.closed()->exists (aliceRootKey));
259  BEAST_EXPECT (env.closed()->exists (alicePageKey));
260 
261  // Delete alice's account.
262  auto const acctDelFee {drops (env.current()->fees().increment)};
263  auto const aliceBalance {env.balance (alice)};
264  env (acctdelete (alice, gw), fee (acctDelFee));
265  verifyDeliveredAmount (env, aliceBalance - acctDelFee);
266  env.close();
267 
268  // Both of alice's directory nodes should be gone.
269  BEAST_EXPECT (! env.closed()->exists (aliceRootKey));
270  BEAST_EXPECT (! env.closed()->exists (alicePageKey));
271  }
272 
274  {
275  using namespace jtx;
276 
277  testcase("Owned types");
278 
279  // We want to test both...
280  // o Old-style PayChannels without a recipient backlink as well as
281  // o New-styled PayChannels with the backlink.
282  // So we start the test using old-style PayChannels. Then we pass
283  // the amendment to get new-style PayChannels.
285  Account const alice("alice");
286  Account const becky("becky");
287  Account const gw("gw");
288 
289  env.fund(XRP(100000), alice, becky, gw);
290  env.close();
291 
292  // Give alice and becky a bunch of offers that we have to search
293  // through before we figure out that there's a non-deletable
294  // entry in their directory.
295  for (int i {0}; i < 200; ++i)
296  {
297  env (offer (alice, gw["USD"](1), XRP (1)));
298  env (offer (becky, gw["USD"](1), XRP (1)));
299  env.close();
300  }
301  env.require (offers (alice, 200));
302  env.require (offers (becky, 200));
303 
304  // Close enough ledgers to be able to delete alice's and becky's
305  // accounts.
306  incLgrSeqForAccDel(env, alice);
307  incLgrSeqForAccDel(env, becky);
308 
309  // alice writes a check to becky. Until that check is cashed or
310  // canceled it will prevent alice's and becky's accounts from being
311  // deleted.
312  uint256 const checkId {getCheckIndex (alice, env.seq (alice))};
313  env (check::create (alice, becky, XRP (1)));
314  env.close();
315 
316  auto const acctDelFee {drops (env.current()->fees().increment)};
317  env (acctdelete (alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
318  env (acctdelete (becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
319  env.close();
320 
321  // Cancel the check, but add an escrow. Again, with the escrow
322  // on board, alice and becky should not be able to delete their
323  // accounts.
324  env (check::cancel (becky, checkId));
325  env.close();
326 
327  // Lambda to create an escrow.
328  auto escrowCreate = [] (
329  jtx::Account const& account, jtx::Account const& to,
330  STAmount const& amount, NetClock::time_point const& cancelAfter)
331  {
332  Json::Value jv;
333  jv[jss::TransactionType] = jss::EscrowCreate;
334  jv[jss::Flags] = tfUniversal;
335  jv[jss::Account] = account.human();
336  jv[jss::Destination] = to.human();
337  jv[jss::Amount] = amount.getJson(JsonOptions::none);
338  jv[sfFinishAfter.jsonName] =
339  cancelAfter.time_since_epoch().count() + 1;
340  jv[sfCancelAfter.jsonName] =
341  cancelAfter.time_since_epoch().count() + 2;
342  return jv;
343  };
344 
345  using namespace std::chrono_literals;
346  std::uint32_t const escrowSeq {env.seq (alice)};
347  env (escrowCreate (alice, becky, XRP(333), env.now() + 2s));
348  env.close();
349 
350  // alice and becky should be unable to delete their accounts because
351  // of the escrow.
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  // Now cancel the escrow, but create a payment channel between
357  // alice and becky.
358 
359  // Lambda to cancel an escrow.
360  auto escrowCancel = [] (Account const& account,
361  Account const& from, std::uint32_t seq)
362  {
363  Json::Value jv;
364  jv[jss::TransactionType] = jss::EscrowCancel;
365  jv[jss::Flags] = tfUniversal;
366  jv[jss::Account] = account.human();
367  jv[sfOwner.jsonName] = from.human();
369  return jv;
370  };
371  env (escrowCancel (becky, alice, escrowSeq));
372  env.close();
373 
374  Keylet const alicePayChanKey {
375  keylet::payChan (alice, becky, env.seq (alice))};
376 
377  env (payChanCreate (
378  alice, becky, XRP(57), 4s, env.now() + 2s, alice.pk()));
379  env.close();
380 
381  // An old-style PayChannel does not add a back link from the
382  // destination. So with the PayChannel in place becky should be
383  // able to delete her account, but alice should not.
384  auto const beckyBalance {env.balance (becky)};
385  env (acctdelete (alice, gw), fee(acctDelFee), ter (tecHAS_OBLIGATIONS));
386  env (acctdelete (becky, gw), fee(acctDelFee));
387  verifyDeliveredAmount (env, beckyBalance - acctDelFee);
388  env.close();
389 
390  // Alice cancels her PayChannel which will leave her with only offers
391  // in her directory.
392 
393  // Lambda to close a PayChannel.
394  auto payChanClose = [] (
395  jtx::Account const& account,
396  Keylet const& payChanKeylet,
397  PublicKey const& pk)
398  {
399  Json::Value jv;
400  jv[jss::TransactionType] = jss::PaymentChannelClaim;
401  jv[jss::Flags] = tfClose;
402  jv[jss::Account] = account.human();
403  jv[sfPayChannel.jsonName] = to_string (payChanKeylet.key);
404  jv[sfPublicKey.jsonName] = strHex(pk.slice());
405  return jv;
406  };
407  env (payChanClose (alice, alicePayChanKey, alice.pk()));
408  env.close();
409 
410  // Now enable the amendment so PayChannels add a backlink from the
411  // destination.
412  env.enableFeature (fixPayChanRecipientOwnerDir);
413  env.close();
414 
415  // gw creates a PayChannel with alice as the destination. With the
416  // amendment passed this should prevent alice from deleting her
417  // account.
418  Keylet const gwPayChanKey {keylet::payChan (gw, alice, env.seq (gw))};
419 
420  env (payChanCreate (
421  gw, alice, XRP(68), 4s, env.now() + 2s, alice.pk()));
422  env.close();
423 
424  // alice can't delete her account because of the PayChannel.
425  env (acctdelete (alice, gw), fee(acctDelFee), ter (tecHAS_OBLIGATIONS));
426  env.close();
427 
428  // alice closes the PayChannel which should (finally) allow her to
429  // delete her account.
430  env (payChanClose (alice, gwPayChanKey, alice.pk()));
431  env.close();
432 
433  // Now alice can successfully delete her account.
434  auto const aliceBalance {env.balance (alice)};
435  env (acctdelete (alice, gw), fee(acctDelFee));
436  verifyDeliveredAmount (env, aliceBalance - acctDelFee);
437  env.close();
438  }
439 
441  {
442  // Create an account with an old-style PayChannel. Delete the
443  // destination of the PayChannel then resurrect the destination.
444  // The PayChannel should still work.
445  using namespace jtx;
446 
447  testcase("Resurrection");
448 
449  // We need an old-style PayChannel that doesn't provide a backlink
450  // from the destination. So don't enable the amendment with that fix.
452  Account const alice("alice");
453  Account const becky("becky");
454 
455  env.fund(XRP(10000), alice, becky);
456  env.close();
457 
458  // Verify that becky's account root is present.
459  Keylet const beckyAcctKey {keylet::account (becky.id())};
460  BEAST_EXPECT (env.closed()->exists (beckyAcctKey));
461 
462  using namespace std::chrono_literals;
463  Keylet const payChanKey {
464  keylet::payChan (alice, becky, env.seq (alice))};
465  auto const payChanXRP = XRP (37);
466 
467  env (payChanCreate (
468  alice, becky, payChanXRP, 4s, env.now() + 1h, alice.pk()));
469  env.close();
470  BEAST_EXPECT (env.closed()->exists (payChanKey));
471 
472  // Close enough ledgers to be able to delete becky's account.
473  incLgrSeqForAccDel(env, becky);
474 
475  auto const beckyPreDelBalance {env.balance (becky)};
476 
477  auto const acctDelFee {drops (env.current()->fees().increment)};
478  env (acctdelete (becky, alice), fee(acctDelFee));
480  env, beckyPreDelBalance - acctDelFee);
481  env.close();
482 
483  // Verify that becky's account root is gone.
484  BEAST_EXPECT (! env.closed()->exists (beckyAcctKey));
485 
486  // All it takes is a large enough XRP payment to resurrect
487  // becky's account. Try too small a payment.
488  env (pay (alice, becky, XRP (19)), ter (tecNO_DST_INSUF_XRP));
489  env.close();
490 
491  // Actually resurrect becky's account.
492  env (pay (alice, becky, XRP (20)));
493  env.close();
494 
495  // becky's account root should be back.
496  BEAST_EXPECT (env.closed()->exists (beckyAcctKey));
497  BEAST_EXPECT (env.balance (becky) == XRP (20));
498 
499  // becky's resurrected account can be the destination of alice's
500  // PayChannel.
501  auto payChanClaim = [&] ()
502  {
503  Json::Value jv;
504  jv[jss::TransactionType] = jss::PaymentChannelClaim;
505  jv[jss::Flags] = tfUniversal;
506  jv[jss::Account] = alice.human ();
507  jv[sfPayChannel.jsonName] = to_string (payChanKey.key);
508  jv[sfBalance.jsonName] =
509  payChanXRP.value().getJson (JsonOptions::none);
510  return jv;
511  };
512  env (payChanClaim());
513  env.close();
514 
515  BEAST_EXPECT (env.balance (becky) == XRP (20) + payChanXRP);
516  }
517 
519  {
520  // Start with the featureDeletableAccounts amendment disabled.
521  // Then enable the amendment and delete an account.
522  using namespace jtx;
523 
524  testcase("Amendment enable");
525 
527  Account const alice("alice");
528  Account const becky("becky");
529 
530  env.fund(XRP(10000), alice, becky);
531  env.close();
532 
533  // Close enough ledgers to be able to delete alice's account.
534  incLgrSeqForAccDel(env, alice);
535 
536  // Verify that alice's account root is present.
537  Keylet const aliceAcctKey {keylet::account (alice.id())};
538  BEAST_EXPECT (env.closed()->exists (aliceAcctKey));
539 
540  auto const alicePreDelBal {env.balance (alice)};
541  auto const beckyPreDelBal {env.balance (becky)};
542 
543  auto const acctDelFee {drops (env.current()->fees().increment)};
544  env (acctdelete (alice, becky), fee(acctDelFee), ter (temDISABLED));
545  env.close();
546 
547  // Verify that alice's account root is still present and alice and
548  // becky both have their XRP.
549  BEAST_EXPECT (env.current()->exists (aliceAcctKey));
550  BEAST_EXPECT (env.balance (alice) == alicePreDelBal);
551  BEAST_EXPECT (env.balance (becky) == beckyPreDelBal);
552 
553  // When the amendment is enabled the previous transaction is
554  // retried into the new open ledger and succeeds.
555  env.enableFeature (featureDeletableAccounts);
556  env.close();
557 
558  // alice's account is still in the most recently closed ledger.
559  BEAST_EXPECT (env.closed()->exists (aliceAcctKey));
560 
561  // Verify that alice's account root is gone from the current ledger
562  // and becky has alice's XRP.
563  BEAST_EXPECT (! env.current()->exists (aliceAcctKey));
564  BEAST_EXPECT (env.balance (becky) ==
565  alicePreDelBal + beckyPreDelBal - acctDelFee);
566 
567  env.close();
568  BEAST_EXPECT (! env.closed()->exists (aliceAcctKey));
569  }
570 
572  {
573  // Put enough offers in an account that we refuse to delete the account.
574  using namespace jtx;
575 
576  testcase("Too many offers");
577 
578  Env env {*this};
579  Account const alice("alice");
580  Account const gw("gw");
581 
582  // Fund alice well so she can afford the reserve on the offers.
583  env.fund(XRP(10000000), alice, gw);
584  env.close();
585 
586  // To increase the number of Books affected, change the currency of
587  // each offer.
588  std::string currency {"AAA"};
589 
590  // Alice creates 1001 offers. This is one greater than the number of
591  // directory entries an AccountDelete will remove.
592  std::uint32_t const offerSeq0 {env.seq (alice)};
593  constexpr int offerCount {1001};
594  for (int i {0}; i < offerCount; ++i)
595  {
596  env (offer (alice, gw[currency](1), XRP (1)));
597  env.close();
598 
599  // Increment to next currency.
600  ++currency[0];
601  if (currency[0] > 'Z')
602  {
603  currency[0] = 'A';
604  ++currency[1];
605  }
606  if (currency[1] > 'Z')
607  {
608  currency[1] = 'A';
609  ++currency[2];
610  }
611  if (currency[2] > 'Z')
612  {
613  currency[0] = 'A';
614  currency[1] = 'A';
615  currency[2] = 'A';
616  }
617  }
618 
619  // Close enough ledgers to be able to delete alice's account.
620  incLgrSeqForAccDel(env, alice);
621 
622  // Verify the existence of the expected ledger entries.
623  Keylet const aliceOwnerDirKey {keylet::ownerDir (alice.id())};
624  {
625  std::shared_ptr<ReadView const> closed {env.closed()};
626  BEAST_EXPECT (closed->exists (keylet::account (alice.id())));
627  BEAST_EXPECT (closed->exists (aliceOwnerDirKey));
628 
629  // alice's directory nodes.
630  for (std::uint32_t i {0}; i < ((offerCount / 32) + 1); ++i)
631  BEAST_EXPECT (closed->exists (
632  keylet::page (aliceOwnerDirKey, i)));
633 
634  // alice's offers.
635  for (std::uint32_t i {0}; i < offerCount; ++i)
636  BEAST_EXPECT (closed->exists (
637  keylet::offer (alice.id(), offerSeq0 + i)));
638  }
639 
640  // Delete alice's account. Should fail because she has too many
641  // offers in her directory.
642  auto const acctDelFee {drops (env.current()->fees().increment)};
643 
644  env (acctdelete (alice, gw), fee (acctDelFee), ter (tefTOO_BIG));
645 
646  // Cancel one of alice's offers. Then the account delete can succeed.
647  env.require (offers (alice, offerCount));
648  env(offer_cancel(alice, offerSeq0));
649  env.close();
650  env.require (offers (alice, offerCount - 1));
651 
652  // alice successfully deletes her account.
653  auto const alicePreDelBal {env.balance (alice)};
654  env (acctdelete (alice, gw), fee (acctDelFee));
655  verifyDeliveredAmount (env, alicePreDelBal - acctDelFee);
656  env.close();
657 
658  // Verify that alice's account root is gone as well as her directory
659  // nodes and all of her offers.
660  {
661  std::shared_ptr<ReadView const> closed {env.closed()};
662  BEAST_EXPECT (! closed->exists (keylet::account (alice.id())));
663  BEAST_EXPECT (! closed->exists (aliceOwnerDirKey));
664 
665  // alice's former directory nodes.
666  for (std::uint32_t i {0}; i < ((offerCount / 32) + 1); ++i)
667  BEAST_EXPECT (! closed->exists (
668  keylet::page (aliceOwnerDirKey, i)));
669 
670  // alice's former offers.
671  for (std::uint32_t i {0}; i < offerCount; ++i)
672  BEAST_EXPECT (! closed->exists (
673  keylet::offer (alice.id(), offerSeq0 + i)));
674  }
675  }
676 
678  {
679  // Show that a trust line that is implicitly created by offer crossing
680  // prevents an account from being deleted.
681  using namespace jtx;
682 
683  testcase("Implicitly created trust line");
684 
685  Env env {*this};
686  Account const alice{"alice"};
687  Account const gw{"gw"};
688  auto const BUX {gw["BUX"]};
689 
690  env.fund(XRP(10000), alice, gw);
691  env.close();
692 
693  // alice creates an offer that, if crossed, will implicitly create
694  // a trust line.
695  env (offer (alice, BUX(30), XRP(30)));
696  env.close();
697 
698  // gw crosses alice's offer. alice should end up with BUX(30).
699  env (offer (gw, XRP(30), BUX(30)));
700  env.close();
701  env.require (balance (alice, BUX(30)));
702 
703  // Close enough ledgers to be able to delete alice's account.
704  incLgrSeqForAccDel(env, alice);
705 
706  // alice and gw can't delete their accounts because of the implicitly
707  // created trust line.
708  auto const acctDelFee {drops (env.current()->fees().increment)};
709  env (acctdelete (alice, gw), fee (acctDelFee), ter(tecHAS_OBLIGATIONS));
710  env.close();
711 
712  env (acctdelete (gw, alice), fee (acctDelFee), ter(tecHAS_OBLIGATIONS));
713  env.close();
714  {
715  std::shared_ptr<ReadView const> closed {env.closed()};
716  BEAST_EXPECT (closed->exists (keylet::account (alice.id())));
717  BEAST_EXPECT (closed->exists (keylet::account (gw.id())));
718  }
719  }
720 
722  {
723  // See what happens when an account with a balance less than the
724  // incremental reserve tries to delete itself.
725  using namespace jtx;
726 
727  testcase("Balance too small for fee");
728 
729  Env env {*this};
730  Account const alice("alice");
731 
732  // Note that the fee structure for unit tests does not match the fees
733  // on the production network (October 2019). Unit tests have a base
734  // reserve of 200 XRP.
735  env.fund (env.current()->fees().accountReserve(0), noripple (alice));
736  env.close();
737 
738  // Burn a chunk of alice's funds so she only has 1 XRP remaining in
739  // her account.
740  env (noop (alice), fee (env.balance(alice) - XRP(1)));
741  env.close();
742 
743  auto const acctDelFee {drops (env.current()->fees().increment)};
744  BEAST_EXPECT (acctDelFee > env.balance (alice));
745 
746  // alice attempts to delete her account even though she can't pay
747  // the full fee. She specifies a fee that is larger than her balance.
748  //
749  // The balance of env.master should not change.
750  auto const masterBalance {env.balance (env.master)};
751  env (acctdelete (alice, env.master), fee(acctDelFee),
752  ter (terINSUF_FEE_B));
753  env.close();
754  {
755  std::shared_ptr<ReadView const> const closed {env.closed()};
756  BEAST_EXPECT (closed->exists (keylet::account (alice.id())));
757  BEAST_EXPECT (env.balance (env.master) == masterBalance);
758  }
759 
760  // alice again attempts to delete her account. This time she specifies
761  // her current balance in XRP. Again the transaction fails.
762  BEAST_EXPECT (env.balance (alice) == XRP (1));
763  env (acctdelete (alice, env.master), fee(XRP (1)),
764  ter (telINSUF_FEE_P));
765  env.close();
766  {
767  std::shared_ptr<ReadView const> closed {env.closed()};
768  BEAST_EXPECT (closed->exists (keylet::account (alice.id())));
769  BEAST_EXPECT (env.balance (env.master) == masterBalance);
770  }
771  }
772 
773  void run() override
774  {
775  testBasics();
776  testDirectories();
777  testOwnedTypes();
783  }
784 };
785 
786 BEAST_DEFINE_TESTSUITE(AccountDelete,app,ripple);
787 
788 } // test
789 } // ripple
ripple::test::jtx::noop
Json::Value noop(Account const &account)
The null transaction.
Definition: noop.h:32
ripple::test::AccountDelete_test::testBasics
void testBasics()
Definition: AccountDelete_test.cpp:104
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:109
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.
ripple::keylet::signers
static const signers_t signers
Definition: Indexes.h:239
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:261
ripple::terINSUF_FEE_B
@ terINSUF_FEE_B
Definition: TER.h:194
ripple::test::jtx::Env::tx
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition: Env.cpp:370
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::AccountDelete_test::testImplicitlyCreatedTrustline
void testImplicitlyCreatedTrustline()
Definition: AccountDelete_test.cpp:677
ripple::tfClose
const std::uint32_t tfClose
Definition: TxFlags.h:103
ripple::test::jtx::balance
A balance matches.
Definition: balance.h:38
ripple::sfDeliveredAmount
const SF_Amount sfDeliveredAmount(access, STI_AMOUNT, 18, "DeliveredAmount")
Definition: SField.h:437
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::STAmount::getJson
Json::Value getJson(JsonOptions) const override
Definition: STAmount.cpp:582
ripple::keylet::payChan
Keylet payChan(AccountID const &source, AccountID const &dst, std::uint32_t seq)
A PaymentChannel.
Definition: Indexes.cpp:350
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:42
ripple::test::AccountDelete_test::testAmendmentEnable
void testAmendmentEnable()
Definition: AccountDelete_test.cpp:518
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:104
ripple::test::AccountDelete_test::testDirectories
void testDirectories()
Definition: AccountDelete_test.cpp:228
ripple::test::AccountDelete_test::verifyDeliveredAmount
void verifyDeliveredAmount(jtx::Env &env, STAmount const &amount)
Definition: AccountDelete_test.cpp:39
ripple::PublicKey::slice
Slice slice() const noexcept
Definition: PublicKey.h:123
ripple::test::AccountDelete_test::testResurrection
void testResurrection()
Definition: AccountDelete_test.cpp:440
ripple::test::AccountDelete_test::openLedgerSeq
std::uint32_t openLedgerSeq(jtx::Env &env)
Definition: AccountDelete_test.cpp:30
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:41
ripple::sfPayChannel
const SF_U256 sfPayChannel(access, STI_HASH256, 22, "Channel")
Definition: SField.h:418
ripple::test::jtx::offer
Json::Value offer(Account const &account, STAmount const &in, STAmount const &out, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
ripple::test::AccountDelete_test::testTooManyOffers
void testTooManyOffers()
Definition: AccountDelete_test.cpp:571
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:140
ripple::test::jtx::msig
Set a multisignature on a JTx.
Definition: multisign.h:59
ripple::temDST_IS_SRC
@ temDST_IS_SRC
Definition: TER.h:106
ripple::sfFinishAfter
const SF_U32 sfFinishAfter(access, STI_UINT32, 37, "FinishAfter")
Definition: SField.h:374
ripple::keylet::depositPreauth
static const depositPreauth_t depositPreauth
Definition: Indexes.h:269
ripple::featureDeletableAccounts
const uint256 featureDeletableAccounts
Definition: Feature.cpp:177
ripple::test::jtx::Account::id
AccountID id() const
Returns the Account ID.
Definition: Account.h:97
ripple::tecNO_DST_INSUF_XRP
@ tecNO_DST_INSUF_XRP
Definition: TER.h:256
ripple::base_uint< 256 >
ripple::getCheckIndex
uint256 getCheckIndex(AccountID const &account, std::uint32_t uSequence)
Definition: Indexes.cpp:186
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:273
ripple::sfOwner
const SF_Account sfOwner(access, STI_ACCOUNT, 2, "Owner")
Definition: SField.h:461
ripple::test::jtx::signers
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:33
ripple::test::AccountDelete_test::testBalanceTooSmallForFee
void testBalanceTooSmallForFee()
Definition: AccountDelete_test.cpp:721
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::keylet::account
static const account_t account
Definition: Indexes.h:116
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:87
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id)
The root page of an account's directory.
Definition: Indexes.cpp:318
ripple::telINSUF_FEE_P
@ telINSUF_FEE_P
Definition: TER.h:58
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:176
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:961
ripple::test::jtx::supported_amendments
FeatureBitset supported_amendments()
Definition: Env.h:71
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:44
ripple::test::jtx::Env::seq
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:192
ripple::tfUniversal
const std::uint32_t tfUniversal
Definition: TxFlags.h:49
ripple::test::jtx::fee
Set the fee on a JTx.
Definition: fee.h:34
ripple::tecTOO_SOON
@ tecTOO_SOON
Definition: TER.h:283
ripple::test::jtx::seq
Set the sequence number on a JTx.
Definition: seq.h:32
ripple::sfPublicKey
const SF_Blob sfPublicKey(access, STI_VL, 1, "PublicKey")
Definition: SField.h:440
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:773
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::sfBalance
const SF_Amount sfBalance(access, STI_AMOUNT, 2, "Balance")
Definition: SField.h:424
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
ripple::sfOfferSequence
const SF_U32 sfOfferSequence(access, STI_UINT32, 25, "OfferSequence")
Definition: SField.h:362
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
ripple::test::jtx::Env::close
void close(NetClock::time_point closeTime, boost::optional< std::chrono::milliseconds > consensusDelay=boost::none)
Close and advance the ledger.
Definition: Env.cpp:114
ripple::tefTOO_BIG
@ tefTOO_BIG
Definition: TER.h:164
ripple::tecHAS_OBLIGATIONS
@ tecHAS_OBLIGATIONS
Definition: TER.h:282
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::sfSettleDelay
const SF_U32 sfSettleDelay(access, STI_UINT32, 39, "SettleDelay")
Definition: SField.h:376
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:70
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:63
ripple::keylet::offer
static const offer_t offer
Definition: Indexes.h:191
ripple::test::jtx::Account::pk
PublicKey const & pk() const
Return the public key.
Definition: Account.h:80
ripple::test::jtx::Env::current
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:296
ripple::sfCancelAfter
const SF_U32 sfCancelAfter(access, STI_UINT32, 36, "CancelAfter")
Definition: SField.h:373
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117
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:688
Json::Value
Represents a JSON value.
Definition: json_value.h:141
ripple::keylet::page
Keylet page(uint256 const &key, std::uint64_t index)
A page in a directory.
Definition: Indexes.cpp:324
ripple::test::jtx::owner_count
Definition: owners.h:50