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