rippled
Loading...
Searching...
No Matches
AccountDelete_test.cpp
1#include <test/jtx.h>
2
3#include <xrpl/protocol/Feature.h>
4#include <xrpl/protocol/jss.h>
5
6namespace xrpl {
7namespace test {
8
10{
11private:
12 // Helper function that verifies the expected DeliveredAmount is present.
13 //
14 // NOTE: the function _infers_ the transaction to operate on by calling
15 // env.tx(), which returns the result from the most recent transaction.
16 void
18 {
19 // Get the hash for the most recent transaction.
20 std::string const txHash{env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
21
22 // Verify DeliveredAmount and delivered_amount metadata are correct.
23 // We can't use env.meta() here, because meta() doesn't include
24 // delivered_amount.
25 env.close();
26 Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
27
28 // Expect there to be a DeliveredAmount field.
29 if (!BEAST_EXPECT(meta.isMember(sfDeliveredAmount.jsonName)))
30 return;
31
32 // DeliveredAmount and delivered_amount should both be present and
33 // equal amount.
34 Json::Value const jsonExpect{amount.getJson(JsonOptions::none)};
35 BEAST_EXPECT(meta[sfDeliveredAmount.jsonName] == jsonExpect);
36 BEAST_EXPECT(meta[jss::delivered_amount] == jsonExpect);
37 }
38
39 // Helper function to create a payment channel.
40 static Json::Value
42 jtx::Account const& account,
43 jtx::Account const& to,
44 STAmount const& amount,
45 NetClock::duration const& settleDelay,
46 NetClock::time_point const& cancelAfter,
47 PublicKey const& pk)
48 {
49 Json::Value jv;
50 jv[jss::TransactionType] = jss::PaymentChannelCreate;
51 jv[jss::Account] = account.human();
52 jv[jss::Destination] = to.human();
53 jv[jss::Amount] = amount.getJson(JsonOptions::none);
54 jv[sfSettleDelay.jsonName] = settleDelay.count();
55 jv[sfCancelAfter.jsonName] = cancelAfter.time_since_epoch().count() + 2;
56 jv[sfPublicKey.jsonName] = strHex(pk.slice());
57 return jv;
58 };
59
60public:
61 void
63 {
64 using namespace jtx;
65
66 testcase("Basics");
67
68 Env env{*this};
69 Account const alice("alice");
70 Account const becky("becky");
71 Account const carol("carol");
72 Account const gw("gw");
73
74 env.fund(XRP(10000), alice, becky, carol, gw);
75 env.close();
76
77 // Alice can't delete her account and then give herself the XRP.
78 env(acctdelete(alice, alice), ter(temDST_IS_SRC));
79
80 // alice can't delete her account with a negative fee.
81 env(acctdelete(alice, becky), fee(drops(-1)), ter(temBAD_FEE));
82
83 // Invalid flags.
84 env(acctdelete(alice, becky), txflags(tfImmediateOrCancel), ter(temINVALID_FLAG));
85
86 // Account deletion has a high fee. Make sure the fee requirement
87 // behaves as we expect.
88 auto const acctDelFee{drops(env.current()->fees().increment)};
89 env(acctdelete(alice, becky), ter(telINSUF_FEE_P));
90
91 // Try a fee one drop less than the required amount.
92 env(acctdelete(alice, becky), fee(acctDelFee - drops(1)), ter(telINSUF_FEE_P));
93
94 // alice's account is created too recently to be deleted.
95 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
96
97 // Give becky a trustline. She is no longer deletable.
98 env(trust(becky, gw["USD"](1000)));
99 env.close();
100
101 // Give carol a deposit pre-authorization, an offer, a ticket,
102 // a signer list, and a DID. Even with all that she's still deletable.
103 env(deposit::auth(carol, becky));
104 std::uint32_t const carolOfferSeq{env.seq(carol)};
105 env(offer(carol, gw["USD"](51), XRP(51)));
106 std::uint32_t const carolTicketSeq{env.seq(carol) + 1};
107 env(ticket::create(carol, 1));
108 env(signers(carol, 1, {{alice, 1}, {becky, 1}}));
109 env(did::setValid(carol));
110
111 // Deleting should fail with TOO_SOON, which is a relatively
112 // cheap check compared to validating the contents of her directory.
113 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
114
115 // Close enough ledgers to almost be able to delete alice's account.
116 incLgrSeqForAccDel(env, alice, 1);
117
118 // alice's account is still created too recently to be deleted.
119 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
120
121 // The most recent delete attempt advanced alice's sequence. So
122 // close two ledgers and her account should be deletable.
123 env.close();
124 env.close();
125
126 {
127 auto const aliceOldBalance{env.balance(alice)};
128 auto const beckyOldBalance{env.balance(becky)};
129
130 // Verify that alice's account exists but she has no directory.
131 BEAST_EXPECT(env.closed()->exists(keylet::account(alice.id())));
132 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
133
134 env(acctdelete(alice, becky), fee(acctDelFee));
135 verifyDeliveredAmount(env, aliceOldBalance - acctDelFee);
136 env.close();
137
138 // Verify that alice's account and directory are actually gone.
139 BEAST_EXPECT(!env.closed()->exists(keylet::account(alice.id())));
140 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
141
142 // Verify that alice's XRP, minus the fee, was transferred to becky.
143 BEAST_EXPECT(env.balance(becky) == aliceOldBalance + beckyOldBalance - acctDelFee);
144 }
145
146 // Attempt to delete becky's account but get stopped by the trust line.
147 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
148 env.close();
149
150 // Verify that becky's account is still there by giving her a regular
151 // key. This has the side effect of setting the lsfPasswordSpent bit
152 // on her account root.
153 Account const beck("beck");
154 env(regkey(becky, beck), fee(drops(0)));
155 env.close();
156
157 // Show that the lsfPasswordSpent bit is set by attempting to change
158 // becky's regular key for free again. That fails.
159 Account const reb("reb");
160 env(regkey(becky, reb), sig(becky), fee(drops(0)), ter(telINSUF_FEE_P));
161
162 // Close enough ledgers that becky's failing regkey transaction is
163 // no longer retried.
164 for (int i = 0; i < 8; ++i)
165 env.close();
166
167 {
168 auto const beckyOldBalance{env.balance(becky)};
169 auto const carolOldBalance{env.balance(carol)};
170
171 // Verify that Carol's account, directory, deposit
172 // pre-authorization, offer, ticket, and signer list exist.
173 BEAST_EXPECT(env.closed()->exists(keylet::account(carol.id())));
174 BEAST_EXPECT(env.closed()->exists(keylet::ownerDir(carol.id())));
175 BEAST_EXPECT(env.closed()->exists(keylet::depositPreauth(carol.id(), becky.id())));
176 BEAST_EXPECT(env.closed()->exists(keylet::offer(carol.id(), carolOfferSeq)));
177 BEAST_EXPECT(env.closed()->exists(keylet::ticket(carol.id(), carolTicketSeq)));
178 BEAST_EXPECT(env.closed()->exists(keylet::signers(carol.id())));
179
180 // Delete carol's account even with stuff in her directory. Show
181 // that multisigning for the delete does not increase carol's fee.
182 env(acctdelete(carol, becky), fee(acctDelFee), msig(alice));
183 verifyDeliveredAmount(env, carolOldBalance - acctDelFee);
184 env.close();
185
186 // Verify that Carol's account, directory, and other stuff are gone.
187 BEAST_EXPECT(!env.closed()->exists(keylet::account(carol.id())));
188 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(carol.id())));
189 BEAST_EXPECT(!env.closed()->exists(keylet::depositPreauth(carol.id(), becky.id())));
190 BEAST_EXPECT(!env.closed()->exists(keylet::offer(carol.id(), carolOfferSeq)));
191 BEAST_EXPECT(!env.closed()->exists(keylet::ticket(carol.id(), carolTicketSeq)));
192 BEAST_EXPECT(!env.closed()->exists(keylet::signers(carol.id())));
193
194 // Verify that Carol's XRP, minus the fee, was transferred to becky.
195 BEAST_EXPECT(env.balance(becky) == carolOldBalance + beckyOldBalance - acctDelFee);
196
197 // Since becky received an influx of XRP, her lsfPasswordSpent bit
198 // is cleared and she can change her regular key for free again.
199 env(regkey(becky, reb), sig(becky), fee(drops(0)));
200 }
201 }
202
203 void
205 {
206 // The code that deletes consecutive directory entries uses a
207 // peculiarity of the implementation. Make sure that peculiarity
208 // behaves as expected across owner directory pages.
209 using namespace jtx;
210
211 testcase("Directories");
212
213 Env env{*this};
214 Account const alice("alice");
215 Account const gw("gw");
216
217 env.fund(XRP(10000), alice, gw);
218 env.close();
219
220 // Alice creates enough offers to require two owner directories.
221 for (int i{0}; i < 45; ++i)
222 {
223 env(offer(alice, gw["USD"](1), XRP(1)));
224 env.close();
225 }
226 env.require(offers(alice, 45));
227
228 // Close enough ledgers to be able to delete alice's account.
229 incLgrSeqForAccDel(env, alice);
230
231 // Verify that both directory nodes exist.
232 Keylet const aliceRootKey{keylet::ownerDir(alice.id())};
233 Keylet const alicePageKey{keylet::page(aliceRootKey, 1)};
234 BEAST_EXPECT(env.closed()->exists(aliceRootKey));
235 BEAST_EXPECT(env.closed()->exists(alicePageKey));
236
237 // Delete alice's account.
238 auto const acctDelFee{drops(env.current()->fees().increment)};
239 auto const aliceBalance{env.balance(alice)};
240 env(acctdelete(alice, gw), fee(acctDelFee));
241 verifyDeliveredAmount(env, aliceBalance - acctDelFee);
242 env.close();
243
244 // Both of alice's directory nodes should be gone.
245 BEAST_EXPECT(!env.closed()->exists(aliceRootKey));
246 BEAST_EXPECT(!env.closed()->exists(alicePageKey));
247 }
248
249 void
251 {
252 using namespace jtx;
253
254 testcase("Owned types");
255
256 // We want to test PayChannels with the backlink.
257 Env env{*this, testable_amendments()};
258 Account const alice("alice");
259 Account const becky("becky");
260 Account const gw("gw");
261
262 env.fund(XRP(100000), alice, becky, gw);
263 env.close();
264
265 // Give alice and becky a bunch of offers that we have to search
266 // through before we figure out that there's a non-deletable
267 // entry in their directory.
268 for (int i{0}; i < 200; ++i)
269 {
270 env(offer(alice, gw["USD"](1), XRP(1)));
271 env(offer(becky, gw["USD"](1), XRP(1)));
272 env.close();
273 }
274 env.require(offers(alice, 200));
275 env.require(offers(becky, 200));
276
277 // Close enough ledgers to be able to delete alice's and becky's
278 // accounts.
279 incLgrSeqForAccDel(env, alice);
280 incLgrSeqForAccDel(env, becky);
281
282 // alice writes a check to becky. Until that check is cashed or
283 // canceled it will prevent alice's and becky's accounts from being
284 // deleted.
285 uint256 const checkId = keylet::check(alice, env.seq(alice)).key;
286 env(check::create(alice, becky, XRP(1)));
287 env.close();
288
289 auto const acctDelFee{drops(env.current()->fees().increment)};
290 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
291 env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
292 env.close();
293
294 // Cancel the check, but add an escrow. Again, with the escrow
295 // on board, alice and becky should not be able to delete their
296 // accounts.
297 env(check::cancel(becky, checkId));
298 env.close();
299
300 using namespace std::chrono_literals;
301 std::uint32_t const escrowSeq{env.seq(alice)};
302 env(escrow::create(alice, becky, XRP(333)),
303 escrow::finish_time(env.now() + 3s),
304 escrow::cancel_time(env.now() + 4s));
305 env.close();
306
307 // alice and becky should be unable to delete their accounts because
308 // of the escrow.
309 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
310 env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
311 env.close();
312
313 // Now cancel the escrow, but create a payment channel between
314 // alice and becky.
315
316 bool const withTokenEscrow = env.current()->rules().enabled(featureTokenEscrow);
317 if (withTokenEscrow)
318 {
319 Account const gw1("gw1");
320 Account const carol("carol");
321 auto const USD = gw1["USD"];
322 env.fund(XRP(100000), carol, gw1);
323 env(fset(gw1, asfAllowTrustLineLocking));
324 env.close();
325 env.trust(USD(10000), carol);
326 env.close();
327 env(pay(gw1, carol, USD(100)));
328 env.close();
329
330 std::uint32_t const escrowSeq{env.seq(carol)};
331 env(escrow::create(carol, becky, USD(1)),
332 escrow::finish_time(env.now() + 3s),
333 escrow::cancel_time(env.now() + 4s));
334 env.close();
335
336 incLgrSeqForAccDel(env, gw1);
337
338 env(acctdelete(gw1, becky), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
339 env.close();
340
341 env(escrow::cancel(becky, carol, escrowSeq));
342 env.close();
343 }
344
345 env(escrow::cancel(becky, alice, escrowSeq));
346 env.close();
347
348 Keylet const alicePayChanKey{keylet::payChan(alice, becky, env.seq(alice))};
349
350 env(payChanCreate(alice, becky, XRP(57), 4s, env.now() + 2s, alice.pk()));
351 env.close();
352
353 // With the PayChannel in place becky and alice should not be
354 // able to delete her account
355 auto const beckyBalance{env.balance(becky)};
356 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
357 env(acctdelete(becky, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
358 env.close();
359
360 // Alice cancels her PayChannel, which will leave her with only offers
361 // in her directory.
362
363 // Lambda to close a PayChannel.
364 auto payChanClose =
365 [](jtx::Account const& account, Keylet const& payChanKeylet, PublicKey const& pk) {
366 Json::Value jv;
367 jv[jss::TransactionType] = jss::PaymentChannelClaim;
368 jv[jss::Flags] = tfClose;
369 jv[jss::Account] = account.human();
370 jv[sfChannel.jsonName] = to_string(payChanKeylet.key);
371 jv[sfPublicKey.jsonName] = strHex(pk.slice());
372 return jv;
373 };
374 env(payChanClose(alice, alicePayChanKey, alice.pk()));
375 env.close();
376
377 // gw creates a PayChannel with alice as the destination, this should
378 // prevent alice from deleting her account.
379 Keylet const gwPayChanKey{keylet::payChan(gw, alice, env.seq(gw))};
380
381 env(payChanCreate(gw, alice, XRP(68), 4s, env.now() + 2s, alice.pk()));
382 env.close();
383
384 // alice can't delete her account because of the PayChannel.
385 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
386 env.close();
387
388 // alice closes the PayChannel which should (finally) allow her to
389 // delete her account.
390 env(payChanClose(alice, gwPayChanKey, alice.pk()));
391 env.close();
392
393 // Now alice can successfully delete her account.
394 auto const aliceBalance{env.balance(alice)};
395 env(acctdelete(alice, gw), fee(acctDelFee));
396 verifyDeliveredAmount(env, aliceBalance - acctDelFee);
397 env.close();
398 }
399
400 void
402 {
403 // Put enough offers in an account that we refuse to delete the account.
404 using namespace jtx;
405
406 testcase("Too many offers");
407
408 Env env{*this};
409 Account const alice("alice");
410 Account const gw("gw");
411
412 // Fund alice well so she can afford the reserve on the offers.
413 env.fund(XRP(10000000), alice, gw);
414 env.close();
415
416 // To increase the number of Books affected, change the currency of
417 // each offer.
418 std::string currency{"AAA"};
419
420 // Alice creates 1001 offers. This is one greater than the number of
421 // directory entries an AccountDelete will remove.
422 std::uint32_t const offerSeq0{env.seq(alice)};
423 constexpr int offerCount{1001};
424 for (int i{0}; i < offerCount; ++i)
425 {
426 env(offer(alice, gw[currency](1), XRP(1)));
427 env.close();
428
429 // Increment to next currency.
430 ++currency[0];
431 if (currency[0] > 'Z')
432 {
433 currency[0] = 'A';
434 ++currency[1];
435 }
436 if (currency[1] > 'Z')
437 {
438 currency[1] = 'A';
439 ++currency[2];
440 }
441 if (currency[2] > 'Z')
442 {
443 currency[0] = 'A';
444 currency[1] = 'A';
445 currency[2] = 'A';
446 }
447 }
448
449 // Close enough ledgers to be able to delete alice's account.
450 incLgrSeqForAccDel(env, alice);
451
452 // Verify the existence of the expected ledger entries.
453 Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
454 {
455 std::shared_ptr<ReadView const> const closed{env.closed()};
456 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
457 BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
458
459 // alice's directory nodes.
460 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
461 BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
462
463 // alice's offers.
464 for (std::uint32_t i{0}; i < offerCount; ++i)
465 BEAST_EXPECT(closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
466 }
467
468 // Delete alice's account. Should fail because she has too many
469 // offers in her directory.
470 auto const acctDelFee{drops(env.current()->fees().increment)};
471
472 env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
473
474 // Cancel one of alice's offers. Then the account delete can succeed.
475 env.require(offers(alice, offerCount));
476 env(offer_cancel(alice, offerSeq0));
477 env.close();
478 env.require(offers(alice, offerCount - 1));
479
480 // alice successfully deletes her account.
481 auto const alicePreDelBal{env.balance(alice)};
482 env(acctdelete(alice, gw), fee(acctDelFee));
483 verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
484 env.close();
485
486 // Verify that alice's account root is gone as well as her directory
487 // nodes and all of her offers.
488 {
489 std::shared_ptr<ReadView const> const closed{env.closed()};
490 BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
491 BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
492
493 // alice's former directory nodes.
494 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
495 BEAST_EXPECT(!closed->exists(keylet::page(aliceOwnerDirKey, i)));
496
497 // alice's former offers.
498 for (std::uint32_t i{0}; i < offerCount; ++i)
499 BEAST_EXPECT(!closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
500 }
501 }
502
503 void
505 {
506 // Show that a trust line that is implicitly created by offer crossing
507 // prevents an account from being deleted.
508 using namespace jtx;
509
510 testcase("Implicitly created trust line");
511
512 Env env{*this};
513 Account const alice{"alice"};
514 Account const gw{"gw"};
515 auto const BUX{gw["BUX"]};
516
517 env.fund(XRP(10000), alice, gw);
518 env.close();
519
520 // alice creates an offer that, if crossed, will implicitly create
521 // a trust line.
522 env(offer(alice, BUX(30), XRP(30)));
523 env.close();
524
525 // gw crosses alice's offer. alice should end up with BUX(30).
526 env(offer(gw, XRP(30), BUX(30)));
527 env.close();
528 env.require(balance(alice, BUX(30)));
529
530 // Close enough ledgers to be able to delete alice's account.
531 incLgrSeqForAccDel(env, alice);
532
533 // alice and gw can't delete their accounts because of the implicitly
534 // created trust line.
535 auto const acctDelFee{drops(env.current()->fees().increment)};
536 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
537 env.close();
538
539 env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
540 env.close();
541 {
542 std::shared_ptr<ReadView const> const closed{env.closed()};
543 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
544 BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
545 }
546 }
547
548 void
550 {
551 // See what happens when an account with a balance less than the
552 // incremental reserve tries to delete itself.
553 using namespace jtx;
554
555 testcase("Balance too small for fee");
556
557 Env env{*this};
558 Account const alice("alice");
559
560 // Note that the fee structure for unit tests does not match the fees
561 // on the production network (October 2019). Unit tests have a base
562 // reserve of 200 XRP.
563 env.fund(env.current()->fees().reserve, noripple(alice));
564 env.close();
565
566 // Burn a chunk of alice's funds so she only has 1 XRP remaining in
567 // her account.
568 env(noop(alice), fee(env.balance(alice) - XRP(1)));
569 env.close();
570
571 auto const acctDelFee{drops(env.current()->fees().increment)};
572 BEAST_EXPECT(acctDelFee > env.balance(alice));
573
574 // alice attempts to delete her account even though she can't pay
575 // the full fee. She specifies a fee that is larger than her balance.
576 //
577 // The balance of env.master should not change.
578 auto const masterBalance{env.balance(env.master)};
579 env(acctdelete(alice, env.master), fee(acctDelFee), ter(terINSUF_FEE_B));
580 env.close();
581 {
582 std::shared_ptr<ReadView const> const closed{env.closed()};
583 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
584 BEAST_EXPECT(env.balance(env.master) == masterBalance);
585 }
586
587 // alice again attempts to delete her account. This time she specifies
588 // her current balance in XRP. Again the transaction fails.
589 BEAST_EXPECT(env.balance(alice) == XRP(1));
590 env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
591 env.close();
592 {
593 std::shared_ptr<ReadView const> const closed{env.closed()};
594 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
595 BEAST_EXPECT(env.balance(env.master) == masterBalance);
596 }
597 }
598
599 void
601 {
602 testcase("With Tickets");
603
604 using namespace test::jtx;
605
606 Account const alice{"alice"};
607 Account const bob{"bob"};
608
609 Env env{*this};
610 env.fund(XRP(100000), alice, bob);
611 env.close();
612
613 // bob grabs as many tickets as he is allowed to have.
614 std::uint32_t const ticketSeq{env.seq(bob) + 1};
615 env(ticket::create(bob, 250));
616 env.close();
617 env.require(owners(bob, 250));
618
619 {
620 std::shared_ptr<ReadView const> const closed{env.closed()};
621 BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
622 for (std::uint32_t i = 0; i < 250; ++i)
623 {
624 BEAST_EXPECT(closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
625 }
626 }
627
628 // Close enough ledgers to be able to delete bob's account.
629 incLgrSeqForAccDel(env, bob);
630
631 // bob deletes his account using a ticket. bob's account and all
632 // of his tickets should be removed from the ledger.
633 auto const acctDelFee{drops(env.current()->fees().increment)};
634 auto const bobOldBalance{env.balance(bob)};
635 env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
636 verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
637 env.close();
638 {
639 std::shared_ptr<ReadView const> const closed{env.closed()};
640 BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
641 for (std::uint32_t i = 0; i < 250; ++i)
642 {
643 BEAST_EXPECT(!closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
644 }
645 }
646 }
647
648 void
650 {
651 testcase("Destination Constraints");
652
653 using namespace test::jtx;
654
655 Account const alice{"alice"};
656 Account const becky{"becky"};
657 Account const carol{"carol"};
658 Account const daria{"daria"};
659
660 Env env{*this};
661 env.fund(XRP(100000), alice, becky, carol);
662 env.close();
663
664 // alice sets the lsfDepositAuth flag on her account. This should
665 // prevent becky from deleting her account while using alice as the
666 // destination.
667 env(fset(alice, asfDepositAuth));
668
669 // carol requires a destination tag.
670 env(fset(carol, asfRequireDest));
671 env.close();
672
673 // Close enough ledgers to be able to delete becky's account.
674 incLgrSeqForAccDel(env, becky);
675
676 // becky attempts to delete her account using daria as the destination.
677 // Since daria is not in the ledger the delete attempt fails.
678 auto const acctDelFee{drops(env.current()->fees().increment)};
679 env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
680 env.close();
681
682 // becky attempts to delete her account, but carol requires a
683 // destination tag which becky has omitted.
684 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
685 env.close();
686
687 // becky attempts to delete her account, but alice won't take her XRP,
688 // so the delete is blocked.
689 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
690 env.close();
691
692 // alice preauthorizes deposits from becky. Now becky can delete her
693 // account and forward the leftovers to alice.
694 env(deposit::auth(alice, becky));
695 env.close();
696
697 auto const beckyOldBalance{env.balance(becky)};
698 env(acctdelete(becky, alice), fee(acctDelFee));
699 verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
700 env.close();
701 }
702
703 void
705 {
706 {
707 testcase("Destination Constraints with DepositPreauth and Credentials");
708
709 using namespace test::jtx;
710
711 Account const alice{"alice"};
712 Account const becky{"becky"};
713 Account const carol{"carol"};
714 Account const daria{"daria"};
715
716 char const credType[] = "abcd";
717
718 Env env{*this};
719 env.fund(XRP(100000), alice, becky, carol, daria);
720 env.close();
721
722 // carol issue credentials for becky
723 env(credentials::create(becky, carol, credType));
724 env.close();
725
726 // get credentials index
727 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
728 std::string const credIdx = jv[jss::result][jss::index].asString();
729
730 // Close enough ledgers to be able to delete becky's account.
731 incLgrSeqForAccDel(env, becky);
732
733 auto const acctDelFee{drops(env.current()->fees().increment)};
734
735 // becky use credentials but they aren't accepted
736 env(acctdelete(becky, alice),
737 credentials::ids({credIdx}),
738 fee(acctDelFee),
740 env.close();
741
742 {
743 // alice sets the lsfDepositAuth flag on her account. This
744 // should prevent becky from deleting her account while using
745 // alice as the destination.
746 env(fset(alice, asfDepositAuth));
747 env.close();
748 }
749
750 // Fail, credentials still not accepted
751 env(acctdelete(becky, alice),
752 credentials::ids({credIdx}),
753 fee(acctDelFee),
755 env.close();
756
757 // becky accept the credentials
758 env(credentials::accept(becky, carol, credType));
759 env.close();
760
761 // Fail, credentials doesn’t belong to carol
762 env(acctdelete(carol, alice),
763 credentials::ids({credIdx}),
764 fee(acctDelFee),
766
767 // Fail, no depositPreauth for provided credentials
768 env(acctdelete(becky, alice),
769 credentials::ids({credIdx}),
770 fee(acctDelFee),
772 env.close();
773
774 // alice create DepositPreauth Object
775 env(deposit::authCredentials(alice, {{carol, credType}}));
776 env.close();
777
778 // becky attempts to delete her account, but alice won't take her
779 // XRP, so the delete is blocked.
780 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
781
782 // becky use empty credentials and can't delete account
783 env(acctdelete(becky, alice), fee(acctDelFee), credentials::ids({}), ter(temMALFORMED));
784
785 // becky use bad credentials and can't delete account
786 env(acctdelete(becky, alice),
787 credentials::ids({"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6E"
788 "A288BE4"}),
789 fee(acctDelFee),
791 env.close();
792
793 // becky use credentials and can delete account
794 env(acctdelete(becky, alice), credentials::ids({credIdx}), fee(acctDelFee));
795 env.close();
796
797 {
798 // check that credential object deleted too
799 auto const jNoCred = credentials::ledgerEntry(env, becky, carol, credType);
800 BEAST_EXPECT(
801 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
802 jNoCred[jss::result].isMember(jss::error) &&
803 jNoCred[jss::result][jss::error] == "entryNotFound");
804 }
805
806 testcase("Credentials that aren't required");
807 { // carol issue credentials for daria
808 env(credentials::create(daria, carol, credType));
809 env.close();
810 env(credentials::accept(daria, carol, credType));
811 env.close();
812 std::string const credDaria =
813 credentials::ledgerEntry(env, daria, carol, credType)[jss::result][jss::index]
814 .asString();
815
816 // daria use valid credentials, which aren't required and can
817 // delete her account
818 env(acctdelete(daria, carol), credentials::ids({credDaria}), fee(acctDelFee));
819 env.close();
820
821 // check that credential object deleted too
822 auto const jNoCred = credentials::ledgerEntry(env, daria, carol, credType);
823
824 BEAST_EXPECT(
825 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
826 jNoCred[jss::result].isMember(jss::error) &&
827 jNoCred[jss::result][jss::error] == "entryNotFound");
828 }
829
830 {
831 Account const eaton{"eaton"};
832 Account const fred{"fred"};
833
834 env.fund(XRP(5000), eaton, fred);
835
836 // carol issue credentials for eaton
837 env(credentials::create(eaton, carol, credType));
838 env.close();
839 env(credentials::accept(eaton, carol, credType));
840 env.close();
841 std::string const credEaton =
842 credentials::ledgerEntry(env, eaton, carol, credType)[jss::result][jss::index]
843 .asString();
844
845 // fred make pre-authorization through authorized account
846 env(fset(fred, asfDepositAuth));
847 env.close();
848 env(deposit::auth(fred, eaton));
849 env.close();
850
851 // Close enough ledgers to be able to delete becky's account.
852 incLgrSeqForAccDel(env, eaton);
853 auto const acctDelFee{drops(env.current()->fees().increment)};
854
855 // eaton use valid credentials, but he already authorized
856 // through "Authorized" field.
857 env(acctdelete(eaton, fred), credentials::ids({credEaton}), fee(acctDelFee));
858 env.close();
859
860 // check that credential object deleted too
861 auto const jNoCred = credentials::ledgerEntry(env, eaton, carol, credType);
862
863 BEAST_EXPECT(
864 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
865 jNoCred[jss::result].isMember(jss::error) &&
866 jNoCred[jss::result][jss::error] == "entryNotFound");
867 }
868
869 testcase("Expired credentials");
870 {
871 Account const john{"john"};
872
873 env.fund(XRP(10000), john);
874 env.close();
875
876 auto jv = credentials::create(john, carol, credType);
877 uint32_t const t =
878 env.current()->header().parentCloseTime.time_since_epoch().count() + 20;
879 jv[sfExpiration.jsonName] = t;
880 env(jv);
881 env.close();
882 env(credentials::accept(john, carol, credType));
883 env.close();
884 jv = credentials::ledgerEntry(env, john, carol, credType);
885 std::string const credIdx = jv[jss::result][jss::index].asString();
886
887 incLgrSeqForAccDel(env, john);
888
889 // credentials are expired
890 // john use credentials but can't delete account
891 env(acctdelete(john, alice),
892 credentials::ids({credIdx}),
893 fee(acctDelFee),
894 ter(tecEXPIRED));
895 env.close();
896
897 {
898 // check that expired credential object deleted
899 auto jv = credentials::ledgerEntry(env, john, carol, credType);
900 BEAST_EXPECT(
901 jv.isObject() && jv.isMember(jss::result) &&
902 jv[jss::result].isMember(jss::error) &&
903 jv[jss::result][jss::error] == "entryNotFound");
904 }
905 }
906 }
907
908 {
909 testcase("Credentials feature disabled");
910 using namespace test::jtx;
911
912 Account const alice{"alice"};
913 Account const becky{"becky"};
914 Account const carol{"carol"};
915
916 Env env{*this, testable_amendments() - featureCredentials};
917 env.fund(XRP(100000), alice, becky, carol);
918 env.close();
919
920 // alice sets the lsfDepositAuth flag on her account. This should
921 // prevent becky from deleting her account while using alice as the
922 // destination.
923 env(fset(alice, asfDepositAuth));
924 env.close();
925
926 // Close enough ledgers to be able to delete becky's account.
927 incLgrSeqForAccDel(env, becky);
928
929 auto const acctDelFee{drops(env.current()->fees().increment)};
930
931 std::string const credIdx =
932 "098B7F1B146470A1C5084DC7832C04A72939E3EBC58E68AB8B579BA072B0CE"
933 "CB";
934
935 // and can't delete even with old DepositPreauth
936 env(deposit::auth(alice, becky));
937 env.close();
938
939 env(acctdelete(becky, alice),
940 credentials::ids({credIdx}),
941 fee(acctDelFee),
943 env.close();
944 }
945 }
946
947 void
949 {
950 {
951 testcase("Deleting Issuer deletes issued credentials");
952
953 using namespace test::jtx;
954
955 Account const alice{"alice"};
956 Account const becky{"becky"};
957 Account const carol{"carol"};
958
959 char const credType[] = "abcd";
960
961 Env env{*this};
962 env.fund(XRP(100000), alice, becky, carol);
963 env.close();
964
965 // carol issue credentials for becky
966 env(credentials::create(becky, carol, credType));
967 env.close();
968 env(credentials::accept(becky, carol, credType));
969 env.close();
970
971 // get credentials index
972 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
973 std::string const credIdx = jv[jss::result][jss::index].asString();
974
975 // Close enough ledgers to be able to delete carol's account.
976 incLgrSeqForAccDel(env, carol);
977
978 auto const acctDelFee{drops(env.current()->fees().increment)};
979 env(acctdelete(carol, alice), fee(acctDelFee));
980 env.close();
981
982 { // check that credential object deleted too
983 BEAST_EXPECT(!env.le(credIdx));
984 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
985 BEAST_EXPECT(
986 jv.isObject() && jv.isMember(jss::result) &&
987 jv[jss::result].isMember(jss::error) &&
988 jv[jss::result][jss::error] == "entryNotFound");
989 }
990 }
991
992 {
993 testcase("Deleting Subject deletes issued credentials");
994
995 using namespace test::jtx;
996
997 Account const alice{"alice"};
998 Account const becky{"becky"};
999 Account const carol{"carol"};
1000
1001 char const credType[] = "abcd";
1002
1003 Env env{*this};
1004 env.fund(XRP(100000), alice, becky, carol);
1005 env.close();
1006
1007 // carol issue credentials for becky
1008 env(credentials::create(becky, carol, credType));
1009 env.close();
1010 env(credentials::accept(becky, carol, credType));
1011 env.close();
1012
1013 // get credentials index
1014 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
1015 std::string const credIdx = jv[jss::result][jss::index].asString();
1016
1017 // Close enough ledgers to be able to delete carol's account.
1018 incLgrSeqForAccDel(env, becky);
1019
1020 auto const acctDelFee{drops(env.current()->fees().increment)};
1021 env(acctdelete(becky, alice), fee(acctDelFee));
1022 env.close();
1023
1024 { // check that credential object deleted too
1025 BEAST_EXPECT(!env.le(credIdx));
1026 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
1027 BEAST_EXPECT(
1028 jv.isObject() && jv.isMember(jss::result) &&
1029 jv[jss::result].isMember(jss::error) &&
1030 jv[jss::result][jss::error] == "entryNotFound");
1031 }
1032 }
1033 }
1034
1035 void
1049};
1050
1051BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2);
1052
1053} // namespace test
1054} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
A public key.
Definition PublicKey.h:42
Slice slice() const noexcept
Definition PublicKey.h:103
void run() override
Runs the suite.
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)
void verifyDeliveredAmount(jtx::Env &env, STAmount const &amount)
Immutable cryptographic account descriptor.
Definition Account.h:19
std::string const & human() const
Returns the human readable public key.
Definition Account.h:94
PublicKey const & pk() const
Return the public key.
Definition Account.h:70
static Account const master
The master account.
Definition Account.h:28
AccountID id() const
Returns the Account ID.
Definition Account.h:87
A transaction testing environment.
Definition Env.h:122
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
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:847
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:505
A balance matches.
Definition balance.h:19
Set the fee on a JTx.
Definition fee.h:17
Set a multisignature on a JTx.
Definition multisign.h:41
Match the number of items in the account's owner directory.
Definition owners.h:52
Set the regular signature on a JTx.
Definition sig.h:15
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
Set a ticket sequence on a JTx.
Definition ticket.h:28
Set the flags on a JTx.
Definition txflags.h:11
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:295
static ticket_t const ticket
Definition Indexes.h:148
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:307
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:336
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:357
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:301
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:243
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:342
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Definition check.cpp:38
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:26
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:53
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:13
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:35
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
Json::Value setValid(jtx::Account const &account)
Definition dids.cpp:23
auto const finish_time
Set the "FinishAfter" time tag on a JTx.
Definition escrow.h:73
Json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount)
Definition escrow.cpp:14
Json::Value cancel(AccountID const &account, Account const &from, std::uint32_t seq)
Definition escrow.cpp:38
auto const cancel_time
Set the "CancelAfter" time tag on a JTx.
Definition escrow.h:76
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition multisign.cpp:15
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:95
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
void incLgrSeqForAccDel(jtx::Env &env, jtx::Account const &acc, std::uint32_t margin=0)
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:27
FeatureBitset testable_amendments()
Definition Env.h:78
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition regkey.cpp:10
owner_count< ltOFFER > offers
Match the number of offers in the account's owner directory.
Definition owners.h:70
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ telINSUF_FEE_P
Definition TER.h:37
@ terINSUF_FEE_B
Definition TER.h:196
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
@ tefTOO_BIG
Definition TER.h:164
@ temBAD_FEE
Definition TER.h:72
@ temINVALID_FLAG
Definition TER.h:91
@ temDST_IS_SRC
Definition TER.h:88
@ temMALFORMED
Definition TER.h:67
@ temDISABLED
Definition TER.h:94
@ tecTOO_SOON
Definition TER.h:299
@ tecBAD_CREDENTIALS
Definition TER.h:340
@ tecEXPIRED
Definition TER.h:295
@ tecNO_PERMISSION
Definition TER.h:286
@ tecDST_TAG_NEEDED
Definition TER.h:290
@ tecHAS_OBLIGATIONS
Definition TER.h:298
@ tecNO_DST
Definition TER.h:271
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
uint256 key
Definition Keylet.h:20
T time_since_epoch(T... args)