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