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