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.
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);
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 = [](jtx::Account const& account, Keylet const& payChanKeylet, PublicKey const& pk) {
365 Json::Value jv;
366 jv[jss::TransactionType] = jss::PaymentChannelClaim;
367 jv[jss::Flags] = tfClose;
368 jv[jss::Account] = account.human();
369 jv[sfChannel.jsonName] = to_string(payChanKeylet.key);
370 jv[sfPublicKey.jsonName] = strHex(pk.slice());
371 return jv;
372 };
373 env(payChanClose(alice, alicePayChanKey, alice.pk()));
374 env.close();
375
376 // gw creates a PayChannel with alice as the destination, this should
377 // prevent alice from deleting her account.
378 Keylet const gwPayChanKey{keylet::payChan(gw, alice, env.seq(gw))};
379
380 env(payChanCreate(gw, alice, XRP(68), 4s, env.now() + 2s, alice.pk()));
381 env.close();
382
383 // alice can't delete her account because of the PayChannel.
384 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
385 env.close();
386
387 // alice closes the PayChannel which should (finally) allow her to
388 // delete her account.
389 env(payChanClose(alice, gwPayChanKey, alice.pk()));
390 env.close();
391
392 // Now alice can successfully delete her account.
393 auto const aliceBalance{env.balance(alice)};
394 env(acctdelete(alice, gw), fee(acctDelFee));
395 verifyDeliveredAmount(env, aliceBalance - acctDelFee);
396 env.close();
397 }
398
399 void
401 {
402 // Put enough offers in an account that we refuse to delete the account.
403 using namespace jtx;
404
405 testcase("Too many offers");
406
407 Env env{*this};
408 Account const alice("alice");
409 Account const gw("gw");
410
411 // Fund alice well so she can afford the reserve on the offers.
412 env.fund(XRP(10000000), alice, gw);
413 env.close();
414
415 // To increase the number of Books affected, change the currency of
416 // each offer.
417 std::string currency{"AAA"};
418
419 // Alice creates 1001 offers. This is one greater than the number of
420 // directory entries an AccountDelete will remove.
421 std::uint32_t const offerSeq0{env.seq(alice)};
422 constexpr int offerCount{1001};
423 for (int i{0}; i < offerCount; ++i)
424 {
425 env(offer(alice, gw[currency](1), XRP(1)));
426 env.close();
427
428 // Increment to next currency.
429 ++currency[0];
430 if (currency[0] > 'Z')
431 {
432 currency[0] = 'A';
433 ++currency[1];
434 }
435 if (currency[1] > 'Z')
436 {
437 currency[1] = 'A';
438 ++currency[2];
439 }
440 if (currency[2] > 'Z')
441 {
442 currency[0] = 'A';
443 currency[1] = 'A';
444 currency[2] = 'A';
445 }
446 }
447
448 // Close enough ledgers to be able to delete alice's account.
449 incLgrSeqForAccDel(env, alice);
450
451 // Verify the existence of the expected ledger entries.
452 Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
453 {
454 std::shared_ptr<ReadView const> closed{env.closed()};
455 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
456 BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
457
458 // alice's directory nodes.
459 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
460 BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
461
462 // alice's offers.
463 for (std::uint32_t i{0}; i < offerCount; ++i)
464 BEAST_EXPECT(closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
465 }
466
467 // Delete alice's account. Should fail because she has too many
468 // offers in her directory.
469 auto const acctDelFee{drops(env.current()->fees().increment)};
470
471 env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
472
473 // Cancel one of alice's offers. Then the account delete can succeed.
474 env.require(offers(alice, offerCount));
475 env(offer_cancel(alice, offerSeq0));
476 env.close();
477 env.require(offers(alice, offerCount - 1));
478
479 // alice successfully deletes her account.
480 auto const alicePreDelBal{env.balance(alice)};
481 env(acctdelete(alice, gw), fee(acctDelFee));
482 verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
483 env.close();
484
485 // Verify that alice's account root is gone as well as her directory
486 // nodes and all of her offers.
487 {
488 std::shared_ptr<ReadView const> closed{env.closed()};
489 BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
490 BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
491
492 // alice's former directory nodes.
493 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
494 BEAST_EXPECT(!closed->exists(keylet::page(aliceOwnerDirKey, i)));
495
496 // alice's former offers.
497 for (std::uint32_t i{0}; i < offerCount; ++i)
498 BEAST_EXPECT(!closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
499 }
500 }
501
502 void
504 {
505 // Show that a trust line that is implicitly created by offer crossing
506 // prevents an account from being deleted.
507 using namespace jtx;
508
509 testcase("Implicitly created trust line");
510
511 Env env{*this};
512 Account const alice{"alice"};
513 Account const gw{"gw"};
514 auto const BUX{gw["BUX"]};
515
516 env.fund(XRP(10000), alice, gw);
517 env.close();
518
519 // alice creates an offer that, if crossed, will implicitly create
520 // a trust line.
521 env(offer(alice, BUX(30), XRP(30)));
522 env.close();
523
524 // gw crosses alice's offer. alice should end up with BUX(30).
525 env(offer(gw, XRP(30), BUX(30)));
526 env.close();
527 env.require(balance(alice, BUX(30)));
528
529 // Close enough ledgers to be able to delete alice's account.
530 incLgrSeqForAccDel(env, alice);
531
532 // alice and gw can't delete their accounts because of the implicitly
533 // created trust line.
534 auto const acctDelFee{drops(env.current()->fees().increment)};
535 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
536 env.close();
537
538 env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
539 env.close();
540 {
541 std::shared_ptr<ReadView const> closed{env.closed()};
542 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
543 BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
544 }
545 }
546
547 void
549 {
550 // See what happens when an account with a balance less than the
551 // incremental reserve tries to delete itself.
552 using namespace jtx;
553
554 testcase("Balance too small for fee");
555
556 Env env{*this};
557 Account const alice("alice");
558
559 // Note that the fee structure for unit tests does not match the fees
560 // on the production network (October 2019). Unit tests have a base
561 // reserve of 200 XRP.
562 env.fund(env.current()->fees().reserve, noripple(alice));
563 env.close();
564
565 // Burn a chunk of alice's funds so she only has 1 XRP remaining in
566 // her account.
567 env(noop(alice), fee(env.balance(alice) - XRP(1)));
568 env.close();
569
570 auto const acctDelFee{drops(env.current()->fees().increment)};
571 BEAST_EXPECT(acctDelFee > env.balance(alice));
572
573 // alice attempts to delete her account even though she can't pay
574 // the full fee. She specifies a fee that is larger than her balance.
575 //
576 // The balance of env.master should not change.
577 auto const masterBalance{env.balance(env.master)};
578 env(acctdelete(alice, env.master), fee(acctDelFee), ter(terINSUF_FEE_B));
579 env.close();
580 {
581 std::shared_ptr<ReadView const> const closed{env.closed()};
582 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
583 BEAST_EXPECT(env.balance(env.master) == masterBalance);
584 }
585
586 // alice again attempts to delete her account. This time she specifies
587 // her current balance in XRP. Again the transaction fails.
588 BEAST_EXPECT(env.balance(alice) == XRP(1));
589 env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
590 env.close();
591 {
592 std::shared_ptr<ReadView const> closed{env.closed()};
593 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
594 BEAST_EXPECT(env.balance(env.master) == masterBalance);
595 }
596 }
597
598 void
600 {
601 testcase("With Tickets");
602
603 using namespace test::jtx;
604
605 Account const alice{"alice"};
606 Account const bob{"bob"};
607
608 Env env{*this};
609 env.fund(XRP(100000), alice, bob);
610 env.close();
611
612 // bob grabs as many tickets as he is allowed to have.
613 std::uint32_t const ticketSeq{env.seq(bob) + 1};
614 env(ticket::create(bob, 250));
615 env.close();
616 env.require(owners(bob, 250));
617
618 {
619 std::shared_ptr<ReadView const> closed{env.closed()};
620 BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
621 for (std::uint32_t i = 0; i < 250; ++i)
622 {
623 BEAST_EXPECT(closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
624 }
625 }
626
627 // Close enough ledgers to be able to delete bob's account.
628 incLgrSeqForAccDel(env, bob);
629
630 // bob deletes his account using a ticket. bob's account and all
631 // of his tickets should be removed from the ledger.
632 auto const acctDelFee{drops(env.current()->fees().increment)};
633 auto const bobOldBalance{env.balance(bob)};
634 env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
635 verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
636 env.close();
637 {
638 std::shared_ptr<ReadView const> closed{env.closed()};
639 BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
640 for (std::uint32_t i = 0; i < 250; ++i)
641 {
642 BEAST_EXPECT(!closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
643 }
644 }
645 }
646
647 void
649 {
650 testcase("Destination Constraints");
651
652 using namespace test::jtx;
653
654 Account const alice{"alice"};
655 Account const becky{"becky"};
656 Account const carol{"carol"};
657 Account const daria{"daria"};
658
659 Env env{*this};
660 env.fund(XRP(100000), alice, becky, carol);
661 env.close();
662
663 // alice sets the lsfDepositAuth flag on her account. This should
664 // prevent becky from deleting her account while using alice as the
665 // destination.
666 env(fset(alice, asfDepositAuth));
667
668 // carol requires a destination tag.
669 env(fset(carol, asfRequireDest));
670 env.close();
671
672 // Close enough ledgers to be able to delete becky's account.
673 incLgrSeqForAccDel(env, becky);
674
675 // becky attempts to delete her account using daria as the destination.
676 // Since daria is not in the ledger the delete attempt fails.
677 auto const acctDelFee{drops(env.current()->fees().increment)};
678 env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
679 env.close();
680
681 // becky attempts to delete her account, but carol requires a
682 // destination tag which becky has omitted.
683 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
684 env.close();
685
686 // becky attempts to delete her account, but alice won't take her XRP,
687 // so the delete is blocked.
688 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
689 env.close();
690
691 // alice preauthorizes deposits from becky. Now becky can delete her
692 // account and forward the leftovers to alice.
693 env(deposit::auth(alice, becky));
694 env.close();
695
696 auto const beckyOldBalance{env.balance(becky)};
697 env(acctdelete(becky, alice), fee(acctDelFee));
698 verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
699 env.close();
700 }
701
702 void
704 {
705 {
706 testcase("Destination Constraints with DepositPreauth and Credentials");
707
708 using namespace test::jtx;
709
710 Account const alice{"alice"};
711 Account const becky{"becky"};
712 Account const carol{"carol"};
713 Account const daria{"daria"};
714
715 char const credType[] = "abcd";
716
717 Env env{*this};
718 env.fund(XRP(100000), alice, becky, carol, daria);
719 env.close();
720
721 // carol issue credentials for becky
722 env(credentials::create(becky, carol, credType));
723 env.close();
724
725 // get credentials index
726 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
727 std::string const credIdx = jv[jss::result][jss::index].asString();
728
729 // Close enough ledgers to be able to delete becky's account.
730 incLgrSeqForAccDel(env, becky);
731
732 auto const acctDelFee{drops(env.current()->fees().increment)};
733
734 // becky use credentials but they aren't accepted
735 env(acctdelete(becky, alice), credentials::ids({credIdx}), fee(acctDelFee), ter(tecBAD_CREDENTIALS));
736 env.close();
737
738 {
739 // alice sets the lsfDepositAuth flag on her account. This
740 // should prevent becky from deleting her account while using
741 // alice as the destination.
742 env(fset(alice, asfDepositAuth));
743 env.close();
744 }
745
746 // Fail, credentials still not accepted
747 env(acctdelete(becky, alice), credentials::ids({credIdx}), fee(acctDelFee), ter(tecBAD_CREDENTIALS));
748 env.close();
749
750 // becky accept the credentials
751 env(credentials::accept(becky, carol, credType));
752 env.close();
753
754 // Fail, credentials doesn’t belong to carol
755 env(acctdelete(carol, alice), credentials::ids({credIdx}), fee(acctDelFee), ter(tecBAD_CREDENTIALS));
756
757 // Fail, no depositPreauth for provided credentials
758 env(acctdelete(becky, alice), credentials::ids({credIdx}), fee(acctDelFee), ter(tecNO_PERMISSION));
759 env.close();
760
761 // alice create DepositPreauth Object
762 env(deposit::authCredentials(alice, {{carol, credType}}));
763 env.close();
764
765 // becky attempts to delete her account, but alice won't take her
766 // XRP, so the delete is blocked.
767 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
768
769 // becky use empty credentials and can't delete account
770 env(acctdelete(becky, alice), fee(acctDelFee), credentials::ids({}), ter(temMALFORMED));
771
772 // becky use bad credentials and can't delete account
773 env(acctdelete(becky, alice),
774 credentials::ids({"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6E"
775 "A288BE4"}),
776 fee(acctDelFee),
778 env.close();
779
780 // becky use credentials and can delete account
781 env(acctdelete(becky, alice), credentials::ids({credIdx}), fee(acctDelFee));
782 env.close();
783
784 {
785 // check that credential object deleted too
786 auto const jNoCred = credentials::ledgerEntry(env, becky, carol, credType);
787 BEAST_EXPECT(
788 jNoCred.isObject() && jNoCred.isMember(jss::result) && jNoCred[jss::result].isMember(jss::error) &&
789 jNoCred[jss::result][jss::error] == "entryNotFound");
790 }
791
792 testcase("Credentials that aren't required");
793 { // carol issue credentials for daria
794 env(credentials::create(daria, carol, credType));
795 env.close();
796 env(credentials::accept(daria, carol, credType));
797 env.close();
798 std::string const credDaria =
799 credentials::ledgerEntry(env, daria, carol, credType)[jss::result][jss::index].asString();
800
801 // daria use valid credentials, which aren't required and can
802 // delete her account
803 env(acctdelete(daria, carol), credentials::ids({credDaria}), fee(acctDelFee));
804 env.close();
805
806 // check that credential object deleted too
807 auto const jNoCred = credentials::ledgerEntry(env, daria, carol, credType);
808
809 BEAST_EXPECT(
810 jNoCred.isObject() && jNoCred.isMember(jss::result) && jNoCred[jss::result].isMember(jss::error) &&
811 jNoCred[jss::result][jss::error] == "entryNotFound");
812 }
813
814 {
815 Account const eaton{"eaton"};
816 Account const fred{"fred"};
817
818 env.fund(XRP(5000), eaton, fred);
819
820 // carol issue credentials for eaton
821 env(credentials::create(eaton, carol, credType));
822 env.close();
823 env(credentials::accept(eaton, carol, credType));
824 env.close();
825 std::string const credEaton =
826 credentials::ledgerEntry(env, eaton, carol, credType)[jss::result][jss::index].asString();
827
828 // fred make pre-authorization through authorized account
829 env(fset(fred, asfDepositAuth));
830 env.close();
831 env(deposit::auth(fred, eaton));
832 env.close();
833
834 // Close enough ledgers to be able to delete becky's account.
835 incLgrSeqForAccDel(env, eaton);
836 auto const acctDelFee{drops(env.current()->fees().increment)};
837
838 // eaton use valid credentials, but he already authorized
839 // through "Authorized" field.
840 env(acctdelete(eaton, fred), credentials::ids({credEaton}), fee(acctDelFee));
841 env.close();
842
843 // check that credential object deleted too
844 auto const jNoCred = credentials::ledgerEntry(env, eaton, carol, credType);
845
846 BEAST_EXPECT(
847 jNoCred.isObject() && jNoCred.isMember(jss::result) && jNoCred[jss::result].isMember(jss::error) &&
848 jNoCred[jss::result][jss::error] == "entryNotFound");
849 }
850
851 testcase("Expired credentials");
852 {
853 Account const john{"john"};
854
855 env.fund(XRP(10000), john);
856 env.close();
857
858 auto jv = credentials::create(john, carol, credType);
859 uint32_t const t = env.current()->header().parentCloseTime.time_since_epoch().count() + 20;
860 jv[sfExpiration.jsonName] = t;
861 env(jv);
862 env.close();
863 env(credentials::accept(john, carol, credType));
864 env.close();
865 jv = credentials::ledgerEntry(env, john, carol, credType);
866 std::string const credIdx = jv[jss::result][jss::index].asString();
867
868 incLgrSeqForAccDel(env, john);
869
870 // credentials are expired
871 // john use credentials but can't delete account
872 env(acctdelete(john, alice), credentials::ids({credIdx}), fee(acctDelFee), ter(tecEXPIRED));
873 env.close();
874
875 {
876 // check that expired credential object deleted
877 auto jv = credentials::ledgerEntry(env, john, carol, credType);
878 BEAST_EXPECT(
879 jv.isObject() && jv.isMember(jss::result) && jv[jss::result].isMember(jss::error) &&
880 jv[jss::result][jss::error] == "entryNotFound");
881 }
882 }
883 }
884
885 {
886 testcase("Credentials feature disabled");
887 using namespace test::jtx;
888
889 Account const alice{"alice"};
890 Account const becky{"becky"};
891 Account const carol{"carol"};
892
893 Env env{*this, testable_amendments() - featureCredentials};
894 env.fund(XRP(100000), alice, becky, carol);
895 env.close();
896
897 // alice sets the lsfDepositAuth flag on her account. This should
898 // prevent becky from deleting her account while using alice as the
899 // destination.
900 env(fset(alice, asfDepositAuth));
901 env.close();
902
903 // Close enough ledgers to be able to delete becky's account.
904 incLgrSeqForAccDel(env, becky);
905
906 auto const acctDelFee{drops(env.current()->fees().increment)};
907
908 std::string const credIdx =
909 "098B7F1B146470A1C5084DC7832C04A72939E3EBC58E68AB8B579BA072B0CE"
910 "CB";
911
912 // and can't delete even with old DepositPreauth
913 env(deposit::auth(alice, becky));
914 env.close();
915
916 env(acctdelete(becky, alice), credentials::ids({credIdx}), fee(acctDelFee), ter(temDISABLED));
917 env.close();
918 }
919 }
920
921 void
923 {
924 {
925 testcase("Deleting Issuer deletes issued credentials");
926
927 using namespace test::jtx;
928
929 Account const alice{"alice"};
930 Account const becky{"becky"};
931 Account const carol{"carol"};
932
933 char const credType[] = "abcd";
934
935 Env env{*this};
936 env.fund(XRP(100000), alice, becky, carol);
937 env.close();
938
939 // carol issue credentials for becky
940 env(credentials::create(becky, carol, credType));
941 env.close();
942 env(credentials::accept(becky, carol, credType));
943 env.close();
944
945 // get credentials index
946 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
947 std::string const credIdx = jv[jss::result][jss::index].asString();
948
949 // Close enough ledgers to be able to delete carol's account.
950 incLgrSeqForAccDel(env, carol);
951
952 auto const acctDelFee{drops(env.current()->fees().increment)};
953 env(acctdelete(carol, alice), fee(acctDelFee));
954 env.close();
955
956 { // check that credential object deleted too
957 BEAST_EXPECT(!env.le(credIdx));
958 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
959 BEAST_EXPECT(
960 jv.isObject() && jv.isMember(jss::result) && jv[jss::result].isMember(jss::error) &&
961 jv[jss::result][jss::error] == "entryNotFound");
962 }
963 }
964
965 {
966 testcase("Deleting Subject deletes issued credentials");
967
968 using namespace test::jtx;
969
970 Account const alice{"alice"};
971 Account const becky{"becky"};
972 Account const carol{"carol"};
973
974 char const credType[] = "abcd";
975
976 Env env{*this};
977 env.fund(XRP(100000), alice, becky, carol);
978 env.close();
979
980 // carol issue credentials for becky
981 env(credentials::create(becky, carol, credType));
982 env.close();
983 env(credentials::accept(becky, carol, credType));
984 env.close();
985
986 // get credentials index
987 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
988 std::string const credIdx = jv[jss::result][jss::index].asString();
989
990 // Close enough ledgers to be able to delete carol's account.
991 incLgrSeqForAccDel(env, becky);
992
993 auto const acctDelFee{drops(env.current()->fees().increment)};
994 env(acctdelete(becky, alice), fee(acctDelFee));
995 env.close();
996
997 { // check that credential object deleted too
998 BEAST_EXPECT(!env.le(credIdx));
999 auto const jv = credentials::ledgerEntry(env, becky, carol, credType);
1000 BEAST_EXPECT(
1001 jv.isObject() && jv.isMember(jss::result) && jv[jss::result].isMember(jss::error) &&
1002 jv[jss::result][jss::error] == "entryNotFound");
1003 }
1004 }
1005 }
1006
1007 void
1021};
1022
1023BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2);
1024
1025} // namespace test
1026} // namespace xrpl
Represents a JSON value.
Definition json_value.h:131
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:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:148
A public key.
Definition PublicKey.h:43
Slice slice() const noexcept
Definition PublicKey.h:104
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:20
std::string const & human() const
Returns the human readable public key.
Definition Account.h:95
PublicKey const & pk() const
Return the public key.
Definition Account.h:71
static Account const master
The master account.
Definition Account.h:29
AccountID id() const
Returns the Account ID.
Definition Account.h:88
A transaction testing environment.
Definition Env.h:98
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:97
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:260
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:749
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:472
A balance matches.
Definition balance.h:20
Set the fee on a JTx.
Definition fee.h:18
Set a multisignature on a JTx.
Definition multisign.h:42
Match the number of items in the account's owner directory.
Definition owners.h:49
Set the regular signature on a JTx.
Definition sig.h:16
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:16
Set a ticket sequence on a JTx.
Definition ticket.h:29
Set the flags on a JTx.
Definition txflags.h:12
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:287
static ticket_t const ticket
Definition Indexes.h:149
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:299
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:325
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:346
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:293
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:235
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:331
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:49
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:74
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:77
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:90
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:23
FeatureBitset testable_amendments()
Definition Env.h:55
auto const amount
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:67
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:6
@ telINSUF_FEE_P
Definition TER.h:38
@ terINSUF_FEE_B
Definition TER.h:197
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
constexpr std::uint32_t tfImmediateOrCancel
Definition TxFlags.h:80
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
@ tefTOO_BIG
Definition TER.h:165
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
constexpr std::uint32_t tfClose
Definition TxFlags.h:116
@ temBAD_FEE
Definition TER.h:73
@ temINVALID_FLAG
Definition TER.h:92
@ temDST_IS_SRC
Definition TER.h:89
@ temMALFORMED
Definition TER.h:68
@ temDISABLED
Definition TER.h:95
@ tecTOO_SOON
Definition TER.h:300
@ tecBAD_CREDENTIALS
Definition TER.h:341
@ tecEXPIRED
Definition TER.h:296
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecNO_DST
Definition TER.h:272
constexpr std::uint32_t asfAllowTrustLineLocking
Definition TxFlags.h:76
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
T time_since_epoch(T... args)