From 31af00546bbe01b45210cfd83a23057d5aa4dc37 Mon Sep 17 00:00:00 2001 From: bthomee Date: Mon, 24 Nov 2025 03:55:12 -0800 Subject: [PATCH] deploy: a791c03dc16085a7beb0f7683b1d7d3c6d1b8a6f --- AccountDelete__test_8cpp_source.html | 1418 +++--- DeleteAccount_8cpp_source.html | 783 ++-- DeleteAccount_8h_source.html | 8 +- Feature__test_8cpp_source.html | 1009 ++-- InvariantCheck_8cpp_source.html | 4169 ++++++++--------- InvariantCheck_8h_source.html | 56 +- LedgerClosed__test_8cpp_source.html | 2 +- LedgerRPC__test_8cpp_source.html | 4 +- LedgerRequestRPC__test_8cpp_source.html | 18 +- NFToken__test_8cpp_source.html | 3765 ++++++++------- Payment_8cpp_source.html | 510 +- TxQ__test_8cpp_source.html | 2 +- View_8cpp_source.html | 2 +- XChainBridge_8cpp_source.html | 3441 +++++++------- XChainBridge_8h_source.html | 52 +- classripple_1_1BridgeModify.html | 8 +- classripple_1_1DeleteAccount.html | 8 +- classripple_1_1Feature__test.html | 18 +- classripple_1_1NFTokenAllFeatures__test.html | 20 +- classripple_1_1NFTokenBaseUtil__test.html | 18 +- classripple_1_1NFTokenCountTracking.html | 4 +- ...pple_1_1NFTokenDisallowIncoming__test.html | 20 +- classripple_1_1NFTokenWOMintOffer__test.html | 20 +- classripple_1_1NFTokenWOModify__test.html | 20 +- classripple_1_1ValidAMM.html | 20 +- classripple_1_1ValidClawback.html | 4 +- classripple_1_1ValidMPTIssuance.html | 4 +- classripple_1_1ValidNFTokenPage.html | 4 +- classripple_1_1ValidPermissionedDEX.html | 4 +- classripple_1_1ValidPermissionedDomain.html | 4 +- classripple_1_1ValidPseudoAccounts.html | 4 +- classripple_1_1ValidVault.html | 4 +- ..._1_1XChainAddAccountCreateAttestation.html | 6 +- classripple_1_1XChainAddClaimAttestation.html | 6 +- classripple_1_1XChainClaim.html | 6 +- classripple_1_1XChainCommit.html | 8 +- classripple_1_1XChainCreateAccountCommit.html | 6 +- classripple_1_1XChainCreateBridge.html | 6 +- classripple_1_1XChainCreateClaimID.html | 6 +- ..._1test_1_1AccountDelete__test-members.html | 37 +- ...ripple_1_1test_1_1AccountDelete__test.html | 37 +- functions_func_s.html | 10 +- functions_func_t.html | 7 +- functions_func_v.html | 12 +- functions_s.html | 15 +- functions_t.html | 5 +- functions_v.html | 13 +- namespaceripple.html | 2 +- search/all_19.js | 4 +- search/all_1a.js | 38 +- search/all_1b.js | 3665 ++++++++------- search/all_1d.js | 10 +- search/functions_12.js | 4 +- search/functions_13.js | 26 +- search/functions_14.js | 2751 ++++++----- search/functions_16.js | 2 +- search/variables_13.js | 4 +- structripple_1_1ValidVault_1_1Shares.html | 2 +- structripple_1_1ValidVault_1_1Vault.html | 2 +- 59 files changed, 11002 insertions(+), 11111 deletions(-) diff --git a/AccountDelete__test_8cpp_source.html b/AccountDelete__test_8cpp_source.html index b65b5e043b..684c468c41 100644 --- a/AccountDelete__test_8cpp_source.html +++ b/AccountDelete__test_8cpp_source.html @@ -514,768 +514,711 @@ $(document).ready(function() { init_codefold(0); });
420
421 void
- +
423 {
-
424 // Start with the featureDeletableAccounts amendment disabled.
-
425 // Then enable the amendment and delete an account.
-
426 using namespace jtx;
-
427
-
428 testcase("Amendment enable");
-
429
-
430 Env env{*this, testable_amendments() - featureDeletableAccounts};
-
431 Account const alice("alice");
-
432 Account const becky("becky");
-
433
-
434 env.fund(XRP(10000), alice, becky);
+
424 // Put enough offers in an account that we refuse to delete the account.
+
425 using namespace jtx;
+
426
+
427 testcase("Too many offers");
+
428
+
429 Env env{*this};
+
430 Account const alice("alice");
+
431 Account const gw("gw");
+
432
+
433 // Fund alice well so she can afford the reserve on the offers.
+
434 env.fund(XRP(10000000), alice, gw);
435 env.close();
436
-
437 // Close enough ledgers to be able to delete alice's account.
-
438 incLgrSeqForAccDel(env, alice);
-
439
-
440 // Verify that alice's account root is present.
-
441 Keylet const aliceAcctKey{keylet::account(alice.id())};
-
442 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
443
-
444 auto const alicePreDelBal{env.balance(alice)};
-
445 auto const beckyPreDelBal{env.balance(becky)};
-
446
-
447 auto const acctDelFee{drops(env.current()->fees().increment)};
-
448 env(acctdelete(alice, becky), fee(acctDelFee), ter(temDISABLED));
-
449 env.close();
-
450
-
451 // Verify that alice's account root is still present and alice and
-
452 // becky both have their XRP.
-
453 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
454 BEAST_EXPECT(env.balance(alice) == alicePreDelBal);
-
455 BEAST_EXPECT(env.balance(becky) == beckyPreDelBal);
-
456
-
457 // When the amendment is enabled the previous transaction is
-
458 // retried into the new open ledger and succeeds.
-
459 env.enableFeature(featureDeletableAccounts);
-
460 env.close();
-
461
-
462 // alice's account is still in the most recently closed ledger.
-
463 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
464
-
465 // Verify that alice's account root is gone from the current ledger
-
466 // and becky has alice's XRP.
-
467 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
-
468 BEAST_EXPECT(
-
469 env.balance(becky) == alicePreDelBal + beckyPreDelBal - acctDelFee);
-
470
-
471 env.close();
-
472 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
-
473 }
+
437 // To increase the number of Books affected, change the currency of
+
438 // each offer.
+
439 std::string currency{"AAA"};
+
440
+
441 // Alice creates 1001 offers. This is one greater than the number of
+
442 // directory entries an AccountDelete will remove.
+
443 std::uint32_t const offerSeq0{env.seq(alice)};
+
444 constexpr int offerCount{1001};
+
445 for (int i{0}; i < offerCount; ++i)
+
446 {
+
447 env(offer(alice, gw[currency](1), XRP(1)));
+
448 env.close();
+
449
+
450 // Increment to next currency.
+
451 ++currency[0];
+
452 if (currency[0] > 'Z')
+
453 {
+
454 currency[0] = 'A';
+
455 ++currency[1];
+
456 }
+
457 if (currency[1] > 'Z')
+
458 {
+
459 currency[1] = 'A';
+
460 ++currency[2];
+
461 }
+
462 if (currency[2] > 'Z')
+
463 {
+
464 currency[0] = 'A';
+
465 currency[1] = 'A';
+
466 currency[2] = 'A';
+
467 }
+
468 }
+
469
+
470 // Close enough ledgers to be able to delete alice's account.
+
471 incLgrSeqForAccDel(env, alice);
+
472
+
473 // Verify the existence of the expected ledger entries.
+
474 Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
+
475 {
+
476 std::shared_ptr<ReadView const> closed{env.closed()};
+
477 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
+
478 BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
+
479
+
480 // alice's directory nodes.
+
481 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
+
482 BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
+
483
+
484 // alice's offers.
+
485 for (std::uint32_t i{0}; i < offerCount; ++i)
+
486 BEAST_EXPECT(
+
487 closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
+
488 }
+
489
+
490 // Delete alice's account. Should fail because she has too many
+
491 // offers in her directory.
+
492 auto const acctDelFee{drops(env.current()->fees().increment)};
+
493
+
494 env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
+
495
+
496 // Cancel one of alice's offers. Then the account delete can succeed.
+
497 env.require(offers(alice, offerCount));
+
498 env(offer_cancel(alice, offerSeq0));
+
499 env.close();
+
500 env.require(offers(alice, offerCount - 1));
+
501
+
502 // alice successfully deletes her account.
+
503 auto const alicePreDelBal{env.balance(alice)};
+
504 env(acctdelete(alice, gw), fee(acctDelFee));
+
505 verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
+
506 env.close();
+
507
+
508 // Verify that alice's account root is gone as well as her directory
+
509 // nodes and all of her offers.
+
510 {
+
511 std::shared_ptr<ReadView const> closed{env.closed()};
+
512 BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
+
513 BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
+
514
+
515 // alice's former directory nodes.
+
516 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
+
517 BEAST_EXPECT(
+
518 !closed->exists(keylet::page(aliceOwnerDirKey, i)));
+
519
+
520 // alice's former offers.
+
521 for (std::uint32_t i{0}; i < offerCount; ++i)
+
522 BEAST_EXPECT(
+
523 !closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
+
524 }
+
525 }
-
474
-
475 void
-
- -
477 {
-
478 // Put enough offers in an account that we refuse to delete the account.
-
479 using namespace jtx;
-
480
-
481 testcase("Too many offers");
-
482
-
483 Env env{*this};
-
484 Account const alice("alice");
-
485 Account const gw("gw");
-
486
-
487 // Fund alice well so she can afford the reserve on the offers.
-
488 env.fund(XRP(10000000), alice, gw);
-
489 env.close();
-
490
-
491 // To increase the number of Books affected, change the currency of
-
492 // each offer.
-
493 std::string currency{"AAA"};
-
494
-
495 // Alice creates 1001 offers. This is one greater than the number of
-
496 // directory entries an AccountDelete will remove.
-
497 std::uint32_t const offerSeq0{env.seq(alice)};
-
498 constexpr int offerCount{1001};
-
499 for (int i{0}; i < offerCount; ++i)
-
500 {
-
501 env(offer(alice, gw[currency](1), XRP(1)));
-
502 env.close();
-
503
-
504 // Increment to next currency.
-
505 ++currency[0];
-
506 if (currency[0] > 'Z')
-
507 {
-
508 currency[0] = 'A';
-
509 ++currency[1];
-
510 }
-
511 if (currency[1] > 'Z')
-
512 {
-
513 currency[1] = 'A';
-
514 ++currency[2];
-
515 }
-
516 if (currency[2] > 'Z')
-
517 {
-
518 currency[0] = 'A';
-
519 currency[1] = 'A';
-
520 currency[2] = 'A';
-
521 }
-
522 }
-
523
-
524 // Close enough ledgers to be able to delete alice's account.
-
525 incLgrSeqForAccDel(env, alice);
526
-
527 // Verify the existence of the expected ledger entries.
-
528 Keylet const aliceOwnerDirKey{keylet::ownerDir(alice.id())};
-
529 {
-
530 std::shared_ptr<ReadView const> closed{env.closed()};
-
531 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
-
532 BEAST_EXPECT(closed->exists(aliceOwnerDirKey));
+
527 void
+
+ +
529 {
+
530 // Show that a trust line that is implicitly created by offer crossing
+
531 // prevents an account from being deleted.
+
532 using namespace jtx;
533
-
534 // alice's directory nodes.
-
535 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
-
536 BEAST_EXPECT(closed->exists(keylet::page(aliceOwnerDirKey, i)));
-
537
-
538 // alice's offers.
-
539 for (std::uint32_t i{0}; i < offerCount; ++i)
-
540 BEAST_EXPECT(
-
541 closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
-
542 }
+
534 testcase("Implicitly created trust line");
+
535
+
536 Env env{*this};
+
537 Account const alice{"alice"};
+
538 Account const gw{"gw"};
+
539 auto const BUX{gw["BUX"]};
+
540
+
541 env.fund(XRP(10000), alice, gw);
+
542 env.close();
543
-
544 // Delete alice's account. Should fail because she has too many
-
545 // offers in her directory.
-
546 auto const acctDelFee{drops(env.current()->fees().increment)};
-
547
-
548 env(acctdelete(alice, gw), fee(acctDelFee), ter(tefTOO_BIG));
-
549
-
550 // Cancel one of alice's offers. Then the account delete can succeed.
-
551 env.require(offers(alice, offerCount));
-
552 env(offer_cancel(alice, offerSeq0));
-
553 env.close();
-
554 env.require(offers(alice, offerCount - 1));
-
555
-
556 // alice successfully deletes her account.
-
557 auto const alicePreDelBal{env.balance(alice)};
-
558 env(acctdelete(alice, gw), fee(acctDelFee));
-
559 verifyDeliveredAmount(env, alicePreDelBal - acctDelFee);
-
560 env.close();
-
561
-
562 // Verify that alice's account root is gone as well as her directory
-
563 // nodes and all of her offers.
-
564 {
-
565 std::shared_ptr<ReadView const> closed{env.closed()};
-
566 BEAST_EXPECT(!closed->exists(keylet::account(alice.id())));
-
567 BEAST_EXPECT(!closed->exists(aliceOwnerDirKey));
-
568
-
569 // alice's former directory nodes.
-
570 for (std::uint32_t i{0}; i < ((offerCount / 32) + 1); ++i)
-
571 BEAST_EXPECT(
-
572 !closed->exists(keylet::page(aliceOwnerDirKey, i)));
-
573
-
574 // alice's former offers.
-
575 for (std::uint32_t i{0}; i < offerCount; ++i)
-
576 BEAST_EXPECT(
-
577 !closed->exists(keylet::offer(alice.id(), offerSeq0 + i)));
-
578 }
-
579 }
+
544 // alice creates an offer that, if crossed, will implicitly create
+
545 // a trust line.
+
546 env(offer(alice, BUX(30), XRP(30)));
+
547 env.close();
+
548
+
549 // gw crosses alice's offer. alice should end up with BUX(30).
+
550 env(offer(gw, XRP(30), BUX(30)));
+
551 env.close();
+
552 env.require(balance(alice, BUX(30)));
+
553
+
554 // Close enough ledgers to be able to delete alice's account.
+
555 incLgrSeqForAccDel(env, alice);
+
556
+
557 // alice and gw can't delete their accounts because of the implicitly
+
558 // created trust line.
+
559 auto const acctDelFee{drops(env.current()->fees().increment)};
+
560 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
+
561 env.close();
+
562
+
563 env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
+
564 env.close();
+
565 {
+
566 std::shared_ptr<ReadView const> closed{env.closed()};
+
567 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
+
568 BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
+
569 }
+
570 }
+
571
+
572 void
+
+ +
574 {
+
575 // See what happens when an account with a balance less than the
+
576 // incremental reserve tries to delete itself.
+
577 using namespace jtx;
+
578
+
579 testcase("Balance too small for fee");
580
-
581 void
-
- -
583 {
-
584 // Show that a trust line that is implicitly created by offer crossing
-
585 // prevents an account from being deleted.
-
586 using namespace jtx;
-
587
-
588 testcase("Implicitly created trust line");
+
581 Env env{*this};
+
582 Account const alice("alice");
+
583
+
584 // Note that the fee structure for unit tests does not match the fees
+
585 // on the production network (October 2019). Unit tests have a base
+
586 // reserve of 200 XRP.
+
587 env.fund(env.current()->fees().reserve, noripple(alice));
+
588 env.close();
589
-
590 Env env{*this};
-
591 Account const alice{"alice"};
-
592 Account const gw{"gw"};
-
593 auto const BUX{gw["BUX"]};
+
590 // Burn a chunk of alice's funds so she only has 1 XRP remaining in
+
591 // her account.
+
592 env(noop(alice), fee(env.balance(alice) - XRP(1)));
+
593 env.close();
594
-
595 env.fund(XRP(10000), alice, gw);
-
596 env.close();
+
595 auto const acctDelFee{drops(env.current()->fees().increment)};
+
596 BEAST_EXPECT(acctDelFee > env.balance(alice));
597
-
598 // alice creates an offer that, if crossed, will implicitly create
-
599 // a trust line.
-
600 env(offer(alice, BUX(30), XRP(30)));
-
601 env.close();
-
602
-
603 // gw crosses alice's offer. alice should end up with BUX(30).
-
604 env(offer(gw, XRP(30), BUX(30)));
-
605 env.close();
-
606 env.require(balance(alice, BUX(30)));
-
607
-
608 // Close enough ledgers to be able to delete alice's account.
-
609 incLgrSeqForAccDel(env, alice);
-
610
-
611 // alice and gw can't delete their accounts because of the implicitly
-
612 // created trust line.
-
613 auto const acctDelFee{drops(env.current()->fees().increment)};
-
614 env(acctdelete(alice, gw), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
-
615 env.close();
-
616
-
617 env(acctdelete(gw, alice), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
-
618 env.close();
-
619 {
-
620 std::shared_ptr<ReadView const> closed{env.closed()};
-
621 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
-
622 BEAST_EXPECT(closed->exists(keylet::account(gw.id())));
-
623 }
-
624 }
+
598 // alice attempts to delete her account even though she can't pay
+
599 // the full fee. She specifies a fee that is larger than her balance.
+
600 //
+
601 // The balance of env.master should not change.
+
602 auto const masterBalance{env.balance(env.master)};
+
603 env(acctdelete(alice, env.master),
+
604 fee(acctDelFee),
+ +
606 env.close();
+
607 {
+
608 std::shared_ptr<ReadView const> const closed{env.closed()};
+
609 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
+
610 BEAST_EXPECT(env.balance(env.master) == masterBalance);
+
611 }
+
612
+
613 // alice again attempts to delete her account. This time she specifies
+
614 // her current balance in XRP. Again the transaction fails.
+
615 BEAST_EXPECT(env.balance(alice) == XRP(1));
+
616 env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
+
617 env.close();
+
618 {
+
619 std::shared_ptr<ReadView const> closed{env.closed()};
+
620 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
+
621 BEAST_EXPECT(env.balance(env.master) == masterBalance);
+
622 }
+
623 }
-
625
-
626 void
-
- -
628 {
-
629 // See what happens when an account with a balance less than the
-
630 // incremental reserve tries to delete itself.
-
631 using namespace jtx;
-
632
-
633 testcase("Balance too small for fee");
+
624
+
625 void
+
+ +
627 {
+
628 testcase("With Tickets");
+
629
+
630 using namespace test::jtx;
+
631
+
632 Account const alice{"alice"};
+
633 Account const bob{"bob"};
634
635 Env env{*this};
-
636 Account const alice("alice");
-
637
-
638 // Note that the fee structure for unit tests does not match the fees
-
639 // on the production network (October 2019). Unit tests have a base
-
640 // reserve of 200 XRP.
-
641 env.fund(env.current()->fees().reserve, noripple(alice));
+
636 env.fund(XRP(100000), alice, bob);
+
637 env.close();
+
638
+
639 // bob grabs as many tickets as he is allowed to have.
+
640 std::uint32_t const ticketSeq{env.seq(bob) + 1};
+
641 env(ticket::create(bob, 250));
642 env.close();
-
643
-
644 // Burn a chunk of alice's funds so she only has 1 XRP remaining in
-
645 // her account.
-
646 env(noop(alice), fee(env.balance(alice) - XRP(1)));
-
647 env.close();
-
648
-
649 auto const acctDelFee{drops(env.current()->fees().increment)};
-
650 BEAST_EXPECT(acctDelFee > env.balance(alice));
-
651
-
652 // alice attempts to delete her account even though she can't pay
-
653 // the full fee. She specifies a fee that is larger than her balance.
-
654 //
-
655 // The balance of env.master should not change.
-
656 auto const masterBalance{env.balance(env.master)};
-
657 env(acctdelete(alice, env.master),
-
658 fee(acctDelFee),
- -
660 env.close();
-
661 {
-
662 std::shared_ptr<ReadView const> const closed{env.closed()};
-
663 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
-
664 BEAST_EXPECT(env.balance(env.master) == masterBalance);
-
665 }
-
666
-
667 // alice again attempts to delete her account. This time she specifies
-
668 // her current balance in XRP. Again the transaction fails.
-
669 BEAST_EXPECT(env.balance(alice) == XRP(1));
-
670 env(acctdelete(alice, env.master), fee(XRP(1)), ter(telINSUF_FEE_P));
-
671 env.close();
-
672 {
-
673 std::shared_ptr<ReadView const> closed{env.closed()};
-
674 BEAST_EXPECT(closed->exists(keylet::account(alice.id())));
-
675 BEAST_EXPECT(env.balance(env.master) == masterBalance);
-
676 }
-
677 }
+
643 env.require(owners(bob, 250));
+
644
+
645 {
+
646 std::shared_ptr<ReadView const> closed{env.closed()};
+
647 BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
+
648 for (std::uint32_t i = 0; i < 250; ++i)
+
649 {
+
650 BEAST_EXPECT(
+
651 closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
+
652 }
+
653 }
+
654
+
655 // Close enough ledgers to be able to delete bob's account.
+
656 incLgrSeqForAccDel(env, bob);
+
657
+
658 // bob deletes his account using a ticket. bob's account and all
+
659 // of his tickets should be removed from the ledger.
+
660 auto const acctDelFee{drops(env.current()->fees().increment)};
+
661 auto const bobOldBalance{env.balance(bob)};
+
662 env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
+
663 verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
+
664 env.close();
+
665 {
+
666 std::shared_ptr<ReadView const> closed{env.closed()};
+
667 BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
+
668 for (std::uint32_t i = 0; i < 250; ++i)
+
669 {
+
670 BEAST_EXPECT(
+
671 !closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
+
672 }
+
673 }
+
674 }
-
678
-
679 void
-
- -
681 {
-
682 testcase("With Tickets");
-
683
-
684 using namespace test::jtx;
-
685
-
686 Account const alice{"alice"};
-
687 Account const bob{"bob"};
-
688
-
689 Env env{*this};
-
690 env.fund(XRP(100000), alice, bob);
-
691 env.close();
-
692
-
693 // bob grabs as many tickets as he is allowed to have.
-
694 std::uint32_t const ticketSeq{env.seq(bob) + 1};
-
695 env(ticket::create(bob, 250));
-
696 env.close();
-
697 env.require(owners(bob, 250));
-
698
-
699 {
-
700 std::shared_ptr<ReadView const> closed{env.closed()};
-
701 BEAST_EXPECT(closed->exists(keylet::account(bob.id())));
-
702 for (std::uint32_t i = 0; i < 250; ++i)
-
703 {
-
704 BEAST_EXPECT(
-
705 closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
-
706 }
-
707 }
-
708
-
709 // Close enough ledgers to be able to delete bob's account.
-
710 incLgrSeqForAccDel(env, bob);
-
711
-
712 // bob deletes his account using a ticket. bob's account and all
-
713 // of his tickets should be removed from the ledger.
-
714 auto const acctDelFee{drops(env.current()->fees().increment)};
-
715 auto const bobOldBalance{env.balance(bob)};
-
716 env(acctdelete(bob, alice), ticket::use(ticketSeq), fee(acctDelFee));
-
717 verifyDeliveredAmount(env, bobOldBalance - acctDelFee);
+
675
+
676 void
+
+ +
678 {
+
679 testcase("Destination Constraints");
+
680
+
681 using namespace test::jtx;
+
682
+
683 Account const alice{"alice"};
+
684 Account const becky{"becky"};
+
685 Account const carol{"carol"};
+
686 Account const daria{"daria"};
+
687
+
688 Env env{*this};
+
689 env.fund(XRP(100000), alice, becky, carol);
+
690 env.close();
+
691
+
692 // alice sets the lsfDepositAuth flag on her account. This should
+
693 // prevent becky from deleting her account while using alice as the
+
694 // destination.
+
695 env(fset(alice, asfDepositAuth));
+
696
+
697 // carol requires a destination tag.
+
698 env(fset(carol, asfRequireDest));
+
699 env.close();
+
700
+
701 // Close enough ledgers to be able to delete becky's account.
+
702 incLgrSeqForAccDel(env, becky);
+
703
+
704 // becky attempts to delete her account using daria as the destination.
+
705 // Since daria is not in the ledger the delete attempt fails.
+
706 auto const acctDelFee{drops(env.current()->fees().increment)};
+
707 env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
+
708 env.close();
+
709
+
710 // becky attempts to delete her account, but carol requires a
+
711 // destination tag which becky has omitted.
+
712 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
+
713 env.close();
+
714
+
715 // becky attempts to delete her account, but alice won't take her XRP,
+
716 // so the delete is blocked.
+
717 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
718 env.close();
-
719 {
-
720 std::shared_ptr<ReadView const> closed{env.closed()};
-
721 BEAST_EXPECT(!closed->exists(keylet::account(bob.id())));
-
722 for (std::uint32_t i = 0; i < 250; ++i)
-
723 {
-
724 BEAST_EXPECT(
-
725 !closed->exists(keylet::ticket(bob.id(), ticketSeq + i)));
-
726 }
-
727 }
-
728 }
+
719
+
720 // alice preauthorizes deposits from becky. Now becky can delete her
+
721 // account and forward the leftovers to alice.
+
722 env(deposit::auth(alice, becky));
+
723 env.close();
+
724
+
725 auto const beckyOldBalance{env.balance(becky)};
+
726 env(acctdelete(becky, alice), fee(acctDelFee));
+
727 verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
+
728 env.close();
+
729 }
-
729
-
730 void
-
- -
732 {
-
733 testcase("Destination Constraints");
-
734
-
735 using namespace test::jtx;
-
736
-
737 Account const alice{"alice"};
-
738 Account const becky{"becky"};
-
739 Account const carol{"carol"};
-
740 Account const daria{"daria"};
-
741
-
742 Env env{*this};
-
743 env.fund(XRP(100000), alice, becky, carol);
-
744 env.close();
-
745
-
746 // alice sets the lsfDepositAuth flag on her account. This should
-
747 // prevent becky from deleting her account while using alice as the
-
748 // destination.
-
749 env(fset(alice, asfDepositAuth));
+
730
+
731 void
+
+ +
733 {
+
734 {
+
735 testcase(
+
736 "Destination Constraints with DepositPreauth and Credentials");
+
737
+
738 using namespace test::jtx;
+
739
+
740 Account const alice{"alice"};
+
741 Account const becky{"becky"};
+
742 Account const carol{"carol"};
+
743 Account const daria{"daria"};
+
744
+
745 char const credType[] = "abcd";
+
746
+
747 Env env{*this};
+
748 env.fund(XRP(100000), alice, becky, carol, daria);
+
749 env.close();
750
-
751 // carol requires a destination tag.
-
752 env(fset(carol, asfRequireDest));
-
753 env.close();
+
751 // carol issue credentials for becky
+
752 env(credentials::create(becky, carol, credType));
+
753 env.close();
754
-
755 // Close enough ledgers to be able to delete becky's account.
-
756 incLgrSeqForAccDel(env, becky);
-
757
-
758 // becky attempts to delete her account using daria as the destination.
-
759 // Since daria is not in the ledger the delete attempt fails.
-
760 auto const acctDelFee{drops(env.current()->fees().increment)};
-
761 env(acctdelete(becky, daria), fee(acctDelFee), ter(tecNO_DST));
-
762 env.close();
-
763
-
764 // becky attempts to delete her account, but carol requires a
-
765 // destination tag which becky has omitted.
-
766 env(acctdelete(becky, carol), fee(acctDelFee), ter(tecDST_TAG_NEEDED));
-
767 env.close();
-
768
-
769 // becky attempts to delete her account, but alice won't take her XRP,
-
770 // so the delete is blocked.
-
771 env(acctdelete(becky, alice), fee(acctDelFee), ter(tecNO_PERMISSION));
-
772 env.close();
-
773
-
774 // alice preauthorizes deposits from becky. Now becky can delete her
-
775 // account and forward the leftovers to alice.
-
776 env(deposit::auth(alice, becky));
-
777 env.close();
-
778
-
779 auto const beckyOldBalance{env.balance(becky)};
-
780 env(acctdelete(becky, alice), fee(acctDelFee));
-
781 verifyDeliveredAmount(env, beckyOldBalance - acctDelFee);
-
782 env.close();
-
783 }
-
-
784
-
785 void
-
- -
787 {
-
788 {
-
789 testcase(
-
790 "Destination Constraints with DepositPreauth and Credentials");
-
791
-
792 using namespace test::jtx;
-
793
-
794 Account const alice{"alice"};
-
795 Account const becky{"becky"};
-
796 Account const carol{"carol"};
-
797 Account const daria{"daria"};
-
798
-
799 char const credType[] = "abcd";
-
800
-
801 Env env{*this};
-
802 env.fund(XRP(100000), alice, becky, carol, daria);
-
803 env.close();
-
804
-
805 // carol issue credentials for becky
-
806 env(credentials::create(becky, carol, credType));
-
807 env.close();
-
808
-
809 // get credentials index
-
810 auto const jv =
-
811 credentials::ledgerEntry(env, becky, carol, credType);
-
812 std::string const credIdx = jv[jss::result][jss::index].asString();
+
755 // get credentials index
+
756 auto const jv =
+
757 credentials::ledgerEntry(env, becky, carol, credType);
+
758 std::string const credIdx = jv[jss::result][jss::index].asString();
+
759
+
760 // Close enough ledgers to be able to delete becky's account.
+
761 incLgrSeqForAccDel(env, becky);
+
762
+
763 auto const acctDelFee{drops(env.current()->fees().increment)};
+
764
+
765 // becky use credentials but they aren't accepted
+
766 env(acctdelete(becky, alice),
+
767 credentials::ids({credIdx}),
+
768 fee(acctDelFee),
+ +
770 env.close();
+
771
+
772 {
+
773 // alice sets the lsfDepositAuth flag on her account. This
+
774 // should prevent becky from deleting her account while using
+
775 // alice as the destination.
+
776 env(fset(alice, asfDepositAuth));
+
777 env.close();
+
778 }
+
779
+
780 // Fail, credentials still not accepted
+
781 env(acctdelete(becky, alice),
+
782 credentials::ids({credIdx}),
+
783 fee(acctDelFee),
+ +
785 env.close();
+
786
+
787 // becky accept the credentials
+
788 env(credentials::accept(becky, carol, credType));
+
789 env.close();
+
790
+
791 // Fail, credentials doesn’t belong to carol
+
792 env(acctdelete(carol, alice),
+
793 credentials::ids({credIdx}),
+
794 fee(acctDelFee),
+ +
796
+
797 // Fail, no depositPreauth for provided credentials
+
798 env(acctdelete(becky, alice),
+
799 credentials::ids({credIdx}),
+
800 fee(acctDelFee),
+ +
802 env.close();
+
803
+
804 // alice create DepositPreauth Object
+
805 env(deposit::authCredentials(alice, {{carol, credType}}));
+
806 env.close();
+
807
+
808 // becky attempts to delete her account, but alice won't take her
+
809 // XRP, so the delete is blocked.
+
810 env(acctdelete(becky, alice),
+
811 fee(acctDelFee),
+
813
-
814 // Close enough ledgers to be able to delete becky's account.
-
815 incLgrSeqForAccDel(env, becky);
-
816
-
817 auto const acctDelFee{drops(env.current()->fees().increment)};
-
818
-
819 // becky use credentials but they aren't accepted
-
820 env(acctdelete(becky, alice),
-
821 credentials::ids({credIdx}),
-
822 fee(acctDelFee),
- -
824 env.close();
-
825
-
826 {
-
827 // alice sets the lsfDepositAuth flag on her account. This
-
828 // should prevent becky from deleting her account while using
-
829 // alice as the destination.
-
830 env(fset(alice, asfDepositAuth));
-
831 env.close();
-
832 }
-
833
-
834 // Fail, credentials still not accepted
-
835 env(acctdelete(becky, alice),
-
836 credentials::ids({credIdx}),
-
837 fee(acctDelFee),
- -
839 env.close();
-
840
-
841 // becky accept the credentials
-
842 env(credentials::accept(becky, carol, credType));
-
843 env.close();
+
814 // becky use empty credentials and can't delete account
+
815 env(acctdelete(becky, alice),
+
816 fee(acctDelFee),
+ + +
819
+
820 // becky use bad credentials and can't delete account
+
821 env(acctdelete(becky, alice),
+ +
823 {"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6E"
+
824 "A288BE4"}),
+
825 fee(acctDelFee),
+ +
827 env.close();
+
828
+
829 // becky use credentials and can delete account
+
830 env(acctdelete(becky, alice),
+
831 credentials::ids({credIdx}),
+
832 fee(acctDelFee));
+
833 env.close();
+
834
+
835 {
+
836 // check that credential object deleted too
+
837 auto const jNoCred =
+
838 credentials::ledgerEntry(env, becky, carol, credType);
+
839 BEAST_EXPECT(
+
840 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
+
841 jNoCred[jss::result].isMember(jss::error) &&
+
842 jNoCred[jss::result][jss::error] == "entryNotFound");
+
843 }
844
-
845 // Fail, credentials doesn’t belong to carol
-
846 env(acctdelete(carol, alice),
-
847 credentials::ids({credIdx}),
-
848 fee(acctDelFee),
- -
850
-
851 // Fail, no depositPreauth for provided credentials
-
852 env(acctdelete(becky, alice),
-
853 credentials::ids({credIdx}),
-
854 fee(acctDelFee),
- -
856 env.close();
-
857
-
858 // alice create DepositPreauth Object
-
859 env(deposit::authCredentials(alice, {{carol, credType}}));
-
860 env.close();
-
861
-
862 // becky attempts to delete her account, but alice won't take her
-
863 // XRP, so the delete is blocked.
-
864 env(acctdelete(becky, alice),
-
865 fee(acctDelFee),
- -
867
-
868 // becky use empty credentials and can't delete account
-
869 env(acctdelete(becky, alice),
-
870 fee(acctDelFee),
- - -
873
-
874 // becky use bad credentials and can't delete account
-
875 env(acctdelete(becky, alice),
- -
877 {"48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6E"
-
878 "A288BE4"}),
-
879 fee(acctDelFee),
- -
881 env.close();
-
882
-
883 // becky use credentials and can delete account
-
884 env(acctdelete(becky, alice),
-
885 credentials::ids({credIdx}),
-
886 fee(acctDelFee));
-
887 env.close();
+
845 testcase("Credentials that aren't required");
+
846 { // carol issue credentials for daria
+
847 env(credentials::create(daria, carol, credType));
+
848 env.close();
+
849 env(credentials::accept(daria, carol, credType));
+
850 env.close();
+
851 std::string const credDaria =
+ +
853 env, daria, carol, credType)[jss::result][jss::index]
+
854 .asString();
+
855
+
856 // daria use valid credentials, which aren't required and can
+
857 // delete her account
+
858 env(acctdelete(daria, carol),
+
859 credentials::ids({credDaria}),
+
860 fee(acctDelFee));
+
861 env.close();
+
862
+
863 // check that credential object deleted too
+
864 auto const jNoCred =
+
865 credentials::ledgerEntry(env, daria, carol, credType);
+
866
+
867 BEAST_EXPECT(
+
868 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
+
869 jNoCred[jss::result].isMember(jss::error) &&
+
870 jNoCred[jss::result][jss::error] == "entryNotFound");
+
871 }
+
872
+
873 {
+
874 Account const eaton{"eaton"};
+
875 Account const fred{"fred"};
+
876
+
877 env.fund(XRP(5000), eaton, fred);
+
878
+
879 // carol issue credentials for eaton
+
880 env(credentials::create(eaton, carol, credType));
+
881 env.close();
+
882 env(credentials::accept(eaton, carol, credType));
+
883 env.close();
+
884 std::string const credEaton =
+ +
886 env, eaton, carol, credType)[jss::result][jss::index]
+
887 .asString();
888
-
889 {
-
890 // check that credential object deleted too
-
891 auto const jNoCred =
-
892 credentials::ledgerEntry(env, becky, carol, credType);
-
893 BEAST_EXPECT(
-
894 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
-
895 jNoCred[jss::result].isMember(jss::error) &&
-
896 jNoCred[jss::result][jss::error] == "entryNotFound");
-
897 }
+
889 // fred make preauthorization through authorized account
+
890 env(fset(fred, asfDepositAuth));
+
891 env.close();
+
892 env(deposit::auth(fred, eaton));
+
893 env.close();
+
894
+
895 // Close enough ledgers to be able to delete becky's account.
+
896 incLgrSeqForAccDel(env, eaton);
+
897 auto const acctDelFee{drops(env.current()->fees().increment)};
898
-
899 testcase("Credentials that aren't required");
-
900 { // carol issue credentials for daria
-
901 env(credentials::create(daria, carol, credType));
-
902 env.close();
-
903 env(credentials::accept(daria, carol, credType));
+
899 // eaton use valid credentials, but he already authorized
+
900 // through "Authorized" field.
+
901 env(acctdelete(eaton, fred),
+
902 credentials::ids({credEaton}),
+
903 fee(acctDelFee));
904 env.close();
-
905 std::string const credDaria =
- -
907 env, daria, carol, credType)[jss::result][jss::index]
-
908 .asString();
+
905
+
906 // check that credential object deleted too
+
907 auto const jNoCred =
+
908 credentials::ledgerEntry(env, eaton, carol, credType);
909
-
910 // daria use valid credentials, which aren't required and can
-
911 // delete her account
-
912 env(acctdelete(daria, carol),
-
913 credentials::ids({credDaria}),
-
914 fee(acctDelFee));
-
915 env.close();
-
916
-
917 // check that credential object deleted too
-
918 auto const jNoCred =
-
919 credentials::ledgerEntry(env, daria, carol, credType);
-
920
-
921 BEAST_EXPECT(
-
922 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
-
923 jNoCred[jss::result].isMember(jss::error) &&
-
924 jNoCred[jss::result][jss::error] == "entryNotFound");
-
925 }
-
926
-
927 {
-
928 Account const eaton{"eaton"};
-
929 Account const fred{"fred"};
-
930
-
931 env.fund(XRP(5000), eaton, fred);
-
932
-
933 // carol issue credentials for eaton
-
934 env(credentials::create(eaton, carol, credType));
-
935 env.close();
-
936 env(credentials::accept(eaton, carol, credType));
-
937 env.close();
-
938 std::string const credEaton =
- -
940 env, eaton, carol, credType)[jss::result][jss::index]
-
941 .asString();
-
942
-
943 // fred make preauthorization through authorized account
-
944 env(fset(fred, asfDepositAuth));
-
945 env.close();
-
946 env(deposit::auth(fred, eaton));
-
947 env.close();
-
948
-
949 // Close enough ledgers to be able to delete becky's account.
-
950 incLgrSeqForAccDel(env, eaton);
-
951 auto const acctDelFee{drops(env.current()->fees().increment)};
-
952
-
953 // eaton use valid credentials, but he already authorized
-
954 // through "Authorized" field.
-
955 env(acctdelete(eaton, fred),
-
956 credentials::ids({credEaton}),
-
957 fee(acctDelFee));
-
958 env.close();
+
910 BEAST_EXPECT(
+
911 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
+
912 jNoCred[jss::result].isMember(jss::error) &&
+
913 jNoCred[jss::result][jss::error] == "entryNotFound");
+
914 }
+
915
+
916 testcase("Expired credentials");
+
917 {
+
918 Account const john{"john"};
+
919
+
920 env.fund(XRP(10000), john);
+
921 env.close();
+
922
+
923 auto jv = credentials::create(john, carol, credType);
+
924 uint32_t const t = env.current()
+
925 ->info()
+
926 .parentCloseTime.time_since_epoch()
+
927 .count() +
+
928 20;
+
929 jv[sfExpiration.jsonName] = t;
+
930 env(jv);
+
931 env.close();
+
932 env(credentials::accept(john, carol, credType));
+
933 env.close();
+
934 jv = credentials::ledgerEntry(env, john, carol, credType);
+
935 std::string const credIdx =
+
936 jv[jss::result][jss::index].asString();
+
937
+
938 incLgrSeqForAccDel(env, john);
+
939
+
940 // credentials are expired
+
941 // john use credentials but can't delete account
+
942 env(acctdelete(john, alice),
+
943 credentials::ids({credIdx}),
+
944 fee(acctDelFee),
+
945 ter(tecEXPIRED));
+
946 env.close();
+
947
+
948 {
+
949 // check that expired credential object deleted
+
950 auto jv =
+
951 credentials::ledgerEntry(env, john, carol, credType);
+
952 BEAST_EXPECT(
+
953 jv.isObject() && jv.isMember(jss::result) &&
+
954 jv[jss::result].isMember(jss::error) &&
+
955 jv[jss::result][jss::error] == "entryNotFound");
+
956 }
+
957 }
+
958 }
959
-
960 // check that credential object deleted too
-
961 auto const jNoCred =
-
962 credentials::ledgerEntry(env, eaton, carol, credType);
+
960 {
+
961 testcase("Credentials feature disabled");
+
962 using namespace test::jtx;
963
-
964 BEAST_EXPECT(
-
965 jNoCred.isObject() && jNoCred.isMember(jss::result) &&
-
966 jNoCred[jss::result].isMember(jss::error) &&
-
967 jNoCred[jss::result][jss::error] == "entryNotFound");
-
968 }
-
969
-
970 testcase("Expired credentials");
-
971 {
-
972 Account const john{"john"};
-
973
-
974 env.fund(XRP(10000), john);
-
975 env.close();
-
976
-
977 auto jv = credentials::create(john, carol, credType);
-
978 uint32_t const t = env.current()
-
979 ->info()
-
980 .parentCloseTime.time_since_epoch()
-
981 .count() +
-
982 20;
-
983 jv[sfExpiration.jsonName] = t;
-
984 env(jv);
-
985 env.close();
-
986 env(credentials::accept(john, carol, credType));
-
987 env.close();
-
988 jv = credentials::ledgerEntry(env, john, carol, credType);
-
989 std::string const credIdx =
-
990 jv[jss::result][jss::index].asString();
-
991
-
992 incLgrSeqForAccDel(env, john);
-
993
-
994 // credentials are expired
-
995 // john use credentials but can't delete account
-
996 env(acctdelete(john, alice),
-
997 credentials::ids({credIdx}),
-
998 fee(acctDelFee),
-
999 ter(tecEXPIRED));
-
1000 env.close();
-
1001
-
1002 {
-
1003 // check that expired credential object deleted
-
1004 auto jv =
-
1005 credentials::ledgerEntry(env, john, carol, credType);
-
1006 BEAST_EXPECT(
-
1007 jv.isObject() && jv.isMember(jss::result) &&
-
1008 jv[jss::result].isMember(jss::error) &&
-
1009 jv[jss::result][jss::error] == "entryNotFound");
-
1010 }
-
1011 }
-
1012 }
-
1013
-
1014 {
-
1015 testcase("Credentials feature disabled");
-
1016 using namespace test::jtx;
-
1017
-
1018 Account const alice{"alice"};
-
1019 Account const becky{"becky"};
-
1020 Account const carol{"carol"};
-
1021
-
1022 Env env{*this, testable_amendments() - featureCredentials};
-
1023 env.fund(XRP(100000), alice, becky, carol);
-
1024 env.close();
-
1025
-
1026 // alice sets the lsfDepositAuth flag on her account. This should
-
1027 // prevent becky from deleting her account while using alice as the
-
1028 // destination.
-
1029 env(fset(alice, asfDepositAuth));
-
1030 env.close();
-
1031
-
1032 // Close enough ledgers to be able to delete becky's account.
-
1033 incLgrSeqForAccDel(env, becky);
+
964 Account const alice{"alice"};
+
965 Account const becky{"becky"};
+
966 Account const carol{"carol"};
+
967
+
968 Env env{*this, testable_amendments() - featureCredentials};
+
969 env.fund(XRP(100000), alice, becky, carol);
+
970 env.close();
+
971
+
972 // alice sets the lsfDepositAuth flag on her account. This should
+
973 // prevent becky from deleting her account while using alice as the
+
974 // destination.
+
975 env(fset(alice, asfDepositAuth));
+
976 env.close();
+
977
+
978 // Close enough ledgers to be able to delete becky's account.
+
979 incLgrSeqForAccDel(env, becky);
+
980
+
981 auto const acctDelFee{drops(env.current()->fees().increment)};
+
982
+
983 std::string const credIdx =
+
984 "098B7F1B146470A1C5084DC7832C04A72939E3EBC58E68AB8B579BA072B0CE"
+
985 "CB";
+
986
+
987 // and can't delete even with old DepositPreauth
+
988 env(deposit::auth(alice, becky));
+
989 env.close();
+
990
+
991 env(acctdelete(becky, alice),
+
992 credentials::ids({credIdx}),
+
993 fee(acctDelFee),
+ +
995 env.close();
+
996 }
+
997 }
+
+
998
+
999 void
+
+ +
1001 {
+
1002 {
+
1003 testcase("Deleting Issuer deletes issued credentials");
+
1004
+
1005 using namespace test::jtx;
+
1006
+
1007 Account const alice{"alice"};
+
1008 Account const becky{"becky"};
+
1009 Account const carol{"carol"};
+
1010
+
1011 char const credType[] = "abcd";
+
1012
+
1013 Env env{*this};
+
1014 env.fund(XRP(100000), alice, becky, carol);
+
1015 env.close();
+
1016
+
1017 // carol issue credentials for becky
+
1018 env(credentials::create(becky, carol, credType));
+
1019 env.close();
+
1020 env(credentials::accept(becky, carol, credType));
+
1021 env.close();
+
1022
+
1023 // get credentials index
+
1024 auto const jv =
+
1025 credentials::ledgerEntry(env, becky, carol, credType);
+
1026 std::string const credIdx = jv[jss::result][jss::index].asString();
+
1027
+
1028 // Close enough ledgers to be able to delete carol's account.
+
1029 incLgrSeqForAccDel(env, carol);
+
1030
+
1031 auto const acctDelFee{drops(env.current()->fees().increment)};
+
1032 env(acctdelete(carol, alice), fee(acctDelFee));
+
1033 env.close();
1034
-
1035 auto const acctDelFee{drops(env.current()->fees().increment)};
-
1036
-
1037 std::string const credIdx =
-
1038 "098B7F1B146470A1C5084DC7832C04A72939E3EBC58E68AB8B579BA072B0CE"
-
1039 "CB";
-
1040
-
1041 // and can't delete even with old DepositPreauth
-
1042 env(deposit::auth(alice, becky));
-
1043 env.close();
-
1044
-
1045 env(acctdelete(becky, alice),
-
1046 credentials::ids({credIdx}),
-
1047 fee(acctDelFee),
-
1048 ter(temDISABLED));
-
1049 env.close();
-
1050 }
-
1051 }
-
-
1052
-
1053 void
-
- -
1055 {
-
1056 {
-
1057 testcase("Deleting Issuer deletes issued credentials");
-
1058
-
1059 using namespace test::jtx;
+
1035 { // check that credential object deleted too
+
1036 BEAST_EXPECT(!env.le(credIdx));
+
1037 auto const jv =
+
1038 credentials::ledgerEntry(env, becky, carol, credType);
+
1039 BEAST_EXPECT(
+
1040 jv.isObject() && jv.isMember(jss::result) &&
+
1041 jv[jss::result].isMember(jss::error) &&
+
1042 jv[jss::result][jss::error] == "entryNotFound");
+
1043 }
+
1044 }
+
1045
+
1046 {
+
1047 testcase("Deleting Subject deletes issued credentials");
+
1048
+
1049 using namespace test::jtx;
+
1050
+
1051 Account const alice{"alice"};
+
1052 Account const becky{"becky"};
+
1053 Account const carol{"carol"};
+
1054
+
1055 char const credType[] = "abcd";
+
1056
+
1057 Env env{*this};
+
1058 env.fund(XRP(100000), alice, becky, carol);
+
1059 env.close();
1060
-
1061 Account const alice{"alice"};
-
1062 Account const becky{"becky"};
-
1063 Account const carol{"carol"};
-
1064
-
1065 char const credType[] = "abcd";
+
1061 // carol issue credentials for becky
+
1062 env(credentials::create(becky, carol, credType));
+
1063 env.close();
+
1064 env(credentials::accept(becky, carol, credType));
+
1065 env.close();
1066
-
1067 Env env{*this};
-
1068 env.fund(XRP(100000), alice, becky, carol);
-
1069 env.close();
-
1070
-
1071 // carol issue credentials for becky
-
1072 env(credentials::create(becky, carol, credType));
-
1073 env.close();
-
1074 env(credentials::accept(becky, carol, credType));
-
1075 env.close();
-
1076
-
1077 // get credentials index
-
1078 auto const jv =
-
1079 credentials::ledgerEntry(env, becky, carol, credType);
-
1080 std::string const credIdx = jv[jss::result][jss::index].asString();
-
1081
-
1082 // Close enough ledgers to be able to delete carol's account.
-
1083 incLgrSeqForAccDel(env, carol);
-
1084
-
1085 auto const acctDelFee{drops(env.current()->fees().increment)};
-
1086 env(acctdelete(carol, alice), fee(acctDelFee));
-
1087 env.close();
-
1088
-
1089 { // check that credential object deleted too
-
1090 BEAST_EXPECT(!env.le(credIdx));
-
1091 auto const jv =
-
1092 credentials::ledgerEntry(env, becky, carol, credType);
-
1093 BEAST_EXPECT(
-
1094 jv.isObject() && jv.isMember(jss::result) &&
-
1095 jv[jss::result].isMember(jss::error) &&
-
1096 jv[jss::result][jss::error] == "entryNotFound");
-
1097 }
-
1098 }
-
1099
-
1100 {
-
1101 testcase("Deleting Subject deletes issued credentials");
-
1102
-
1103 using namespace test::jtx;
-
1104
-
1105 Account const alice{"alice"};
-
1106 Account const becky{"becky"};
-
1107 Account const carol{"carol"};
+
1067 // get credentials index
+
1068 auto const jv =
+
1069 credentials::ledgerEntry(env, becky, carol, credType);
+
1070 std::string const credIdx = jv[jss::result][jss::index].asString();
+
1071
+
1072 // Close enough ledgers to be able to delete carol's account.
+
1073 incLgrSeqForAccDel(env, becky);
+
1074
+
1075 auto const acctDelFee{drops(env.current()->fees().increment)};
+
1076 env(acctdelete(becky, alice), fee(acctDelFee));
+
1077 env.close();
+
1078
+
1079 { // check that credential object deleted too
+
1080 BEAST_EXPECT(!env.le(credIdx));
+
1081 auto const jv =
+
1082 credentials::ledgerEntry(env, becky, carol, credType);
+
1083 BEAST_EXPECT(
+
1084 jv.isObject() && jv.isMember(jss::result) &&
+
1085 jv[jss::result].isMember(jss::error) &&
+
1086 jv[jss::result][jss::error] == "entryNotFound");
+
1087 }
+
1088 }
+
1089 }
+
+
1090
+
1091 void
+
+
1092 run() override
+
1093 {
+
1094 testBasics();
+ + + + + + +
1101 testDest();
+ + +
1104 }
+
+
1105};
+
+
1106
+
1107BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2);
1108
-
1109 char const credType[] = "abcd";
-
1110
-
1111 Env env{*this};
-
1112 env.fund(XRP(100000), alice, becky, carol);
-
1113 env.close();
-
1114
-
1115 // carol issue credentials for becky
-
1116 env(credentials::create(becky, carol, credType));
-
1117 env.close();
-
1118 env(credentials::accept(becky, carol, credType));
-
1119 env.close();
-
1120
-
1121 // get credentials index
-
1122 auto const jv =
-
1123 credentials::ledgerEntry(env, becky, carol, credType);
-
1124 std::string const credIdx = jv[jss::result][jss::index].asString();
-
1125
-
1126 // Close enough ledgers to be able to delete carol's account.
-
1127 incLgrSeqForAccDel(env, becky);
-
1128
-
1129 auto const acctDelFee{drops(env.current()->fees().increment)};
-
1130 env(acctdelete(becky, alice), fee(acctDelFee));
-
1131 env.close();
-
1132
-
1133 { // check that credential object deleted too
-
1134 BEAST_EXPECT(!env.le(credIdx));
-
1135 auto const jv =
-
1136 credentials::ledgerEntry(env, becky, carol, credType);
-
1137 BEAST_EXPECT(
-
1138 jv.isObject() && jv.isMember(jss::result) &&
-
1139 jv[jss::result].isMember(jss::error) &&
-
1140 jv[jss::result][jss::error] == "entryNotFound");
-
1141 }
-
1142 }
-
1143 }
-
-
1144
-
1145 void
-
-
1146 run() override
-
1147 {
-
1148 testBasics();
- - - - - - - -
1156 testDest();
- - -
1159 }
-
-
1160};
-
-
1161
-
1162BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2);
-
1163
-
1164} // namespace test
-
1165} // namespace ripple
+
1109} // namespace test
+
1110} // namespace ripple
Represents a JSON value.
Definition json_value.h:131
std::string asString() const
Returns the unquoted string value.
@@ -1289,20 +1232,19 @@ $(document).ready(function() { init_codefold(0); });
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:753
- + - - + +
void verifyDeliveredAmount(jtx::Env &env, STAmount const &amount)
- -
void run() override
Runs the suite.
- + +
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)
- +
Immutable cryptographic account descriptor.
Definition Account.h:20
PublicKey const & pk() const
Return the public key.
Definition Account.h:75
AccountID id() const
Returns the Account ID.
Definition Account.h:92
diff --git a/DeleteAccount_8cpp_source.html b/DeleteAccount_8cpp_source.html index c018cae41b..5be4e069ce 100644 --- a/DeleteAccount_8cpp_source.html +++ b/DeleteAccount_8cpp_source.html @@ -106,410 +106,407 @@ $(document).ready(function() { init_codefold(0); });
24{
-
25 if (!ctx.rules.enabled(featureDeletableAccounts))
-
26 return false;
-
27
-
28 if (ctx.tx.isFieldPresent(sfCredentialIDs) &&
-
29 !ctx.rules.enabled(featureCredentials))
-
30 return false;
+
25 if (ctx.tx.isFieldPresent(sfCredentialIDs) &&
+
26 !ctx.rules.enabled(featureCredentials))
+
27 return false;
+
28
+
29 return true;
+
30}
+
31
-
32 return true;
-
33}
+ +
+ +
34{
+
35 if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
+
36 // An account cannot be deleted and give itself the resulting XRP.
+
37 return temDST_IS_SRC;
+
38
+
39 if (auto const err = credentials::checkFields(ctx.tx, ctx.j);
+
40 !isTesSuccess(err))
+
41 return err;
+
42
+
43 return tesSUCCESS;
+
44}
-
34
- -
- -
37{
-
38 if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
-
39 // An account cannot be deleted and give itself the resulting XRP.
-
40 return temDST_IS_SRC;
-
41
-
42 if (auto const err = credentials::checkFields(ctx.tx, ctx.j);
-
43 !isTesSuccess(err))
-
44 return err;
45
-
46 return tesSUCCESS;
-
47}
+ +
+ +
48{
+
49 // The fee required for AccountDelete is one owner reserve.
+ +
51}
-
48
- -
- -
51{
-
52 // The fee required for AccountDelete is one owner reserve.
- -
54}
-
-
55
-
56namespace {
-
57// Define a function pointer type that can be used to delete ledger node types.
-
58using DeleterFuncPtr = TER (*)(
-
59 Application& app,
-
60 ApplyView& view,
-
61 AccountID const& account,
-
62 uint256 const& delIndex,
-
63 std::shared_ptr<SLE> const& sleDel,
- -
65
-
66// Local function definitions that provides signature compatibility.
-
67TER
- -
69 Application& app,
-
70 ApplyView& view,
-
71 AccountID const& account,
-
72 uint256 const& delIndex,
-
73 std::shared_ptr<SLE> const& sleDel,
- -
75{
-
76 return offerDelete(view, sleDel, j);
-
77}
-
78
-
79TER
- -
81 Application& app,
-
82 ApplyView& view,
-
83 AccountID const& account,
-
84 uint256 const& delIndex,
-
85 std::shared_ptr<SLE> const& sleDel,
- -
87{
-
88 return SetSignerList::removeFromLedger(app, view, account, j);
-
89}
-
90
-
91TER
-
92removeTicketFromLedger(
-
93 Application&,
-
94 ApplyView& view,
-
95 AccountID const& account,
-
96 uint256 const& delIndex,
- - -
99{
-
100 return Transactor::ticketDelete(view, account, delIndex, j);
-
101}
-
102
-
103TER
-
104removeDepositPreauthFromLedger(
-
105 Application&,
-
106 ApplyView& view,
-
107 AccountID const&,
-
108 uint256 const& delIndex,
- - -
111{
-
112 return DepositPreauth::removeFromLedger(view, delIndex, j);
-
113}
-
114
-
115TER
-
116removeNFTokenOfferFromLedger(
-
117 Application& app,
-
118 ApplyView& view,
-
119 AccountID const& account,
-
120 uint256 const& delIndex,
-
121 std::shared_ptr<SLE> const& sleDel,
- -
123{
-
124 if (!nft::deleteTokenOffer(view, sleDel))
-
125 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
52
+
53namespace {
+
54// Define a function pointer type that can be used to delete ledger node types.
+
55using DeleterFuncPtr = TER (*)(
+
56 Application& app,
+
57 ApplyView& view,
+
58 AccountID const& account,
+
59 uint256 const& delIndex,
+
60 std::shared_ptr<SLE> const& sleDel,
+ +
62
+
63// Local function definitions that provides signature compatibility.
+
64TER
+ +
66 Application& app,
+
67 ApplyView& view,
+
68 AccountID const& account,
+
69 uint256 const& delIndex,
+
70 std::shared_ptr<SLE> const& sleDel,
+ +
72{
+
73 return offerDelete(view, sleDel, j);
+
74}
+
75
+
76TER
+ +
78 Application& app,
+
79 ApplyView& view,
+
80 AccountID const& account,
+
81 uint256 const& delIndex,
+
82 std::shared_ptr<SLE> const& sleDel,
+ +
84{
+
85 return SetSignerList::removeFromLedger(app, view, account, j);
+
86}
+
87
+
88TER
+
89removeTicketFromLedger(
+
90 Application&,
+
91 ApplyView& view,
+
92 AccountID const& account,
+
93 uint256 const& delIndex,
+ + +
96{
+
97 return Transactor::ticketDelete(view, account, delIndex, j);
+
98}
+
99
+
100TER
+
101removeDepositPreauthFromLedger(
+
102 Application&,
+
103 ApplyView& view,
+
104 AccountID const&,
+
105 uint256 const& delIndex,
+ + +
108{
+
109 return DepositPreauth::removeFromLedger(view, delIndex, j);
+
110}
+
111
+
112TER
+
113removeNFTokenOfferFromLedger(
+
114 Application& app,
+
115 ApplyView& view,
+
116 AccountID const& account,
+
117 uint256 const& delIndex,
+
118 std::shared_ptr<SLE> const& sleDel,
+ +
120{
+
121 if (!nft::deleteTokenOffer(view, sleDel))
+
122 return tefBAD_LEDGER; // LCOV_EXCL_LINE
+
123
+
124 return tesSUCCESS;
+
125}
126
-
127 return tesSUCCESS;
-
128}
-
129
-
130TER
-
131removeDIDFromLedger(
-
132 Application& app,
-
133 ApplyView& view,
-
134 AccountID const& account,
-
135 uint256 const& delIndex,
-
136 std::shared_ptr<SLE> const& sleDel,
- -
138{
-
139 return DIDDelete::deleteSLE(view, sleDel, account, j);
-
140}
-
141
-
142TER
-
143removeOracleFromLedger(
-
144 Application&,
-
145 ApplyView& view,
-
146 AccountID const& account,
-
147 uint256 const&,
-
148 std::shared_ptr<SLE> const& sleDel,
- -
150{
-
151 return DeleteOracle::deleteOracle(view, sleDel, account, j);
-
152}
-
153
-
154TER
-
155removeCredentialFromLedger(
-
156 Application&,
-
157 ApplyView& view,
-
158 AccountID const&,
-
159 uint256 const&,
-
160 std::shared_ptr<SLE> const& sleDel,
- -
162{
-
163 return credentials::deleteSLE(view, sleDel, j);
-
164}
-
165
-
166TER
-
167removeDelegateFromLedger(
-
168 Application& app,
-
169 ApplyView& view,
-
170 AccountID const& account,
-
171 uint256 const& delIndex,
-
172 std::shared_ptr<SLE> const& sleDel,
- -
174{
-
175 return DelegateSet::deleteDelegate(view, sleDel, account, j);
-
176}
-
177
-
178// Return nullptr if the LedgerEntryType represents an obligation that can't
-
179// be deleted. Otherwise return the pointer to the function that can delete
-
180// the non-obligation
-
181DeleterFuncPtr
-
182nonObligationDeleter(LedgerEntryType t)
-
183{
-
184 switch (t)
-
185 {
-
186 case ltOFFER:
-
187 return offerDelete;
-
188 case ltSIGNER_LIST:
- -
190 case ltTICKET:
-
191 return removeTicketFromLedger;
-
192 case ltDEPOSIT_PREAUTH:
-
193 return removeDepositPreauthFromLedger;
-
194 case ltNFTOKEN_OFFER:
-
195 return removeNFTokenOfferFromLedger;
-
196 case ltDID:
-
197 return removeDIDFromLedger;
-
198 case ltORACLE:
-
199 return removeOracleFromLedger;
-
200 case ltCREDENTIAL:
-
201 return removeCredentialFromLedger;
-
202 case ltDELEGATE:
-
203 return removeDelegateFromLedger;
-
204 default:
-
205 return nullptr;
-
206 }
-
207}
-
208
-
209} // namespace
-
210
-
211TER
-
- -
213{
-
214 AccountID const account{ctx.tx[sfAccount]};
-
215 AccountID const dst{ctx.tx[sfDestination]};
-
216
-
217 auto sleDst = ctx.view.read(keylet::account(dst));
+
127TER
+
128removeDIDFromLedger(
+
129 Application& app,
+
130 ApplyView& view,
+
131 AccountID const& account,
+
132 uint256 const& delIndex,
+
133 std::shared_ptr<SLE> const& sleDel,
+ +
135{
+
136 return DIDDelete::deleteSLE(view, sleDel, account, j);
+
137}
+
138
+
139TER
+
140removeOracleFromLedger(
+
141 Application&,
+
142 ApplyView& view,
+
143 AccountID const& account,
+
144 uint256 const&,
+
145 std::shared_ptr<SLE> const& sleDel,
+ +
147{
+
148 return DeleteOracle::deleteOracle(view, sleDel, account, j);
+
149}
+
150
+
151TER
+
152removeCredentialFromLedger(
+
153 Application&,
+
154 ApplyView& view,
+
155 AccountID const&,
+
156 uint256 const&,
+
157 std::shared_ptr<SLE> const& sleDel,
+ +
159{
+
160 return credentials::deleteSLE(view, sleDel, j);
+
161}
+
162
+
163TER
+
164removeDelegateFromLedger(
+
165 Application& app,
+
166 ApplyView& view,
+
167 AccountID const& account,
+
168 uint256 const& delIndex,
+
169 std::shared_ptr<SLE> const& sleDel,
+ +
171{
+
172 return DelegateSet::deleteDelegate(view, sleDel, account, j);
+
173}
+
174
+
175// Return nullptr if the LedgerEntryType represents an obligation that can't
+
176// be deleted. Otherwise return the pointer to the function that can delete
+
177// the non-obligation
+
178DeleterFuncPtr
+
179nonObligationDeleter(LedgerEntryType t)
+
180{
+
181 switch (t)
+
182 {
+
183 case ltOFFER:
+
184 return offerDelete;
+
185 case ltSIGNER_LIST:
+ +
187 case ltTICKET:
+
188 return removeTicketFromLedger;
+
189 case ltDEPOSIT_PREAUTH:
+
190 return removeDepositPreauthFromLedger;
+
191 case ltNFTOKEN_OFFER:
+
192 return removeNFTokenOfferFromLedger;
+
193 case ltDID:
+
194 return removeDIDFromLedger;
+
195 case ltORACLE:
+
196 return removeOracleFromLedger;
+
197 case ltCREDENTIAL:
+
198 return removeCredentialFromLedger;
+
199 case ltDELEGATE:
+
200 return removeDelegateFromLedger;
+
201 default:
+
202 return nullptr;
+
203 }
+
204}
+
205
+
206} // namespace
+
207
+
208TER
+
+ +
210{
+
211 AccountID const account{ctx.tx[sfAccount]};
+
212 AccountID const dst{ctx.tx[sfDestination]};
+
213
+
214 auto sleDst = ctx.view.read(keylet::account(dst));
+
215
+
216 if (!sleDst)
+
217 return tecNO_DST;
218
-
219 if (!sleDst)
-
220 return tecNO_DST;
+
219 if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
+
220 return tecDST_TAG_NEEDED;
221
-
222 if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
-
223 return tecDST_TAG_NEEDED;
-
224
-
225 // If credentials are provided - check them anyway
-
226 if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j);
-
227 !isTesSuccess(err))
-
228 return err;
-
229
-
230 // if credentials then postpone auth check to doApply, to check for expired
-
231 // credentials
-
232 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
-
233 {
-
234 // Check whether the destination account requires deposit authorization.
-
235 if (sleDst->getFlags() & lsfDepositAuth)
-
236 {
-
237 if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
-
238 return tecNO_PERMISSION;
-
239 }
-
240 }
-
241
-
242 auto sleAccount = ctx.view.read(keylet::account(account));
-
243 XRPL_ASSERT(
-
244 sleAccount, "ripple::DeleteAccount::preclaim : non-null account");
-
245 if (!sleAccount)
-
246 return terNO_ACCOUNT;
-
247
-
248 // If an issuer has any issued NFTs resident in the ledger then it
-
249 // cannot be deleted.
-
250 if ((*sleAccount)[~sfMintedNFTokens] != (*sleAccount)[~sfBurnedNFTokens])
-
251 return tecHAS_OBLIGATIONS;
-
252
-
253 // If the account owns any NFTs it cannot be deleted.
-
254 Keylet const first = keylet::nftpage_min(account);
-
255 Keylet const last = keylet::nftpage_max(account);
-
256
-
257 auto const cp = ctx.view.read(Keylet(
-
258 ltNFTOKEN_PAGE,
-
259 ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
-
260 if (cp)
-
261 return tecHAS_OBLIGATIONS;
-
262
-
263 // We don't allow an account to be deleted if its sequence number
-
264 // is within 256 of the current ledger. This prevents replay of old
-
265 // transactions if this account is resurrected after it is deleted.
-
266 //
-
267 // We look at the account's Sequence rather than the transaction's
-
268 // Sequence in preparation for Tickets.
-
269 constexpr std::uint32_t seqDelta{255};
-
270 if ((*sleAccount)[sfSequence] + seqDelta > ctx.view.seq())
-
271 return tecTOO_SOON;
-
272
-
273 // We don't allow an account to be deleted if
-
274 // <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
-
275 // current ledger. This is to prevent having duplicate NFTokenIDs after
-
276 // account re-creation.
-
277 //
-
278 // Without this restriction, duplicate NFTokenIDs can be reproduced when
-
279 // authorized minting is involved. Because when the minter mints a NFToken,
-
280 // the issuer's sequence does not change. So when the issuer re-creates
-
281 // their account and mints a NFToken, it is possible that the
-
282 // NFTokenSequence of this NFToken is the same as the one that the
-
283 // authorized minter minted in a previous ledger.
-
284 if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
-
285 (*sleAccount)[~sfMintedNFTokens].value_or(0) + seqDelta >
-
286 ctx.view.seq())
-
287 return tecTOO_SOON;
-
288
-
289 // Verify that the account does not own any objects that would prevent
-
290 // the account from being deleted.
-
291 Keylet const ownerDirKeylet{keylet::ownerDir(account)};
-
292 if (dirIsEmpty(ctx.view, ownerDirKeylet))
-
293 return tesSUCCESS;
-
294
-
295 std::shared_ptr<SLE const> sleDirNode{};
-
296 unsigned int uDirEntry{0};
-
297 uint256 dirEntry{beast::zero};
-
298
-
299 // Account has no directory at all. This _should_ have been caught
-
300 // by the dirIsEmpty() check earlier, but it's okay to catch it here.
-
301 if (!cdirFirst(
-
302 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
-
303 return tesSUCCESS;
-
304
-
305 std::int32_t deletableDirEntryCount{0};
-
306 do
-
307 {
-
308 // Make sure any directory node types that we find are the kind
-
309 // we can delete.
-
310 auto sleItem = ctx.view.read(keylet::child(dirEntry));
-
311 if (!sleItem)
-
312 {
-
313 // Directory node has an invalid index. Bail out.
-
314 // LCOV_EXCL_START
-
315 JLOG(ctx.j.fatal())
-
316 << "DeleteAccount: directory node in ledger " << ctx.view.seq()
-
317 << " has index to object that is missing: "
-
318 << to_string(dirEntry);
-
319 return tefBAD_LEDGER;
-
320 // LCOV_EXCL_STOP
-
321 }
+
222 // If credentials are provided - check them anyway
+
223 if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j);
+
224 !isTesSuccess(err))
+
225 return err;
+
226
+
227 // if credentials then postpone auth check to doApply, to check for expired
+
228 // credentials
+
229 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
+
230 {
+
231 // Check whether the destination account requires deposit authorization.
+
232 if (sleDst->getFlags() & lsfDepositAuth)
+
233 {
+
234 if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
+
235 return tecNO_PERMISSION;
+
236 }
+
237 }
+
238
+
239 auto sleAccount = ctx.view.read(keylet::account(account));
+
240 XRPL_ASSERT(
+
241 sleAccount, "ripple::DeleteAccount::preclaim : non-null account");
+
242 if (!sleAccount)
+
243 return terNO_ACCOUNT;
+
244
+
245 // If an issuer has any issued NFTs resident in the ledger then it
+
246 // cannot be deleted.
+
247 if ((*sleAccount)[~sfMintedNFTokens] != (*sleAccount)[~sfBurnedNFTokens])
+
248 return tecHAS_OBLIGATIONS;
+
249
+
250 // If the account owns any NFTs it cannot be deleted.
+
251 Keylet const first = keylet::nftpage_min(account);
+
252 Keylet const last = keylet::nftpage_max(account);
+
253
+
254 auto const cp = ctx.view.read(Keylet(
+
255 ltNFTOKEN_PAGE,
+
256 ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
+
257 if (cp)
+
258 return tecHAS_OBLIGATIONS;
+
259
+
260 // We don't allow an account to be deleted if its sequence number
+
261 // is within 256 of the current ledger. This prevents replay of old
+
262 // transactions if this account is resurrected after it is deleted.
+
263 //
+
264 // We look at the account's Sequence rather than the transaction's
+
265 // Sequence in preparation for Tickets.
+
266 constexpr std::uint32_t seqDelta{255};
+
267 if ((*sleAccount)[sfSequence] + seqDelta > ctx.view.seq())
+
268 return tecTOO_SOON;
+
269
+
270 // We don't allow an account to be deleted if
+
271 // <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
+
272 // current ledger. This is to prevent having duplicate NFTokenIDs after
+
273 // account re-creation.
+
274 //
+
275 // Without this restriction, duplicate NFTokenIDs can be reproduced when
+
276 // authorized minting is involved. Because when the minter mints a NFToken,
+
277 // the issuer's sequence does not change. So when the issuer re-creates
+
278 // their account and mints a NFToken, it is possible that the
+
279 // NFTokenSequence of this NFToken is the same as the one that the
+
280 // authorized minter minted in a previous ledger.
+
281 if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
+
282 (*sleAccount)[~sfMintedNFTokens].value_or(0) + seqDelta >
+
283 ctx.view.seq())
+
284 return tecTOO_SOON;
+
285
+
286 // Verify that the account does not own any objects that would prevent
+
287 // the account from being deleted.
+
288 Keylet const ownerDirKeylet{keylet::ownerDir(account)};
+
289 if (dirIsEmpty(ctx.view, ownerDirKeylet))
+
290 return tesSUCCESS;
+
291
+
292 std::shared_ptr<SLE const> sleDirNode{};
+
293 unsigned int uDirEntry{0};
+
294 uint256 dirEntry{beast::zero};
+
295
+
296 // Account has no directory at all. This _should_ have been caught
+
297 // by the dirIsEmpty() check earlier, but it's okay to catch it here.
+
298 if (!cdirFirst(
+
299 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
+
300 return tesSUCCESS;
+
301
+
302 std::int32_t deletableDirEntryCount{0};
+
303 do
+
304 {
+
305 // Make sure any directory node types that we find are the kind
+
306 // we can delete.
+
307 auto sleItem = ctx.view.read(keylet::child(dirEntry));
+
308 if (!sleItem)
+
309 {
+
310 // Directory node has an invalid index. Bail out.
+
311 // LCOV_EXCL_START
+
312 JLOG(ctx.j.fatal())
+
313 << "DeleteAccount: directory node in ledger " << ctx.view.seq()
+
314 << " has index to object that is missing: "
+
315 << to_string(dirEntry);
+
316 return tefBAD_LEDGER;
+
317 // LCOV_EXCL_STOP
+
318 }
+
319
+
320 LedgerEntryType const nodeType{
+
321 safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
322
-
323 LedgerEntryType const nodeType{
-
324 safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
+
323 if (!nonObligationDeleter(nodeType))
+
324 return tecHAS_OBLIGATIONS;
325
-
326 if (!nonObligationDeleter(nodeType))
-
327 return tecHAS_OBLIGATIONS;
-
328
-
329 // We found a deletable directory entry. Count it. If we find too
-
330 // many deletable directory entries then bail out.
-
331 if (++deletableDirEntryCount > maxDeletableDirEntries)
-
332 return tefTOO_BIG;
+
326 // We found a deletable directory entry. Count it. If we find too
+
327 // many deletable directory entries then bail out.
+
328 if (++deletableDirEntryCount > maxDeletableDirEntries)
+
329 return tefTOO_BIG;
+
330
+
331 } while (cdirNext(
+
332 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
333
-
334 } while (cdirNext(
-
335 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
+
334 return tesSUCCESS;
+
335}
+
336
-
337 return tesSUCCESS;
-
338}
-
-
339
-
340TER
-
- -
342{
-
343 auto src = view().peek(keylet::account(account_));
-
344 XRPL_ASSERT(
-
345 src, "ripple::DeleteAccount::doApply : non-null source account");
-
346
-
347 auto const dstID = ctx_.tx[sfDestination];
-
348 auto dst = view().peek(keylet::account(dstID));
-
349 XRPL_ASSERT(
-
350 dst, "ripple::DeleteAccount::doApply : non-null destination account");
+
337TER
+
+ +
339{
+
340 auto src = view().peek(keylet::account(account_));
+
341 XRPL_ASSERT(
+
342 src, "ripple::DeleteAccount::doApply : non-null source account");
+
343
+
344 auto const dstID = ctx_.tx[sfDestination];
+
345 auto dst = view().peek(keylet::account(dstID));
+
346 XRPL_ASSERT(
+
347 dst, "ripple::DeleteAccount::doApply : non-null destination account");
+
348
+
349 if (!src || !dst)
+
350 return tefBAD_LEDGER; // LCOV_EXCL_LINE
351
-
352 if (!src || !dst)
-
353 return tefBAD_LEDGER; // LCOV_EXCL_LINE
-
354
-
355 if (ctx_.tx.isFieldPresent(sfCredentialIDs))
-
356 {
-
357 if (auto err = verifyDepositPreauth(
-
358 ctx_.tx, ctx_.view(), account_, dstID, dst, ctx_.journal);
-
359 !isTesSuccess(err))
-
360 return err;
-
361 }
-
362
-
363 Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
-
364 auto const ter = cleanupOnAccountDelete(
-
365 view(),
-
366 ownerDirKeylet,
-
367 [&](LedgerEntryType nodeType,
-
368 uint256 const& dirEntry,
- -
370 if (auto deleter = nonObligationDeleter(nodeType))
-
371 {
-
372 TER const result{
-
373 deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)};
+
352 if (ctx_.tx.isFieldPresent(sfCredentialIDs))
+
353 {
+
354 if (auto err = verifyDepositPreauth(
+
355 ctx_.tx, ctx_.view(), account_, dstID, dst, ctx_.journal);
+
356 !isTesSuccess(err))
+
357 return err;
+
358 }
+
359
+
360 Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
+
361 auto const ter = cleanupOnAccountDelete(
+
362 view(),
+
363 ownerDirKeylet,
+
364 [&](LedgerEntryType nodeType,
+
365 uint256 const& dirEntry,
+ +
367 if (auto deleter = nonObligationDeleter(nodeType))
+
368 {
+
369 TER const result{
+
370 deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)};
+
371
+
372 return {result, SkipEntry::No};
+
373 }
374
-
375 return {result, SkipEntry::No};
-
376 }
-
377
-
378 // LCOV_EXCL_START
-
379 UNREACHABLE(
-
380 "ripple::DeleteAccount::doApply : undeletable item not found "
-
381 "in preclaim");
-
382 JLOG(j_.error()) << "DeleteAccount undeletable item not "
-
383 "found in preclaim.";
- -
385 // LCOV_EXCL_STOP
-
386 },
-
387 ctx_.journal);
-
388 if (ter != tesSUCCESS)
-
389 return ter;
-
390
-
391 // Transfer any XRP remaining after the fee is paid to the destination:
-
392 (*dst)[sfBalance] = (*dst)[sfBalance] + mSourceBalance;
-
393 (*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
- -
395
-
396 XRPL_ASSERT(
-
397 (*src)[sfBalance] == XRPAmount(0),
-
398 "ripple::DeleteAccount::doApply : source balance is zero");
-
399
-
400 // If there's still an owner directory associated with the source account
-
401 // delete it.
-
402 if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
-
403 {
-
404 JLOG(j_.error()) << "DeleteAccount cannot delete root dir node of "
-
405 << toBase58(account_);
-
406 return tecHAS_OBLIGATIONS;
-
407 }
-
408
-
409 // Re-arm the password change fee if we can and need to.
-
410 if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
-
411 dst->clearFlag(lsfPasswordSpent);
+
375 // LCOV_EXCL_START
+
376 UNREACHABLE(
+
377 "ripple::DeleteAccount::doApply : undeletable item not found "
+
378 "in preclaim");
+
379 JLOG(j_.error()) << "DeleteAccount undeletable item not "
+
380 "found in preclaim.";
+ +
382 // LCOV_EXCL_STOP
+
383 },
+
384 ctx_.journal);
+
385 if (ter != tesSUCCESS)
+
386 return ter;
+
387
+
388 // Transfer any XRP remaining after the fee is paid to the destination:
+
389 (*dst)[sfBalance] = (*dst)[sfBalance] + mSourceBalance;
+
390 (*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
+ +
392
+
393 XRPL_ASSERT(
+
394 (*src)[sfBalance] == XRPAmount(0),
+
395 "ripple::DeleteAccount::doApply : source balance is zero");
+
396
+
397 // If there's still an owner directory associated with the source account
+
398 // delete it.
+
399 if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
+
400 {
+
401 JLOG(j_.error()) << "DeleteAccount cannot delete root dir node of "
+
402 << toBase58(account_);
+
403 return tecHAS_OBLIGATIONS;
+
404 }
+
405
+
406 // Re-arm the password change fee if we can and need to.
+
407 if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
+
408 dst->clearFlag(lsfPasswordSpent);
+
409
+
410 view().update(dst);
+
411 view().erase(src);
412
-
413 view().update(dst);
-
414 view().erase(src);
-
415
-
416 return tesSUCCESS;
-
417}
+
413 return tesSUCCESS;
+
414}
-
418
-
419} // namespace ripple
+
415
+
416} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:333
Stream error() const
Definition Journal.h:327
@@ -525,11 +522,11 @@ $(document).ready(function() { init_codefold(0); });
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
static TER deleteSLE(ApplyContext &ctx, Keylet sleKeylet, AccountID const owner)
Definition DID.cpp:153
static TER deleteDelegate(ApplyView &view, std::shared_ptr< SLE > const &sle, AccountID const &account, beast::Journal j)
-
static NotTEC preflight(PreflightContext const &ctx)
-
static TER preclaim(PreclaimContext const &ctx)
- +
static NotTEC preflight(PreflightContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
+
static bool checkExtraFeatures(PreflightContext const &ctx)
-
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
+
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static TER deleteOracle(ApplyView &view, std::shared_ptr< SLE > const &sle, AccountID const &account, beast::Journal j)
static TER removeFromLedger(ApplyView &view, uint256 const &delIndex, beast::Journal j)
A view into a ledger.
Definition ReadView.h:32
diff --git a/DeleteAccount_8h_source.html b/DeleteAccount_8h_source.html index 312534bf15..a25d3cbcb1 100644 --- a/DeleteAccount_8h_source.html +++ b/DeleteAccount_8h_source.html @@ -126,11 +126,11 @@ $(document).ready(function() { init_codefold(0); });
DeleteAccount(ApplyContext &ctx)
static constexpr ConsequencesFactoryType ConsequencesFactory
-
static NotTEC preflight(PreflightContext const &ctx)
-
static TER preclaim(PreclaimContext const &ctx)
- +
static NotTEC preflight(PreflightContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
+
static bool checkExtraFeatures(PreflightContext const &ctx)
-
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
+
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
A view into a ledger.
Definition ReadView.h:32
diff --git a/Feature__test_8cpp_source.html b/Feature__test_8cpp_source.html index ff4f4a48c9..65df925bf9 100644 --- a/Feature__test_8cpp_source.html +++ b/Feature__test_8cpp_source.html @@ -211,513 +211,512 @@ $(document).ready(function() { init_codefold(0); });
124 featureToName(fixRemoveNFTokenAutoTrustLine) ==
125 "fixRemoveNFTokenAutoTrustLine");
126 BEAST_EXPECT(featureToName(featureFlow) == "Flow");
-
127 BEAST_EXPECT(
-
128 featureToName(featureDeletableAccounts) == "DeletableAccounts");
-
129 BEAST_EXPECT(
-
130 featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
-
131 BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
-
132 }
+
127 BEAST_EXPECT(featureToName(featureDID) == "DID");
+
128 BEAST_EXPECT(
+
129 featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields");
+
130 BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow");
+
131 }
-
133
-
134 void
-
- -
136 {
-
137 testcase("No Params, None Enabled");
-
138
-
139 using namespace test::jtx;
-
140 Env env{*this};
-
141
- - -
144
-
145 auto jrr = env.rpc("feature")[jss::result];
-
146 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
-
147 return;
-
148 for (auto const& feature : jrr[jss::features])
-
149 {
-
150 if (!BEAST_EXPECT(feature.isMember(jss::name)))
-
151 return;
-
152 // default config - so all should be disabled, and
-
153 // supported. Some may be vetoed.
-
154 bool expectVeto =
-
155 (votes.at(feature[jss::name].asString()) ==
- -
157 bool expectObsolete =
-
158 (votes.at(feature[jss::name].asString()) ==
- -
160 BEAST_EXPECTS(
-
161 feature.isMember(jss::enabled) &&
-
162 !feature[jss::enabled].asBool(),
-
163 feature[jss::name].asString() + " enabled");
-
164 BEAST_EXPECTS(
-
165 feature.isMember(jss::vetoed) &&
-
166 feature[jss::vetoed].isBool() == !expectObsolete &&
-
167 (!feature[jss::vetoed].isBool() ||
-
168 feature[jss::vetoed].asBool() == expectVeto) &&
-
169 (feature[jss::vetoed].isBool() ||
-
170 feature[jss::vetoed].asString() == "Obsolete"),
-
171 feature[jss::name].asString() + " vetoed");
-
172 BEAST_EXPECTS(
-
173 feature.isMember(jss::supported) &&
-
174 feature[jss::supported].asBool(),
-
175 feature[jss::name].asString() + " supported");
-
176 }
-
177 }
+
132
+
133 void
+
+ +
135 {
+
136 testcase("No Params, None Enabled");
+
137
+
138 using namespace test::jtx;
+
139 Env env{*this};
+
140
+ + +
143
+
144 auto jrr = env.rpc("feature")[jss::result];
+
145 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
+
146 return;
+
147 for (auto const& feature : jrr[jss::features])
+
148 {
+
149 if (!BEAST_EXPECT(feature.isMember(jss::name)))
+
150 return;
+
151 // default config - so all should be disabled, and
+
152 // supported. Some may be vetoed.
+
153 bool expectVeto =
+
154 (votes.at(feature[jss::name].asString()) ==
+ +
156 bool expectObsolete =
+
157 (votes.at(feature[jss::name].asString()) ==
+ +
159 BEAST_EXPECTS(
+
160 feature.isMember(jss::enabled) &&
+
161 !feature[jss::enabled].asBool(),
+
162 feature[jss::name].asString() + " enabled");
+
163 BEAST_EXPECTS(
+
164 feature.isMember(jss::vetoed) &&
+
165 feature[jss::vetoed].isBool() == !expectObsolete &&
+
166 (!feature[jss::vetoed].isBool() ||
+
167 feature[jss::vetoed].asBool() == expectVeto) &&
+
168 (feature[jss::vetoed].isBool() ||
+
169 feature[jss::vetoed].asString() == "Obsolete"),
+
170 feature[jss::name].asString() + " vetoed");
+
171 BEAST_EXPECTS(
+
172 feature.isMember(jss::supported) &&
+
173 feature[jss::supported].asBool(),
+
174 feature[jss::name].asString() + " supported");
+
175 }
+
176 }
-
178
-
179 void
-
- -
181 {
-
182 testcase("Feature Param");
-
183
-
184 using namespace test::jtx;
-
185 Env env{*this};
-
186
-
187 auto jrr = env.rpc("feature", "RequireFullyCanonicalSig")[jss::result];
-
188 BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
-
189 jrr.removeMember(jss::status);
-
190 BEAST_EXPECT(jrr.size() == 1);
-
191 BEAST_EXPECT(
-
192 jrr.isMember("00C1FC4A53E60AB02C864641002B3172F38677E29C26C54066851"
-
193 "79B37E1EDAC"));
-
194 auto feature = *(jrr.begin());
-
195
-
196 BEAST_EXPECTS(feature[jss::name] == "RequireFullyCanonicalSig", "name");
-
197 BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
-
198 BEAST_EXPECTS(
-
199 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
-
200 "vetoed");
-
201 BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
-
202
-
203 // feature names are case-sensitive - expect error here
-
204 jrr = env.rpc("feature", "requireFullyCanonicalSig")[jss::result];
-
205 BEAST_EXPECT(jrr[jss::error] == "badFeature");
-
206 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
-
207 }
+
177
+
178 void
+
+ +
180 {
+
181 testcase("Feature Param");
+
182
+
183 using namespace test::jtx;
+
184 Env env{*this};
+
185
+
186 auto jrr = env.rpc("feature", "RequireFullyCanonicalSig")[jss::result];
+
187 BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
+
188 jrr.removeMember(jss::status);
+
189 BEAST_EXPECT(jrr.size() == 1);
+
190 BEAST_EXPECT(
+
191 jrr.isMember("00C1FC4A53E60AB02C864641002B3172F38677E29C26C54066851"
+
192 "79B37E1EDAC"));
+
193 auto feature = *(jrr.begin());
+
194
+
195 BEAST_EXPECTS(feature[jss::name] == "RequireFullyCanonicalSig", "name");
+
196 BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
+
197 BEAST_EXPECTS(
+
198 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
+
199 "vetoed");
+
200 BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
+
201
+
202 // feature names are case-sensitive - expect error here
+
203 jrr = env.rpc("feature", "requireFullyCanonicalSig")[jss::result];
+
204 BEAST_EXPECT(jrr[jss::error] == "badFeature");
+
205 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
+
206 }
-
208
-
209 void
-
- -
211 {
-
212 testcase("Invalid Feature");
-
213
-
214 using namespace test::jtx;
-
215 Env env{*this};
-
216
-
217 auto testInvalidParam = [&](auto const& param) {
-
218 Json::Value params;
-
219 params[jss::feature] = param;
-
220 auto jrr =
-
221 env.rpc("json", "feature", to_string(params))[jss::result];
-
222 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
223 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
-
224 };
-
225
-
226 testInvalidParam(1);
-
227 testInvalidParam(1.1);
-
228 testInvalidParam(true);
-
229 testInvalidParam(Json::Value(Json::nullValue));
-
230 testInvalidParam(Json::Value(Json::objectValue));
-
231 testInvalidParam(Json::Value(Json::arrayValue));
-
232
-
233 {
-
234 auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
-
235 BEAST_EXPECT(jrr[jss::error] == "badFeature");
-
236 BEAST_EXPECT(
-
237 jrr[jss::error_message] == "Feature unknown or invalid.");
-
238 }
-
239 }
+
207
+
208 void
+
+ +
210 {
+
211 testcase("Invalid Feature");
+
212
+
213 using namespace test::jtx;
+
214 Env env{*this};
+
215
+
216 auto testInvalidParam = [&](auto const& param) {
+
217 Json::Value params;
+
218 params[jss::feature] = param;
+
219 auto jrr =
+
220 env.rpc("json", "feature", to_string(params))[jss::result];
+
221 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
222 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
+
223 };
+
224
+
225 testInvalidParam(1);
+
226 testInvalidParam(1.1);
+
227 testInvalidParam(true);
+
228 testInvalidParam(Json::Value(Json::nullValue));
+
229 testInvalidParam(Json::Value(Json::objectValue));
+
230 testInvalidParam(Json::Value(Json::arrayValue));
+
231
+
232 {
+
233 auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
+
234 BEAST_EXPECT(jrr[jss::error] == "badFeature");
+
235 BEAST_EXPECT(
+
236 jrr[jss::error_message] == "Feature unknown or invalid.");
+
237 }
+
238 }
-
240
-
241 void
-
- -
243 {
-
244 testcase("Feature Without Admin");
-
245
-
246 using namespace test::jtx;
-
247 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
-
248 (*cfg)["port_rpc"].set("admin", "");
-
249 (*cfg)["port_ws"].set("admin", "");
-
250 return cfg;
-
251 })};
-
252
-
253 {
-
254 auto result = env.rpc("feature")[jss::result];
-
255 BEAST_EXPECT(result.isMember(jss::features));
-
256 // There should be at least 50 amendments. Don't do exact
-
257 // comparison to avoid maintenance as more amendments are added in
-
258 // the future.
-
259 BEAST_EXPECT(result[jss::features].size() >= 50);
-
260 for (auto it = result[jss::features].begin();
-
261 it != result[jss::features].end();
-
262 ++it)
-
263 {
-
264 uint256 id;
-
265 (void)id.parseHex(it.key().asString().c_str());
-
266 if (!BEAST_EXPECT((*it).isMember(jss::name)))
-
267 return;
-
268 bool expectEnabled =
-
269 env.app().getAmendmentTable().isEnabled(id);
-
270 bool expectSupported =
-
271 env.app().getAmendmentTable().isSupported(id);
-
272 BEAST_EXPECTS(
-
273 (*it).isMember(jss::enabled) &&
-
274 (*it)[jss::enabled].asBool() == expectEnabled,
-
275 (*it)[jss::name].asString() + " enabled");
-
276 BEAST_EXPECTS(
-
277 (*it).isMember(jss::supported) &&
-
278 (*it)[jss::supported].asBool() == expectSupported,
-
279 (*it)[jss::name].asString() + " supported");
-
280 BEAST_EXPECT(!(*it).isMember(jss::vetoed));
-
281 BEAST_EXPECT(!(*it).isMember(jss::majority));
-
282 BEAST_EXPECT(!(*it).isMember(jss::count));
-
283 BEAST_EXPECT(!(*it).isMember(jss::validations));
-
284 BEAST_EXPECT(!(*it).isMember(jss::threshold));
-
285 }
-
286 }
-
287
-
288 {
-
289 Json::Value params;
-
290 // invalid feature
-
291 params[jss::feature] =
-
292 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
-
293 "EF";
-
294 auto const result =
-
295 env.rpc("json", "feature", to_string(params))[jss::result];
-
296 BEAST_EXPECTS(
-
297 result[jss::error] == "badFeature", result.toStyledString());
-
298 BEAST_EXPECT(
-
299 result[jss::error_message] == "Feature unknown or invalid.");
-
300 }
-
301
-
302 {
-
303 Json::Value params;
-
304 params[jss::feature] =
-
305 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
-
306 "A7";
-
307 // invalid param
-
308 params[jss::vetoed] = true;
-
309 auto const result =
-
310 env.rpc("json", "feature", to_string(params))[jss::result];
-
311 BEAST_EXPECTS(
-
312 result[jss::error] == "noPermission",
-
313 result[jss::error].asString());
-
314 BEAST_EXPECT(
-
315 result[jss::error_message] ==
-
316 "You don't have permission for this command.");
-
317 }
-
318 }
+
239
+
240 void
+
+ +
242 {
+
243 testcase("Feature Without Admin");
+
244
+
245 using namespace test::jtx;
+
246 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
+
247 (*cfg)["port_rpc"].set("admin", "");
+
248 (*cfg)["port_ws"].set("admin", "");
+
249 return cfg;
+
250 })};
+
251
+
252 {
+
253 auto result = env.rpc("feature")[jss::result];
+
254 BEAST_EXPECT(result.isMember(jss::features));
+
255 // There should be at least 50 amendments. Don't do exact
+
256 // comparison to avoid maintenance as more amendments are added in
+
257 // the future.
+
258 BEAST_EXPECT(result[jss::features].size() >= 50);
+
259 for (auto it = result[jss::features].begin();
+
260 it != result[jss::features].end();
+
261 ++it)
+
262 {
+
263 uint256 id;
+
264 (void)id.parseHex(it.key().asString().c_str());
+
265 if (!BEAST_EXPECT((*it).isMember(jss::name)))
+
266 return;
+
267 bool expectEnabled =
+
268 env.app().getAmendmentTable().isEnabled(id);
+
269 bool expectSupported =
+
270 env.app().getAmendmentTable().isSupported(id);
+
271 BEAST_EXPECTS(
+
272 (*it).isMember(jss::enabled) &&
+
273 (*it)[jss::enabled].asBool() == expectEnabled,
+
274 (*it)[jss::name].asString() + " enabled");
+
275 BEAST_EXPECTS(
+
276 (*it).isMember(jss::supported) &&
+
277 (*it)[jss::supported].asBool() == expectSupported,
+
278 (*it)[jss::name].asString() + " supported");
+
279 BEAST_EXPECT(!(*it).isMember(jss::vetoed));
+
280 BEAST_EXPECT(!(*it).isMember(jss::majority));
+
281 BEAST_EXPECT(!(*it).isMember(jss::count));
+
282 BEAST_EXPECT(!(*it).isMember(jss::validations));
+
283 BEAST_EXPECT(!(*it).isMember(jss::threshold));
+
284 }
+
285 }
+
286
+
287 {
+
288 Json::Value params;
+
289 // invalid feature
+
290 params[jss::feature] =
+
291 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
+
292 "EF";
+
293 auto const result =
+
294 env.rpc("json", "feature", to_string(params))[jss::result];
+
295 BEAST_EXPECTS(
+
296 result[jss::error] == "badFeature", result.toStyledString());
+
297 BEAST_EXPECT(
+
298 result[jss::error_message] == "Feature unknown or invalid.");
+
299 }
+
300
+
301 {
+
302 Json::Value params;
+
303 params[jss::feature] =
+
304 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
+
305 "A7";
+
306 // invalid param
+
307 params[jss::vetoed] = true;
+
308 auto const result =
+
309 env.rpc("json", "feature", to_string(params))[jss::result];
+
310 BEAST_EXPECTS(
+
311 result[jss::error] == "noPermission",
+
312 result[jss::error].asString());
+
313 BEAST_EXPECT(
+
314 result[jss::error_message] ==
+
315 "You don't have permission for this command.");
+
316 }
+
317 }
-
319
-
320 void
-
- -
322 {
-
323 testcase("No Params, Some Enabled");
-
324
-
325 using namespace test::jtx;
-
326 Env env{*this, FeatureBitset{}};
-
327
- - -
330
-
331 auto jrr = env.rpc("feature")[jss::result];
-
332 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
-
333 return;
-
334 for (auto it = jrr[jss::features].begin();
-
335 it != jrr[jss::features].end();
-
336 ++it)
-
337 {
-
338 uint256 id;
-
339 (void)id.parseHex(it.key().asString().c_str());
-
340 if (!BEAST_EXPECT((*it).isMember(jss::name)))
-
341 return;
-
342 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
-
343 bool expectSupported =
-
344 env.app().getAmendmentTable().isSupported(id);
-
345 bool expectVeto =
-
346 (votes.at((*it)[jss::name].asString()) ==
- -
348 bool expectObsolete =
-
349 (votes.at((*it)[jss::name].asString()) ==
- -
351 BEAST_EXPECTS(
-
352 (*it).isMember(jss::enabled) &&
-
353 (*it)[jss::enabled].asBool() == expectEnabled,
-
354 (*it)[jss::name].asString() + " enabled");
-
355 if (expectEnabled)
-
356 BEAST_EXPECTS(
-
357 !(*it).isMember(jss::vetoed),
-
358 (*it)[jss::name].asString() + " vetoed");
-
359 else
-
360 BEAST_EXPECTS(
-
361 (*it).isMember(jss::vetoed) &&
-
362 (*it)[jss::vetoed].isBool() == !expectObsolete &&
-
363 (!(*it)[jss::vetoed].isBool() ||
-
364 (*it)[jss::vetoed].asBool() == expectVeto) &&
-
365 ((*it)[jss::vetoed].isBool() ||
-
366 (*it)[jss::vetoed].asString() == "Obsolete"),
-
367 (*it)[jss::name].asString() + " vetoed");
-
368 BEAST_EXPECTS(
-
369 (*it).isMember(jss::supported) &&
-
370 (*it)[jss::supported].asBool() == expectSupported,
-
371 (*it)[jss::name].asString() + " supported");
-
372 }
-
373 }
+
318
+
319 void
+
+ +
321 {
+
322 testcase("No Params, Some Enabled");
+
323
+
324 using namespace test::jtx;
+
325 Env env{*this, FeatureBitset{}};
+
326
+ + +
329
+
330 auto jrr = env.rpc("feature")[jss::result];
+
331 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
+
332 return;
+
333 for (auto it = jrr[jss::features].begin();
+
334 it != jrr[jss::features].end();
+
335 ++it)
+
336 {
+
337 uint256 id;
+
338 (void)id.parseHex(it.key().asString().c_str());
+
339 if (!BEAST_EXPECT((*it).isMember(jss::name)))
+
340 return;
+
341 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
+
342 bool expectSupported =
+
343 env.app().getAmendmentTable().isSupported(id);
+
344 bool expectVeto =
+
345 (votes.at((*it)[jss::name].asString()) ==
+ +
347 bool expectObsolete =
+
348 (votes.at((*it)[jss::name].asString()) ==
+ +
350 BEAST_EXPECTS(
+
351 (*it).isMember(jss::enabled) &&
+
352 (*it)[jss::enabled].asBool() == expectEnabled,
+
353 (*it)[jss::name].asString() + " enabled");
+
354 if (expectEnabled)
+
355 BEAST_EXPECTS(
+
356 !(*it).isMember(jss::vetoed),
+
357 (*it)[jss::name].asString() + " vetoed");
+
358 else
+
359 BEAST_EXPECTS(
+
360 (*it).isMember(jss::vetoed) &&
+
361 (*it)[jss::vetoed].isBool() == !expectObsolete &&
+
362 (!(*it)[jss::vetoed].isBool() ||
+
363 (*it)[jss::vetoed].asBool() == expectVeto) &&
+
364 ((*it)[jss::vetoed].isBool() ||
+
365 (*it)[jss::vetoed].asString() == "Obsolete"),
+
366 (*it)[jss::name].asString() + " vetoed");
+
367 BEAST_EXPECTS(
+
368 (*it).isMember(jss::supported) &&
+
369 (*it)[jss::supported].asBool() == expectSupported,
+
370 (*it)[jss::name].asString() + " supported");
+
371 }
+
372 }
-
374
-
375 void
-
- -
377 {
-
378 testcase("With Majorities");
-
379
-
380 using namespace test::jtx;
-
381 Env env{*this, envconfig(validator, "")};
-
382
-
383 auto jrr = env.rpc("feature")[jss::result];
-
384 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
-
385 return;
-
386
-
387 // at this point, there are no majorities so no fields related to
-
388 // amendment voting
-
389 for (auto const& feature : jrr[jss::features])
-
390 {
-
391 if (!BEAST_EXPECT(feature.isMember(jss::name)))
-
392 return;
-
393 BEAST_EXPECTS(
-
394 !feature.isMember(jss::majority),
-
395 feature[jss::name].asString() + " majority");
-
396 BEAST_EXPECTS(
-
397 !feature.isMember(jss::count),
-
398 feature[jss::name].asString() + " count");
-
399 BEAST_EXPECTS(
-
400 !feature.isMember(jss::threshold),
-
401 feature[jss::name].asString() + " threshold");
-
402 BEAST_EXPECTS(
-
403 !feature.isMember(jss::validations),
-
404 feature[jss::name].asString() + " validations");
-
405 BEAST_EXPECTS(
-
406 !feature.isMember(jss::vote),
-
407 feature[jss::name].asString() + " vote");
-
408 }
-
409
-
410 auto majorities = getMajorityAmendments(*env.closed());
-
411 if (!BEAST_EXPECT(majorities.empty()))
-
412 return;
-
413
-
414 // close ledgers until the amendments show up.
-
415 for (auto i = 0; i <= 256; ++i)
-
416 {
-
417 env.close();
-
418 majorities = getMajorityAmendments(*env.closed());
-
419 if (!majorities.empty())
-
420 break;
-
421 }
-
422
-
423 // There should be at least 5 amendments. Don't do exact comparison
-
424 // to avoid maintenance as more amendments are added in the future.
-
425 BEAST_EXPECT(majorities.size() >= 5);
- - -
428
-
429 jrr = env.rpc("feature")[jss::result];
-
430 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
-
431 return;
-
432 for (auto const& feature : jrr[jss::features])
-
433 {
-
434 if (!BEAST_EXPECT(feature.isMember(jss::name)))
-
435 return;
-
436 bool expectVeto =
-
437 (votes.at(feature[jss::name].asString()) ==
- -
439 bool expectObsolete =
-
440 (votes.at(feature[jss::name].asString()) ==
- -
442 BEAST_EXPECTS(
-
443 (expectVeto || expectObsolete) ^
-
444 feature.isMember(jss::majority),
-
445 feature[jss::name].asString() + " majority");
-
446 BEAST_EXPECTS(
-
447 feature.isMember(jss::vetoed) &&
-
448 feature[jss::vetoed].isBool() == !expectObsolete &&
-
449 (!feature[jss::vetoed].isBool() ||
-
450 feature[jss::vetoed].asBool() == expectVeto) &&
-
451 (feature[jss::vetoed].isBool() ||
-
452 feature[jss::vetoed].asString() == "Obsolete"),
-
453 feature[jss::name].asString() + " vetoed");
-
454 BEAST_EXPECTS(
-
455 feature.isMember(jss::count),
-
456 feature[jss::name].asString() + " count");
-
457 BEAST_EXPECTS(
-
458 feature.isMember(jss::threshold),
-
459 feature[jss::name].asString() + " threshold");
-
460 BEAST_EXPECTS(
-
461 feature.isMember(jss::validations),
-
462 feature[jss::name].asString() + " validations");
-
463 BEAST_EXPECT(
-
464 feature[jss::count] ==
-
465 ((expectVeto || expectObsolete) ? 0 : 1));
-
466 BEAST_EXPECT(feature[jss::threshold] == 1);
-
467 BEAST_EXPECT(feature[jss::validations] == 1);
-
468 BEAST_EXPECTS(
-
469 expectVeto || expectObsolete || feature[jss::majority] == 2540,
-
470 "Majority: " + feature[jss::majority].asString());
-
471 }
-
472 }
+
373
+
374 void
+
+ +
376 {
+
377 testcase("With Majorities");
+
378
+
379 using namespace test::jtx;
+
380 Env env{*this, envconfig(validator, "")};
+
381
+
382 auto jrr = env.rpc("feature")[jss::result];
+
383 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
+
384 return;
+
385
+
386 // at this point, there are no majorities so no fields related to
+
387 // amendment voting
+
388 for (auto const& feature : jrr[jss::features])
+
389 {
+
390 if (!BEAST_EXPECT(feature.isMember(jss::name)))
+
391 return;
+
392 BEAST_EXPECTS(
+
393 !feature.isMember(jss::majority),
+
394 feature[jss::name].asString() + " majority");
+
395 BEAST_EXPECTS(
+
396 !feature.isMember(jss::count),
+
397 feature[jss::name].asString() + " count");
+
398 BEAST_EXPECTS(
+
399 !feature.isMember(jss::threshold),
+
400 feature[jss::name].asString() + " threshold");
+
401 BEAST_EXPECTS(
+
402 !feature.isMember(jss::validations),
+
403 feature[jss::name].asString() + " validations");
+
404 BEAST_EXPECTS(
+
405 !feature.isMember(jss::vote),
+
406 feature[jss::name].asString() + " vote");
+
407 }
+
408
+
409 auto majorities = getMajorityAmendments(*env.closed());
+
410 if (!BEAST_EXPECT(majorities.empty()))
+
411 return;
+
412
+
413 // close ledgers until the amendments show up.
+
414 for (auto i = 0; i <= 256; ++i)
+
415 {
+
416 env.close();
+
417 majorities = getMajorityAmendments(*env.closed());
+
418 if (!majorities.empty())
+
419 break;
+
420 }
+
421
+
422 // There should be at least 5 amendments. Don't do exact comparison
+
423 // to avoid maintenance as more amendments are added in the future.
+
424 BEAST_EXPECT(majorities.size() >= 5);
+ + +
427
+
428 jrr = env.rpc("feature")[jss::result];
+
429 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
+
430 return;
+
431 for (auto const& feature : jrr[jss::features])
+
432 {
+
433 if (!BEAST_EXPECT(feature.isMember(jss::name)))
+
434 return;
+
435 bool expectVeto =
+
436 (votes.at(feature[jss::name].asString()) ==
+ +
438 bool expectObsolete =
+
439 (votes.at(feature[jss::name].asString()) ==
+ +
441 BEAST_EXPECTS(
+
442 (expectVeto || expectObsolete) ^
+
443 feature.isMember(jss::majority),
+
444 feature[jss::name].asString() + " majority");
+
445 BEAST_EXPECTS(
+
446 feature.isMember(jss::vetoed) &&
+
447 feature[jss::vetoed].isBool() == !expectObsolete &&
+
448 (!feature[jss::vetoed].isBool() ||
+
449 feature[jss::vetoed].asBool() == expectVeto) &&
+
450 (feature[jss::vetoed].isBool() ||
+
451 feature[jss::vetoed].asString() == "Obsolete"),
+
452 feature[jss::name].asString() + " vetoed");
+
453 BEAST_EXPECTS(
+
454 feature.isMember(jss::count),
+
455 feature[jss::name].asString() + " count");
+
456 BEAST_EXPECTS(
+
457 feature.isMember(jss::threshold),
+
458 feature[jss::name].asString() + " threshold");
+
459 BEAST_EXPECTS(
+
460 feature.isMember(jss::validations),
+
461 feature[jss::name].asString() + " validations");
+
462 BEAST_EXPECT(
+
463 feature[jss::count] ==
+
464 ((expectVeto || expectObsolete) ? 0 : 1));
+
465 BEAST_EXPECT(feature[jss::threshold] == 1);
+
466 BEAST_EXPECT(feature[jss::validations] == 1);
+
467 BEAST_EXPECTS(
+
468 expectVeto || expectObsolete || feature[jss::majority] == 2540,
+
469 "Majority: " + feature[jss::majority].asString());
+
470 }
+
471 }
-
473
-
474 void
-
- -
476 {
-
477 testcase("Veto");
-
478
-
479 using namespace test::jtx;
-
480 Env env{*this, FeatureBitset{featureRequireFullyCanonicalSig}};
-
481 constexpr char const* featureName = "RequireFullyCanonicalSig";
-
482
-
483 auto jrr = env.rpc("feature", featureName)[jss::result];
-
484 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
-
485 return;
-
486 jrr.removeMember(jss::status);
-
487 if (!BEAST_EXPECT(jrr.size() == 1))
-
488 return;
-
489 auto feature = *(jrr.begin());
-
490 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
-
491 BEAST_EXPECTS(
-
492 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
-
493 "vetoed");
-
494
-
495 jrr = env.rpc("feature", featureName, "reject")[jss::result];
-
496 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
-
497 return;
-
498 jrr.removeMember(jss::status);
-
499 if (!BEAST_EXPECT(jrr.size() == 1))
-
500 return;
-
501 feature = *(jrr.begin());
-
502 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
-
503 BEAST_EXPECTS(
-
504 feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
-
505 "vetoed");
-
506
-
507 jrr = env.rpc("feature", featureName, "accept")[jss::result];
-
508 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
-
509 return;
-
510 jrr.removeMember(jss::status);
-
511 if (!BEAST_EXPECT(jrr.size() == 1))
-
512 return;
-
513 feature = *(jrr.begin());
-
514 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
-
515 BEAST_EXPECTS(
-
516 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
-
517 "vetoed");
-
518
-
519 // anything other than accept or reject is an error
-
520 jrr = env.rpc("feature", featureName, "maybe");
-
521 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
522 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
-
523 }
+
472
+
473 void
+
+ +
475 {
+
476 testcase("Veto");
+
477
+
478 using namespace test::jtx;
+
479 Env env{*this, FeatureBitset{featureRequireFullyCanonicalSig}};
+
480 constexpr char const* featureName = "RequireFullyCanonicalSig";
+
481
+
482 auto jrr = env.rpc("feature", featureName)[jss::result];
+
483 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
+
484 return;
+
485 jrr.removeMember(jss::status);
+
486 if (!BEAST_EXPECT(jrr.size() == 1))
+
487 return;
+
488 auto feature = *(jrr.begin());
+
489 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
+
490 BEAST_EXPECTS(
+
491 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
+
492 "vetoed");
+
493
+
494 jrr = env.rpc("feature", featureName, "reject")[jss::result];
+
495 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
+
496 return;
+
497 jrr.removeMember(jss::status);
+
498 if (!BEAST_EXPECT(jrr.size() == 1))
+
499 return;
+
500 feature = *(jrr.begin());
+
501 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
+
502 BEAST_EXPECTS(
+
503 feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
+
504 "vetoed");
+
505
+
506 jrr = env.rpc("feature", featureName, "accept")[jss::result];
+
507 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
+
508 return;
+
509 jrr.removeMember(jss::status);
+
510 if (!BEAST_EXPECT(jrr.size() == 1))
+
511 return;
+
512 feature = *(jrr.begin());
+
513 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
+
514 BEAST_EXPECTS(
+
515 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
+
516 "vetoed");
+
517
+
518 // anything other than accept or reject is an error
+
519 jrr = env.rpc("feature", featureName, "maybe");
+
520 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
521 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
+
522 }
-
524
-
525 void
-
- -
527 {
-
528 testcase("Obsolete");
-
529
-
530 using namespace test::jtx;
-
531 Env env{*this};
-
532
-
533 auto const& supportedAmendments = detail::supportedAmendments();
-
534 auto obsoleteFeature = std::find_if(
-
535 std::begin(supportedAmendments),
-
536 std::end(supportedAmendments),
-
537 [](auto const& pair) {
-
538 return pair.second == VoteBehavior::Obsolete;
-
539 });
-
540
-
541 if (obsoleteFeature == std::end(supportedAmendments))
-
542 {
-
543 pass();
-
544 return;
-
545 }
-
546
-
547 auto const featureName = obsoleteFeature->first;
-
548
-
549 auto jrr = env.rpc("feature", featureName)[jss::result];
-
550 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
-
551 return;
-
552 jrr.removeMember(jss::status);
-
553 if (!BEAST_EXPECT(jrr.size() == 1))
-
554 return;
-
555 auto feature = *(jrr.begin());
-
556 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
-
557 BEAST_EXPECTS(
-
558 feature[jss::vetoed].isString() &&
-
559 feature[jss::vetoed].asString() == "Obsolete",
-
560 "vetoed");
-
561
-
562 jrr = env.rpc("feature", featureName, "reject")[jss::result];
-
563 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
-
564 return;
-
565 jrr.removeMember(jss::status);
-
566 if (!BEAST_EXPECT(jrr.size() == 1))
-
567 return;
-
568 feature = *(jrr.begin());
-
569 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
-
570 BEAST_EXPECTS(
-
571 feature[jss::vetoed].isString() &&
-
572 feature[jss::vetoed].asString() == "Obsolete",
-
573 "vetoed");
-
574
-
575 jrr = env.rpc("feature", featureName, "accept")[jss::result];
-
576 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
-
577 return;
-
578 jrr.removeMember(jss::status);
-
579 if (!BEAST_EXPECT(jrr.size() == 1))
-
580 return;
-
581 feature = *(jrr.begin());
-
582 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
-
583 BEAST_EXPECTS(
-
584 feature[jss::vetoed].isString() &&
-
585 feature[jss::vetoed].asString() == "Obsolete",
-
586 "vetoed");
-
587
-
588 // anything other than accept or reject is an error
-
589 jrr = env.rpc("feature", featureName, "maybe");
-
590 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
-
591 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
-
592 }
+
523
+
524 void
+
+ +
526 {
+
527 testcase("Obsolete");
+
528
+
529 using namespace test::jtx;
+
530 Env env{*this};
+
531
+
532 auto const& supportedAmendments = detail::supportedAmendments();
+
533 auto obsoleteFeature = std::find_if(
+
534 std::begin(supportedAmendments),
+
535 std::end(supportedAmendments),
+
536 [](auto const& pair) {
+
537 return pair.second == VoteBehavior::Obsolete;
+
538 });
+
539
+
540 if (obsoleteFeature == std::end(supportedAmendments))
+
541 {
+
542 pass();
+
543 return;
+
544 }
+
545
+
546 auto const featureName = obsoleteFeature->first;
+
547
+
548 auto jrr = env.rpc("feature", featureName)[jss::result];
+
549 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
+
550 return;
+
551 jrr.removeMember(jss::status);
+
552 if (!BEAST_EXPECT(jrr.size() == 1))
+
553 return;
+
554 auto feature = *(jrr.begin());
+
555 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
+
556 BEAST_EXPECTS(
+
557 feature[jss::vetoed].isString() &&
+
558 feature[jss::vetoed].asString() == "Obsolete",
+
559 "vetoed");
+
560
+
561 jrr = env.rpc("feature", featureName, "reject")[jss::result];
+
562 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
+
563 return;
+
564 jrr.removeMember(jss::status);
+
565 if (!BEAST_EXPECT(jrr.size() == 1))
+
566 return;
+
567 feature = *(jrr.begin());
+
568 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
+
569 BEAST_EXPECTS(
+
570 feature[jss::vetoed].isString() &&
+
571 feature[jss::vetoed].asString() == "Obsolete",
+
572 "vetoed");
+
573
+
574 jrr = env.rpc("feature", featureName, "accept")[jss::result];
+
575 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
+
576 return;
+
577 jrr.removeMember(jss::status);
+
578 if (!BEAST_EXPECT(jrr.size() == 1))
+
579 return;
+
580 feature = *(jrr.begin());
+
581 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
+
582 BEAST_EXPECTS(
+
583 feature[jss::vetoed].isString() &&
+
584 feature[jss::vetoed].asString() == "Obsolete",
+
585 "vetoed");
+
586
+
587 // anything other than accept or reject is an error
+
588 jrr = env.rpc("feature", featureName, "maybe");
+
589 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
+
590 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
+
591 }
-
593
-
594public:
-
595 void
-
-
596 run() override
-
597 {
- - -
600 testNoParams();
- - -
603 testNonAdmin();
- - -
606 testVeto();
-
607 testObsolete();
-
608 }
+
592
+
593public:
+
594 void
+
+
595 run() override
+
596 {
+ + +
599 testNoParams();
+ + +
602 testNonAdmin();
+ + +
605 testVeto();
+
606 testObsolete();
+
607 }
-
609};
+
608};
-
610
-
611BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
-
612
-
613} // namespace ripple
+
609
+
610BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
+
611
+
612} // namespace ripple
T at(T... args)
T begin(T... args)
Represents a JSON value.
Definition json_value.h:131
@@ -727,17 +726,17 @@ $(document).ready(function() { init_codefold(0); });
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
- - + + - - - + + + - - - -
void run() override
Runs the suite.
+ + + +
void run() override
Runs the suite.
T end(T... args)
T find_if(T... args)
diff --git a/InvariantCheck_8cpp_source.html b/InvariantCheck_8cpp_source.html index 86ba8594f8..f53e3c9ea5 100644 --- a/InvariantCheck_8cpp_source.html +++ b/InvariantCheck_8cpp_source.html @@ -1178,2134 +1178,2129 @@ $(document).ready(function() { init_codefold(0); });
1028 return false;
1029 }
1030
-
1031 std::uint32_t const startingSeq = //
-
1032 pseudoAccount //
-
1033 ? 0 //
-
1034 : view.rules().enabled(featureDeletableAccounts) //
-
1035 ? view.seq() //
-
1036 : 1;
-
1037
-
1038 if (accountSeq_ != startingSeq)
-
1039 {
-
1040 JLOG(j.fatal()) << "Invariant failed: account created with "
-
1041 "wrong starting sequence number";
-
1042 return false;
-
1043 }
-
1044
-
1045 if (pseudoAccount)
-
1046 {
-
1047 std::uint32_t const expected =
- -
1049 if (flags_ != expected)
-
1050 {
-
1051 JLOG(j.fatal())
-
1052 << "Invariant failed: pseudo-account created with "
-
1053 "wrong flags";
-
1054 return false;
-
1055 }
-
1056 }
-
1057
-
1058 return true;
-
1059 }
-
1060
-
1061 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
-
1062 return false;
-
1063} // namespace ripple
+
1031 std::uint32_t const startingSeq = pseudoAccount ? 0 : view.seq();
+
1032
+
1033 if (accountSeq_ != startingSeq)
+
1034 {
+
1035 JLOG(j.fatal()) << "Invariant failed: account created with "
+
1036 "wrong starting sequence number";
+
1037 return false;
+
1038 }
+
1039
+
1040 if (pseudoAccount)
+
1041 {
+
1042 std::uint32_t const expected =
+ +
1044 if (flags_ != expected)
+
1045 {
+
1046 JLOG(j.fatal())
+
1047 << "Invariant failed: pseudo-account created with "
+
1048 "wrong flags";
+
1049 return false;
+
1050 }
+
1051 }
+
1052
+
1053 return true;
+
1054 }
+
1055
+
1056 JLOG(j.fatal()) << "Invariant failed: account root created illegally";
+
1057 return false;
+
1058} // namespace ripple
-
1064
-
1065//------------------------------------------------------------------------------
-
1066
-
1067void
-
- -
1069 bool isDelete,
-
1070 std::shared_ptr<SLE const> const& before,
- -
1072{
-
1073 static constexpr uint256 const& pageBits = nft::pageMask;
-
1074 static constexpr uint256 const accountBits = ~pageBits;
-
1075
-
1076 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
-
1077 (after && after->getType() != ltNFTOKEN_PAGE))
-
1078 return;
+
1059
+
1060//------------------------------------------------------------------------------
+
1061
+
1062void
+
+ +
1064 bool isDelete,
+
1065 std::shared_ptr<SLE const> const& before,
+ +
1067{
+
1068 static constexpr uint256 const& pageBits = nft::pageMask;
+
1069 static constexpr uint256 const accountBits = ~pageBits;
+
1070
+
1071 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
+
1072 (after && after->getType() != ltNFTOKEN_PAGE))
+
1073 return;
+
1074
+
1075 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
+
1076 uint256 const account = sle->key() & accountBits;
+
1077 uint256 const hiLimit = sle->key() & pageBits;
+
1078 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
1079
-
1080 auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
-
1081 uint256 const account = sle->key() & accountBits;
-
1082 uint256 const hiLimit = sle->key() & pageBits;
-
1083 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
-
1084
-
1085 // Make sure that any page links...
-
1086 // 1. Are properly associated with the owning account and
-
1087 // 2. The page is correctly ordered between links.
-
1088 if (prev)
-
1089 {
-
1090 if (account != (*prev & accountBits))
-
1091 badLink_ = true;
-
1092
-
1093 if (hiLimit <= (*prev & pageBits))
-
1094 badLink_ = true;
-
1095 }
+
1080 // Make sure that any page links...
+
1081 // 1. Are properly associated with the owning account and
+
1082 // 2. The page is correctly ordered between links.
+
1083 if (prev)
+
1084 {
+
1085 if (account != (*prev & accountBits))
+
1086 badLink_ = true;
+
1087
+
1088 if (hiLimit <= (*prev & pageBits))
+
1089 badLink_ = true;
+
1090 }
+
1091
+
1092 if (auto const next = (*sle)[~sfNextPageMin])
+
1093 {
+
1094 if (account != (*next & accountBits))
+
1095 badLink_ = true;
1096
-
1097 if (auto const next = (*sle)[~sfNextPageMin])
-
1098 {
-
1099 if (account != (*next & accountBits))
-
1100 badLink_ = true;
-
1101
-
1102 if (hiLimit >= (*next & pageBits))
-
1103 badLink_ = true;
-
1104 }
-
1105
-
1106 {
-
1107 auto const& nftokens = sle->getFieldArray(sfNFTokens);
-
1108
-
1109 // An NFTokenPage should never contain too many tokens or be empty.
-
1110 if (std::size_t const nftokenCount = nftokens.size();
-
1111 (!isDelete && nftokenCount == 0) ||
-
1112 nftokenCount > dirMaxTokensPerPage)
-
1113 invalidSize_ = true;
+
1097 if (hiLimit >= (*next & pageBits))
+
1098 badLink_ = true;
+
1099 }
+
1100
+
1101 {
+
1102 auto const& nftokens = sle->getFieldArray(sfNFTokens);
+
1103
+
1104 // An NFTokenPage should never contain too many tokens or be empty.
+
1105 if (std::size_t const nftokenCount = nftokens.size();
+
1106 (!isDelete && nftokenCount == 0) ||
+
1107 nftokenCount > dirMaxTokensPerPage)
+
1108 invalidSize_ = true;
+
1109
+
1110 // If prev is valid, use it to establish a lower bound for
+
1111 // page entries. If prev is not valid the lower bound is zero.
+
1112 uint256 const loLimit =
+
1113 prev ? *prev & pageBits : uint256(beast::zero);
1114
-
1115 // If prev is valid, use it to establish a lower bound for
-
1116 // page entries. If prev is not valid the lower bound is zero.
-
1117 uint256 const loLimit =
-
1118 prev ? *prev & pageBits : uint256(beast::zero);
-
1119
-
1120 // Also verify that all NFTokenIDs in the page are sorted.
-
1121 uint256 loCmp = loLimit;
-
1122 for (auto const& obj : nftokens)
-
1123 {
-
1124 uint256 const tokenID = obj[sfNFTokenID];
-
1125 if (!nft::compareTokens(loCmp, tokenID))
-
1126 badSort_ = true;
-
1127 loCmp = tokenID;
-
1128
-
1129 // None of the NFTs on this page should belong on lower or
-
1130 // higher pages.
-
1131 if (uint256 const tokenPageBits = tokenID & pageBits;
-
1132 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
-
1133 badEntry_ = true;
-
1134
-
1135 if (auto uri = obj[~sfURI]; uri && uri->empty())
-
1136 badURI_ = true;
-
1137 }
-
1138 }
-
1139 };
-
1140
-
1141 if (before)
-
1142 {
-
1143 check(before);
-
1144
-
1145 // While an account's NFToken directory contains any NFTokens, the last
-
1146 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
-
1147 // never be deleted.
-
1148 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
-
1149 before->isFieldPresent(sfPreviousPageMin))
-
1150 {
-
1151 deletedFinalPage_ = true;
-
1152 }
-
1153 }
-
1154
-
1155 if (after)
-
1156 check(after);
-
1157
-
1158 if (!isDelete && before && after)
-
1159 {
-
1160 // If the NFTokenPage
-
1161 // 1. Has a NextMinPage field in before, but loses it in after, and
-
1162 // 2. This is not the last page in the directory
-
1163 // Then we have identified a corruption in the links between the
-
1164 // NFToken pages in the NFToken directory.
-
1165 if ((before->key() & nft::pageMask) != nft::pageMask &&
-
1166 before->isFieldPresent(sfNextPageMin) &&
-
1167 !after->isFieldPresent(sfNextPageMin))
-
1168 {
-
1169 deletedLink_ = true;
-
1170 }
-
1171 }
-
1172}
+
1115 // Also verify that all NFTokenIDs in the page are sorted.
+
1116 uint256 loCmp = loLimit;
+
1117 for (auto const& obj : nftokens)
+
1118 {
+
1119 uint256 const tokenID = obj[sfNFTokenID];
+
1120 if (!nft::compareTokens(loCmp, tokenID))
+
1121 badSort_ = true;
+
1122 loCmp = tokenID;
+
1123
+
1124 // None of the NFTs on this page should belong on lower or
+
1125 // higher pages.
+
1126 if (uint256 const tokenPageBits = tokenID & pageBits;
+
1127 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
+
1128 badEntry_ = true;
+
1129
+
1130 if (auto uri = obj[~sfURI]; uri && uri->empty())
+
1131 badURI_ = true;
+
1132 }
+
1133 }
+
1134 };
+
1135
+
1136 if (before)
+
1137 {
+
1138 check(before);
+
1139
+
1140 // While an account's NFToken directory contains any NFTokens, the last
+
1141 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
+
1142 // never be deleted.
+
1143 if (isDelete && (before->key() & nft::pageMask) == nft::pageMask &&
+
1144 before->isFieldPresent(sfPreviousPageMin))
+
1145 {
+
1146 deletedFinalPage_ = true;
+
1147 }
+
1148 }
+
1149
+
1150 if (after)
+
1151 check(after);
+
1152
+
1153 if (!isDelete && before && after)
+
1154 {
+
1155 // If the NFTokenPage
+
1156 // 1. Has a NextMinPage field in before, but loses it in after, and
+
1157 // 2. This is not the last page in the directory
+
1158 // Then we have identified a corruption in the links between the
+
1159 // NFToken pages in the NFToken directory.
+
1160 if ((before->key() & nft::pageMask) != nft::pageMask &&
+
1161 before->isFieldPresent(sfNextPageMin) &&
+
1162 !after->isFieldPresent(sfNextPageMin))
+
1163 {
+
1164 deletedLink_ = true;
+
1165 }
+
1166 }
+
1167}
-
1173
-
1174bool
-
- -
1176 STTx const& tx,
-
1177 TER const result,
-
1178 XRPAmount const,
-
1179 ReadView const& view,
-
1180 beast::Journal const& j)
-
1181{
-
1182 if (badLink_)
-
1183 {
-
1184 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
-
1185 return false;
-
1186 }
-
1187
-
1188 if (badEntry_)
-
1189 {
-
1190 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
-
1191 return false;
-
1192 }
-
1193
-
1194 if (badSort_)
-
1195 {
-
1196 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
-
1197 return false;
-
1198 }
-
1199
-
1200 if (badURI_)
-
1201 {
-
1202 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
-
1203 return false;
-
1204 }
-
1205
-
1206 if (invalidSize_)
-
1207 {
-
1208 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
-
1209 return false;
-
1210 }
-
1211
-
1212 if (view.rules().enabled(fixNFTokenPageLinks))
-
1213 {
- -
1215 {
-
1216 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
-
1217 "non-empty directory.";
+
1168
+
1169bool
+
+ +
1171 STTx const& tx,
+
1172 TER const result,
+
1173 XRPAmount const,
+
1174 ReadView const& view,
+
1175 beast::Journal const& j)
+
1176{
+
1177 if (badLink_)
+
1178 {
+
1179 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
+
1180 return false;
+
1181 }
+
1182
+
1183 if (badEntry_)
+
1184 {
+
1185 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
+
1186 return false;
+
1187 }
+
1188
+
1189 if (badSort_)
+
1190 {
+
1191 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
+
1192 return false;
+
1193 }
+
1194
+
1195 if (badURI_)
+
1196 {
+
1197 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
+
1198 return false;
+
1199 }
+
1200
+
1201 if (invalidSize_)
+
1202 {
+
1203 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
+
1204 return false;
+
1205 }
+
1206
+
1207 if (view.rules().enabled(fixNFTokenPageLinks))
+
1208 {
+ +
1210 {
+
1211 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
+
1212 "non-empty directory.";
+
1213 return false;
+
1214 }
+
1215 if (deletedLink_)
+
1216 {
+
1217 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
1218 return false;
1219 }
-
1220 if (deletedLink_)
-
1221 {
-
1222 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
-
1223 return false;
-
1224 }
-
1225 }
-
1226
-
1227 return true;
-
1228}
+
1220 }
+
1221
+
1222 return true;
+
1223}
-
1229
-
1230//------------------------------------------------------------------------------
-
1231void
-
- -
1233 bool,
-
1234 std::shared_ptr<SLE const> const& before,
- -
1236{
-
1237 if (before && before->getType() == ltACCOUNT_ROOT)
-
1238 {
-
1239 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
-
1240 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
-
1241 }
-
1242
-
1243 if (after && after->getType() == ltACCOUNT_ROOT)
-
1244 {
-
1245 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
-
1246 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
-
1247 }
-
1248}
+
1224
+
1225//------------------------------------------------------------------------------
+
1226void
+
+ +
1228 bool,
+
1229 std::shared_ptr<SLE const> const& before,
+ +
1231{
+
1232 if (before && before->getType() == ltACCOUNT_ROOT)
+
1233 {
+
1234 beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
+
1235 beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
+
1236 }
+
1237
+
1238 if (after && after->getType() == ltACCOUNT_ROOT)
+
1239 {
+
1240 afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
+
1241 afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
+
1242 }
+
1243}
-
1249
-
1250bool
-
- -
1252 STTx const& tx,
-
1253 TER const result,
-
1254 XRPAmount const,
-
1255 ReadView const& view,
-
1256 beast::Journal const& j)
-
1257{
-
1258 if (!hasPrivilege(tx, changeNFTCounts))
-
1259 {
- -
1261 {
-
1262 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
-
1263 "changed without a mint transaction!";
-
1264 return false;
-
1265 }
-
1266
- -
1268 {
-
1269 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
-
1270 "changed without a burn transaction!";
-
1271 return false;
-
1272 }
-
1273
-
1274 return true;
-
1275 }
-
1276
-
1277 if (tx.getTxnType() == ttNFTOKEN_MINT)
-
1278 {
-
1279 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
-
1280 {
-
1281 JLOG(j.fatal())
-
1282 << "Invariant failed: successful minting didn't increase "
-
1283 "the number of minted tokens.";
-
1284 return false;
-
1285 }
-
1286
-
1287 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
-
1288 {
-
1289 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
-
1290 "number of minted tokens.";
-
1291 return false;
-
1292 }
-
1293
- -
1295 {
-
1296 JLOG(j.fatal())
-
1297 << "Invariant failed: minting changed the number of "
-
1298 "burned tokens.";
-
1299 return false;
-
1300 }
-
1301 }
-
1302
-
1303 if (tx.getTxnType() == ttNFTOKEN_BURN)
-
1304 {
-
1305 if (result == tesSUCCESS)
-
1306 {
- -
1308 {
-
1309 JLOG(j.fatal())
-
1310 << "Invariant failed: successful burning didn't increase "
-
1311 "the number of burned tokens.";
-
1312 return false;
-
1313 }
-
1314 }
-
1315
-
1316 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
-
1317 {
-
1318 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
-
1319 "number of burned tokens.";
-
1320 return false;
-
1321 }
-
1322
- -
1324 {
-
1325 JLOG(j.fatal())
-
1326 << "Invariant failed: burning changed the number of "
-
1327 "minted tokens.";
-
1328 return false;
-
1329 }
-
1330 }
+
1244
+
1245bool
+
+ +
1247 STTx const& tx,
+
1248 TER const result,
+
1249 XRPAmount const,
+
1250 ReadView const& view,
+
1251 beast::Journal const& j)
+
1252{
+
1253 if (!hasPrivilege(tx, changeNFTCounts))
+
1254 {
+ +
1256 {
+
1257 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
+
1258 "changed without a mint transaction!";
+
1259 return false;
+
1260 }
+
1261
+ +
1263 {
+
1264 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
+
1265 "changed without a burn transaction!";
+
1266 return false;
+
1267 }
+
1268
+
1269 return true;
+
1270 }
+
1271
+
1272 if (tx.getTxnType() == ttNFTOKEN_MINT)
+
1273 {
+
1274 if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
+
1275 {
+
1276 JLOG(j.fatal())
+
1277 << "Invariant failed: successful minting didn't increase "
+
1278 "the number of minted tokens.";
+
1279 return false;
+
1280 }
+
1281
+
1282 if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
+
1283 {
+
1284 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
+
1285 "number of minted tokens.";
+
1286 return false;
+
1287 }
+
1288
+ +
1290 {
+
1291 JLOG(j.fatal())
+
1292 << "Invariant failed: minting changed the number of "
+
1293 "burned tokens.";
+
1294 return false;
+
1295 }
+
1296 }
+
1297
+
1298 if (tx.getTxnType() == ttNFTOKEN_BURN)
+
1299 {
+
1300 if (result == tesSUCCESS)
+
1301 {
+ +
1303 {
+
1304 JLOG(j.fatal())
+
1305 << "Invariant failed: successful burning didn't increase "
+
1306 "the number of burned tokens.";
+
1307 return false;
+
1308 }
+
1309 }
+
1310
+
1311 if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
+
1312 {
+
1313 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
+
1314 "number of burned tokens.";
+
1315 return false;
+
1316 }
+
1317
+ +
1319 {
+
1320 JLOG(j.fatal())
+
1321 << "Invariant failed: burning changed the number of "
+
1322 "minted tokens.";
+
1323 return false;
+
1324 }
+
1325 }
+
1326
+
1327 return true;
+
1328}
+
+
1329
+
1330//------------------------------------------------------------------------------
1331
-
1332 return true;
-
1333}
+
1332void
+
+ +
1334 bool,
+
1335 std::shared_ptr<SLE const> const& before,
+ +
1337{
+
1338 if (before && before->getType() == ltRIPPLE_STATE)
+ +
1340
+
1341 if (before && before->getType() == ltMPTOKEN)
+ +
1343}
-
1334
-
1335//------------------------------------------------------------------------------
-
1336
-
1337void
-
- -
1339 bool,
-
1340 std::shared_ptr<SLE const> const& before,
- -
1342{
-
1343 if (before && before->getType() == ltRIPPLE_STATE)
- -
1345
-
1346 if (before && before->getType() == ltMPTOKEN)
- -
1348}
+
1344
+
1345bool
+
+ +
1347 STTx const& tx,
+
1348 TER const result,
+
1349 XRPAmount const,
+
1350 ReadView const& view,
+
1351 beast::Journal const& j)
+
1352{
+
1353 if (tx.getTxnType() != ttCLAWBACK)
+
1354 return true;
+
1355
+
1356 if (result == tesSUCCESS)
+
1357 {
+
1358 if (trustlinesChanged > 1)
+
1359 {
+
1360 JLOG(j.fatal())
+
1361 << "Invariant failed: more than one trustline changed.";
+
1362 return false;
+
1363 }
+
1364
+
1365 if (mptokensChanged > 1)
+
1366 {
+
1367 JLOG(j.fatal())
+
1368 << "Invariant failed: more than one mptokens changed.";
+
1369 return false;
+
1370 }
+
1371
+
1372 if (trustlinesChanged == 1)
+
1373 {
+
1374 AccountID const issuer = tx.getAccountID(sfAccount);
+
1375 STAmount const& amount = tx.getFieldAmount(sfAmount);
+
1376 AccountID const& holder = amount.getIssuer();
+
1377 STAmount const holderBalance = accountHolds(
+
1378 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
+
1379
+
1380 if (holderBalance.signum() < 0)
+
1381 {
+
1382 JLOG(j.fatal())
+
1383 << "Invariant failed: trustline balance is negative";
+
1384 return false;
+
1385 }
+
1386 }
+
1387 }
+
1388 else
+
1389 {
+
1390 if (trustlinesChanged != 0)
+
1391 {
+
1392 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
+
1393 "despite failure of the transaction.";
+
1394 return false;
+
1395 }
+
1396
+
1397 if (mptokensChanged != 0)
+
1398 {
+
1399 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
+
1400 "despite failure of the transaction.";
+
1401 return false;
+
1402 }
+
1403 }
+
1404
+
1405 return true;
+
1406}
-
1349
-
1350bool
-
- -
1352 STTx const& tx,
-
1353 TER const result,
-
1354 XRPAmount const,
-
1355 ReadView const& view,
-
1356 beast::Journal const& j)
-
1357{
-
1358 if (tx.getTxnType() != ttCLAWBACK)
-
1359 return true;
-
1360
-
1361 if (result == tesSUCCESS)
-
1362 {
-
1363 if (trustlinesChanged > 1)
-
1364 {
-
1365 JLOG(j.fatal())
-
1366 << "Invariant failed: more than one trustline changed.";
-
1367 return false;
-
1368 }
-
1369
-
1370 if (mptokensChanged > 1)
-
1371 {
-
1372 JLOG(j.fatal())
-
1373 << "Invariant failed: more than one mptokens changed.";
-
1374 return false;
-
1375 }
-
1376
-
1377 if (trustlinesChanged == 1)
-
1378 {
-
1379 AccountID const issuer = tx.getAccountID(sfAccount);
-
1380 STAmount const& amount = tx.getFieldAmount(sfAmount);
-
1381 AccountID const& holder = amount.getIssuer();
-
1382 STAmount const holderBalance = accountHolds(
-
1383 view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
-
1384
-
1385 if (holderBalance.signum() < 0)
-
1386 {
-
1387 JLOG(j.fatal())
-
1388 << "Invariant failed: trustline balance is negative";
-
1389 return false;
-
1390 }
-
1391 }
-
1392 }
-
1393 else
-
1394 {
-
1395 if (trustlinesChanged != 0)
-
1396 {
-
1397 JLOG(j.fatal()) << "Invariant failed: some trustlines were changed "
-
1398 "despite failure of the transaction.";
-
1399 return false;
-
1400 }
-
1401
-
1402 if (mptokensChanged != 0)
-
1403 {
-
1404 JLOG(j.fatal()) << "Invariant failed: some mptokens were changed "
-
1405 "despite failure of the transaction.";
-
1406 return false;
-
1407 }
-
1408 }
+
1407
+
1408//------------------------------------------------------------------------------
1409
-
1410 return true;
-
1411}
+
1410void
+
+ +
1412 bool isDelete,
+
1413 std::shared_ptr<SLE const> const& before,
+ +
1415{
+
1416 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
+
1417 {
+
1418 if (isDelete)
+ +
1420 else if (!before)
+ +
1422 }
+
1423
+
1424 if (after && after->getType() == ltMPTOKEN)
+
1425 {
+
1426 if (isDelete)
+ +
1428 else if (!before)
+ +
1430 }
+
1431}
-
1412
-
1413//------------------------------------------------------------------------------
-
1414
-
1415void
-
- -
1417 bool isDelete,
-
1418 std::shared_ptr<SLE const> const& before,
- -
1420{
-
1421 if (after && after->getType() == ltMPTOKEN_ISSUANCE)
-
1422 {
-
1423 if (isDelete)
- -
1425 else if (!before)
- -
1427 }
-
1428
-
1429 if (after && after->getType() == ltMPTOKEN)
-
1430 {
-
1431 if (isDelete)
- -
1433 else if (!before)
- -
1435 }
-
1436}
-
-
1437
-
1438bool
-
- -
1440 STTx const& tx,
-
1441 TER const result,
-
1442 XRPAmount const _fee,
-
1443 ReadView const& view,
-
1444 beast::Journal const& j)
-
1445{
-
1446 if (result == tesSUCCESS)
-
1447 {
- -
1449 {
-
1450 if (mptIssuancesCreated_ == 0)
+
1432
+
1433bool
+
+ +
1435 STTx const& tx,
+
1436 TER const result,
+
1437 XRPAmount const _fee,
+
1438 ReadView const& view,
+
1439 beast::Journal const& j)
+
1440{
+
1441 if (result == tesSUCCESS)
+
1442 {
+ +
1444 {
+
1445 if (mptIssuancesCreated_ == 0)
+
1446 {
+
1447 JLOG(j.fatal()) << "Invariant failed: transaction "
+
1448 "succeeded without creating a MPT issuance";
+
1449 }
+
1450 else if (mptIssuancesDeleted_ != 0)
1451 {
1452 JLOG(j.fatal()) << "Invariant failed: transaction "
-
1453 "succeeded without creating a MPT issuance";
+
1453 "succeeded while removing MPT issuances";
1454 }
-
1455 else if (mptIssuancesDeleted_ != 0)
+
1455 else if (mptIssuancesCreated_ > 1)
1456 {
1457 JLOG(j.fatal()) << "Invariant failed: transaction "
-
1458 "succeeded while removing MPT issuances";
+
1458 "succeeded but created multiple issuances";
1459 }
-
1460 else if (mptIssuancesCreated_ > 1)
-
1461 {
-
1462 JLOG(j.fatal()) << "Invariant failed: transaction "
-
1463 "succeeded but created multiple issuances";
-
1464 }
-
1465
-
1466 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
-
1467 }
-
1468
- -
1470 {
-
1471 if (mptIssuancesDeleted_ == 0)
+
1460
+
1461 return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
+
1462 }
+
1463
+ +
1465 {
+
1466 if (mptIssuancesDeleted_ == 0)
+
1467 {
+
1468 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
+
1469 "succeeded without removing a MPT issuance";
+
1470 }
+
1471 else if (mptIssuancesCreated_ > 0)
1472 {
1473 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
-
1474 "succeeded without removing a MPT issuance";
+
1474 "succeeded while creating MPT issuances";
1475 }
-
1476 else if (mptIssuancesCreated_ > 0)
+
1476 else if (mptIssuancesDeleted_ > 1)
1477 {
1478 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
-
1479 "succeeded while creating MPT issuances";
+
1479 "succeeded but deleted multiple issuances";
1480 }
-
1481 else if (mptIssuancesDeleted_ > 1)
-
1482 {
-
1483 JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion "
-
1484 "succeeded but deleted multiple issuances";
-
1485 }
-
1486
-
1487 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
-
1488 }
-
1489
-
1490 // ttESCROW_FINISH may authorize an MPT, but it can't have the
-
1491 // mayAuthorizeMPT privilege, because that may cause
-
1492 // non-amendment-gated side effects.
-
1493 bool const enforceEscrowFinish = (tx.getTxnType() == ttESCROW_FINISH) &&
-
1494 (view.rules().enabled(featureSingleAssetVault)
-
1495 /*
-
1496 TODO: Uncomment when LendingProtocol is defined
-
1497 || view.rules().enabled(featureLendingProtocol)*/
-
1498 );
- -
1500 enforceEscrowFinish)
-
1501 {
-
1502 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
-
1503
-
1504 if (mptIssuancesCreated_ > 0)
-
1505 {
-
1506 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
-
1507 "succeeded but created MPT issuances";
-
1508 return false;
-
1509 }
-
1510 else if (mptIssuancesDeleted_ > 0)
-
1511 {
-
1512 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
-
1513 "succeeded but deleted issuances";
-
1514 return false;
-
1515 }
-
1516 else if (
-
1517 submittedByIssuer &&
- -
1519 {
-
1520 JLOG(j.fatal())
-
1521 << "Invariant failed: MPT authorize submitted by issuer "
-
1522 "succeeded but created/deleted mptokens";
-
1523 return false;
-
1524 }
-
1525 else if (
-
1526 !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
- -
1528 {
-
1529 // if the holder submitted this tx, then a mptoken must be
-
1530 // either created or deleted.
-
1531 JLOG(j.fatal())
-
1532 << "Invariant failed: MPT authorize submitted by holder "
-
1533 "succeeded but created/deleted bad number of mptokens";
-
1534 return false;
-
1535 }
-
1536
-
1537 return true;
-
1538 }
-
1539 if (tx.getTxnType() == ttESCROW_FINISH)
-
1540 {
-
1541 // ttESCROW_FINISH may authorize an MPT, but it can't have the
-
1542 // mayAuthorizeMPT privilege, because that may cause
-
1543 // non-amendment-gated side effects.
-
1544 XRPL_ASSERT_PARTS(
-
1545 !enforceEscrowFinish,
-
1546 "ripple::ValidMPTIssuance::finalize",
-
1547 "not escrow finish tx");
-
1548 return true;
-
1549 }
-
1550
-
1551 if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 &&
- - -
1554 return true;
+
1481
+
1482 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1;
+
1483 }
+
1484
+
1485 // ttESCROW_FINISH may authorize an MPT, but it can't have the
+
1486 // mayAuthorizeMPT privilege, because that may cause
+
1487 // non-amendment-gated side effects.
+
1488 bool const enforceEscrowFinish = (tx.getTxnType() == ttESCROW_FINISH) &&
+
1489 (view.rules().enabled(featureSingleAssetVault)
+
1490 /*
+
1491 TODO: Uncomment when LendingProtocol is defined
+
1492 || view.rules().enabled(featureLendingProtocol)*/
+
1493 );
+ +
1495 enforceEscrowFinish)
+
1496 {
+
1497 bool const submittedByIssuer = tx.isFieldPresent(sfHolder);
+
1498
+
1499 if (mptIssuancesCreated_ > 0)
+
1500 {
+
1501 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
+
1502 "succeeded but created MPT issuances";
+
1503 return false;
+
1504 }
+
1505 else if (mptIssuancesDeleted_ > 0)
+
1506 {
+
1507 JLOG(j.fatal()) << "Invariant failed: MPT authorize "
+
1508 "succeeded but deleted issuances";
+
1509 return false;
+
1510 }
+
1511 else if (
+
1512 submittedByIssuer &&
+ +
1514 {
+
1515 JLOG(j.fatal())
+
1516 << "Invariant failed: MPT authorize submitted by issuer "
+
1517 "succeeded but created/deleted mptokens";
+
1518 return false;
+
1519 }
+
1520 else if (
+
1521 !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) &&
+ +
1523 {
+
1524 // if the holder submitted this tx, then a mptoken must be
+
1525 // either created or deleted.
+
1526 JLOG(j.fatal())
+
1527 << "Invariant failed: MPT authorize submitted by holder "
+
1528 "succeeded but created/deleted bad number of mptokens";
+
1529 return false;
+
1530 }
+
1531
+
1532 return true;
+
1533 }
+
1534 if (tx.getTxnType() == ttESCROW_FINISH)
+
1535 {
+
1536 // ttESCROW_FINISH may authorize an MPT, but it can't have the
+
1537 // mayAuthorizeMPT privilege, because that may cause
+
1538 // non-amendment-gated side effects.
+
1539 XRPL_ASSERT_PARTS(
+
1540 !enforceEscrowFinish,
+
1541 "ripple::ValidMPTIssuance::finalize",
+
1542 "not escrow finish tx");
+
1543 return true;
+
1544 }
+
1545
+
1546 if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 &&
+ + +
1549 return true;
+
1550 }
+
1551
+
1552 if (mptIssuancesCreated_ != 0)
+
1553 {
+
1554 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
1555 }
-
1556
-
1557 if (mptIssuancesCreated_ != 0)
-
1558 {
-
1559 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created";
-
1560 }
-
1561 else if (mptIssuancesDeleted_ != 0)
-
1562 {
-
1563 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
-
1564 }
-
1565 else if (mptokensCreated_ != 0)
-
1566 {
-
1567 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
-
1568 }
-
1569 else if (mptokensDeleted_ != 0)
-
1570 {
-
1571 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
-
1572 }
-
1573
-
1574 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
- -
1576}
+
1556 else if (mptIssuancesDeleted_ != 0)
+
1557 {
+
1558 JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted";
+
1559 }
+
1560 else if (mptokensCreated_ != 0)
+
1561 {
+
1562 JLOG(j.fatal()) << "Invariant failed: a MPToken was created";
+
1563 }
+
1564 else if (mptokensDeleted_ != 0)
+
1565 {
+
1566 JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted";
+
1567 }
+
1568
+
1569 return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 &&
+ +
1571}
-
1577
-
1578//------------------------------------------------------------------------------
-
1579
-
1580void
-
- -
1582 bool,
-
1583 std::shared_ptr<SLE const> const& before,
- -
1585{
-
1586 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
-
1587 return;
-
1588 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
-
1589 return;
-
1590
-
1591 auto check = [](SleStatus& sleStatus,
-
1592 std::shared_ptr<SLE const> const& sle) {
-
1593 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
-
1594 sleStatus.credentialsSize_ = credentials.size();
-
1595 auto const sorted = credentials::makeSorted(credentials);
-
1596 sleStatus.isUnique_ = !sorted.empty();
-
1597
-
1598 // If array have duplicates then all the other checks are invalid
-
1599 sleStatus.isSorted_ = false;
-
1600
-
1601 if (sleStatus.isUnique_)
-
1602 {
-
1603 unsigned i = 0;
-
1604 for (auto const& cred : sorted)
-
1605 {
-
1606 auto const& credTx = credentials[i++];
-
1607 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
-
1608 (cred.second == credTx[sfCredentialType]);
-
1609 if (!sleStatus.isSorted_)
-
1610 break;
-
1611 }
-
1612 }
-
1613 };
-
1614
-
1615 if (before)
-
1616 {
-
1617 sleStatus_[0] = SleStatus();
-
1618 check(*sleStatus_[0], after);
-
1619 }
-
1620
-
1621 if (after)
-
1622 {
-
1623 sleStatus_[1] = SleStatus();
-
1624 check(*sleStatus_[1], after);
-
1625 }
-
1626}
+
1572
+
1573//------------------------------------------------------------------------------
+
1574
+
1575void
+
+ +
1577 bool,
+
1578 std::shared_ptr<SLE const> const& before,
+ +
1580{
+
1581 if (before && before->getType() != ltPERMISSIONED_DOMAIN)
+
1582 return;
+
1583 if (after && after->getType() != ltPERMISSIONED_DOMAIN)
+
1584 return;
+
1585
+
1586 auto check = [](SleStatus& sleStatus,
+
1587 std::shared_ptr<SLE const> const& sle) {
+
1588 auto const& credentials = sle->getFieldArray(sfAcceptedCredentials);
+
1589 sleStatus.credentialsSize_ = credentials.size();
+
1590 auto const sorted = credentials::makeSorted(credentials);
+
1591 sleStatus.isUnique_ = !sorted.empty();
+
1592
+
1593 // If array have duplicates then all the other checks are invalid
+
1594 sleStatus.isSorted_ = false;
+
1595
+
1596 if (sleStatus.isUnique_)
+
1597 {
+
1598 unsigned i = 0;
+
1599 for (auto const& cred : sorted)
+
1600 {
+
1601 auto const& credTx = credentials[i++];
+
1602 sleStatus.isSorted_ = (cred.first == credTx[sfIssuer]) &&
+
1603 (cred.second == credTx[sfCredentialType]);
+
1604 if (!sleStatus.isSorted_)
+
1605 break;
+
1606 }
+
1607 }
+
1608 };
+
1609
+
1610 if (before)
+
1611 {
+
1612 sleStatus_[0] = SleStatus();
+
1613 check(*sleStatus_[0], after);
+
1614 }
+
1615
+
1616 if (after)
+
1617 {
+
1618 sleStatus_[1] = SleStatus();
+
1619 check(*sleStatus_[1], after);
+
1620 }
+
1621}
-
1627
-
1628bool
-
- -
1630 STTx const& tx,
-
1631 TER const result,
-
1632 XRPAmount const,
-
1633 ReadView const& view,
-
1634 beast::Journal const& j)
-
1635{
-
1636 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
-
1637 return true;
-
1638
-
1639 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
-
1640 if (!sleStatus.credentialsSize_)
-
1641 {
-
1642 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
-
1643 "no rules.";
-
1644 return false;
-
1645 }
-
1646
-
1647 if (sleStatus.credentialsSize_ >
- -
1649 {
-
1650 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
-
1651 "credentials size "
-
1652 << sleStatus.credentialsSize_;
-
1653 return false;
-
1654 }
-
1655
-
1656 if (!sleStatus.isUnique_)
-
1657 {
-
1658 JLOG(j.fatal())
-
1659 << "Invariant failed: permissioned domain credentials "
-
1660 "aren't unique";
-
1661 return false;
-
1662 }
-
1663
-
1664 if (!sleStatus.isSorted_)
-
1665 {
-
1666 JLOG(j.fatal())
-
1667 << "Invariant failed: permissioned domain credentials "
-
1668 "aren't sorted";
-
1669 return false;
-
1670 }
-
1671
-
1672 return true;
-
1673 };
-
1674
-
1675 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
-
1676 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
-
1677}
+
1622
+
1623bool
+
+ +
1625 STTx const& tx,
+
1626 TER const result,
+
1627 XRPAmount const,
+
1628 ReadView const& view,
+
1629 beast::Journal const& j)
+
1630{
+
1631 if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS)
+
1632 return true;
+
1633
+
1634 auto check = [](SleStatus const& sleStatus, beast::Journal const& j) {
+
1635 if (!sleStatus.credentialsSize_)
+
1636 {
+
1637 JLOG(j.fatal()) << "Invariant failed: permissioned domain with "
+
1638 "no rules.";
+
1639 return false;
+
1640 }
+
1641
+
1642 if (sleStatus.credentialsSize_ >
+ +
1644 {
+
1645 JLOG(j.fatal()) << "Invariant failed: permissioned domain bad "
+
1646 "credentials size "
+
1647 << sleStatus.credentialsSize_;
+
1648 return false;
+
1649 }
+
1650
+
1651 if (!sleStatus.isUnique_)
+
1652 {
+
1653 JLOG(j.fatal())
+
1654 << "Invariant failed: permissioned domain credentials "
+
1655 "aren't unique";
+
1656 return false;
+
1657 }
+
1658
+
1659 if (!sleStatus.isSorted_)
+
1660 {
+
1661 JLOG(j.fatal())
+
1662 << "Invariant failed: permissioned domain credentials "
+
1663 "aren't sorted";
+
1664 return false;
+
1665 }
+
1666
+
1667 return true;
+
1668 };
+
1669
+
1670 return (sleStatus_[0] ? check(*sleStatus_[0], j) : true) &&
+
1671 (sleStatus_[1] ? check(*sleStatus_[1], j) : true);
+
1672}
-
1678
-
1679//------------------------------------------------------------------------------
-
1680
-
1681void
-
- -
1683 bool isDelete,
-
1684 std::shared_ptr<SLE const> const& before,
- -
1686{
-
1687 if (isDelete)
-
1688 // Deletion is ignored
-
1689 return;
-
1690
-
1691 if (after && after->getType() == ltACCOUNT_ROOT)
-
1692 {
-
1693 bool const isPseudo = [&]() {
-
1694 // isPseudoAccount checks that any of the pseudo-account fields are
-
1695 // set.
- -
1697 return true;
-
1698 // Not all pseudo-accounts have a zero sequence, but all accounts
-
1699 // with a zero sequence had better be pseudo-accounts.
-
1700 if (after->at(sfSequence) == 0)
-
1701 return true;
-
1702
-
1703 return false;
-
1704 }();
-
1705 if (isPseudo)
-
1706 {
-
1707 // Pseudo accounts must have the following properties:
-
1708 // 1. Exactly one of the pseudo-account fields is set.
-
1709 // 2. The sequence number is not changed.
-
1710 // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
-
1711 // flags are set.
-
1712 // 4. The RegularKey is not set.
-
1713 {
-
1714 std::vector<SField const*> const& fields =
- -
1716
-
1717 auto const numFields = std::count_if(
-
1718 fields.begin(),
-
1719 fields.end(),
-
1720 [&after](SField const* sf) -> bool {
-
1721 return after->isFieldPresent(*sf);
-
1722 });
-
1723 if (numFields != 1)
-
1724 {
-
1725 std::stringstream error;
-
1726 error << "pseudo-account has " << numFields
-
1727 << " pseudo-account fields set";
-
1728 errors_.emplace_back(error.str());
-
1729 }
-
1730 }
-
1731 if (before && before->at(sfSequence) != after->at(sfSequence))
+
1673
+
1674//------------------------------------------------------------------------------
+
1675
+
1676void
+
+ +
1678 bool isDelete,
+
1679 std::shared_ptr<SLE const> const& before,
+ +
1681{
+
1682 if (isDelete)
+
1683 // Deletion is ignored
+
1684 return;
+
1685
+
1686 if (after && after->getType() == ltACCOUNT_ROOT)
+
1687 {
+
1688 bool const isPseudo = [&]() {
+
1689 // isPseudoAccount checks that any of the pseudo-account fields are
+
1690 // set.
+ +
1692 return true;
+
1693 // Not all pseudo-accounts have a zero sequence, but all accounts
+
1694 // with a zero sequence had better be pseudo-accounts.
+
1695 if (after->at(sfSequence) == 0)
+
1696 return true;
+
1697
+
1698 return false;
+
1699 }();
+
1700 if (isPseudo)
+
1701 {
+
1702 // Pseudo accounts must have the following properties:
+
1703 // 1. Exactly one of the pseudo-account fields is set.
+
1704 // 2. The sequence number is not changed.
+
1705 // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
+
1706 // flags are set.
+
1707 // 4. The RegularKey is not set.
+
1708 {
+
1709 std::vector<SField const*> const& fields =
+ +
1711
+
1712 auto const numFields = std::count_if(
+
1713 fields.begin(),
+
1714 fields.end(),
+
1715 [&after](SField const* sf) -> bool {
+
1716 return after->isFieldPresent(*sf);
+
1717 });
+
1718 if (numFields != 1)
+
1719 {
+
1720 std::stringstream error;
+
1721 error << "pseudo-account has " << numFields
+
1722 << " pseudo-account fields set";
+
1723 errors_.emplace_back(error.str());
+
1724 }
+
1725 }
+
1726 if (before && before->at(sfSequence) != after->at(sfSequence))
+
1727 {
+
1728 errors_.emplace_back("pseudo-account sequence changed");
+
1729 }
+
1730 if (!after->isFlag(
+
1732 {
-
1733 errors_.emplace_back("pseudo-account sequence changed");
+
1733 errors_.emplace_back("pseudo-account flags are not set");
1734 }
-
1735 if (!after->isFlag(
- -
1737 {
-
1738 errors_.emplace_back("pseudo-account flags are not set");
-
1739 }
-
1740 if (after->isFieldPresent(sfRegularKey))
-
1741 {
-
1742 errors_.emplace_back("pseudo-account has a regular key");
-
1743 }
-
1744 }
-
1745 }
-
1746}
+
1735 if (after->isFieldPresent(sfRegularKey))
+
1736 {
+
1737 errors_.emplace_back("pseudo-account has a regular key");
+
1738 }
+
1739 }
+
1740 }
+
1741}
-
1747
-
1748bool
-
- -
1750 STTx const& tx,
-
1751 TER const,
-
1752 XRPAmount const,
-
1753 ReadView const& view,
-
1754 beast::Journal const& j)
-
1755{
-
1756 bool const enforce = view.rules().enabled(featureSingleAssetVault);
-
1757
-
1758 // The comment above starting with "assert(enforce)" explains this assert.
-
1759 XRPL_ASSERT(
-
1760 errors_.empty() || enforce,
-
1761 "ripple::ValidPseudoAccounts::finalize : no bad "
-
1762 "changes or enforce invariant");
-
1763 if (!errors_.empty())
-
1764 {
-
1765 for (auto const& error : errors_)
-
1766 {
-
1767 JLOG(j.fatal()) << "Invariant failed: " << error;
-
1768 }
-
1769 if (enforce)
-
1770 return false;
-
1771 }
-
1772 return true;
-
1773}
+
1742
+
1743bool
+
+ +
1745 STTx const& tx,
+
1746 TER const,
+
1747 XRPAmount const,
+
1748 ReadView const& view,
+
1749 beast::Journal const& j)
+
1750{
+
1751 bool const enforce = view.rules().enabled(featureSingleAssetVault);
+
1752
+
1753 // The comment above starting with "assert(enforce)" explains this assert.
+
1754 XRPL_ASSERT(
+
1755 errors_.empty() || enforce,
+
1756 "ripple::ValidPseudoAccounts::finalize : no bad "
+
1757 "changes or enforce invariant");
+
1758 if (!errors_.empty())
+
1759 {
+
1760 for (auto const& error : errors_)
+
1761 {
+
1762 JLOG(j.fatal()) << "Invariant failed: " << error;
+
1763 }
+
1764 if (enforce)
+
1765 return false;
+
1766 }
+
1767 return true;
+
1768}
-
1774
-
1775//------------------------------------------------------------------------------
-
1776
-
1777void
-
- -
1779 bool,
-
1780 std::shared_ptr<SLE const> const& before,
- -
1782{
-
1783 if (after && after->getType() == ltDIR_NODE)
-
1784 {
-
1785 if (after->isFieldPresent(sfDomainID))
-
1786 domains_.insert(after->getFieldH256(sfDomainID));
-
1787 }
-
1788
-
1789 if (after && after->getType() == ltOFFER)
-
1790 {
-
1791 if (after->isFieldPresent(sfDomainID))
-
1792 domains_.insert(after->getFieldH256(sfDomainID));
-
1793 else
-
1794 regularOffers_ = true;
-
1795
-
1796 // if a hybrid offer is missing domain or additional book, there's
-
1797 // something wrong
-
1798 if (after->isFlag(lsfHybrid) &&
-
1799 (!after->isFieldPresent(sfDomainID) ||
-
1800 !after->isFieldPresent(sfAdditionalBooks) ||
-
1801 after->getFieldArray(sfAdditionalBooks).size() > 1))
-
1802 badHybrids_ = true;
-
1803 }
-
1804}
+
1769
+
1770//------------------------------------------------------------------------------
+
1771
+
1772void
+
+ +
1774 bool,
+
1775 std::shared_ptr<SLE const> const& before,
+ +
1777{
+
1778 if (after && after->getType() == ltDIR_NODE)
+
1779 {
+
1780 if (after->isFieldPresent(sfDomainID))
+
1781 domains_.insert(after->getFieldH256(sfDomainID));
+
1782 }
+
1783
+
1784 if (after && after->getType() == ltOFFER)
+
1785 {
+
1786 if (after->isFieldPresent(sfDomainID))
+
1787 domains_.insert(after->getFieldH256(sfDomainID));
+
1788 else
+
1789 regularOffers_ = true;
+
1790
+
1791 // if a hybrid offer is missing domain or additional book, there's
+
1792 // something wrong
+
1793 if (after->isFlag(lsfHybrid) &&
+
1794 (!after->isFieldPresent(sfDomainID) ||
+
1795 !after->isFieldPresent(sfAdditionalBooks) ||
+
1796 after->getFieldArray(sfAdditionalBooks).size() > 1))
+
1797 badHybrids_ = true;
+
1798 }
+
1799}
-
1805
-
1806bool
-
- -
1808 STTx const& tx,
-
1809 TER const result,
-
1810 XRPAmount const,
-
1811 ReadView const& view,
-
1812 beast::Journal const& j)
-
1813{
-
1814 auto const txType = tx.getTxnType();
-
1815 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
-
1816 result != tesSUCCESS)
-
1817 return true;
-
1818
-
1819 // For each offercreate transaction, check if
-
1820 // permissioned offers are valid
-
1821 if (txType == ttOFFER_CREATE && badHybrids_)
-
1822 {
-
1823 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
-
1824 return false;
-
1825 }
+
1800
+
1801bool
+
+ +
1803 STTx const& tx,
+
1804 TER const result,
+
1805 XRPAmount const,
+
1806 ReadView const& view,
+
1807 beast::Journal const& j)
+
1808{
+
1809 auto const txType = tx.getTxnType();
+
1810 if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) ||
+
1811 result != tesSUCCESS)
+
1812 return true;
+
1813
+
1814 // For each offercreate transaction, check if
+
1815 // permissioned offers are valid
+
1816 if (txType == ttOFFER_CREATE && badHybrids_)
+
1817 {
+
1818 JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed";
+
1819 return false;
+
1820 }
+
1821
+
1822 if (!tx.isFieldPresent(sfDomainID))
+
1823 return true;
+
1824
+
1825 auto const domain = tx.getFieldH256(sfDomainID);
1826
-
1827 if (!tx.isFieldPresent(sfDomainID))
-
1828 return true;
-
1829
-
1830 auto const domain = tx.getFieldH256(sfDomainID);
-
1831
-
1832 if (!view.exists(keylet::permissionedDomain(domain)))
-
1833 {
-
1834 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
-
1835 return false;
-
1836 }
-
1837
-
1838 // for both payment and offercreate, there shouldn't be another domain
-
1839 // that's different from the domain specified
-
1840 for (auto const& d : domains_)
-
1841 {
-
1842 if (d != domain)
-
1843 {
-
1844 JLOG(j.fatal()) << "Invariant failed: transaction"
-
1845 " consumed wrong domains";
-
1846 return false;
-
1847 }
-
1848 }
-
1849
-
1850 if (regularOffers_)
-
1851 {
-
1852 JLOG(j.fatal()) << "Invariant failed: domain transaction"
-
1853 " affected regular offers";
-
1854 return false;
-
1855 }
-
1856
-
1857 return true;
-
1858}
+
1827 if (!view.exists(keylet::permissionedDomain(domain)))
+
1828 {
+
1829 JLOG(j.fatal()) << "Invariant failed: domain doesn't exist";
+
1830 return false;
+
1831 }
+
1832
+
1833 // for both payment and offercreate, there shouldn't be another domain
+
1834 // that's different from the domain specified
+
1835 for (auto const& d : domains_)
+
1836 {
+
1837 if (d != domain)
+
1838 {
+
1839 JLOG(j.fatal()) << "Invariant failed: transaction"
+
1840 " consumed wrong domains";
+
1841 return false;
+
1842 }
+
1843 }
+
1844
+
1845 if (regularOffers_)
+
1846 {
+
1847 JLOG(j.fatal()) << "Invariant failed: domain transaction"
+
1848 " affected regular offers";
+
1849 return false;
+
1850 }
+
1851
+
1852 return true;
+
1853}
-
1859
-
1860void
-
- -
1862 bool isDelete,
-
1863 std::shared_ptr<SLE const> const& before,
- -
1865{
-
1866 if (isDelete)
-
1867 return;
-
1868
-
1869 if (after)
-
1870 {
-
1871 auto const type = after->getType();
-
1872 // AMM object changed
-
1873 if (type == ltAMM)
-
1874 {
-
1875 ammAccount_ = after->getAccountID(sfAccount);
-
1876 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
-
1877 }
-
1878 // AMM pool changed
-
1879 else if (
-
1880 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
-
1881 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
-
1882 {
-
1883 ammPoolChanged_ = true;
-
1884 }
-
1885 }
-
1886
-
1887 if (before)
-
1888 {
-
1889 // AMM object changed
-
1890 if (before->getType() == ltAMM)
-
1891 {
-
1892 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
-
1893 }
-
1894 }
-
1895}
+
1854
+
1855void
+
+ +
1857 bool isDelete,
+
1858 std::shared_ptr<SLE const> const& before,
+ +
1860{
+
1861 if (isDelete)
+
1862 return;
+
1863
+
1864 if (after)
+
1865 {
+
1866 auto const type = after->getType();
+
1867 // AMM object changed
+
1868 if (type == ltAMM)
+
1869 {
+
1870 ammAccount_ = after->getAccountID(sfAccount);
+
1871 lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
+
1872 }
+
1873 // AMM pool changed
+
1874 else if (
+
1875 (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) ||
+
1876 (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
+
1877 {
+
1878 ammPoolChanged_ = true;
+
1879 }
+
1880 }
+
1881
+
1882 if (before)
+
1883 {
+
1884 // AMM object changed
+
1885 if (before->getType() == ltAMM)
+
1886 {
+
1887 lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance);
+
1888 }
+
1889 }
+
1890}
-
1896
-
1897static bool
-
- -
1899 STAmount const& amount,
-
1900 STAmount const& amount2,
-
1901 STAmount const& lptAMMBalance,
-
1902 ValidAMM::ZeroAllowed zeroAllowed)
-
1903{
-
1904 bool const positive = amount > beast::zero && amount2 > beast::zero &&
-
1905 lptAMMBalance > beast::zero;
-
1906 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
-
1907 return positive ||
-
1908 (amount == beast::zero && amount2 == beast::zero &&
-
1909 lptAMMBalance == beast::zero);
-
1910 return positive;
-
1911}
+
1891
+
1892static bool
+
+ +
1894 STAmount const& amount,
+
1895 STAmount const& amount2,
+
1896 STAmount const& lptAMMBalance,
+
1897 ValidAMM::ZeroAllowed zeroAllowed)
+
1898{
+
1899 bool const positive = amount > beast::zero && amount2 > beast::zero &&
+
1900 lptAMMBalance > beast::zero;
+
1901 if (zeroAllowed == ValidAMM::ZeroAllowed::Yes)
+
1902 return positive ||
+
1903 (amount == beast::zero && amount2 == beast::zero &&
+
1904 lptAMMBalance == beast::zero);
+
1905 return positive;
+
1906}
-
1912
-
1913bool
-
-
1914ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
-
1915{
- -
1917 {
-
1918 // LPTokens and the pool can not change on vote
-
1919 // LCOV_EXCL_START
-
1920 JLOG(j.error()) << "AMMVote invariant failed: "
- - -
1923 << ammPoolChanged_;
-
1924 if (enforce)
-
1925 return false;
-
1926 // LCOV_EXCL_STOP
-
1927 }
-
1928
-
1929 return true;
-
1930}
+
1907
+
1908bool
+
+
1909ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
+
1910{
+ +
1912 {
+
1913 // LPTokens and the pool can not change on vote
+
1914 // LCOV_EXCL_START
+
1915 JLOG(j.error()) << "AMMVote invariant failed: "
+ + +
1918 << ammPoolChanged_;
+
1919 if (enforce)
+
1920 return false;
+
1921 // LCOV_EXCL_STOP
+
1922 }
+
1923
+
1924 return true;
+
1925}
-
1931
-
1932bool
-
-
1933ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
-
1934{
-
1935 if (ammPoolChanged_)
-
1936 {
-
1937 // The pool can not change on bid
-
1938 // LCOV_EXCL_START
-
1939 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
-
1940 if (enforce)
-
1941 return false;
-
1942 // LCOV_EXCL_STOP
-
1943 }
-
1944 // LPTokens are burnt, therefore there should be fewer LPTokens
-
1945 else if (
- - -
1948 *lptAMMBalanceAfter_ <= beast::zero))
-
1949 {
-
1950 // LCOV_EXCL_START
-
1951 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
-
1952 << " " << *lptAMMBalanceAfter_;
-
1953 if (enforce)
-
1954 return false;
-
1955 // LCOV_EXCL_STOP
-
1956 }
-
1957
-
1958 return true;
-
1959}
+
1926
+
1927bool
+
+
1928ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
+
1929{
+
1930 if (ammPoolChanged_)
+
1931 {
+
1932 // The pool can not change on bid
+
1933 // LCOV_EXCL_START
+
1934 JLOG(j.error()) << "AMMBid invariant failed: pool changed";
+
1935 if (enforce)
+
1936 return false;
+
1937 // LCOV_EXCL_STOP
+
1938 }
+
1939 // LPTokens are burnt, therefore there should be fewer LPTokens
+
1940 else if (
+ + +
1943 *lptAMMBalanceAfter_ <= beast::zero))
+
1944 {
+
1945 // LCOV_EXCL_START
+
1946 JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_
+
1947 << " " << *lptAMMBalanceAfter_;
+
1948 if (enforce)
+
1949 return false;
+
1950 // LCOV_EXCL_STOP
+
1951 }
+
1952
+
1953 return true;
+
1954}
-
1960
-
1961bool
-
- -
1963 STTx const& tx,
-
1964 ReadView const& view,
-
1965 bool enforce,
-
1966 beast::Journal const& j) const
-
1967{
-
1968 if (!ammAccount_)
-
1969 {
-
1970 // LCOV_EXCL_START
-
1971 JLOG(j.error())
-
1972 << "AMMCreate invariant failed: AMM object is not created";
-
1973 if (enforce)
-
1974 return false;
-
1975 // LCOV_EXCL_STOP
-
1976 }
-
1977 else
-
1978 {
-
1979 auto const [amount, amount2] = ammPoolHolds(
-
1980 view,
-
1981 *ammAccount_,
-
1982 tx[sfAmount].get<Issue>(),
-
1983 tx[sfAmount2].get<Issue>(),
- -
1985 j);
-
1986 // Create invariant:
-
1987 // sqrt(amount * amount2) == LPTokens
-
1988 // all balances are greater than zero
-
1989 if (!validBalances(
-
1990 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
-
1991 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
- -
1993 {
-
1994 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
-
1995 << amount2 << " " << *lptAMMBalanceAfter_;
-
1996 if (enforce)
-
1997 return false;
-
1998 }
-
1999 }
-
2000
-
2001 return true;
-
2002}
+
1955
+
1956bool
+
+ +
1958 STTx const& tx,
+
1959 ReadView const& view,
+
1960 bool enforce,
+
1961 beast::Journal const& j) const
+
1962{
+
1963 if (!ammAccount_)
+
1964 {
+
1965 // LCOV_EXCL_START
+
1966 JLOG(j.error())
+
1967 << "AMMCreate invariant failed: AMM object is not created";
+
1968 if (enforce)
+
1969 return false;
+
1970 // LCOV_EXCL_STOP
+
1971 }
+
1972 else
+
1973 {
+
1974 auto const [amount, amount2] = ammPoolHolds(
+
1975 view,
+
1976 *ammAccount_,
+
1977 tx[sfAmount].get<Issue>(),
+
1978 tx[sfAmount2].get<Issue>(),
+ +
1980 j);
+
1981 // Create invariant:
+
1982 // sqrt(amount * amount2) == LPTokens
+
1983 // all balances are greater than zero
+
1984 if (!validBalances(
+
1985 amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
+
1986 ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) !=
+ +
1988 {
+
1989 JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " "
+
1990 << amount2 << " " << *lptAMMBalanceAfter_;
+
1991 if (enforce)
+
1992 return false;
+
1993 }
+
1994 }
+
1995
+
1996 return true;
+
1997}
-
2003
-
2004bool
-
-
2005ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
-
2006{
-
2007 if (ammAccount_)
-
2008 {
-
2009 // LCOV_EXCL_START
-
2010 std::string const msg = (res == tesSUCCESS)
-
2011 ? "AMM object is not deleted on tesSUCCESS"
-
2012 : "AMM object is changed on tecINCOMPLETE";
-
2013 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
-
2014 if (enforce)
-
2015 return false;
-
2016 // LCOV_EXCL_STOP
-
2017 }
-
2018
-
2019 return true;
-
2020}
+
1998
+
1999bool
+
+
2000ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
+
2001{
+
2002 if (ammAccount_)
+
2003 {
+
2004 // LCOV_EXCL_START
+
2005 std::string const msg = (res == tesSUCCESS)
+
2006 ? "AMM object is not deleted on tesSUCCESS"
+
2007 : "AMM object is changed on tecINCOMPLETE";
+
2008 JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
+
2009 if (enforce)
+
2010 return false;
+
2011 // LCOV_EXCL_STOP
+
2012 }
+
2013
+
2014 return true;
+
2015}
-
2021
-
2022bool
-
-
2023ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
-
2024{
-
2025 if (ammAccount_)
-
2026 {
-
2027 // LCOV_EXCL_START
-
2028 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
-
2029 if (enforce)
-
2030 return false;
-
2031 // LCOV_EXCL_STOP
-
2032 }
-
2033
-
2034 return true;
-
2035}
+
2016
+
2017bool
+
+
2018ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
+
2019{
+
2020 if (ammAccount_)
+
2021 {
+
2022 // LCOV_EXCL_START
+
2023 JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
+
2024 if (enforce)
+
2025 return false;
+
2026 // LCOV_EXCL_STOP
+
2027 }
+
2028
+
2029 return true;
+
2030}
-
2036
-
2037bool
-
- -
2039 ripple::STTx const& tx,
-
2040 ripple::ReadView const& view,
-
2041 ZeroAllowed zeroAllowed,
-
2042 beast::Journal const& j) const
-
2043{
-
2044 auto const [amount, amount2] = ammPoolHolds(
-
2045 view,
-
2046 *ammAccount_,
-
2047 tx[sfAsset].get<Issue>(),
-
2048 tx[sfAsset2].get<Issue>(),
- -
2050 j);
-
2051 // Deposit and Withdrawal invariant:
-
2052 // sqrt(amount * amount2) >= LPTokens
-
2053 // all balances are greater than zero
-
2054 // unless on last withdrawal
-
2055 auto const poolProductMean = root2(amount * amount2);
-
2056 bool const nonNegativeBalances =
-
2057 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
-
2058 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
-
2059 // Allow for a small relative error if strongInvariantCheck fails
-
2060 auto weakInvariantCheck = [&]() {
-
2061 return *lptAMMBalanceAfter_ != beast::zero &&
- -
2063 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
-
2064 };
-
2065 if (!nonNegativeBalances ||
-
2066 (!strongInvariantCheck && !weakInvariantCheck()))
-
2067 {
-
2068 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
- -
2070 << ammPoolChanged_ << " " << amount << " " << amount2
-
2071 << " " << poolProductMean << " "
-
2072 << lptAMMBalanceAfter_->getText() << " "
-
2073 << ((*lptAMMBalanceAfter_ == beast::zero)
-
2074 ? Number{1}
-
2075 : ((*lptAMMBalanceAfter_ - poolProductMean) /
-
2076 poolProductMean));
-
2077 return false;
-
2078 }
-
2079
-
2080 return true;
-
2081}
+
2031
+
2032bool
+
+ +
2034 ripple::STTx const& tx,
+
2035 ripple::ReadView const& view,
+
2036 ZeroAllowed zeroAllowed,
+
2037 beast::Journal const& j) const
+
2038{
+
2039 auto const [amount, amount2] = ammPoolHolds(
+
2040 view,
+
2041 *ammAccount_,
+
2042 tx[sfAsset].get<Issue>(),
+
2043 tx[sfAsset2].get<Issue>(),
+ +
2045 j);
+
2046 // Deposit and Withdrawal invariant:
+
2047 // sqrt(amount * amount2) >= LPTokens
+
2048 // all balances are greater than zero
+
2049 // unless on last withdrawal
+
2050 auto const poolProductMean = root2(amount * amount2);
+
2051 bool const nonNegativeBalances =
+
2052 validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed);
+
2053 bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_;
+
2054 // Allow for a small relative error if strongInvariantCheck fails
+
2055 auto weakInvariantCheck = [&]() {
+
2056 return *lptAMMBalanceAfter_ != beast::zero &&
+ +
2058 poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11});
+
2059 };
+
2060 if (!nonNegativeBalances ||
+
2061 (!strongInvariantCheck && !weakInvariantCheck()))
+
2062 {
+
2063 JLOG(j.error()) << "AMM " << tx.getTxnType() << " invariant failed: "
+ +
2065 << ammPoolChanged_ << " " << amount << " " << amount2
+
2066 << " " << poolProductMean << " "
+
2067 << lptAMMBalanceAfter_->getText() << " "
+
2068 << ((*lptAMMBalanceAfter_ == beast::zero)
+
2069 ? Number{1}
+
2070 : ((*lptAMMBalanceAfter_ - poolProductMean) /
+
2071 poolProductMean));
+
2072 return false;
+
2073 }
+
2074
+
2075 return true;
+
2076}
-
2082
-
2083bool
-
- -
2085 ripple::STTx const& tx,
-
2086 ripple::ReadView const& view,
-
2087 bool enforce,
-
2088 beast::Journal const& j) const
-
2089{
-
2090 if (!ammAccount_)
-
2091 {
-
2092 // LCOV_EXCL_START
-
2093 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
-
2094 if (enforce)
-
2095 return false;
-
2096 // LCOV_EXCL_STOP
-
2097 }
-
2098 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
-
2099 return false;
-
2100
-
2101 return true;
-
2102}
+
2077
+
2078bool
+
+ +
2080 ripple::STTx const& tx,
+
2081 ripple::ReadView const& view,
+
2082 bool enforce,
+
2083 beast::Journal const& j) const
+
2084{
+
2085 if (!ammAccount_)
+
2086 {
+
2087 // LCOV_EXCL_START
+
2088 JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
+
2089 if (enforce)
+
2090 return false;
+
2091 // LCOV_EXCL_STOP
+
2092 }
+
2093 else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce)
+
2094 return false;
+
2095
+
2096 return true;
+
2097}
-
2103
-
2104bool
-
- -
2106 ripple::STTx const& tx,
-
2107 ripple::ReadView const& view,
-
2108 bool enforce,
-
2109 beast::Journal const& j) const
-
2110{
-
2111 if (!ammAccount_)
-
2112 {
-
2113 // Last Withdraw or Clawback deleted AMM
+
2098
+
2099bool
+
+ +
2101 ripple::STTx const& tx,
+
2102 ripple::ReadView const& view,
+
2103 bool enforce,
+
2104 beast::Journal const& j) const
+
2105{
+
2106 if (!ammAccount_)
+
2107 {
+
2108 // Last Withdraw or Clawback deleted AMM
+
2109 }
+
2110 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
+
2111 {
+
2112 if (enforce)
+
2113 return false;
2114 }
-
2115 else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j))
-
2116 {
-
2117 if (enforce)
-
2118 return false;
-
2119 }
-
2120
-
2121 return true;
-
2122}
+
2115
+
2116 return true;
+
2117}
-
2123
-
2124bool
-
- -
2126 STTx const& tx,
-
2127 TER const result,
-
2128 XRPAmount const,
-
2129 ReadView const& view,
-
2130 beast::Journal const& j)
-
2131{
-
2132 // Delete may return tecINCOMPLETE if there are too many
-
2133 // trustlines to delete.
-
2134 if (result != tesSUCCESS && result != tecINCOMPLETE)
-
2135 return true;
-
2136
-
2137 bool const enforce = view.rules().enabled(fixAMMv1_3);
-
2138
-
2139 switch (tx.getTxnType())
-
2140 {
-
2141 case ttAMM_CREATE:
-
2142 return finalizeCreate(tx, view, enforce, j);
-
2143 case ttAMM_DEPOSIT:
-
2144 return finalizeDeposit(tx, view, enforce, j);
-
2145 case ttAMM_CLAWBACK:
-
2146 case ttAMM_WITHDRAW:
-
2147 return finalizeWithdraw(tx, view, enforce, j);
-
2148 case ttAMM_BID:
-
2149 return finalizeBid(enforce, j);
-
2150 case ttAMM_VOTE:
-
2151 return finalizeVote(enforce, j);
-
2152 case ttAMM_DELETE:
-
2153 return finalizeDelete(enforce, result, j);
-
2154 case ttCHECK_CASH:
-
2155 case ttOFFER_CREATE:
-
2156 case ttPAYMENT:
-
2157 return finalizeDEX(enforce, j);
-
2158 default:
-
2159 break;
-
2160 }
+
2118
+
2119bool
+
+ +
2121 STTx const& tx,
+
2122 TER const result,
+
2123 XRPAmount const,
+
2124 ReadView const& view,
+
2125 beast::Journal const& j)
+
2126{
+
2127 // Delete may return tecINCOMPLETE if there are too many
+
2128 // trustlines to delete.
+
2129 if (result != tesSUCCESS && result != tecINCOMPLETE)
+
2130 return true;
+
2131
+
2132 bool const enforce = view.rules().enabled(fixAMMv1_3);
+
2133
+
2134 switch (tx.getTxnType())
+
2135 {
+
2136 case ttAMM_CREATE:
+
2137 return finalizeCreate(tx, view, enforce, j);
+
2138 case ttAMM_DEPOSIT:
+
2139 return finalizeDeposit(tx, view, enforce, j);
+
2140 case ttAMM_CLAWBACK:
+
2141 case ttAMM_WITHDRAW:
+
2142 return finalizeWithdraw(tx, view, enforce, j);
+
2143 case ttAMM_BID:
+
2144 return finalizeBid(enforce, j);
+
2145 case ttAMM_VOTE:
+
2146 return finalizeVote(enforce, j);
+
2147 case ttAMM_DELETE:
+
2148 return finalizeDelete(enforce, result, j);
+
2149 case ttCHECK_CASH:
+
2150 case ttOFFER_CREATE:
+
2151 case ttPAYMENT:
+
2152 return finalizeDEX(enforce, j);
+
2153 default:
+
2154 break;
+
2155 }
+
2156
+
2157 return true;
+
2158}
+
+
2159
+
2160//------------------------------------------------------------------------------
2161
-
2162 return true;
-
2163}
+ +
+ +
2164{
+
2165 XRPL_ASSERT(
+
2166 from.getType() == ltVAULT,
+
2167 "ValidVault::Vault::make : from Vault object");
+
2168
+
2169 ValidVault::Vault self;
+
2170 self.key = from.key();
+
2171 self.asset = from.at(sfAsset);
+
2172 self.pseudoId = from.getAccountID(sfAccount);
+
2173 self.shareMPTID = from.getFieldH192(sfShareMPTID);
+
2174 self.assetsTotal = from.at(sfAssetsTotal);
+
2175 self.assetsAvailable = from.at(sfAssetsAvailable);
+
2176 self.assetsMaximum = from.at(sfAssetsMaximum);
+
2177 self.lossUnrealized = from.at(sfLossUnrealized);
+
2178 return self;
+
2179}
-
2164
-
2165//------------------------------------------------------------------------------
-
2166
- -
- -
2169{
-
2170 XRPL_ASSERT(
-
2171 from.getType() == ltVAULT,
-
2172 "ValidVault::Vault::make : from Vault object");
-
2173
-
2174 ValidVault::Vault self;
-
2175 self.key = from.key();
-
2176 self.asset = from.at(sfAsset);
-
2177 self.pseudoId = from.getAccountID(sfAccount);
-
2178 self.shareMPTID = from.getFieldH192(sfShareMPTID);
-
2179 self.assetsTotal = from.at(sfAssetsTotal);
-
2180 self.assetsAvailable = from.at(sfAssetsAvailable);
-
2181 self.assetsMaximum = from.at(sfAssetsMaximum);
-
2182 self.lossUnrealized = from.at(sfLossUnrealized);
-
2183 return self;
-
2184}
+
2180
+ +
+ +
2183{
+
2184 XRPL_ASSERT(
+
2185 from.getType() == ltMPTOKEN_ISSUANCE,
+
2186 "ValidVault::Shares::make : from MPTokenIssuance object");
+
2187
+
2188 ValidVault::Shares self;
+
2189 self.share = MPTIssue(
+
2190 makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
+
2191 self.sharesTotal = from.at(sfOutstandingAmount);
+
2192 self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
+
2193 return self;
+
2194}
-
2185
- -
- -
2188{
-
2189 XRPL_ASSERT(
-
2190 from.getType() == ltMPTOKEN_ISSUANCE,
-
2191 "ValidVault::Shares::make : from MPTokenIssuance object");
-
2192
-
2193 ValidVault::Shares self;
-
2194 self.share = MPTIssue(
-
2195 makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer)));
-
2196 self.sharesTotal = from.at(sfOutstandingAmount);
-
2197 self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount);
-
2198 return self;
-
2199}
-
-
2200
-
2201void
-
- -
2203 bool isDelete,
-
2204 std::shared_ptr<SLE const> const& before,
- -
2206{
-
2207 // If `before` is empty, this means an object is being created, in which
-
2208 // case `isDelete` must be false. Otherwise `before` and `after` are set and
-
2209 // `isDelete` indicates whether an object is being deleted or modified.
-
2210 XRPL_ASSERT(
-
2211 after != nullptr && (before != nullptr || !isDelete),
-
2212 "ripple::ValidVault::visitEntry : some object is available");
-
2213
-
2214 // Number balanceDelta will capture the difference (delta) between "before"
-
2215 // state (zero if created) and "after" state (zero if destroyed), so the
-
2216 // invariants can validate that the change in account balances matches the
-
2217 // change in vault balances, stored to deltas_ at the end of this function.
-
2218 Number balanceDelta{};
-
2219
-
2220 std::int8_t sign = 0;
-
2221 if (before)
-
2222 {
-
2223 switch (before->getType())
-
2224 {
-
2225 case ltVAULT:
-
2226 beforeVault_.push_back(Vault::make(*before));
-
2227 break;
-
2228 case ltMPTOKEN_ISSUANCE:
-
2229 // At this moment we have no way of telling if this object holds
-
2230 // vault shares or something else. Save it for finalize.
-
2231 beforeMPTs_.push_back(Shares::make(*before));
-
2232 balanceDelta = static_cast<std::int64_t>(
-
2233 before->getFieldU64(sfOutstandingAmount));
-
2234 sign = 1;
+
2195
+
2196void
+
+ +
2198 bool isDelete,
+
2199 std::shared_ptr<SLE const> const& before,
+ +
2201{
+
2202 // If `before` is empty, this means an object is being created, in which
+
2203 // case `isDelete` must be false. Otherwise `before` and `after` are set and
+
2204 // `isDelete` indicates whether an object is being deleted or modified.
+
2205 XRPL_ASSERT(
+
2206 after != nullptr && (before != nullptr || !isDelete),
+
2207 "ripple::ValidVault::visitEntry : some object is available");
+
2208
+
2209 // Number balanceDelta will capture the difference (delta) between "before"
+
2210 // state (zero if created) and "after" state (zero if destroyed), so the
+
2211 // invariants can validate that the change in account balances matches the
+
2212 // change in vault balances, stored to deltas_ at the end of this function.
+
2213 Number balanceDelta{};
+
2214
+
2215 std::int8_t sign = 0;
+
2216 if (before)
+
2217 {
+
2218 switch (before->getType())
+
2219 {
+
2220 case ltVAULT:
+
2221 beforeVault_.push_back(Vault::make(*before));
+
2222 break;
+
2223 case ltMPTOKEN_ISSUANCE:
+
2224 // At this moment we have no way of telling if this object holds
+
2225 // vault shares or something else. Save it for finalize.
+
2226 beforeMPTs_.push_back(Shares::make(*before));
+
2227 balanceDelta = static_cast<std::int64_t>(
+
2228 before->getFieldU64(sfOutstandingAmount));
+
2229 sign = 1;
+
2230 break;
+
2231 case ltMPTOKEN:
+
2232 balanceDelta =
+
2233 static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
+
2234 sign = -1;
2235 break;
-
2236 case ltMPTOKEN:
-
2237 balanceDelta =
-
2238 static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
+
2236 case ltACCOUNT_ROOT:
+
2237 case ltRIPPLE_STATE:
+
2238 balanceDelta = before->getFieldAmount(sfBalance);
2239 sign = -1;
2240 break;
-
2241 case ltACCOUNT_ROOT:
-
2242 case ltRIPPLE_STATE:
-
2243 balanceDelta = before->getFieldAmount(sfBalance);
-
2244 sign = -1;
-
2245 break;
-
2246 default:;
-
2247 }
-
2248 }
-
2249
-
2250 if (!isDelete && after)
-
2251 {
-
2252 switch (after->getType())
-
2253 {
-
2254 case ltVAULT:
-
2255 afterVault_.push_back(Vault::make(*after));
-
2256 break;
-
2257 case ltMPTOKEN_ISSUANCE:
-
2258 // At this moment we have no way of telling if this object holds
-
2259 // vault shares or something else. Save it for finalize.
-
2260 afterMPTs_.push_back(Shares::make(*after));
-
2261 balanceDelta -= Number(static_cast<std::int64_t>(
-
2262 after->getFieldU64(sfOutstandingAmount)));
-
2263 sign = 1;
+
2241 default:;
+
2242 }
+
2243 }
+
2244
+
2245 if (!isDelete && after)
+
2246 {
+
2247 switch (after->getType())
+
2248 {
+
2249 case ltVAULT:
+
2250 afterVault_.push_back(Vault::make(*after));
+
2251 break;
+
2252 case ltMPTOKEN_ISSUANCE:
+
2253 // At this moment we have no way of telling if this object holds
+
2254 // vault shares or something else. Save it for finalize.
+
2255 afterMPTs_.push_back(Shares::make(*after));
+
2256 balanceDelta -= Number(static_cast<std::int64_t>(
+
2257 after->getFieldU64(sfOutstandingAmount)));
+
2258 sign = 1;
+
2259 break;
+
2260 case ltMPTOKEN:
+
2261 balanceDelta -= Number(
+
2262 static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
+
2263 sign = -1;
2264 break;
-
2265 case ltMPTOKEN:
-
2266 balanceDelta -= Number(
-
2267 static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount)));
+
2265 case ltACCOUNT_ROOT:
+
2266 case ltRIPPLE_STATE:
+
2267 balanceDelta -= Number(after->getFieldAmount(sfBalance));
2268 sign = -1;
2269 break;
-
2270 case ltACCOUNT_ROOT:
-
2271 case ltRIPPLE_STATE:
-
2272 balanceDelta -= Number(after->getFieldAmount(sfBalance));
-
2273 sign = -1;
-
2274 break;
-
2275 default:;
-
2276 }
-
2277 }
-
2278
-
2279 uint256 const key = (before ? before->key() : after->key());
-
2280 // Append to deltas if sign is non-zero, i.e. an object of an interesting
-
2281 // type has been updated. A transaction may update an object even when
-
2282 // its balance has not changed, e.g. transaction fee equals the amount
-
2283 // transferred to the account. We intentionally do not compare balanceDelta
-
2284 // against zero, to avoid missing such updates.
-
2285 if (sign != 0)
-
2286 deltas_[key] = balanceDelta * sign;
-
2287}
+
2270 default:;
+
2271 }
+
2272 }
+
2273
+
2274 uint256 const key = (before ? before->key() : after->key());
+
2275 // Append to deltas if sign is non-zero, i.e. an object of an interesting
+
2276 // type has been updated. A transaction may update an object even when
+
2277 // its balance has not changed, e.g. transaction fee equals the amount
+
2278 // transferred to the account. We intentionally do not compare balanceDelta
+
2279 // against zero, to avoid missing such updates.
+
2280 if (sign != 0)
+
2281 deltas_[key] = balanceDelta * sign;
+
2282}
-
2288
-
2289bool
-
- -
2291 STTx const& tx,
-
2292 TER const ret,
-
2293 XRPAmount const fee,
-
2294 ReadView const& view,
-
2295 beast::Journal const& j)
-
2296{
-
2297 bool const enforce = view.rules().enabled(featureSingleAssetVault);
-
2298
-
2299 if (!isTesSuccess(ret))
-
2300 return true; // Do not perform checks
-
2301
-
2302 if (afterVault_.empty() && beforeVault_.empty())
-
2303 {
- -
2305 {
-
2306 JLOG(j.fatal()) << //
-
2307 "Invariant failed: vault operation succeeded without modifying "
-
2308 "a vault";
-
2309 XRPL_ASSERT(
-
2310 enforce, "ripple::ValidVault::finalize : vault noop invariant");
-
2311 return !enforce;
-
2312 }
-
2313
-
2314 return true; // Not a vault operation
-
2315 }
-
2316 else if (!hasPrivilege(tx, mustModifyVault)) // TODO: mayModifyVault
-
2317 {
-
2318 JLOG(j.fatal()) << //
-
2319 "Invariant failed: vault updated by a wrong transaction type";
-
2320 XRPL_ASSERT(
-
2321 enforce,
-
2322 "ripple::ValidVault::finalize : illegal vault transaction "
-
2323 "invariant");
-
2324 return !enforce; // Also not a vault operation
-
2325 }
-
2326
-
2327 if (beforeVault_.size() > 1 || afterVault_.size() > 1)
-
2328 {
-
2329 JLOG(j.fatal()) << //
-
2330 "Invariant failed: vault operation updated more than single vault";
-
2331 XRPL_ASSERT(
-
2332 enforce, "ripple::ValidVault::finalize : single vault invariant");
-
2333 return !enforce; // That's all we can do here
-
2334 }
-
2335
-
2336 auto const txnType = tx.getTxnType();
-
2337
-
2338 // We do special handling for ttVAULT_DELETE first, because it's the only
-
2339 // vault-modifying transaction without an "after" state of the vault
-
2340 if (afterVault_.empty())
-
2341 {
-
2342 if (txnType != ttVAULT_DELETE)
-
2343 {
-
2344 JLOG(j.fatal()) << //
-
2345 "Invariant failed: vault deleted by a wrong transaction type";
-
2346 XRPL_ASSERT(
-
2347 enforce,
-
2348 "ripple::ValidVault::finalize : illegal vault deletion "
-
2349 "invariant");
-
2350 return !enforce; // That's all we can do here
-
2351 }
-
2352
-
2353 // Note, if afterVault_ is empty then we know that beforeVault_ is not
-
2354 // empty, as enforced at the top of this function
-
2355 auto const& beforeVault = beforeVault_[0];
-
2356
-
2357 // At this moment we only know a vault is being deleted and there
-
2358 // might be some MPTokenIssuance objects which are deleted in the
-
2359 // same transaction. Find the one matching this vault.
-
2360 auto const deletedShares = [&]() -> std::optional<Shares> {
-
2361 for (auto const& e : beforeMPTs_)
-
2362 {
-
2363 if (e.share.getMptID() == beforeVault.shareMPTID)
-
2364 return std::move(e);
-
2365 }
-
2366 return std::nullopt;
-
2367 }();
-
2368
-
2369 if (!deletedShares)
-
2370 {
-
2371 JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
-
2372 "delete shares";
-
2373 XRPL_ASSERT(
-
2374 enforce,
-
2375 "ripple::ValidVault::finalize : shares deletion invariant");
-
2376 return !enforce; // That's all we can do here
-
2377 }
-
2378
-
2379 bool result = true;
-
2380 if (deletedShares->sharesTotal != 0)
-
2381 {
-
2382 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
-
2383 "shares outstanding";
-
2384 result = false;
-
2385 }
-
2386 if (beforeVault.assetsTotal != zero)
-
2387 {
-
2388 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
-
2389 "assets outstanding";
-
2390 result = false;
-
2391 }
-
2392 if (beforeVault.assetsAvailable != zero)
-
2393 {
-
2394 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
-
2395 "assets available";
-
2396 result = false;
-
2397 }
-
2398
-
2399 return result;
-
2400 }
-
2401 else if (txnType == ttVAULT_DELETE)
-
2402 {
-
2403 JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
-
2404 "deleting a vault";
-
2405 XRPL_ASSERT(
-
2406 enforce, "ripple::ValidVault::finalize : vault deletion invariant");
-
2407 return !enforce; // That's all we can do here
-
2408 }
-
2409
-
2410 // Note, `afterVault_.empty()` is handled above
-
2411 auto const& afterVault = afterVault_[0];
-
2412 XRPL_ASSERT(
-
2413 beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
-
2414 "ripple::ValidVault::finalize : single vault operation");
-
2415
-
2416 auto const updatedShares = [&]() -> std::optional<Shares> {
-
2417 // At this moment we only know that a vault is being updated and there
-
2418 // might be some MPTokenIssuance objects which are also updated in the
-
2419 // same transaction. Find the one matching the shares to this vault.
-
2420 // Note, we expect updatedMPTs collection to be extremely small. For
-
2421 // such collections linear search is faster than lookup.
-
2422 for (auto const& e : afterMPTs_)
-
2423 {
-
2424 if (e.share.getMptID() == afterVault.shareMPTID)
-
2425 return e;
-
2426 }
-
2427
-
2428 auto const sleShares =
-
2429 view.read(keylet::mptIssuance(afterVault.shareMPTID));
-
2430
-
2431 return sleShares ? std::optional<Shares>(Shares::make(*sleShares))
-
2432 : std::nullopt;
-
2433 }();
-
2434
-
2435 bool result = true;
-
2436
-
2437 // Universal transaction checks
-
2438 if (!beforeVault_.empty())
-
2439 {
-
2440 auto const& beforeVault = beforeVault_[0];
-
2441 if (afterVault.asset != beforeVault.asset ||
-
2442 afterVault.pseudoId != beforeVault.pseudoId ||
-
2443 afterVault.shareMPTID != beforeVault.shareMPTID)
-
2444 {
-
2445 JLOG(j.fatal())
-
2446 << "Invariant failed: violation of vault immutable data";
-
2447 result = false;
-
2448 }
-
2449 }
-
2450
-
2451 if (!updatedShares)
-
2452 {
-
2453 JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
-
2454 XRPL_ASSERT(
-
2455 enforce,
-
2456 "ripple::ValidVault::finalize : vault has shares invariant");
-
2457 return !enforce; // That's all we can do here
-
2458 }
-
2459
-
2460 if (updatedShares->sharesTotal == 0)
-
2461 {
-
2462 if (afterVault.assetsTotal != zero)
-
2463 {
-
2464 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
-
2465 "vault must have no assets outstanding";
-
2466 result = false;
-
2467 }
-
2468 if (afterVault.assetsAvailable != zero)
-
2469 {
-
2470 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
-
2471 "vault must have no assets available";
-
2472 result = false;
-
2473 }
-
2474 }
-
2475 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
-
2476 {
-
2477 JLOG(j.fatal()) //
-
2478 << "Invariant failed: updated shares must not exceed maximum "
-
2479 << updatedShares->sharesMaximum;
-
2480 result = false;
-
2481 }
-
2482
-
2483 if (afterVault.assetsAvailable < zero)
-
2484 {
-
2485 JLOG(j.fatal())
-
2486 << "Invariant failed: assets available must be positive";
-
2487 result = false;
-
2488 }
-
2489
-
2490 if (afterVault.assetsAvailable > afterVault.assetsTotal)
-
2491 {
-
2492 JLOG(j.fatal()) << "Invariant failed: assets available must "
-
2493 "not be greater than assets outstanding";
-
2494 result = false;
-
2495 }
-
2496 else if (
-
2497 afterVault.lossUnrealized >
-
2498 afterVault.assetsTotal - afterVault.assetsAvailable)
-
2499 {
-
2500 JLOG(j.fatal()) //
-
2501 << "Invariant failed: loss unrealized must not exceed "
-
2502 "the difference between assets outstanding and available";
-
2503 result = false;
-
2504 }
-
2505
-
2506 if (afterVault.assetsTotal < zero)
-
2507 {
-
2508 JLOG(j.fatal())
-
2509 << "Invariant failed: assets outstanding must be positive";
-
2510 result = false;
-
2511 }
-
2512
-
2513 if (afterVault.assetsMaximum < zero)
-
2514 {
-
2515 JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
-
2516 result = false;
-
2517 }
-
2518
-
2519 // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when
-
2520 // enforcing invariants on transaction types other than ttVAULT_CREATE
-
2521 if (beforeVault_.empty() && txnType != ttVAULT_CREATE)
-
2522 {
-
2523 JLOG(j.fatal()) << //
-
2524 "Invariant failed: vault created by a wrong transaction type";
-
2525 XRPL_ASSERT(
-
2526 enforce, "ripple::ValidVault::finalize : vault creation invariant");
-
2527 return !enforce; // That's all we can do here
-
2528 }
-
2529
-
2530 if (!beforeVault_.empty() &&
-
2531 afterVault.lossUnrealized != beforeVault_[0].lossUnrealized)
-
2532 {
-
2533 JLOG(j.fatal()) << //
-
2534 "Invariant failed: vault transaction must not change loss "
-
2535 "unrealized";
-
2536 result = false;
-
2537 }
+
2283
+
2284bool
+
+ +
2286 STTx const& tx,
+
2287 TER const ret,
+
2288 XRPAmount const fee,
+
2289 ReadView const& view,
+
2290 beast::Journal const& j)
+
2291{
+
2292 bool const enforce = view.rules().enabled(featureSingleAssetVault);
+
2293
+
2294 if (!isTesSuccess(ret))
+
2295 return true; // Do not perform checks
+
2296
+
2297 if (afterVault_.empty() && beforeVault_.empty())
+
2298 {
+ +
2300 {
+
2301 JLOG(j.fatal()) << //
+
2302 "Invariant failed: vault operation succeeded without modifying "
+
2303 "a vault";
+
2304 XRPL_ASSERT(
+
2305 enforce, "ripple::ValidVault::finalize : vault noop invariant");
+
2306 return !enforce;
+
2307 }
+
2308
+
2309 return true; // Not a vault operation
+
2310 }
+
2311 else if (!hasPrivilege(tx, mustModifyVault)) // TODO: mayModifyVault
+
2312 {
+
2313 JLOG(j.fatal()) << //
+
2314 "Invariant failed: vault updated by a wrong transaction type";
+
2315 XRPL_ASSERT(
+
2316 enforce,
+
2317 "ripple::ValidVault::finalize : illegal vault transaction "
+
2318 "invariant");
+
2319 return !enforce; // Also not a vault operation
+
2320 }
+
2321
+
2322 if (beforeVault_.size() > 1 || afterVault_.size() > 1)
+
2323 {
+
2324 JLOG(j.fatal()) << //
+
2325 "Invariant failed: vault operation updated more than single vault";
+
2326 XRPL_ASSERT(
+
2327 enforce, "ripple::ValidVault::finalize : single vault invariant");
+
2328 return !enforce; // That's all we can do here
+
2329 }
+
2330
+
2331 auto const txnType = tx.getTxnType();
+
2332
+
2333 // We do special handling for ttVAULT_DELETE first, because it's the only
+
2334 // vault-modifying transaction without an "after" state of the vault
+
2335 if (afterVault_.empty())
+
2336 {
+
2337 if (txnType != ttVAULT_DELETE)
+
2338 {
+
2339 JLOG(j.fatal()) << //
+
2340 "Invariant failed: vault deleted by a wrong transaction type";
+
2341 XRPL_ASSERT(
+
2342 enforce,
+
2343 "ripple::ValidVault::finalize : illegal vault deletion "
+
2344 "invariant");
+
2345 return !enforce; // That's all we can do here
+
2346 }
+
2347
+
2348 // Note, if afterVault_ is empty then we know that beforeVault_ is not
+
2349 // empty, as enforced at the top of this function
+
2350 auto const& beforeVault = beforeVault_[0];
+
2351
+
2352 // At this moment we only know a vault is being deleted and there
+
2353 // might be some MPTokenIssuance objects which are deleted in the
+
2354 // same transaction. Find the one matching this vault.
+
2355 auto const deletedShares = [&]() -> std::optional<Shares> {
+
2356 for (auto const& e : beforeMPTs_)
+
2357 {
+
2358 if (e.share.getMptID() == beforeVault.shareMPTID)
+
2359 return std::move(e);
+
2360 }
+
2361 return std::nullopt;
+
2362 }();
+
2363
+
2364 if (!deletedShares)
+
2365 {
+
2366 JLOG(j.fatal()) << "Invariant failed: deleted vault must also "
+
2367 "delete shares";
+
2368 XRPL_ASSERT(
+
2369 enforce,
+
2370 "ripple::ValidVault::finalize : shares deletion invariant");
+
2371 return !enforce; // That's all we can do here
+
2372 }
+
2373
+
2374 bool result = true;
+
2375 if (deletedShares->sharesTotal != 0)
+
2376 {
+
2377 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
+
2378 "shares outstanding";
+
2379 result = false;
+
2380 }
+
2381 if (beforeVault.assetsTotal != zero)
+
2382 {
+
2383 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
+
2384 "assets outstanding";
+
2385 result = false;
+
2386 }
+
2387 if (beforeVault.assetsAvailable != zero)
+
2388 {
+
2389 JLOG(j.fatal()) << "Invariant failed: deleted vault must have no "
+
2390 "assets available";
+
2391 result = false;
+
2392 }
+
2393
+
2394 return result;
+
2395 }
+
2396 else if (txnType == ttVAULT_DELETE)
+
2397 {
+
2398 JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without "
+
2399 "deleting a vault";
+
2400 XRPL_ASSERT(
+
2401 enforce, "ripple::ValidVault::finalize : vault deletion invariant");
+
2402 return !enforce; // That's all we can do here
+
2403 }
+
2404
+
2405 // Note, `afterVault_.empty()` is handled above
+
2406 auto const& afterVault = afterVault_[0];
+
2407 XRPL_ASSERT(
+
2408 beforeVault_.empty() || beforeVault_[0].key == afterVault.key,
+
2409 "ripple::ValidVault::finalize : single vault operation");
+
2410
+
2411 auto const updatedShares = [&]() -> std::optional<Shares> {
+
2412 // At this moment we only know that a vault is being updated and there
+
2413 // might be some MPTokenIssuance objects which are also updated in the
+
2414 // same transaction. Find the one matching the shares to this vault.
+
2415 // Note, we expect updatedMPTs collection to be extremely small. For
+
2416 // such collections linear search is faster than lookup.
+
2417 for (auto const& e : afterMPTs_)
+
2418 {
+
2419 if (e.share.getMptID() == afterVault.shareMPTID)
+
2420 return e;
+
2421 }
+
2422
+
2423 auto const sleShares =
+
2424 view.read(keylet::mptIssuance(afterVault.shareMPTID));
+
2425
+
2426 return sleShares ? std::optional<Shares>(Shares::make(*sleShares))
+
2427 : std::nullopt;
+
2428 }();
+
2429
+
2430 bool result = true;
+
2431
+
2432 // Universal transaction checks
+
2433 if (!beforeVault_.empty())
+
2434 {
+
2435 auto const& beforeVault = beforeVault_[0];
+
2436 if (afterVault.asset != beforeVault.asset ||
+
2437 afterVault.pseudoId != beforeVault.pseudoId ||
+
2438 afterVault.shareMPTID != beforeVault.shareMPTID)
+
2439 {
+
2440 JLOG(j.fatal())
+
2441 << "Invariant failed: violation of vault immutable data";
+
2442 result = false;
+
2443 }
+
2444 }
+
2445
+
2446 if (!updatedShares)
+
2447 {
+
2448 JLOG(j.fatal()) << "Invariant failed: updated vault must have shares";
+
2449 XRPL_ASSERT(
+
2450 enforce,
+
2451 "ripple::ValidVault::finalize : vault has shares invariant");
+
2452 return !enforce; // That's all we can do here
+
2453 }
+
2454
+
2455 if (updatedShares->sharesTotal == 0)
+
2456 {
+
2457 if (afterVault.assetsTotal != zero)
+
2458 {
+
2459 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
+
2460 "vault must have no assets outstanding";
+
2461 result = false;
+
2462 }
+
2463 if (afterVault.assetsAvailable != zero)
+
2464 {
+
2465 JLOG(j.fatal()) << "Invariant failed: updated zero sized "
+
2466 "vault must have no assets available";
+
2467 result = false;
+
2468 }
+
2469 }
+
2470 else if (updatedShares->sharesTotal > updatedShares->sharesMaximum)
+
2471 {
+
2472 JLOG(j.fatal()) //
+
2473 << "Invariant failed: updated shares must not exceed maximum "
+
2474 << updatedShares->sharesMaximum;
+
2475 result = false;
+
2476 }
+
2477
+
2478 if (afterVault.assetsAvailable < zero)
+
2479 {
+
2480 JLOG(j.fatal())
+
2481 << "Invariant failed: assets available must be positive";
+
2482 result = false;
+
2483 }
+
2484
+
2485 if (afterVault.assetsAvailable > afterVault.assetsTotal)
+
2486 {
+
2487 JLOG(j.fatal()) << "Invariant failed: assets available must "
+
2488 "not be greater than assets outstanding";
+
2489 result = false;
+
2490 }
+
2491 else if (
+
2492 afterVault.lossUnrealized >
+
2493 afterVault.assetsTotal - afterVault.assetsAvailable)
+
2494 {
+
2495 JLOG(j.fatal()) //
+
2496 << "Invariant failed: loss unrealized must not exceed "
+
2497 "the difference between assets outstanding and available";
+
2498 result = false;
+
2499 }
+
2500
+
2501 if (afterVault.assetsTotal < zero)
+
2502 {
+
2503 JLOG(j.fatal())
+
2504 << "Invariant failed: assets outstanding must be positive";
+
2505 result = false;
+
2506 }
+
2507
+
2508 if (afterVault.assetsMaximum < zero)
+
2509 {
+
2510 JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive";
+
2511 result = false;
+
2512 }
+
2513
+
2514 // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when
+
2515 // enforcing invariants on transaction types other than ttVAULT_CREATE
+
2516 if (beforeVault_.empty() && txnType != ttVAULT_CREATE)
+
2517 {
+
2518 JLOG(j.fatal()) << //
+
2519 "Invariant failed: vault created by a wrong transaction type";
+
2520 XRPL_ASSERT(
+
2521 enforce, "ripple::ValidVault::finalize : vault creation invariant");
+
2522 return !enforce; // That's all we can do here
+
2523 }
+
2524
+
2525 if (!beforeVault_.empty() &&
+
2526 afterVault.lossUnrealized != beforeVault_[0].lossUnrealized)
+
2527 {
+
2528 JLOG(j.fatal()) << //
+
2529 "Invariant failed: vault transaction must not change loss "
+
2530 "unrealized";
+
2531 result = false;
+
2532 }
+
2533
+
2534 auto const beforeShares = [&]() -> std::optional<Shares> {
+
2535 if (beforeVault_.empty())
+
2536 return std::nullopt;
+
2537 auto const& beforeVault = beforeVault_[0];
2538
-
2539 auto const beforeShares = [&]() -> std::optional<Shares> {
-
2540 if (beforeVault_.empty())
-
2541 return std::nullopt;
-
2542 auto const& beforeVault = beforeVault_[0];
-
2543
-
2544 for (auto const& e : beforeMPTs_)
-
2545 {
-
2546 if (e.share.getMptID() == beforeVault.shareMPTID)
-
2547 return std::move(e);
-
2548 }
-
2549 return std::nullopt;
-
2550 }();
-
2551
-
2552 if (!beforeShares &&
-
2553 (tx.getTxnType() == ttVAULT_DEPOSIT || //
-
2554 tx.getTxnType() == ttVAULT_WITHDRAW || //
-
2555 tx.getTxnType() == ttVAULT_CLAWBACK))
-
2556 {
-
2557 JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
-
2558 "without updating shares";
-
2559 XRPL_ASSERT(
-
2560 enforce, "ripple::ValidVault::finalize : shares noop invariant");
-
2561 return !enforce; // That's all we can do here
-
2562 }
-
2563
-
2564 auto const& vaultAsset = afterVault.asset;
-
2565 auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
-
2566 auto const get = //
-
2567 [&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
-
2568 if (it == deltas_.end())
-
2569 return std::nullopt;
-
2570
-
2571 return it->second * sign;
-
2572 };
-
2573
-
2574 return std::visit(
-
2575 [&]<typename TIss>(TIss const& issue) {
-
2576 if constexpr (std::is_same_v<TIss, Issue>)
-
2577 {
-
2578 if (isXRP(issue))
-
2579 return get(deltas_.find(keylet::account(id).key));
-
2580 return get(
-
2581 deltas_.find(keylet::line(id, issue).key),
-
2582 id > issue.getIssuer() ? -1 : 1);
+
2539 for (auto const& e : beforeMPTs_)
+
2540 {
+
2541 if (e.share.getMptID() == beforeVault.shareMPTID)
+
2542 return std::move(e);
+
2543 }
+
2544 return std::nullopt;
+
2545 }();
+
2546
+
2547 if (!beforeShares &&
+
2548 (tx.getTxnType() == ttVAULT_DEPOSIT || //
+
2549 tx.getTxnType() == ttVAULT_WITHDRAW || //
+
2550 tx.getTxnType() == ttVAULT_CLAWBACK))
+
2551 {
+
2552 JLOG(j.fatal()) << "Invariant failed: vault operation succeeded "
+
2553 "without updating shares";
+
2554 XRPL_ASSERT(
+
2555 enforce, "ripple::ValidVault::finalize : shares noop invariant");
+
2556 return !enforce; // That's all we can do here
+
2557 }
+
2558
+
2559 auto const& vaultAsset = afterVault.asset;
+
2560 auto const deltaAssets = [&](AccountID const& id) -> std::optional<Number> {
+
2561 auto const get = //
+
2562 [&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
+
2563 if (it == deltas_.end())
+
2564 return std::nullopt;
+
2565
+
2566 return it->second * sign;
+
2567 };
+
2568
+
2569 return std::visit(
+
2570 [&]<typename TIss>(TIss const& issue) {
+
2571 if constexpr (std::is_same_v<TIss, Issue>)
+
2572 {
+
2573 if (isXRP(issue))
+
2574 return get(deltas_.find(keylet::account(id).key));
+
2575 return get(
+
2576 deltas_.find(keylet::line(id, issue).key),
+
2577 id > issue.getIssuer() ? -1 : 1);
+
2578 }
+
2579 else if constexpr (std::is_same_v<TIss, MPTIssue>)
+
2580 {
+
2581 return get(deltas_.find(
+
2582 keylet::mptoken(issue.getMptID(), id).key));
2583 }
-
2584 else if constexpr (std::is_same_v<TIss, MPTIssue>)
-
2585 {
-
2586 return get(deltas_.find(
-
2587 keylet::mptoken(issue.getMptID(), id).key));
-
2588 }
-
2589 },
-
2590 vaultAsset.value());
-
2591 };
-
2592 auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
-
2593 auto ret = deltaAssets(tx[sfAccount]);
-
2594 // Nothing returned or not XRP transaction
-
2595 if (!ret.has_value() || !vaultAsset.native())
+
2584 },
+
2585 vaultAsset.value());
+
2586 };
+
2587 auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
+
2588 auto ret = deltaAssets(tx[sfAccount]);
+
2589 // Nothing returned or not XRP transaction
+
2590 if (!ret.has_value() || !vaultAsset.native())
+
2591 return ret;
+
2592
+
2593 // Delegated transaction; no need to compensate for fees
+
2594 if (auto const delegate = tx[~sfDelegate];
+
2595 delegate.has_value() && *delegate != tx[sfAccount])
2596 return ret;
2597
-
2598 // Delegated transaction; no need to compensate for fees
-
2599 if (auto const delegate = tx[~sfDelegate];
-
2600 delegate.has_value() && *delegate != tx[sfAccount])
-
2601 return ret;
-
2602
-
2603 *ret += fee.drops();
-
2604 if (*ret == zero)
-
2605 return std::nullopt;
-
2606
-
2607 return ret;
-
2608 };
-
2609 auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
-
2610 auto const it = [&]() {
-
2611 if (id == afterVault.pseudoId)
-
2612 return deltas_.find(
-
2613 keylet::mptIssuance(afterVault.shareMPTID).key);
-
2614 return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
-
2615 }();
-
2616
-
2617 return it != deltas_.end() ? std::optional<Number>(it->second)
-
2618 : std::nullopt;
-
2619 };
-
2620
-
2621 // Technically this does not need to be a lambda, but it's more
-
2622 // convenient thanks to early "return false"; the not-so-nice
-
2623 // alternatives are several layers of nested if/else or more complex
-
2624 // (i.e. brittle) if statements.
-
2625 result &= [&]() {
-
2626 switch (txnType)
-
2627 {
-
2628 case ttVAULT_CREATE: {
-
2629 bool result = true;
-
2630
-
2631 if (!beforeVault_.empty())
-
2632 {
-
2633 JLOG(j.fatal()) //
-
2634 << "Invariant failed: create operation must not have "
-
2635 "updated a vault";
-
2636 result = false;
-
2637 }
-
2638
-
2639 if (afterVault.assetsAvailable != zero ||
-
2640 afterVault.assetsTotal != zero ||
-
2641 afterVault.lossUnrealized != zero ||
-
2642 updatedShares->sharesTotal != 0)
-
2643 {
-
2644 JLOG(j.fatal()) //
-
2645 << "Invariant failed: created vault must be empty";
-
2646 result = false;
-
2647 }
-
2648
-
2649 if (afterVault.pseudoId != updatedShares->share.getIssuer())
-
2650 {
-
2651 JLOG(j.fatal()) //
-
2652 << "Invariant failed: shares issuer and vault "
-
2653 "pseudo-account must be the same";
-
2654 result = false;
-
2655 }
-
2656
-
2657 auto const sleSharesIssuer = view.read(
-
2658 keylet::account(updatedShares->share.getIssuer()));
-
2659 if (!sleSharesIssuer)
-
2660 {
-
2661 JLOG(j.fatal()) //
-
2662 << "Invariant failed: shares issuer must exist";
-
2663 return false;
-
2664 }
-
2665
-
2666 if (!isPseudoAccount(sleSharesIssuer))
-
2667 {
-
2668 JLOG(j.fatal()) //
-
2669 << "Invariant failed: shares issuer must be a "
-
2670 "pseudo-account";
-
2671 result = false;
-
2672 }
-
2673
-
2674 if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
-
2675 !vaultId || *vaultId != afterVault.key)
-
2676 {
-
2677 JLOG(j.fatal()) //
-
2678 << "Invariant failed: shares issuer pseudo-account "
-
2679 "must point back to the vault";
-
2680 result = false;
-
2681 }
+
2598 *ret += fee.drops();
+
2599 if (*ret == zero)
+
2600 return std::nullopt;
+
2601
+
2602 return ret;
+
2603 };
+
2604 auto const deltaShares = [&](AccountID const& id) -> std::optional<Number> {
+
2605 auto const it = [&]() {
+
2606 if (id == afterVault.pseudoId)
+
2607 return deltas_.find(
+
2608 keylet::mptIssuance(afterVault.shareMPTID).key);
+
2609 return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key);
+
2610 }();
+
2611
+
2612 return it != deltas_.end() ? std::optional<Number>(it->second)
+
2613 : std::nullopt;
+
2614 };
+
2615
+
2616 // Technically this does not need to be a lambda, but it's more
+
2617 // convenient thanks to early "return false"; the not-so-nice
+
2618 // alternatives are several layers of nested if/else or more complex
+
2619 // (i.e. brittle) if statements.
+
2620 result &= [&]() {
+
2621 switch (txnType)
+
2622 {
+
2623 case ttVAULT_CREATE: {
+
2624 bool result = true;
+
2625
+
2626 if (!beforeVault_.empty())
+
2627 {
+
2628 JLOG(j.fatal()) //
+
2629 << "Invariant failed: create operation must not have "
+
2630 "updated a vault";
+
2631 result = false;
+
2632 }
+
2633
+
2634 if (afterVault.assetsAvailable != zero ||
+
2635 afterVault.assetsTotal != zero ||
+
2636 afterVault.lossUnrealized != zero ||
+
2637 updatedShares->sharesTotal != 0)
+
2638 {
+
2639 JLOG(j.fatal()) //
+
2640 << "Invariant failed: created vault must be empty";
+
2641 result = false;
+
2642 }
+
2643
+
2644 if (afterVault.pseudoId != updatedShares->share.getIssuer())
+
2645 {
+
2646 JLOG(j.fatal()) //
+
2647 << "Invariant failed: shares issuer and vault "
+
2648 "pseudo-account must be the same";
+
2649 result = false;
+
2650 }
+
2651
+
2652 auto const sleSharesIssuer = view.read(
+
2653 keylet::account(updatedShares->share.getIssuer()));
+
2654 if (!sleSharesIssuer)
+
2655 {
+
2656 JLOG(j.fatal()) //
+
2657 << "Invariant failed: shares issuer must exist";
+
2658 return false;
+
2659 }
+
2660
+
2661 if (!isPseudoAccount(sleSharesIssuer))
+
2662 {
+
2663 JLOG(j.fatal()) //
+
2664 << "Invariant failed: shares issuer must be a "
+
2665 "pseudo-account";
+
2666 result = false;
+
2667 }
+
2668
+
2669 if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
+
2670 !vaultId || *vaultId != afterVault.key)
+
2671 {
+
2672 JLOG(j.fatal()) //
+
2673 << "Invariant failed: shares issuer pseudo-account "
+
2674 "must point back to the vault";
+
2675 result = false;
+
2676 }
+
2677
+
2678 return result;
+
2679 }
+
2680 case ttVAULT_SET: {
+
2681 bool result = true;
2682
-
2683 return result;
-
2684 }
-
2685 case ttVAULT_SET: {
-
2686 bool result = true;
+
2683 XRPL_ASSERT(
+
2684 !beforeVault_.empty(),
+
2685 "ripple::ValidVault::finalize : set updated a vault");
+
2686 auto const& beforeVault = beforeVault_[0];
2687
-
2688 XRPL_ASSERT(
-
2689 !beforeVault_.empty(),
-
2690 "ripple::ValidVault::finalize : set updated a vault");
-
2691 auto const& beforeVault = beforeVault_[0];
-
2692
-
2693 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
-
2694 if (vaultDeltaAssets)
-
2695 {
-
2696 JLOG(j.fatal()) << //
-
2697 "Invariant failed: set must not change vault balance";
-
2698 result = false;
-
2699 }
-
2700
-
2701 if (beforeVault.assetsTotal != afterVault.assetsTotal)
-
2702 {
-
2703 JLOG(j.fatal()) << //
-
2704 "Invariant failed: set must not change assets "
-
2705 "outstanding";
-
2706 result = false;
-
2707 }
-
2708
-
2709 if (afterVault.assetsMaximum > zero &&
-
2710 afterVault.assetsTotal > afterVault.assetsMaximum)
-
2711 {
-
2712 JLOG(j.fatal()) << //
-
2713 "Invariant failed: set assets outstanding must not "
-
2714 "exceed assets maximum";
-
2715 result = false;
-
2716 }
-
2717
-
2718 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
-
2719 {
-
2720 JLOG(j.fatal()) << //
-
2721 "Invariant failed: set must not change assets "
-
2722 "available";
-
2723 result = false;
-
2724 }
-
2725
-
2726 if (beforeShares && updatedShares &&
-
2727 beforeShares->sharesTotal != updatedShares->sharesTotal)
-
2728 {
-
2729 JLOG(j.fatal()) << //
-
2730 "Invariant failed: set must not change shares "
-
2731 "outstanding";
-
2732 result = false;
-
2733 }
+
2688 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
+
2689 if (vaultDeltaAssets)
+
2690 {
+
2691 JLOG(j.fatal()) << //
+
2692 "Invariant failed: set must not change vault balance";
+
2693 result = false;
+
2694 }
+
2695
+
2696 if (beforeVault.assetsTotal != afterVault.assetsTotal)
+
2697 {
+
2698 JLOG(j.fatal()) << //
+
2699 "Invariant failed: set must not change assets "
+
2700 "outstanding";
+
2701 result = false;
+
2702 }
+
2703
+
2704 if (afterVault.assetsMaximum > zero &&
+
2705 afterVault.assetsTotal > afterVault.assetsMaximum)
+
2706 {
+
2707 JLOG(j.fatal()) << //
+
2708 "Invariant failed: set assets outstanding must not "
+
2709 "exceed assets maximum";
+
2710 result = false;
+
2711 }
+
2712
+
2713 if (beforeVault.assetsAvailable != afterVault.assetsAvailable)
+
2714 {
+
2715 JLOG(j.fatal()) << //
+
2716 "Invariant failed: set must not change assets "
+
2717 "available";
+
2718 result = false;
+
2719 }
+
2720
+
2721 if (beforeShares && updatedShares &&
+
2722 beforeShares->sharesTotal != updatedShares->sharesTotal)
+
2723 {
+
2724 JLOG(j.fatal()) << //
+
2725 "Invariant failed: set must not change shares "
+
2726 "outstanding";
+
2727 result = false;
+
2728 }
+
2729
+
2730 return result;
+
2731 }
+
2732 case ttVAULT_DEPOSIT: {
+
2733 bool result = true;
2734
-
2735 return result;
-
2736 }
-
2737 case ttVAULT_DEPOSIT: {
-
2738 bool result = true;
+
2735 XRPL_ASSERT(
+
2736 !beforeVault_.empty(),
+
2737 "ripple::ValidVault::finalize : deposit updated a vault");
+
2738 auto const& beforeVault = beforeVault_[0];
2739
-
2740 XRPL_ASSERT(
-
2741 !beforeVault_.empty(),
-
2742 "ripple::ValidVault::finalize : deposit updated a vault");
-
2743 auto const& beforeVault = beforeVault_[0];
-
2744
-
2745 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
-
2746
-
2747 if (!vaultDeltaAssets)
-
2748 {
-
2749 JLOG(j.fatal()) << //
-
2750 "Invariant failed: deposit must change vault balance";
-
2751 return false; // That's all we can do
-
2752 }
-
2753
-
2754 if (*vaultDeltaAssets > tx[sfAmount])
-
2755 {
-
2756 JLOG(j.fatal()) << //
-
2757 "Invariant failed: deposit must not change vault "
-
2758 "balance by more than deposited amount";
-
2759 result = false;
-
2760 }
-
2761
-
2762 if (*vaultDeltaAssets <= zero)
-
2763 {
-
2764 JLOG(j.fatal()) << //
-
2765 "Invariant failed: deposit must increase vault balance";
-
2766 result = false;
-
2767 }
-
2768
-
2769 // Any payments (including deposits) made by the issuer
-
2770 // do not change their balance, but create funds instead.
-
2771 bool const issuerDeposit = [&]() -> bool {
-
2772 if (vaultAsset.native())
-
2773 return false;
-
2774 return tx[sfAccount] == vaultAsset.getIssuer();
-
2775 }();
-
2776
-
2777 if (!issuerDeposit)
-
2778 {
-
2779 auto const accountDeltaAssets = deltaAssetsTxAccount();
-
2780 if (!accountDeltaAssets)
-
2781 {
-
2782 JLOG(j.fatal()) << //
-
2783 "Invariant failed: deposit must change depositor "
-
2784 "balance";
-
2785 return false;
-
2786 }
-
2787
-
2788 if (*accountDeltaAssets >= zero)
-
2789 {
-
2790 JLOG(j.fatal()) << //
-
2791 "Invariant failed: deposit must decrease depositor "
-
2792 "balance";
-
2793 result = false;
-
2794 }
-
2795
-
2796 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
-
2797 {
-
2798 JLOG(j.fatal()) << //
-
2799 "Invariant failed: deposit must change vault and "
-
2800 "depositor balance by equal amount";
-
2801 result = false;
-
2802 }
-
2803 }
-
2804
-
2805 if (afterVault.assetsMaximum > zero &&
-
2806 afterVault.assetsTotal > afterVault.assetsMaximum)
-
2807 {
-
2808 JLOG(j.fatal()) << //
-
2809 "Invariant failed: deposit assets outstanding must not "
-
2810 "exceed assets maximum";
-
2811 result = false;
-
2812 }
-
2813
-
2814 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
-
2815 if (!accountDeltaShares)
-
2816 {
-
2817 JLOG(j.fatal()) << //
-
2818 "Invariant failed: deposit must change depositor "
-
2819 "shares";
-
2820 return false; // That's all we can do
-
2821 }
-
2822
-
2823 if (*accountDeltaShares <= zero)
-
2824 {
-
2825 JLOG(j.fatal()) << //
-
2826 "Invariant failed: deposit must increase depositor "
-
2827 "shares";
-
2828 result = false;
-
2829 }
-
2830
-
2831 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
-
2832 if (!vaultDeltaShares || *vaultDeltaShares == zero)
-
2833 {
-
2834 JLOG(j.fatal()) << //
-
2835 "Invariant failed: deposit must change vault shares";
-
2836 return false; // That's all we can do
-
2837 }
-
2838
-
2839 if (*vaultDeltaShares * -1 != *accountDeltaShares)
-
2840 {
-
2841 JLOG(j.fatal()) << //
-
2842 "Invariant failed: deposit must change depositor and "
-
2843 "vault shares by equal amount";
-
2844 result = false;
-
2845 }
-
2846
-
2847 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
-
2848 afterVault.assetsTotal)
-
2849 {
-
2850 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
-
2851 "outstanding must add up";
-
2852 result = false;
-
2853 }
-
2854 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
-
2855 afterVault.assetsAvailable)
-
2856 {
-
2857 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
-
2858 "available must add up";
-
2859 result = false;
-
2860 }
+
2740 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
+
2741
+
2742 if (!vaultDeltaAssets)
+
2743 {
+
2744 JLOG(j.fatal()) << //
+
2745 "Invariant failed: deposit must change vault balance";
+
2746 return false; // That's all we can do
+
2747 }
+
2748
+
2749 if (*vaultDeltaAssets > tx[sfAmount])
+
2750 {
+
2751 JLOG(j.fatal()) << //
+
2752 "Invariant failed: deposit must not change vault "
+
2753 "balance by more than deposited amount";
+
2754 result = false;
+
2755 }
+
2756
+
2757 if (*vaultDeltaAssets <= zero)
+
2758 {
+
2759 JLOG(j.fatal()) << //
+
2760 "Invariant failed: deposit must increase vault balance";
+
2761 result = false;
+
2762 }
+
2763
+
2764 // Any payments (including deposits) made by the issuer
+
2765 // do not change their balance, but create funds instead.
+
2766 bool const issuerDeposit = [&]() -> bool {
+
2767 if (vaultAsset.native())
+
2768 return false;
+
2769 return tx[sfAccount] == vaultAsset.getIssuer();
+
2770 }();
+
2771
+
2772 if (!issuerDeposit)
+
2773 {
+
2774 auto const accountDeltaAssets = deltaAssetsTxAccount();
+
2775 if (!accountDeltaAssets)
+
2776 {
+
2777 JLOG(j.fatal()) << //
+
2778 "Invariant failed: deposit must change depositor "
+
2779 "balance";
+
2780 return false;
+
2781 }
+
2782
+
2783 if (*accountDeltaAssets >= zero)
+
2784 {
+
2785 JLOG(j.fatal()) << //
+
2786 "Invariant failed: deposit must decrease depositor "
+
2787 "balance";
+
2788 result = false;
+
2789 }
+
2790
+
2791 if (*accountDeltaAssets * -1 != *vaultDeltaAssets)
+
2792 {
+
2793 JLOG(j.fatal()) << //
+
2794 "Invariant failed: deposit must change vault and "
+
2795 "depositor balance by equal amount";
+
2796 result = false;
+
2797 }
+
2798 }
+
2799
+
2800 if (afterVault.assetsMaximum > zero &&
+
2801 afterVault.assetsTotal > afterVault.assetsMaximum)
+
2802 {
+
2803 JLOG(j.fatal()) << //
+
2804 "Invariant failed: deposit assets outstanding must not "
+
2805 "exceed assets maximum";
+
2806 result = false;
+
2807 }
+
2808
+
2809 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
+
2810 if (!accountDeltaShares)
+
2811 {
+
2812 JLOG(j.fatal()) << //
+
2813 "Invariant failed: deposit must change depositor "
+
2814 "shares";
+
2815 return false; // That's all we can do
+
2816 }
+
2817
+
2818 if (*accountDeltaShares <= zero)
+
2819 {
+
2820 JLOG(j.fatal()) << //
+
2821 "Invariant failed: deposit must increase depositor "
+
2822 "shares";
+
2823 result = false;
+
2824 }
+
2825
+
2826 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
+
2827 if (!vaultDeltaShares || *vaultDeltaShares == zero)
+
2828 {
+
2829 JLOG(j.fatal()) << //
+
2830 "Invariant failed: deposit must change vault shares";
+
2831 return false; // That's all we can do
+
2832 }
+
2833
+
2834 if (*vaultDeltaShares * -1 != *accountDeltaShares)
+
2835 {
+
2836 JLOG(j.fatal()) << //
+
2837 "Invariant failed: deposit must change depositor and "
+
2838 "vault shares by equal amount";
+
2839 result = false;
+
2840 }
+
2841
+
2842 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
+
2843 afterVault.assetsTotal)
+
2844 {
+
2845 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
+
2846 "outstanding must add up";
+
2847 result = false;
+
2848 }
+
2849 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
+
2850 afterVault.assetsAvailable)
+
2851 {
+
2852 JLOG(j.fatal()) << "Invariant failed: deposit and assets "
+
2853 "available must add up";
+
2854 result = false;
+
2855 }
+
2856
+
2857 return result;
+
2858 }
+
2859 case ttVAULT_WITHDRAW: {
+
2860 bool result = true;
2861
-
2862 return result;
-
2863 }
-
2864 case ttVAULT_WITHDRAW: {
-
2865 bool result = true;
-
2866
-
2867 XRPL_ASSERT(
-
2868 !beforeVault_.empty(),
-
2869 "ripple::ValidVault::finalize : withdrawal updated a "
-
2870 "vault");
-
2871 auto const& beforeVault = beforeVault_[0];
-
2872
-
2873 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
-
2874
-
2875 if (!vaultDeltaAssets)
-
2876 {
-
2877 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
-
2878 "change vault balance";
-
2879 return false; // That's all we can do
-
2880 }
-
2881
-
2882 if (*vaultDeltaAssets >= zero)
-
2883 {
-
2884 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
-
2885 "decrease vault balance";
-
2886 result = false;
-
2887 }
-
2888
-
2889 // Any payments (including withdrawal) going to the issuer
-
2890 // do not change their balance, but destroy funds instead.
-
2891 bool const issuerWithdrawal = [&]() -> bool {
-
2892 if (vaultAsset.native())
-
2893 return false;
-
2894 auto const destination =
-
2895 tx[~sfDestination].value_or(tx[sfAccount]);
-
2896 return destination == vaultAsset.getIssuer();
-
2897 }();
-
2898
-
2899 if (!issuerWithdrawal)
-
2900 {
-
2901 auto const accountDeltaAssets = deltaAssetsTxAccount();
-
2902 auto const otherAccountDelta =
-
2903 [&]() -> std::optional<Number> {
-
2904 if (auto const destination = tx[~sfDestination];
-
2905 destination && *destination != tx[sfAccount])
-
2906 return deltaAssets(*destination);
-
2907 return std::nullopt;
-
2908 }();
-
2909
-
2910 if (accountDeltaAssets.has_value() ==
-
2911 otherAccountDelta.has_value())
-
2912 {
-
2913 JLOG(j.fatal()) << //
-
2914 "Invariant failed: withdrawal must change one "
-
2915 "destination balance";
-
2916 return false;
-
2917 }
-
2918
-
2919 auto const destinationDelta = //
-
2920 accountDeltaAssets ? *accountDeltaAssets
-
2921 : *otherAccountDelta;
-
2922
-
2923 if (destinationDelta <= zero)
-
2924 {
-
2925 JLOG(j.fatal()) << //
-
2926 "Invariant failed: withdrawal must increase "
-
2927 "destination balance";
-
2928 result = false;
-
2929 }
-
2930
-
2931 if (*vaultDeltaAssets * -1 != destinationDelta)
-
2932 {
-
2933 JLOG(j.fatal()) << //
-
2934 "Invariant failed: withdrawal must change vault "
-
2935 "and destination balance by equal amount";
-
2936 result = false;
-
2937 }
-
2938 }
-
2939
-
2940 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
-
2941 if (!accountDeltaShares)
-
2942 {
-
2943 JLOG(j.fatal()) << //
-
2944 "Invariant failed: withdrawal must change depositor "
-
2945 "shares";
-
2946 return false;
-
2947 }
-
2948
-
2949 if (*accountDeltaShares >= zero)
-
2950 {
-
2951 JLOG(j.fatal()) << //
-
2952 "Invariant failed: withdrawal must decrease depositor "
-
2953 "shares";
-
2954 result = false;
-
2955 }
-
2956
-
2957 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
-
2958 if (!vaultDeltaShares || *vaultDeltaShares == zero)
-
2959 {
-
2960 JLOG(j.fatal()) << //
-
2961 "Invariant failed: withdrawal must change vault shares";
-
2962 return false; // That's all we can do
-
2963 }
-
2964
-
2965 if (*vaultDeltaShares * -1 != *accountDeltaShares)
-
2966 {
-
2967 JLOG(j.fatal()) << //
-
2968 "Invariant failed: withdrawal must change depositor "
-
2969 "and vault shares by equal amount";
-
2970 result = false;
-
2971 }
-
2972
-
2973 // Note, vaultBalance is negative (see check above)
-
2974 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
-
2975 afterVault.assetsTotal)
-
2976 {
-
2977 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
-
2978 "assets outstanding must add up";
-
2979 result = false;
-
2980 }
-
2981
-
2982 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
-
2983 afterVault.assetsAvailable)
-
2984 {
-
2985 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
-
2986 "assets available must add up";
-
2987 result = false;
-
2988 }
+
2862 XRPL_ASSERT(
+
2863 !beforeVault_.empty(),
+
2864 "ripple::ValidVault::finalize : withdrawal updated a "
+
2865 "vault");
+
2866 auto const& beforeVault = beforeVault_[0];
+
2867
+
2868 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
+
2869
+
2870 if (!vaultDeltaAssets)
+
2871 {
+
2872 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
+
2873 "change vault balance";
+
2874 return false; // That's all we can do
+
2875 }
+
2876
+
2877 if (*vaultDeltaAssets >= zero)
+
2878 {
+
2879 JLOG(j.fatal()) << "Invariant failed: withdrawal must "
+
2880 "decrease vault balance";
+
2881 result = false;
+
2882 }
+
2883
+
2884 // Any payments (including withdrawal) going to the issuer
+
2885 // do not change their balance, but destroy funds instead.
+
2886 bool const issuerWithdrawal = [&]() -> bool {
+
2887 if (vaultAsset.native())
+
2888 return false;
+
2889 auto const destination =
+
2890 tx[~sfDestination].value_or(tx[sfAccount]);
+
2891 return destination == vaultAsset.getIssuer();
+
2892 }();
+
2893
+
2894 if (!issuerWithdrawal)
+
2895 {
+
2896 auto const accountDeltaAssets = deltaAssetsTxAccount();
+
2897 auto const otherAccountDelta =
+
2898 [&]() -> std::optional<Number> {
+
2899 if (auto const destination = tx[~sfDestination];
+
2900 destination && *destination != tx[sfAccount])
+
2901 return deltaAssets(*destination);
+
2902 return std::nullopt;
+
2903 }();
+
2904
+
2905 if (accountDeltaAssets.has_value() ==
+
2906 otherAccountDelta.has_value())
+
2907 {
+
2908 JLOG(j.fatal()) << //
+
2909 "Invariant failed: withdrawal must change one "
+
2910 "destination balance";
+
2911 return false;
+
2912 }
+
2913
+
2914 auto const destinationDelta = //
+
2915 accountDeltaAssets ? *accountDeltaAssets
+
2916 : *otherAccountDelta;
+
2917
+
2918 if (destinationDelta <= zero)
+
2919 {
+
2920 JLOG(j.fatal()) << //
+
2921 "Invariant failed: withdrawal must increase "
+
2922 "destination balance";
+
2923 result = false;
+
2924 }
+
2925
+
2926 if (*vaultDeltaAssets * -1 != destinationDelta)
+
2927 {
+
2928 JLOG(j.fatal()) << //
+
2929 "Invariant failed: withdrawal must change vault "
+
2930 "and destination balance by equal amount";
+
2931 result = false;
+
2932 }
+
2933 }
+
2934
+
2935 auto const accountDeltaShares = deltaShares(tx[sfAccount]);
+
2936 if (!accountDeltaShares)
+
2937 {
+
2938 JLOG(j.fatal()) << //
+
2939 "Invariant failed: withdrawal must change depositor "
+
2940 "shares";
+
2941 return false;
+
2942 }
+
2943
+
2944 if (*accountDeltaShares >= zero)
+
2945 {
+
2946 JLOG(j.fatal()) << //
+
2947 "Invariant failed: withdrawal must decrease depositor "
+
2948 "shares";
+
2949 result = false;
+
2950 }
+
2951
+
2952 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
+
2953 if (!vaultDeltaShares || *vaultDeltaShares == zero)
+
2954 {
+
2955 JLOG(j.fatal()) << //
+
2956 "Invariant failed: withdrawal must change vault shares";
+
2957 return false; // That's all we can do
+
2958 }
+
2959
+
2960 if (*vaultDeltaShares * -1 != *accountDeltaShares)
+
2961 {
+
2962 JLOG(j.fatal()) << //
+
2963 "Invariant failed: withdrawal must change depositor "
+
2964 "and vault shares by equal amount";
+
2965 result = false;
+
2966 }
+
2967
+
2968 // Note, vaultBalance is negative (see check above)
+
2969 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
+
2970 afterVault.assetsTotal)
+
2971 {
+
2972 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
+
2973 "assets outstanding must add up";
+
2974 result = false;
+
2975 }
+
2976
+
2977 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
+
2978 afterVault.assetsAvailable)
+
2979 {
+
2980 JLOG(j.fatal()) << "Invariant failed: withdrawal and "
+
2981 "assets available must add up";
+
2982 result = false;
+
2983 }
+
2984
+
2985 return result;
+
2986 }
+
2987 case ttVAULT_CLAWBACK: {
+
2988 bool result = true;
2989
-
2990 return result;
-
2991 }
-
2992 case ttVAULT_CLAWBACK: {
-
2993 bool result = true;
+
2990 XRPL_ASSERT(
+
2991 !beforeVault_.empty(),
+
2992 "ripple::ValidVault::finalize : clawback updated a vault");
+
2993 auto const& beforeVault = beforeVault_[0];
2994
-
2995 XRPL_ASSERT(
-
2996 !beforeVault_.empty(),
-
2997 "ripple::ValidVault::finalize : clawback updated a vault");
-
2998 auto const& beforeVault = beforeVault_[0];
-
2999
-
3000 if (vaultAsset.native() ||
-
3001 vaultAsset.getIssuer() != tx[sfAccount])
-
3002 {
-
3003 JLOG(j.fatal()) << //
-
3004 "Invariant failed: clawback may only be performed by "
-
3005 "the asset issuer";
-
3006 return false; // That's all we can do
-
3007 }
-
3008
-
3009 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
-
3010
-
3011 if (!vaultDeltaAssets)
-
3012 {
-
3013 JLOG(j.fatal()) << //
-
3014 "Invariant failed: clawback must change vault balance";
-
3015 return false; // That's all we can do
-
3016 }
-
3017
-
3018 if (*vaultDeltaAssets >= zero)
-
3019 {
-
3020 JLOG(j.fatal()) << //
-
3021 "Invariant failed: clawback must decrease vault "
-
3022 "balance";
-
3023 result = false;
-
3024 }
-
3025
-
3026 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
-
3027 if (!accountDeltaShares)
-
3028 {
-
3029 JLOG(j.fatal()) << //
-
3030 "Invariant failed: clawback must change holder shares";
-
3031 return false; // That's all we can do
-
3032 }
-
3033
-
3034 if (*accountDeltaShares >= zero)
-
3035 {
-
3036 JLOG(j.fatal()) << //
-
3037 "Invariant failed: clawback must decrease holder "
-
3038 "shares";
-
3039 result = false;
-
3040 }
-
3041
-
3042 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
-
3043 if (!vaultDeltaShares || *vaultDeltaShares == zero)
-
3044 {
-
3045 JLOG(j.fatal()) << //
-
3046 "Invariant failed: clawback must change vault shares";
-
3047 return false; // That's all we can do
-
3048 }
-
3049
-
3050 if (*vaultDeltaShares * -1 != *accountDeltaShares)
-
3051 {
-
3052 JLOG(j.fatal()) << //
-
3053 "Invariant failed: clawback must change holder and "
-
3054 "vault shares by equal amount";
-
3055 result = false;
-
3056 }
-
3057
-
3058 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
-
3059 afterVault.assetsTotal)
-
3060 {
-
3061 JLOG(j.fatal()) << //
-
3062 "Invariant failed: clawback and assets outstanding "
-
3063 "must add up";
-
3064 result = false;
-
3065 }
-
3066
-
3067 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
-
3068 afterVault.assetsAvailable)
-
3069 {
-
3070 JLOG(j.fatal()) << //
-
3071 "Invariant failed: clawback and assets available must "
-
3072 "add up";
-
3073 result = false;
-
3074 }
-
3075
-
3076 return result;
-
3077 }
-
3078
-
3079 default:
-
3080 // LCOV_EXCL_START
-
3081 UNREACHABLE(
-
3082 "ripple::ValidVault::finalize : unknown transaction type");
-
3083 return false;
-
3084 // LCOV_EXCL_STOP
-
3085 }
-
3086 }();
-
3087
-
3088 if (!result)
-
3089 {
-
3090 // The comment at the top of this file starting with "assert(enforce)"
-
3091 // explains this assert.
-
3092 XRPL_ASSERT(enforce, "ripple::ValidVault::finalize : vault invariants");
-
3093 return !enforce;
-
3094 }
-
3095
-
3096 return true;
-
3097}
+
2995 if (vaultAsset.native() ||
+
2996 vaultAsset.getIssuer() != tx[sfAccount])
+
2997 {
+
2998 JLOG(j.fatal()) << //
+
2999 "Invariant failed: clawback may only be performed by "
+
3000 "the asset issuer";
+
3001 return false; // That's all we can do
+
3002 }
+
3003
+
3004 auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId);
+
3005
+
3006 if (!vaultDeltaAssets)
+
3007 {
+
3008 JLOG(j.fatal()) << //
+
3009 "Invariant failed: clawback must change vault balance";
+
3010 return false; // That's all we can do
+
3011 }
+
3012
+
3013 if (*vaultDeltaAssets >= zero)
+
3014 {
+
3015 JLOG(j.fatal()) << //
+
3016 "Invariant failed: clawback must decrease vault "
+
3017 "balance";
+
3018 result = false;
+
3019 }
+
3020
+
3021 auto const accountDeltaShares = deltaShares(tx[sfHolder]);
+
3022 if (!accountDeltaShares)
+
3023 {
+
3024 JLOG(j.fatal()) << //
+
3025 "Invariant failed: clawback must change holder shares";
+
3026 return false; // That's all we can do
+
3027 }
+
3028
+
3029 if (*accountDeltaShares >= zero)
+
3030 {
+
3031 JLOG(j.fatal()) << //
+
3032 "Invariant failed: clawback must decrease holder "
+
3033 "shares";
+
3034 result = false;
+
3035 }
+
3036
+
3037 auto const vaultDeltaShares = deltaShares(afterVault.pseudoId);
+
3038 if (!vaultDeltaShares || *vaultDeltaShares == zero)
+
3039 {
+
3040 JLOG(j.fatal()) << //
+
3041 "Invariant failed: clawback must change vault shares";
+
3042 return false; // That's all we can do
+
3043 }
+
3044
+
3045 if (*vaultDeltaShares * -1 != *accountDeltaShares)
+
3046 {
+
3047 JLOG(j.fatal()) << //
+
3048 "Invariant failed: clawback must change holder and "
+
3049 "vault shares by equal amount";
+
3050 result = false;
+
3051 }
+
3052
+
3053 if (beforeVault.assetsTotal + *vaultDeltaAssets !=
+
3054 afterVault.assetsTotal)
+
3055 {
+
3056 JLOG(j.fatal()) << //
+
3057 "Invariant failed: clawback and assets outstanding "
+
3058 "must add up";
+
3059 result = false;
+
3060 }
+
3061
+
3062 if (beforeVault.assetsAvailable + *vaultDeltaAssets !=
+
3063 afterVault.assetsAvailable)
+
3064 {
+
3065 JLOG(j.fatal()) << //
+
3066 "Invariant failed: clawback and assets available must "
+
3067 "add up";
+
3068 result = false;
+
3069 }
+
3070
+
3071 return result;
+
3072 }
+
3073
+
3074 default:
+
3075 // LCOV_EXCL_START
+
3076 UNREACHABLE(
+
3077 "ripple::ValidVault::finalize : unknown transaction type");
+
3078 return false;
+
3079 // LCOV_EXCL_STOP
+
3080 }
+
3081 }();
+
3082
+
3083 if (!result)
+
3084 {
+
3085 // The comment at the top of this file starting with "assert(enforce)"
+
3086 // explains this assert.
+
3087 XRPL_ASSERT(enforce, "ripple::ValidVault::finalize : vault invariants");
+
3088 return !enforce;
+
3089 }
+
3090
+
3091 return true;
+
3092}
-
3098
-
3099} // namespace ripple
+
3093
+
3094} // namespace ripple
T begin(T... args)
@@ -3329,9 +3324,9 @@ $(document).ready(function() { init_codefold(0); }); -
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
@@ -3393,29 +3388,29 @@ $(document).ready(function() { init_codefold(0); }); -
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
-
bool finalizeDEX(bool enforce, beast::Journal const &) const
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool finalizeDEX(bool enforce, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceAfter_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
bool finalizeBid(bool enforce, beast::Journal const &) const
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalizeBid(bool enforce, beast::Journal const &) const
std::optional< AccountID > ammAccount_
-
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
-
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
-
bool finalizeVote(bool enforce, beast::Journal const &) const
-
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
-
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
+
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
+
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool finalizeVote(bool enforce, beast::Journal const &) const
+
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceBefore_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptIssuancesCreated_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t mptIssuancesDeleted_
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
@@ -3423,8 +3418,8 @@ $(document).ready(function() { init_codefold(0); }); -
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
@@ -3432,25 +3427,25 @@ $(document).ready(function() { init_codefold(0); });
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
hash_set< uint256 > domains_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::optional< SleStatus > sleStatus_[2]
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::string > errors_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< Shares > beforeMPTs_
std::vector< Vault > beforeVault_
std::unordered_map< uint256, Number > deltas_
static Number constexpr zero
std::vector< Shares > afterMPTs_
std::vector< Vault > afterVault_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:158
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
@@ -3486,7 +3481,7 @@ $(document).ready(function() { init_codefold(0); });
@ fhIGNORE_FREEZE
Definition View.h:58
bool isXRP(AccountID const &c)
Definition AccountID.h:71
constexpr base_uint< Bits, Tag > operator|(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition base_uint.h:596
-
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
+
static bool validBalances(STAmount const &amount, STAmount const &amount2, STAmount const &lptAMMBalance, ValidAMM::ZeroAllowed zeroAllowed)
base_uint< 256 > uint256
Definition base_uint.h:539
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:94
bool hasPrivilege(STTx const &tx, Privilege priv)
@@ -3551,11 +3546,11 @@ $(document).ready(function() { init_codefold(0); }); -
static Shares make(SLE const &)
+
static Shares make(SLE const &)
-
static Vault make(SLE const &)
+
static Vault make(SLE const &)
diff --git a/InvariantCheck_8h_source.html b/InvariantCheck_8h_source.html index 293ecba246..80463124f0 100644 --- a/InvariantCheck_8h_source.html +++ b/InvariantCheck_8h_source.html @@ -807,9 +807,9 @@ $(document).ready(function() { init_codefold(0); });
Invariant: Validates counts of NFTokens after all transaction types.
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
Invariant: offers should be for non-negative amounts and must not be XRP to XRP.
@@ -853,32 +853,32 @@ $(document).ready(function() { init_codefold(0); }); -
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
-
bool finalizeDEX(bool enforce, beast::Journal const &) const
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalizeWithdraw(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool finalizeDEX(bool enforce, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceAfter_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
bool finalizeBid(bool enforce, beast::Journal const &) const
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalizeBid(bool enforce, beast::Journal const &) const
std::optional< AccountID > ammAccount_
-
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
-
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
-
bool finalizeVote(bool enforce, beast::Journal const &) const
-
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
-
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
+
bool finalizeDelete(bool enforce, TER res, beast::Journal const &) const
+
bool finalizeCreate(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool finalizeVote(bool enforce, beast::Journal const &) const
+
bool finalizeDeposit(STTx const &, ReadView const &, bool enforce, beast::Journal const &) const
+
bool generalInvariant(STTx const &, ReadView const &, ZeroAllowed zeroAllowed, beast::Journal const &) const
std::optional< STAmount > lptAMMBalanceBefore_
Invariant: Token holder's trustline balance cannot be negative after Clawback.
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t trustlinesChanged
std::uint32_t mptokensChanged
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::uint32_t mptIssuancesCreated_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::uint32_t mptIssuancesDeleted_
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
Invariant: Validates several invariants for NFToken pages.
@@ -887,8 +887,8 @@ $(document).ready(function() { init_codefold(0); }); -
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
Invariant: a new account root must be the consequence of a payment, must have the right starting sequ...
@@ -898,28 +898,28 @@ $(document).ready(function() { init_codefold(0); });
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
hash_set< uint256 > domains_
Invariants: Permissioned Domains must have some rules and AcceptedCredentials must have length betwee...
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
std::optional< SleStatus > sleStatus_[2]
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
Invariants: Pseudo-accounts have valid and consisent properties.
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< std::string > errors_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
Invariants: Vault object and MPTokenIssuance for vault shares.
-
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
+
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
std::vector< Shares > beforeMPTs_
std::vector< Vault > beforeVault_
std::unordered_map< uint256, Number > deltas_
static Number constexpr zero
std::vector< Shares > afterMPTs_
std::vector< Vault > afterVault_
-
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
+
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
Invariant: An account XRP balance must be in XRP and take a value between 0 and INITIAL_XRP drops,...
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
@@ -951,11 +951,11 @@ $(document).ready(function() { init_codefold(0); }); -
static Shares make(SLE const &)
+
static Shares make(SLE const &)
-
static Vault make(SLE const &)
+
static Vault make(SLE const &)
diff --git a/LedgerClosed__test_8cpp_source.html b/LedgerClosed__test_8cpp_source.html index ec7eeb2b2f..8153c5b634 100644 --- a/LedgerClosed__test_8cpp_source.html +++ b/LedgerClosed__test_8cpp_source.html @@ -123,7 +123,7 @@ $(document).ready(function() { init_codefold(0); });
38 lc_result = env.rpc("ledger_closed")[jss::result];
39 BEAST_EXPECT(
40 lc_result[jss::ledger_hash] ==
-
41 "E86DE7F3D7A4D9CE17EF7C8BA08A8F4D8F643B9552F0D895A31CDA78F541DE4E");
+
41 "0F1A9E0C109ADEF6DA2BDE19217C12BBEC57174CBDBD212B0EBDC1CEDB853185");
42 BEAST_EXPECT(lc_result[jss::ledger_index] == 3);
43 }
diff --git a/LedgerRPC__test_8cpp_source.html b/LedgerRPC__test_8cpp_source.html index b5c61cc856..bcca6c7bbf 100644 --- a/LedgerRPC__test_8cpp_source.html +++ b/LedgerRPC__test_8cpp_source.html @@ -404,8 +404,8 @@ $(document).ready(function() { init_codefold(0); });
307
308 {
309 std::string const hash3{
-
310 "E86DE7F3D7A4D9CE17EF7C8BA08A8F4D"
-
311 "8F643B9552F0D895A31CDA78F541DE4E"};
+
310 "0F1A9E0C109ADEF6DA2BDE19217C12BBEC57174CBDBD212B0EBDC1CEDB8531"
+
311 "85"};
312 // access via the ledger_hash field
313 Json::Value jvParams;
314 jvParams[jss::ledger_hash] = hash3;
diff --git a/LedgerRequestRPC__test_8cpp_source.html b/LedgerRequestRPC__test_8cpp_source.html index 5be9434084..6155253dde 100644 --- a/LedgerRequestRPC__test_8cpp_source.html +++ b/LedgerRequestRPC__test_8cpp_source.html @@ -287,7 +287,7 @@ $(document).ready(function() { init_codefold(0); });
200
201 result = env.rpc("ledger_request", "3")[jss::result];
202 constexpr char const* hash3 =
-
203 "8D631B20BC989AF568FBA97375290544B0703A5ADC1CF9E9053580461690C9EE";
+
203 "9FFD8AE09190D5002FE4252A1B29EABCF40DABBCE3B42619C6BD0BE381D51103";
204 BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "3");
205 BEAST_EXPECT(
206 result[jss::ledger][jss::total_coins] == "99999999999999980");
@@ -296,14 +296,14 @@ $(document).ready(function() { init_codefold(0); });
209 BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == hash2);
210 BEAST_EXPECT(
211 result[jss::ledger][jss::account_hash] ==
-
212 "BC9EF2A16BFF80BCFABA6FA84688D858D33BD0FA0435CAA9DF6DA4105A39A29E");
+
212 "35738B8517F37D08983AF6BC7DA483CCA9CF6B41B1FECB31A20028D78FE0BB22");
213 BEAST_EXPECT(
214 result[jss::ledger][jss::transaction_hash] ==
-
215 "0213EC486C058B3942FBE3DAC6839949A5C5B02B8B4244C8998EFDF04DBD8222");
+
215 "CBD7F0948EBFA2241DE4EA627939A0FFEE6B80A90FE09C42C825DA546E9B73FF");
216
217 result = env.rpc("ledger_request", "4")[jss::result];
218 constexpr char const* hash4 =
-
219 "1A8E7098B23597E73094DADA58C9D62F3AB93A12C6F7666D56CA85A6CFDE530F";
+
219 "7C9B614445517B8C6477E0AB10A35FFC1A23A34FEA41A91ECBDE884CC097C6E1";
220 BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "4");
221 BEAST_EXPECT(
222 result[jss::ledger][jss::total_coins] == "99999999999999960");
@@ -312,14 +312,14 @@ $(document).ready(function() { init_codefold(0); });
225 BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == hash3);
226 BEAST_EXPECT(
227 result[jss::ledger][jss::account_hash] ==
-
228 "C690188F123C91355ADA8BDF4AC5B5C927076D3590C215096868A5255264C6DD");
+
228 "1EE701DD2A150205173E1EDE8D474DF6803EC95253DAAEE965B9D896CFC32A04");
229 BEAST_EXPECT(
230 result[jss::ledger][jss::transaction_hash] ==
-
231 "3CBDB8F42E04333E1642166BFB93AC9A7E1C6C067092CD5D881D6F3AB3D67E76");
+
231 "9BBDDBF926100DFFF364E16268F544B19F5B9BC6ECCBBC104F98D13FA9F3BC35");
232
233 result = env.rpc("ledger_request", "5")[jss::result];
234 constexpr char const* hash5 =
-
235 "C6A222D71AE65D7B4F240009EAD5DEB20D7EEDE5A4064F28BBDBFEEB6FBE48E5";
+
235 "98885D02145CCE4AD2605F1809F17188DB2053B14ED399CAC985DD8E03DCA8C0";
236 BEAST_EXPECT(result[jss::ledger][jss::ledger_index] == "5");
237 BEAST_EXPECT(
238 result[jss::ledger][jss::total_coins] == "99999999999999940");
@@ -328,10 +328,10 @@ $(document).ready(function() { init_codefold(0); });
241 BEAST_EXPECT(result[jss::ledger][jss::parent_hash] == hash4);
242 BEAST_EXPECT(
243 result[jss::ledger][jss::account_hash] ==
-
244 "EA81CD9D36740736F00CB747E0D0E32D3C10B695823D961F0FB9A1CE7133DD4D");
+
244 "41D64D64796468DEA7AE2A7282C0BB525D6FD7ABC29453C5E5BC6406E947CBCE");
245 BEAST_EXPECT(
246 result[jss::ledger][jss::transaction_hash] ==
-
247 "C3D086CD6BDB9E97AD1D513B2C049EF2840BD21D0B3E22D84EBBB89B6D2EF59D");
+
247 "8FE8592EF22FBC2E8C774C7A1ED76AA3FCE64BED17D748CBA9AFDF7072FE36C7");
248
249 result = env.rpc("ledger_request", "6")[jss::result];
250 BEAST_EXPECT(result[jss::error] == "invalidParams");
diff --git a/NFToken__test_8cpp_source.html b/NFToken__test_8cpp_source.html index d116db6ecc..ddfdc9956d 100644 --- a/NFToken__test_8cpp_source.html +++ b/NFToken__test_8cpp_source.html @@ -5871,1970 +5871,1963 @@ $(document).ready(function() { init_codefold(0); });
5720
5721 // Close the ledger until the ledger sequence is large enough to delete
5722 // the account (no longer within <Sequence + 256>)
-
5723 // This is enforced by the featureDeletableAccounts amendment
-
5724 auto incLgrSeqForAcctDel = [&](Env& env, Account const& acct) {
-
5725 int const delta = [&]() -> int {
-
5726 if (env.seq(acct) + 255 > openLedgerSeq(env))
-
5727 return env.seq(acct) - openLedgerSeq(env) + 255;
-
5728 return 0;
-
5729 }();
-
5730 BEAST_EXPECT(delta >= 0);
-
5731 for (int i = 0; i < delta; ++i)
-
5732 env.close();
-
5733 BEAST_EXPECT(openLedgerSeq(env) == env.seq(acct) + 255);
-
5734 };
-
5735
-
5736 // Close the ledger until the ledger sequence is no longer
-
5737 // within <FirstNFTokenSequence + MintedNFTokens + 256>.
-
5738 auto incLgrSeqForFixNftRemint = [&](Env& env, Account const& acct) {
-
5739 int delta = 0;
-
5740 auto const deletableLgrSeq =
-
5741 (*env.le(acct))[~sfFirstNFTokenSequence].value_or(0) +
-
5742 (*env.le(acct))[sfMintedNFTokens] + 255;
-
5743
-
5744 if (deletableLgrSeq > openLedgerSeq(env))
-
5745 delta = deletableLgrSeq - openLedgerSeq(env);
-
5746
-
5747 BEAST_EXPECT(delta >= 0);
-
5748 for (int i = 0; i < delta; ++i)
-
5749 env.close();
-
5750 BEAST_EXPECT(openLedgerSeq(env) == deletableLgrSeq);
-
5751 };
-
5752
-
5753 // We check if NFTokenIDs can be duplicated by
-
5754 // re-creation of an account
-
5755 {
-
5756 Env env{*this, features};
-
5757 Account const alice("alice");
-
5758 Account const becky("becky");
-
5759
-
5760 env.fund(XRP(10000), alice, becky);
-
5761 env.close();
-
5762
-
5763 // alice mint and burn a NFT
-
5764 uint256 const prevNFTokenID = token::getNextID(env, alice, 0u);
-
5765 env(token::mint(alice));
-
5766 env.close();
-
5767 env(token::burn(alice, prevNFTokenID));
-
5768 env.close();
-
5769
-
5770 // alice has minted 1 NFToken
-
5771 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 1);
-
5772
-
5773 // Close enough ledgers to delete alice's account
-
5774 incLgrSeqForAcctDel(env, alice);
-
5775
-
5776 // alice's account is deleted
-
5777 Keylet const aliceAcctKey{keylet::account(alice.id())};
-
5778 auto const acctDelFee{drops(env.current()->fees().increment)};
-
5779 env(acctdelete(alice, becky), fee(acctDelFee));
-
5780 env.close();
-
5781
-
5782 // alice's account root is gone from the most recently
-
5783 // closed ledger and the current ledger.
-
5784 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
-
5785 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
-
5786
-
5787 // Fund alice to re-create her account
-
5788 env.fund(XRP(10000), alice);
-
5789 env.close();
-
5790
-
5791 // alice's account now exists and has minted 0 NFTokens
-
5792 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
5793 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
5794 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
-
5795
-
5796 // alice mints a NFT with same params as prevNFTokenID
-
5797 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
-
5798 env(token::mint(alice));
-
5799 env.close();
-
5800
-
5801 // burn the NFT to make sure alice owns remintNFTokenID
-
5802 env(token::burn(alice, remintNFTokenID));
-
5803 env.close();
-
5804
-
5805 // Check that two NFTs don't have the same ID
-
5806 BEAST_EXPECT(remintNFTokenID != prevNFTokenID);
-
5807 }
-
5808
-
5809 // Test if the issuer account can be deleted after an authorized
-
5810 // minter mints and burns a batch of NFTokens.
-
5811 {
-
5812 Env env{*this, features};
-
5813 Account const alice("alice");
-
5814 Account const becky("becky");
-
5815 Account const minter{"minter"};
-
5816
-
5817 env.fund(XRP(10000), alice, becky, minter);
-
5818 env.close();
-
5819
-
5820 // alice sets minter as her authorized minter
-
5821 env(token::setMinter(alice, minter));
-
5822 env.close();
-
5823
-
5824 // minter mints 500 NFTs for alice
-
5825 std::vector<uint256> nftIDs;
-
5826 nftIDs.reserve(500);
-
5827 for (int i = 0; i < 500; i++)
-
5828 {
-
5829 uint256 const nftokenID = token::getNextID(env, alice, 0u);
-
5830 nftIDs.push_back(nftokenID);
-
5831 env(token::mint(minter), token::issuer(alice));
-
5832 }
-
5833 env.close();
-
5834
-
5835 // minter burns 500 NFTs
-
5836 for (auto const nftokenID : nftIDs)
-
5837 {
-
5838 env(token::burn(minter, nftokenID));
-
5839 }
-
5840 env.close();
-
5841
-
5842 // Increment ledger sequence to the number that is
-
5843 // enforced by the featureDeletableAccounts amendment
-
5844 incLgrSeqForAcctDel(env, alice);
-
5845
-
5846 // Verify that alice's account root is present.
-
5847 Keylet const aliceAcctKey{keylet::account(alice.id())};
-
5848 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
5849 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
5850
-
5851 auto const acctDelFee{drops(env.current()->fees().increment)};
-
5852
-
5853 // alice tries to delete her account, but is unsuccessful.
-
5854 // Due to authorized minting, alice's account sequence does not
-
5855 // advance while minter mints NFTokens for her.
-
5856 // The new account deletion retriction <FirstNFTokenSequence +
-
5857 // MintedNFTokens + 256> enabled by this amendment will enforce
-
5858 // alice to wait for more ledgers to close before she can
-
5859 // delete her account, to prevent duplicate NFTokenIDs
-
5860 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
-
5861 env.close();
+
5723 auto incLgrSeqForAcctDel = [&](Env& env, Account const& acct) {
+
5724 int const delta = [&]() -> int {
+
5725 if (env.seq(acct) + 255 > openLedgerSeq(env))
+
5726 return env.seq(acct) - openLedgerSeq(env) + 255;
+
5727 return 0;
+
5728 }();
+
5729 BEAST_EXPECT(delta >= 0);
+
5730 for (int i = 0; i < delta; ++i)
+
5731 env.close();
+
5732 BEAST_EXPECT(openLedgerSeq(env) == env.seq(acct) + 255);
+
5733 };
+
5734
+
5735 // Close the ledger until the ledger sequence is no longer
+
5736 // within <FirstNFTokenSequence + MintedNFTokens + 256>.
+
5737 auto incLgrSeqForFixNftRemint = [&](Env& env, Account const& acct) {
+
5738 int delta = 0;
+
5739 auto const deletableLgrSeq =
+
5740 (*env.le(acct))[~sfFirstNFTokenSequence].value_or(0) +
+
5741 (*env.le(acct))[sfMintedNFTokens] + 255;
+
5742
+
5743 if (deletableLgrSeq > openLedgerSeq(env))
+
5744 delta = deletableLgrSeq - openLedgerSeq(env);
+
5745
+
5746 BEAST_EXPECT(delta >= 0);
+
5747 for (int i = 0; i < delta; ++i)
+
5748 env.close();
+
5749 BEAST_EXPECT(openLedgerSeq(env) == deletableLgrSeq);
+
5750 };
+
5751
+
5752 // We check if NFTokenIDs can be duplicated by
+
5753 // re-creation of an account
+
5754 {
+
5755 Env env{*this, features};
+
5756 Account const alice("alice");
+
5757 Account const becky("becky");
+
5758
+
5759 env.fund(XRP(10000), alice, becky);
+
5760 env.close();
+
5761
+
5762 // alice mint and burn a NFT
+
5763 uint256 const prevNFTokenID = token::getNextID(env, alice, 0u);
+
5764 env(token::mint(alice));
+
5765 env.close();
+
5766 env(token::burn(alice, prevNFTokenID));
+
5767 env.close();
+
5768
+
5769 // alice has minted 1 NFToken
+
5770 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 1);
+
5771
+
5772 // Close enough ledgers to delete alice's account
+
5773 incLgrSeqForAcctDel(env, alice);
+
5774
+
5775 // alice's account is deleted
+
5776 Keylet const aliceAcctKey{keylet::account(alice.id())};
+
5777 auto const acctDelFee{drops(env.current()->fees().increment)};
+
5778 env(acctdelete(alice, becky), fee(acctDelFee));
+
5779 env.close();
+
5780
+
5781 // alice's account root is gone from the most recently
+
5782 // closed ledger and the current ledger.
+
5783 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
5784 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
5785
+
5786 // Fund alice to re-create her account
+
5787 env.fund(XRP(10000), alice);
+
5788 env.close();
+
5789
+
5790 // alice's account now exists and has minted 0 NFTokens
+
5791 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
5792 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
5793 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
5794
+
5795 // alice mints a NFT with same params as prevNFTokenID
+
5796 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
+
5797 env(token::mint(alice));
+
5798 env.close();
+
5799
+
5800 // burn the NFT to make sure alice owns remintNFTokenID
+
5801 env(token::burn(alice, remintNFTokenID));
+
5802 env.close();
+
5803
+
5804 // Check that two NFTs don't have the same ID
+
5805 BEAST_EXPECT(remintNFTokenID != prevNFTokenID);
+
5806 }
+
5807
+
5808 // Test if the issuer account can be deleted after an authorized
+
5809 // minter mints and burns a batch of NFTokens.
+
5810 {
+
5811 Env env{*this, features};
+
5812 Account const alice("alice");
+
5813 Account const becky("becky");
+
5814 Account const minter{"minter"};
+
5815
+
5816 env.fund(XRP(10000), alice, becky, minter);
+
5817 env.close();
+
5818
+
5819 // alice sets minter as her authorized minter
+
5820 env(token::setMinter(alice, minter));
+
5821 env.close();
+
5822
+
5823 // minter mints 500 NFTs for alice
+
5824 std::vector<uint256> nftIDs;
+
5825 nftIDs.reserve(500);
+
5826 for (int i = 0; i < 500; i++)
+
5827 {
+
5828 uint256 const nftokenID = token::getNextID(env, alice, 0u);
+
5829 nftIDs.push_back(nftokenID);
+
5830 env(token::mint(minter), token::issuer(alice));
+
5831 }
+
5832 env.close();
+
5833
+
5834 // minter burns 500 NFTs
+
5835 for (auto const nftokenID : nftIDs)
+
5836 {
+
5837 env(token::burn(minter, nftokenID));
+
5838 }
+
5839 env.close();
+
5840
+
5841 incLgrSeqForAcctDel(env, alice);
+
5842
+
5843 // Verify that alice's account root is present.
+
5844 Keylet const aliceAcctKey{keylet::account(alice.id())};
+
5845 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
5846 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
5847
+
5848 auto const acctDelFee{drops(env.current()->fees().increment)};
+
5849
+
5850 // alice tries to delete her account, but is unsuccessful.
+
5851 // Due to authorized minting, alice's account sequence does not
+
5852 // advance while minter mints NFTokens for her.
+
5853 // The new account deletion retriction <FirstNFTokenSequence +
+
5854 // MintedNFTokens + 256> enabled by this amendment will enforce
+
5855 // alice to wait for more ledgers to close before she can
+
5856 // delete her account, to prevent duplicate NFTokenIDs
+
5857 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
+
5858 env.close();
+
5859
+
5860 // alice's account is still present
+
5861 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5862
-
5863 // alice's account is still present
-
5864 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
5865
-
5866 // Close more ledgers until it is no longer within
-
5867 // <FirstNFTokenSequence + MintedNFTokens + 256>
-
5868 // to be able to delete alice's account
-
5869 incLgrSeqForFixNftRemint(env, alice);
-
5870
-
5871 // alice's account is deleted
-
5872 env(acctdelete(alice, becky), fee(acctDelFee));
-
5873 env.close();
-
5874
-
5875 // alice's account root is gone from the most recently
-
5876 // closed ledger and the current ledger.
-
5877 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
-
5878 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
-
5879
-
5880 // Fund alice to re-create her account
-
5881 env.fund(XRP(10000), alice);
-
5882 env.close();
-
5883
-
5884 // alice's account now exists and has minted 0 NFTokens
-
5885 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
5886 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
5887 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
-
5888
-
5889 // alice mints a NFT with same params as the first one before
-
5890 // the account delete.
-
5891 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
-
5892 env(token::mint(alice));
-
5893 env.close();
-
5894
-
5895 // burn the NFT to make sure alice owns remintNFTokenID
-
5896 env(token::burn(alice, remintNFTokenID));
-
5897 env.close();
-
5898
-
5899 // The new NFT minted will not have the same ID
-
5900 // as any of the NFTs authorized minter minted
-
5901 BEAST_EXPECT(
-
5902 std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
-
5903 nftIDs.end());
-
5904 }
-
5905
-
5906 // When an account mints and burns a batch of NFTokens using tickets,
-
5907 // see if the account can be deleted.
-
5908 {
-
5909 Env env{*this, features};
-
5910
-
5911 Account const alice{"alice"};
-
5912 Account const becky{"becky"};
-
5913 env.fund(XRP(10000), alice, becky);
-
5914 env.close();
-
5915
-
5916 // alice grab enough tickets for all of the following
-
5917 // transactions. Note that once the tickets are acquired alice's
-
5918 // account sequence number should not advance.
-
5919 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
-
5920 env(ticket::create(alice, 100));
-
5921 env.close();
+
5863 // Close more ledgers until it is no longer within
+
5864 // <FirstNFTokenSequence + MintedNFTokens + 256>
+
5865 // to be able to delete alice's account
+
5866 incLgrSeqForFixNftRemint(env, alice);
+
5867
+
5868 // alice's account is deleted
+
5869 env(acctdelete(alice, becky), fee(acctDelFee));
+
5870 env.close();
+
5871
+
5872 // alice's account root is gone from the most recently
+
5873 // closed ledger and the current ledger.
+
5874 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
5875 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
5876
+
5877 // Fund alice to re-create her account
+
5878 env.fund(XRP(10000), alice);
+
5879 env.close();
+
5880
+
5881 // alice's account now exists and has minted 0 NFTokens
+
5882 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
5883 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
5884 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
5885
+
5886 // alice mints a NFT with same params as the first one before
+
5887 // the account delete.
+
5888 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
+
5889 env(token::mint(alice));
+
5890 env.close();
+
5891
+
5892 // burn the NFT to make sure alice owns remintNFTokenID
+
5893 env(token::burn(alice, remintNFTokenID));
+
5894 env.close();
+
5895
+
5896 // The new NFT minted will not have the same ID
+
5897 // as any of the NFTs authorized minter minted
+
5898 BEAST_EXPECT(
+
5899 std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
+
5900 nftIDs.end());
+
5901 }
+
5902
+
5903 // When an account mints and burns a batch of NFTokens using tickets,
+
5904 // see if the account can be deleted.
+
5905 {
+
5906 Env env{*this, features};
+
5907
+
5908 Account const alice{"alice"};
+
5909 Account const becky{"becky"};
+
5910 env.fund(XRP(10000), alice, becky);
+
5911 env.close();
+
5912
+
5913 // alice grab enough tickets for all of the following
+
5914 // transactions. Note that once the tickets are acquired alice's
+
5915 // account sequence number should not advance.
+
5916 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
+
5917 env(ticket::create(alice, 100));
+
5918 env.close();
+
5919
+
5920 BEAST_EXPECT(ticketCount(env, alice) == 100);
+
5921 BEAST_EXPECT(ownerCount(env, alice) == 100);
5922
-
5923 BEAST_EXPECT(ticketCount(env, alice) == 100);
-
5924 BEAST_EXPECT(ownerCount(env, alice) == 100);
-
5925
-
5926 // alice mints 50 NFTs using tickets
-
5927 std::vector<uint256> nftIDs;
-
5928 nftIDs.reserve(50);
-
5929 for (int i = 0; i < 50; i++)
-
5930 {
-
5931 nftIDs.push_back(token::getNextID(env, alice, 0u));
-
5932 env(token::mint(alice, 0u), ticket::use(aliceTicketSeq++));
-
5933 env.close();
-
5934 }
-
5935
-
5936 // alice burns 50 NFTs using tickets
-
5937 for (auto const nftokenID : nftIDs)
-
5938 {
-
5939 env(token::burn(alice, nftokenID),
-
5940 ticket::use(aliceTicketSeq++));
-
5941 }
-
5942 env.close();
-
5943
-
5944 BEAST_EXPECT(ticketCount(env, alice) == 0);
-
5945
-
5946 // Increment ledger sequence to the number that is
-
5947 // enforced by the featureDeletableAccounts amendment
-
5948 incLgrSeqForAcctDel(env, alice);
+
5923 // alice mints 50 NFTs using tickets
+
5924 std::vector<uint256> nftIDs;
+
5925 nftIDs.reserve(50);
+
5926 for (int i = 0; i < 50; i++)
+
5927 {
+
5928 nftIDs.push_back(token::getNextID(env, alice, 0u));
+
5929 env(token::mint(alice, 0u), ticket::use(aliceTicketSeq++));
+
5930 env.close();
+
5931 }
+
5932
+
5933 // alice burns 50 NFTs using tickets
+
5934 for (auto const nftokenID : nftIDs)
+
5935 {
+
5936 env(token::burn(alice, nftokenID),
+
5937 ticket::use(aliceTicketSeq++));
+
5938 }
+
5939 env.close();
+
5940
+
5941 BEAST_EXPECT(ticketCount(env, alice) == 0);
+
5942
+
5943 incLgrSeqForAcctDel(env, alice);
+
5944
+
5945 // Verify that alice's account root is present.
+
5946 Keylet const aliceAcctKey{keylet::account(alice.id())};
+
5947 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
5948 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5949
-
5950 // Verify that alice's account root is present.
-
5951 Keylet const aliceAcctKey{keylet::account(alice.id())};
-
5952 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
5953 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
5954
-
5955 auto const acctDelFee{drops(env.current()->fees().increment)};
-
5956
-
5957 // alice tries to delete her account, but is unsuccessful.
-
5958 // Due to authorized minting, alice's account sequence does not
-
5959 // advance while minter mints NFTokens for her using tickets.
-
5960 // The new account deletion retriction <FirstNFTokenSequence +
-
5961 // MintedNFTokens + 256> enabled by this amendment will enforce
-
5962 // alice to wait for more ledgers to close before she can
-
5963 // delete her account, to prevent duplicate NFTokenIDs
-
5964 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
-
5965 env.close();
-
5966
-
5967 // alice's account is still present
-
5968 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
5950 auto const acctDelFee{drops(env.current()->fees().increment)};
+
5951
+
5952 // alice tries to delete her account, but is unsuccessful.
+
5953 // Due to authorized minting, alice's account sequence does not
+
5954 // advance while minter mints NFTokens for her using tickets.
+
5955 // The new account deletion retriction <FirstNFTokenSequence +
+
5956 // MintedNFTokens + 256> enabled by this amendment will enforce
+
5957 // alice to wait for more ledgers to close before she can
+
5958 // delete her account, to prevent duplicate NFTokenIDs
+
5959 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
+
5960 env.close();
+
5961
+
5962 // alice's account is still present
+
5963 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
5964
+
5965 // Close more ledgers until it is no longer within
+
5966 // <FirstNFTokenSequence + MintedNFTokens + 256>
+
5967 // to be able to delete alice's account
+
5968 incLgrSeqForFixNftRemint(env, alice);
5969
-
5970 // Close more ledgers until it is no longer within
-
5971 // <FirstNFTokenSequence + MintedNFTokens + 256>
-
5972 // to be able to delete alice's account
-
5973 incLgrSeqForFixNftRemint(env, alice);
-
5974
-
5975 // alice's account is deleted
-
5976 env(acctdelete(alice, becky), fee(acctDelFee));
-
5977 env.close();
+
5970 // alice's account is deleted
+
5971 env(acctdelete(alice, becky), fee(acctDelFee));
+
5972 env.close();
+
5973
+
5974 // alice's account root is gone from the most recently
+
5975 // closed ledger and the current ledger.
+
5976 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
5977 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
5978
-
5979 // alice's account root is gone from the most recently
-
5980 // closed ledger and the current ledger.
-
5981 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
-
5982 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
-
5983
-
5984 // Fund alice to re-create her account
-
5985 env.fund(XRP(10000), alice);
-
5986 env.close();
+
5979 // Fund alice to re-create her account
+
5980 env.fund(XRP(10000), alice);
+
5981 env.close();
+
5982
+
5983 // alice's account now exists and has minted 0 NFTokens
+
5984 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
5985 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
5986 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
5987
-
5988 // alice's account now exists and has minted 0 NFTokens
-
5989 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
5990 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
5991 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
-
5992
-
5993 // alice mints a NFT with same params as the first one before
-
5994 // the account delete.
-
5995 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
-
5996 env(token::mint(alice));
-
5997 env.close();
-
5998
-
5999 // burn the NFT to make sure alice owns remintNFTokenID
-
6000 env(token::burn(alice, remintNFTokenID));
-
6001 env.close();
-
6002
-
6003 // The new NFT minted will not have the same ID
-
6004 // as any of the NFTs authorized minter minted using tickets
-
6005 BEAST_EXPECT(
-
6006 std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
-
6007 nftIDs.end());
-
6008 }
-
6009 // When an authorized minter mints and burns a batch of NFTokens using
-
6010 // tickets, issuer's account needs to wait a longer time before it can
-
6011 // be deleted.
-
6012 // After the issuer's account is re-created and mints a NFT, it should
-
6013 // not have the same NFTokenID as the ones authorized minter minted.
-
6014 Env env{*this, features};
-
6015 Account const alice("alice");
-
6016 Account const becky("becky");
-
6017 Account const minter{"minter"};
-
6018
-
6019 env.fund(XRP(10000), alice, becky, minter);
-
6020 env.close();
-
6021
-
6022 // alice sets minter as her authorized minter
-
6023 env(token::setMinter(alice, minter));
+
5988 // alice mints a NFT with same params as the first one before
+
5989 // the account delete.
+
5990 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
+
5991 env(token::mint(alice));
+
5992 env.close();
+
5993
+
5994 // burn the NFT to make sure alice owns remintNFTokenID
+
5995 env(token::burn(alice, remintNFTokenID));
+
5996 env.close();
+
5997
+
5998 // The new NFT minted will not have the same ID
+
5999 // as any of the NFTs authorized minter minted using tickets
+
6000 BEAST_EXPECT(
+
6001 std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
+
6002 nftIDs.end());
+
6003 }
+
6004 // When an authorized minter mints and burns a batch of NFTokens using
+
6005 // tickets, issuer's account needs to wait a longer time before it can
+
6006 // be deleted.
+
6007 // After the issuer's account is re-created and mints a NFT, it should
+
6008 // not have the same NFTokenID as the ones authorized minter minted.
+
6009 Env env{*this, features};
+
6010 Account const alice("alice");
+
6011 Account const becky("becky");
+
6012 Account const minter{"minter"};
+
6013
+
6014 env.fund(XRP(10000), alice, becky, minter);
+
6015 env.close();
+
6016
+
6017 // alice sets minter as her authorized minter
+
6018 env(token::setMinter(alice, minter));
+
6019 env.close();
+
6020
+
6021 // minter creates 100 tickets
+
6022 std::uint32_t minterTicketSeq{env.seq(minter) + 1};
+
6023 env(ticket::create(minter, 100));
6024 env.close();
6025
-
6026 // minter creates 100 tickets
-
6027 std::uint32_t minterTicketSeq{env.seq(minter) + 1};
-
6028 env(ticket::create(minter, 100));
-
6029 env.close();
-
6030
-
6031 BEAST_EXPECT(ticketCount(env, minter) == 100);
-
6032 BEAST_EXPECT(ownerCount(env, minter) == 100);
-
6033
-
6034 // minter mints 50 NFTs for alice using tickets
-
6035 std::vector<uint256> nftIDs;
-
6036 nftIDs.reserve(50);
-
6037 for (int i = 0; i < 50; i++)
-
6038 {
-
6039 uint256 const nftokenID = token::getNextID(env, alice, 0u);
-
6040 nftIDs.push_back(nftokenID);
-
6041 env(token::mint(minter),
-
6042 token::issuer(alice),
-
6043 ticket::use(minterTicketSeq++));
-
6044 }
-
6045 env.close();
-
6046
-
6047 // minter burns 50 NFTs using tickets
-
6048 for (auto const nftokenID : nftIDs)
-
6049 {
-
6050 env(token::burn(minter, nftokenID), ticket::use(minterTicketSeq++));
-
6051 }
-
6052 env.close();
-
6053
-
6054 BEAST_EXPECT(ticketCount(env, minter) == 0);
-
6055
-
6056 // Increment ledger sequence to the number that is
-
6057 // enforced by the featureDeletableAccounts amendment
-
6058 incLgrSeqForAcctDel(env, alice);
-
6059
-
6060 // Verify that alice's account root is present.
-
6061 Keylet const aliceAcctKey{keylet::account(alice.id())};
-
6062 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
6063 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
6064
-
6065 // alice tries to delete her account, but is unsuccessful.
-
6066 // Due to authorized minting, alice's account sequence does not
-
6067 // advance while minter mints NFTokens for her using tickets.
-
6068 // The new account deletion retriction <FirstNFTokenSequence +
-
6069 // MintedNFTokens + 256> enabled by this amendment will enforce
-
6070 // alice to wait for more ledgers to close before she can delete her
-
6071 // account, to prevent duplicate NFTokenIDs
-
6072 auto const acctDelFee{drops(env.current()->fees().increment)};
-
6073 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
-
6074 env.close();
-
6075
-
6076 // alice's account is still present
-
6077 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
6078
-
6079 // Close more ledgers until it is no longer within
-
6080 // <FirstNFTokenSequence + MintedNFTokens + 256>
-
6081 // to be able to delete alice's account
-
6082 incLgrSeqForFixNftRemint(env, alice);
-
6083
-
6084 // alice's account is deleted
-
6085 env(acctdelete(alice, becky), fee(acctDelFee));
-
6086 env.close();
-
6087
-
6088 // alice's account root is gone from the most recently
-
6089 // closed ledger and the current ledger.
-
6090 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
-
6091 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
-
6092
-
6093 // Fund alice to re-create her account
-
6094 env.fund(XRP(10000), alice);
-
6095 env.close();
-
6096
-
6097 // alice's account now exists and has minted 0 NFTokens
-
6098 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
-
6099 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
-
6100 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
-
6101
-
6102 // The new NFT minted will not have the same ID
-
6103 // as any of the NFTs authorized minter minted using tickets
-
6104 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
-
6105 env(token::mint(alice));
-
6106 env.close();
-
6107
-
6108 // burn the NFT to make sure alice owns remintNFTokenID
-
6109 env(token::burn(alice, remintNFTokenID));
-
6110 env.close();
-
6111
-
6112 // The new NFT minted will not have the same ID
-
6113 // as one of NFTs authorized minter minted using tickets
-
6114 BEAST_EXPECT(
-
6115 std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
-
6116 nftIDs.end());
-
6117 }
+
6026 BEAST_EXPECT(ticketCount(env, minter) == 100);
+
6027 BEAST_EXPECT(ownerCount(env, minter) == 100);
+
6028
+
6029 // minter mints 50 NFTs for alice using tickets
+
6030 std::vector<uint256> nftIDs;
+
6031 nftIDs.reserve(50);
+
6032 for (int i = 0; i < 50; i++)
+
6033 {
+
6034 uint256 const nftokenID = token::getNextID(env, alice, 0u);
+
6035 nftIDs.push_back(nftokenID);
+
6036 env(token::mint(minter),
+
6037 token::issuer(alice),
+
6038 ticket::use(minterTicketSeq++));
+
6039 }
+
6040 env.close();
+
6041
+
6042 // minter burns 50 NFTs using tickets
+
6043 for (auto const nftokenID : nftIDs)
+
6044 {
+
6045 env(token::burn(minter, nftokenID), ticket::use(minterTicketSeq++));
+
6046 }
+
6047 env.close();
+
6048
+
6049 BEAST_EXPECT(ticketCount(env, minter) == 0);
+
6050
+
6051 incLgrSeqForAcctDel(env, alice);
+
6052
+
6053 // Verify that alice's account root is present.
+
6054 Keylet const aliceAcctKey{keylet::account(alice.id())};
+
6055 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6056 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6057
+
6058 // alice tries to delete her account, but is unsuccessful.
+
6059 // Due to authorized minting, alice's account sequence does not
+
6060 // advance while minter mints NFTokens for her using tickets.
+
6061 // The new account deletion retriction <FirstNFTokenSequence +
+
6062 // MintedNFTokens + 256> enabled by this amendment will enforce
+
6063 // alice to wait for more ledgers to close before she can delete her
+
6064 // account, to prevent duplicate NFTokenIDs
+
6065 auto const acctDelFee{drops(env.current()->fees().increment)};
+
6066 env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
+
6067 env.close();
+
6068
+
6069 // alice's account is still present
+
6070 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6071
+
6072 // Close more ledgers until it is no longer within
+
6073 // <FirstNFTokenSequence + MintedNFTokens + 256>
+
6074 // to be able to delete alice's account
+
6075 incLgrSeqForFixNftRemint(env, alice);
+
6076
+
6077 // alice's account is deleted
+
6078 env(acctdelete(alice, becky), fee(acctDelFee));
+
6079 env.close();
+
6080
+
6081 // alice's account root is gone from the most recently
+
6082 // closed ledger and the current ledger.
+
6083 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
6084 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
6085
+
6086 // Fund alice to re-create her account
+
6087 env.fund(XRP(10000), alice);
+
6088 env.close();
+
6089
+
6090 // alice's account now exists and has minted 0 NFTokens
+
6091 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6092 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6093 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
6094
+
6095 // The new NFT minted will not have the same ID
+
6096 // as any of the NFTs authorized minter minted using tickets
+
6097 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
+
6098 env(token::mint(alice));
+
6099 env.close();
+
6100
+
6101 // burn the NFT to make sure alice owns remintNFTokenID
+
6102 env(token::burn(alice, remintNFTokenID));
+
6103 env.close();
+
6104
+
6105 // The new NFT minted will not have the same ID
+
6106 // as one of NFTs authorized minter minted using tickets
+
6107 BEAST_EXPECT(
+
6108 std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
+
6109 nftIDs.end());
+
6110 }
+
6111
+
6112 void
+
+ +
6114 {
+
6115 testcase("NFTokenMint with Create NFTokenOffer");
+
6116
+
6117 using namespace test::jtx;
6118
-
6119 void
-
- -
6121 {
-
6122 testcase("NFTokenMint with Create NFTokenOffer");
-
6123
-
6124 using namespace test::jtx;
-
6125
-
6126 if (!features[featureNFTokenMintOffer])
-
6127 {
-
6128 Env env{*this, features};
-
6129 Account const alice("alice");
-
6130 Account const buyer("buyer");
-
6131
-
6132 env.fund(XRP(10000), alice, buyer);
-
6133 env.close();
-
6134
-
6135 env(token::mint(alice),
-
6136 token::amount(XRP(10000)),
-
6137 ter(temDISABLED));
-
6138 env.close();
-
6139
-
6140 env(token::mint(alice),
-
6141 token::destination("buyer"),
-
6142 ter(temDISABLED));
-
6143 env.close();
-
6144
-
6145 env(token::mint(alice),
-
6146 token::expiration(lastClose(env) + 25),
-
6147 ter(temDISABLED));
-
6148 env.close();
-
6149
-
6150 return;
-
6151 }
-
6152
-
6153 // The remaining tests assume featureNFTokenMintOffer is enabled.
-
6154 {
-
6155 Env env{*this, features};
-
6156 auto const baseFee = env.current()->fees().base;
-
6157 Account const alice("alice");
-
6158 Account const buyer{"buyer"};
-
6159 Account const gw("gw");
-
6160 Account const issuer("issuer");
-
6161 Account const minter("minter");
-
6162 Account const bob("bob");
-
6163 IOU const gwAUD(gw["AUD"]);
-
6164
-
6165 env.fund(XRP(10000), alice, buyer, gw, issuer, minter);
-
6166 env.close();
-
6167
-
6168 {
-
6169 // Destination field specified but Amount field not specified
+
6119 if (!features[featureNFTokenMintOffer])
+
6120 {
+
6121 Env env{*this, features};
+
6122 Account const alice("alice");
+
6123 Account const buyer("buyer");
+
6124
+
6125 env.fund(XRP(10000), alice, buyer);
+
6126 env.close();
+
6127
+
6128 env(token::mint(alice),
+
6129 token::amount(XRP(10000)),
+
6130 ter(temDISABLED));
+
6131 env.close();
+
6132
+
6133 env(token::mint(alice),
+
6134 token::destination("buyer"),
+
6135 ter(temDISABLED));
+
6136 env.close();
+
6137
+
6138 env(token::mint(alice),
+
6139 token::expiration(lastClose(env) + 25),
+
6140 ter(temDISABLED));
+
6141 env.close();
+
6142
+
6143 return;
+
6144 }
+
6145
+
6146 // The remaining tests assume featureNFTokenMintOffer is enabled.
+
6147 {
+
6148 Env env{*this, features};
+
6149 auto const baseFee = env.current()->fees().base;
+
6150 Account const alice("alice");
+
6151 Account const buyer{"buyer"};
+
6152 Account const gw("gw");
+
6153 Account const issuer("issuer");
+
6154 Account const minter("minter");
+
6155 Account const bob("bob");
+
6156 IOU const gwAUD(gw["AUD"]);
+
6157
+
6158 env.fund(XRP(10000), alice, buyer, gw, issuer, minter);
+
6159 env.close();
+
6160
+
6161 {
+
6162 // Destination field specified but Amount field not specified
+
6163 env(token::mint(alice),
+
6164 token::destination(buyer),
+
6165 ter(temMALFORMED));
+
6166 env.close();
+
6167 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6168
+
6169 // Expiration field specified but Amount field not specified
6170 env(token::mint(alice),
-
6171 token::destination(buyer),
+
6171 token::expiration(lastClose(env) + 25),
6172 ter(temMALFORMED));
6173 env.close();
-
6174 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6175
-
6176 // Expiration field specified but Amount field not specified
-
6177 env(token::mint(alice),
-
6178 token::expiration(lastClose(env) + 25),
-
6179 ter(temMALFORMED));
-
6180 env.close();
-
6181 BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
6182 }
-
6183
-
6184 {
-
6185 // The destination may not be the account submitting the
-
6186 // transaction.
-
6187 env(token::mint(alice),
-
6188 token::amount(XRP(1000)),
-
6189 token::destination(alice),
-
6190 ter(temMALFORMED));
-
6191 env.close();
-
6192 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6193
-
6194 // The destination must be an account already established in the
-
6195 // ledger.
-
6196 env(token::mint(alice),
-
6197 token::amount(XRP(1000)),
-
6198 token::destination(Account("demon")),
-
6199 ter(tecNO_DST));
-
6200 env.close();
-
6201 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6202 }
-
6203
-
6204 {
-
6205 // Set a bad expiration.
-
6206 env(token::mint(alice),
-
6207 token::amount(XRP(1000)),
-
6208 token::expiration(0),
-
6209 ter(temBAD_EXPIRATION));
-
6210 env.close();
-
6211 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6212
-
6213 // The new NFTokenOffer may not have passed its expiration time.
-
6214 env(token::mint(alice),
-
6215 token::amount(XRP(1000)),
-
6216 token::expiration(lastClose(env)),
-
6217 ter(tecEXPIRED));
-
6218 env.close();
-
6219 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6220 }
-
6221
-
6222 {
-
6223 // Set an invalid amount.
-
6224 env(token::mint(alice),
-
6225 token::amount(buyer["USD"](1)),
-
6226 txflags(tfOnlyXRP),
-
6227 ter(temBAD_AMOUNT));
+
6174 BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
6175 }
+
6176
+
6177 {
+
6178 // The destination may not be the account submitting the
+
6179 // transaction.
+
6180 env(token::mint(alice),
+
6181 token::amount(XRP(1000)),
+
6182 token::destination(alice),
+
6183 ter(temMALFORMED));
+
6184 env.close();
+
6185 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6186
+
6187 // The destination must be an account already established in the
+
6188 // ledger.
+
6189 env(token::mint(alice),
+
6190 token::amount(XRP(1000)),
+
6191 token::destination(Account("demon")),
+
6192 ter(tecNO_DST));
+
6193 env.close();
+
6194 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6195 }
+
6196
+
6197 {
+
6198 // Set a bad expiration.
+
6199 env(token::mint(alice),
+
6200 token::amount(XRP(1000)),
+
6201 token::expiration(0),
+
6202 ter(temBAD_EXPIRATION));
+
6203 env.close();
+
6204 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6205
+
6206 // The new NFTokenOffer may not have passed its expiration time.
+
6207 env(token::mint(alice),
+
6208 token::amount(XRP(1000)),
+
6209 token::expiration(lastClose(env)),
+
6210 ter(tecEXPIRED));
+
6211 env.close();
+
6212 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6213 }
+
6214
+
6215 {
+
6216 // Set an invalid amount.
+
6217 env(token::mint(alice),
+
6218 token::amount(buyer["USD"](1)),
+
6219 txflags(tfOnlyXRP),
+
6220 ter(temBAD_AMOUNT));
+
6221 env(token::mint(alice),
+
6222 token::amount(buyer["USD"](0)),
+
6223 ter(temBAD_AMOUNT));
+
6224 env.close();
+
6225 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6226
+
6227 // Issuer (alice) must have a trust line for the offered funds.
6228 env(token::mint(alice),
-
6229 token::amount(buyer["USD"](0)),
-
6230 ter(temBAD_AMOUNT));
-
6231 env.close();
-
6232 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6233
-
6234 // Issuer (alice) must have a trust line for the offered funds.
-
6235 env(token::mint(alice),
-
6236 token::amount(gwAUD(1000)),
-
6237 txflags(tfTransferable),
-
6238 token::xferFee(10),
-
6239 ter(tecNO_LINE));
-
6240 env.close();
-
6241 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6242
-
6243 // If the IOU issuer and the NFToken issuer are the same,
-
6244 // then that issuer does not need a trust line to accept their
-
6245 // fee.
-
6246 env(token::mint(gw),
-
6247 token::amount(gwAUD(1000)),
-
6248 txflags(tfTransferable),
-
6249 token::xferFee(10));
-
6250 env.close();
-
6251
-
6252 // Give alice the needed trust line, but freeze it.
-
6253 env(trust(gw, alice["AUD"](999), tfSetFreeze));
-
6254 env.close();
-
6255
-
6256 // Issuer (alice) must have a trust line for the offered funds
-
6257 // and the trust line may not be frozen.
-
6258 env(token::mint(alice),
-
6259 token::amount(gwAUD(1000)),
-
6260 txflags(tfTransferable),
-
6261 token::xferFee(10),
+
6229 token::amount(gwAUD(1000)),
+
6230 txflags(tfTransferable),
+
6231 token::xferFee(10),
+
6232 ter(tecNO_LINE));
+
6233 env.close();
+
6234 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6235
+
6236 // If the IOU issuer and the NFToken issuer are the same,
+
6237 // then that issuer does not need a trust line to accept their
+
6238 // fee.
+
6239 env(token::mint(gw),
+
6240 token::amount(gwAUD(1000)),
+
6241 txflags(tfTransferable),
+
6242 token::xferFee(10));
+
6243 env.close();
+
6244
+
6245 // Give alice the needed trust line, but freeze it.
+
6246 env(trust(gw, alice["AUD"](999), tfSetFreeze));
+
6247 env.close();
+
6248
+
6249 // Issuer (alice) must have a trust line for the offered funds
+
6250 // and the trust line may not be frozen.
+
6251 env(token::mint(alice),
+
6252 token::amount(gwAUD(1000)),
+
6253 txflags(tfTransferable),
+
6254 token::xferFee(10),
+
6255 ter(tecFROZEN));
+
6256 env.close();
+
6257 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6258
+
6259 // Seller (alice) must have a trust line may not be frozen.
+
6260 env(token::mint(alice),
+
6261 token::amount(gwAUD(1000)),
6262 ter(tecFROZEN));
6263 env.close();
6264 BEAST_EXPECT(ownerCount(env, alice) == 0);
6265
-
6266 // Seller (alice) must have a trust line may not be frozen.
-
6267 env(token::mint(alice),
-
6268 token::amount(gwAUD(1000)),
-
6269 ter(tecFROZEN));
-
6270 env.close();
-
6271 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6272
-
6273 // Unfreeze alice's trustline.
-
6274 env(trust(gw, alice["AUD"](999), tfClearFreeze));
-
6275 env.close();
-
6276 }
-
6277
-
6278 {
-
6279 // check reserve
-
6280 auto const acctReserve = env.current()->fees().reserve;
-
6281 auto const incReserve = env.current()->fees().increment;
-
6282
-
6283 env.fund(acctReserve + incReserve, bob);
-
6284 env.close();
-
6285
-
6286 // doesn't have reserve for 2 objects (NFTokenPage, Offer)
-
6287 env(token::mint(bob),
-
6288 token::amount(XRP(0)),
- -
6290 env.close();
-
6291
-
6292 // have reserve for NFTokenPage, Offer
-
6293 env(pay(env.master, bob, incReserve + drops(baseFee)));
-
6294 env.close();
-
6295 env(token::mint(bob), token::amount(XRP(0)));
-
6296 env.close();
-
6297
-
6298 // doesn't have reserve for Offer
-
6299 env(pay(env.master, bob, drops(baseFee)));
-
6300 env.close();
-
6301 env(token::mint(bob),
-
6302 token::amount(XRP(0)),
- -
6304 env.close();
+
6266 // Unfreeze alice's trustline.
+
6267 env(trust(gw, alice["AUD"](999), tfClearFreeze));
+
6268 env.close();
+
6269 }
+
6270
+
6271 {
+
6272 // check reserve
+
6273 auto const acctReserve = env.current()->fees().reserve;
+
6274 auto const incReserve = env.current()->fees().increment;
+
6275
+
6276 env.fund(acctReserve + incReserve, bob);
+
6277 env.close();
+
6278
+
6279 // doesn't have reserve for 2 objects (NFTokenPage, Offer)
+
6280 env(token::mint(bob),
+
6281 token::amount(XRP(0)),
+ +
6283 env.close();
+
6284
+
6285 // have reserve for NFTokenPage, Offer
+
6286 env(pay(env.master, bob, incReserve + drops(baseFee)));
+
6287 env.close();
+
6288 env(token::mint(bob), token::amount(XRP(0)));
+
6289 env.close();
+
6290
+
6291 // doesn't have reserve for Offer
+
6292 env(pay(env.master, bob, drops(baseFee)));
+
6293 env.close();
+
6294 env(token::mint(bob),
+
6295 token::amount(XRP(0)),
+ +
6297 env.close();
+
6298
+
6299 // have reserve for Offer
+
6300 env(pay(env.master, bob, incReserve + drops(baseFee)));
+
6301 env.close();
+
6302 env(token::mint(bob), token::amount(XRP(0)));
+
6303 env.close();
+
6304 }
6305
-
6306 // have reserve for Offer
-
6307 env(pay(env.master, bob, incReserve + drops(baseFee)));
-
6308 env.close();
-
6309 env(token::mint(bob), token::amount(XRP(0)));
-
6310 env.close();
-
6311 }
-
6312
-
6313 // Amount field specified
-
6314 BEAST_EXPECT(ownerCount(env, alice) == 0);
-
6315 env(token::mint(alice), token::amount(XRP(10)));
-
6316 BEAST_EXPECT(ownerCount(env, alice) == 2);
+
6306 // Amount field specified
+
6307 BEAST_EXPECT(ownerCount(env, alice) == 0);
+
6308 env(token::mint(alice), token::amount(XRP(10)));
+
6309 BEAST_EXPECT(ownerCount(env, alice) == 2);
+
6310 env.close();
+
6311
+
6312 // Amount field and Destination field, Expiration field specified
+
6313 env(token::mint(alice),
+
6314 token::amount(XRP(10)),
+
6315 token::destination(buyer),
+
6316 token::expiration(lastClose(env) + 25));
6317 env.close();
6318
-
6319 // Amount field and Destination field, Expiration field specified
-
6320 env(token::mint(alice),
-
6321 token::amount(XRP(10)),
-
6322 token::destination(buyer),
-
6323 token::expiration(lastClose(env) + 25));
-
6324 env.close();
-
6325
-
6326 // With TransferFee field
-
6327 env(trust(alice, gwAUD(1000)));
+
6319 // With TransferFee field
+
6320 env(trust(alice, gwAUD(1000)));
+
6321 env.close();
+
6322 env(token::mint(alice),
+
6323 token::amount(gwAUD(1)),
+
6324 token::destination(buyer),
+
6325 token::expiration(lastClose(env) + 25),
+
6326 txflags(tfTransferable),
+
6327 token::xferFee(10));
6328 env.close();
-
6329 env(token::mint(alice),
-
6330 token::amount(gwAUD(1)),
-
6331 token::destination(buyer),
-
6332 token::expiration(lastClose(env) + 25),
-
6333 txflags(tfTransferable),
-
6334 token::xferFee(10));
-
6335 env.close();
-
6336
-
6337 // Can be canceled by the issuer.
-
6338 env(token::mint(alice),
-
6339 token::amount(XRP(10)),
-
6340 token::destination(buyer),
-
6341 token::expiration(lastClose(env) + 25));
-
6342 uint256 const offerAliceSellsToBuyer =
-
6343 keylet::nftoffer(alice, env.seq(alice)).key;
-
6344 env(token::cancelOffer(alice, {offerAliceSellsToBuyer}));
-
6345 env.close();
-
6346
-
6347 // Can be canceled by the buyer.
-
6348 env(token::mint(buyer),
-
6349 token::amount(XRP(10)),
-
6350 token::destination(alice),
-
6351 token::expiration(lastClose(env) + 25));
-
6352 uint256 const offerBuyerSellsToAlice =
-
6353 keylet::nftoffer(buyer, env.seq(buyer)).key;
-
6354 env(token::cancelOffer(alice, {offerBuyerSellsToAlice}));
-
6355 env.close();
-
6356
-
6357 env(token::setMinter(issuer, minter));
-
6358 env.close();
-
6359
-
6360 // Minter will have offer not issuer
-
6361 BEAST_EXPECT(ownerCount(env, minter) == 0);
-
6362 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
6363 env(token::mint(minter),
-
6364 token::issuer(issuer),
-
6365 token::amount(drops(1)));
-
6366 env.close();
-
6367 BEAST_EXPECT(ownerCount(env, minter) == 2);
-
6368 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
6369 }
+
6329
+
6330 // Can be canceled by the issuer.
+
6331 env(token::mint(alice),
+
6332 token::amount(XRP(10)),
+
6333 token::destination(buyer),
+
6334 token::expiration(lastClose(env) + 25));
+
6335 uint256 const offerAliceSellsToBuyer =
+
6336 keylet::nftoffer(alice, env.seq(alice)).key;
+
6337 env(token::cancelOffer(alice, {offerAliceSellsToBuyer}));
+
6338 env.close();
+
6339
+
6340 // Can be canceled by the buyer.
+
6341 env(token::mint(buyer),
+
6342 token::amount(XRP(10)),
+
6343 token::destination(alice),
+
6344 token::expiration(lastClose(env) + 25));
+
6345 uint256 const offerBuyerSellsToAlice =
+
6346 keylet::nftoffer(buyer, env.seq(buyer)).key;
+
6347 env(token::cancelOffer(alice, {offerBuyerSellsToAlice}));
+
6348 env.close();
+
6349
+
6350 env(token::setMinter(issuer, minter));
+
6351 env.close();
+
6352
+
6353 // Minter will have offer not issuer
+
6354 BEAST_EXPECT(ownerCount(env, minter) == 0);
+
6355 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
6356 env(token::mint(minter),
+
6357 token::issuer(issuer),
+
6358 token::amount(drops(1)));
+
6359 env.close();
+
6360 BEAST_EXPECT(ownerCount(env, minter) == 2);
+
6361 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
6362 }
+
6363
+
6364 Env env{*this, features};
+
6365 Account const alice("alice");
+
6366
+
6367 env.fund(XRP(1000000), alice);
+
6368
+
6369 TER const offerCreateTER = temBAD_AMOUNT;
6370
-
6371 Env env{*this, features};
-
6372 Account const alice("alice");
-
6373
-
6374 env.fund(XRP(1000000), alice);
-
6375
-
6376 TER const offerCreateTER = temBAD_AMOUNT;
-
6377
-
6378 // Make offers with negative amounts for the NFTs
-
6379 env(token::mint(alice), token::amount(XRP(-2)), ter(offerCreateTER));
-
6380 env.close();
-
6381 }
+
6371 // Make offers with negative amounts for the NFTs
+
6372 env(token::mint(alice), token::amount(XRP(-2)), ter(offerCreateTER));
+
6373 env.close();
+
6374 }
-
6382
-
6383 void
-
- -
6385 {
-
6386 // `nftoken_id` is added in the `tx` response for NFTokenMint and
-
6387 // NFTokenAcceptOffer.
-
6388 //
-
6389 // `nftoken_ids` is added in the `tx` response for NFTokenCancelOffer
-
6390 //
-
6391 // `offer_id` is added in the `tx` response for NFTokenCreateOffer
-
6392 //
-
6393 // The values of these fields are dependent on the NFTokenID/OfferID
-
6394 // changed in its corresponding transaction. We want to validate each
-
6395 // transaction to make sure the synethic fields hold the right values.
-
6396
-
6397 testcase("Test synthetic fields from JSON response");
-
6398
-
6399 using namespace test::jtx;
-
6400
-
6401 Account const alice{"alice"};
-
6402 Account const bob{"bob"};
-
6403 Account const broker{"broker"};
-
6404
-
6405 Env env{*this, features};
-
6406 env.fund(XRP(10000), alice, bob, broker);
-
6407 env.close();
-
6408
-
6409 // Verify `nftoken_id` value equals to the NFTokenID that was
-
6410 // changed in the most recent NFTokenMint or NFTokenAcceptOffer
-
6411 // transaction
-
6412 auto verifyNFTokenID = [&](uint256 const& actualNftID) {
-
6413 // Get the hash for the most recent transaction.
-
6414 std::string const txHash{
-
6415 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
-
6416
-
6417 env.close();
-
6418 Json::Value const meta =
-
6419 env.rpc("tx", txHash)[jss::result][jss::meta];
-
6420
-
6421 // Expect nftokens_id field
-
6422 if (!BEAST_EXPECT(meta.isMember(jss::nftoken_id)))
-
6423 return;
+
6375
+
6376 void
+
+ +
6378 {
+
6379 // `nftoken_id` is added in the `tx` response for NFTokenMint and
+
6380 // NFTokenAcceptOffer.
+
6381 //
+
6382 // `nftoken_ids` is added in the `tx` response for NFTokenCancelOffer
+
6383 //
+
6384 // `offer_id` is added in the `tx` response for NFTokenCreateOffer
+
6385 //
+
6386 // The values of these fields are dependent on the NFTokenID/OfferID
+
6387 // changed in its corresponding transaction. We want to validate each
+
6388 // transaction to make sure the synethic fields hold the right values.
+
6389
+
6390 testcase("Test synthetic fields from JSON response");
+
6391
+
6392 using namespace test::jtx;
+
6393
+
6394 Account const alice{"alice"};
+
6395 Account const bob{"bob"};
+
6396 Account const broker{"broker"};
+
6397
+
6398 Env env{*this, features};
+
6399 env.fund(XRP(10000), alice, bob, broker);
+
6400 env.close();
+
6401
+
6402 // Verify `nftoken_id` value equals to the NFTokenID that was
+
6403 // changed in the most recent NFTokenMint or NFTokenAcceptOffer
+
6404 // transaction
+
6405 auto verifyNFTokenID = [&](uint256 const& actualNftID) {
+
6406 // Get the hash for the most recent transaction.
+
6407 std::string const txHash{
+
6408 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
+
6409
+
6410 env.close();
+
6411 Json::Value const meta =
+
6412 env.rpc("tx", txHash)[jss::result][jss::meta];
+
6413
+
6414 // Expect nftokens_id field
+
6415 if (!BEAST_EXPECT(meta.isMember(jss::nftoken_id)))
+
6416 return;
+
6417
+
6418 // Check the value of NFT ID in the meta with the
+
6419 // actual value
+
6420 uint256 nftID;
+
6421 BEAST_EXPECT(nftID.parseHex(meta[jss::nftoken_id].asString()));
+
6422 BEAST_EXPECT(nftID == actualNftID);
+
6423 };
6424
-
6425 // Check the value of NFT ID in the meta with the
-
6426 // actual value
-
6427 uint256 nftID;
-
6428 BEAST_EXPECT(nftID.parseHex(meta[jss::nftoken_id].asString()));
-
6429 BEAST_EXPECT(nftID == actualNftID);
-
6430 };
-
6431
-
6432 // Verify `nftoken_ids` value equals to the NFTokenIDs that were
-
6433 // changed in the most recent NFTokenCancelOffer transaction
-
6434 auto verifyNFTokenIDsInCancelOffer =
-
6435 [&](std::vector<uint256> actualNftIDs) {
-
6436 // Get the hash for the most recent transaction.
-
6437 std::string const txHash{
-
6438 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
-
6439
-
6440 env.close();
-
6441 Json::Value const meta =
-
6442 env.rpc("tx", txHash)[jss::result][jss::meta];
-
6443
-
6444 // Expect nftokens_ids field and verify the values
-
6445 if (!BEAST_EXPECT(meta.isMember(jss::nftoken_ids)))
-
6446 return;
-
6447
-
6448 // Convert NFT IDs from Json::Value to uint256
-
6449 std::vector<uint256> metaIDs;
- -
6451 meta[jss::nftoken_ids].begin(),
-
6452 meta[jss::nftoken_ids].end(),
-
6453 std::back_inserter(metaIDs),
-
6454 [this](Json::Value id) {
-
6455 uint256 nftID;
-
6456 BEAST_EXPECT(nftID.parseHex(id.asString()));
-
6457 return nftID;
-
6458 });
+
6425 // Verify `nftoken_ids` value equals to the NFTokenIDs that were
+
6426 // changed in the most recent NFTokenCancelOffer transaction
+
6427 auto verifyNFTokenIDsInCancelOffer =
+
6428 [&](std::vector<uint256> actualNftIDs) {
+
6429 // Get the hash for the most recent transaction.
+
6430 std::string const txHash{
+
6431 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
+
6432
+
6433 env.close();
+
6434 Json::Value const meta =
+
6435 env.rpc("tx", txHash)[jss::result][jss::meta];
+
6436
+
6437 // Expect nftokens_ids field and verify the values
+
6438 if (!BEAST_EXPECT(meta.isMember(jss::nftoken_ids)))
+
6439 return;
+
6440
+
6441 // Convert NFT IDs from Json::Value to uint256
+
6442 std::vector<uint256> metaIDs;
+ +
6444 meta[jss::nftoken_ids].begin(),
+
6445 meta[jss::nftoken_ids].end(),
+
6446 std::back_inserter(metaIDs),
+
6447 [this](Json::Value id) {
+
6448 uint256 nftID;
+
6449 BEAST_EXPECT(nftID.parseHex(id.asString()));
+
6450 return nftID;
+
6451 });
+
6452
+
6453 // Sort both array to prepare for comparison
+
6454 std::sort(metaIDs.begin(), metaIDs.end());
+
6455 std::sort(actualNftIDs.begin(), actualNftIDs.end());
+
6456
+
6457 // Make sure the expect number of NFTs is correct
+
6458 BEAST_EXPECT(metaIDs.size() == actualNftIDs.size());
6459
-
6460 // Sort both array to prepare for comparison
-
6461 std::sort(metaIDs.begin(), metaIDs.end());
-
6462 std::sort(actualNftIDs.begin(), actualNftIDs.end());
-
6463
-
6464 // Make sure the expect number of NFTs is correct
-
6465 BEAST_EXPECT(metaIDs.size() == actualNftIDs.size());
-
6466
-
6467 // Check the value of NFT ID in the meta with the
-
6468 // actual values
-
6469 for (size_t i = 0; i < metaIDs.size(); ++i)
-
6470 BEAST_EXPECT(metaIDs[i] == actualNftIDs[i]);
-
6471 };
+
6460 // Check the value of NFT ID in the meta with the
+
6461 // actual values
+
6462 for (size_t i = 0; i < metaIDs.size(); ++i)
+
6463 BEAST_EXPECT(metaIDs[i] == actualNftIDs[i]);
+
6464 };
+
6465
+
6466 // Verify `offer_id` value equals to the offerID that was
+
6467 // changed in the most recent NFTokenCreateOffer tx
+
6468 auto verifyNFTokenOfferID = [&](uint256 const& offerID) {
+
6469 // Get the hash for the most recent transaction.
+
6470 std::string const txHash{
+
6471 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
6472
-
6473 // Verify `offer_id` value equals to the offerID that was
-
6474 // changed in the most recent NFTokenCreateOffer tx
-
6475 auto verifyNFTokenOfferID = [&](uint256 const& offerID) {
-
6476 // Get the hash for the most recent transaction.
-
6477 std::string const txHash{
-
6478 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
-
6479
-
6480 env.close();
-
6481 Json::Value const meta =
-
6482 env.rpc("tx", txHash)[jss::result][jss::meta];
-
6483
-
6484 // Expect offer_id field and verify the value
-
6485 if (!BEAST_EXPECT(meta.isMember(jss::offer_id)))
-
6486 return;
-
6487
-
6488 uint256 metaOfferID;
-
6489 BEAST_EXPECT(metaOfferID.parseHex(meta[jss::offer_id].asString()));
-
6490 BEAST_EXPECT(metaOfferID == offerID);
-
6491 };
-
6492
-
6493 // Check new fields in tx meta when for all NFTtransactions
-
6494 {
-
6495 // Alice mints 2 NFTs
-
6496 // Verify the NFTokenIDs are correct in the NFTokenMint tx meta
-
6497 uint256 const nftId1{
-
6498 token::getNextID(env, alice, 0u, tfTransferable)};
-
6499 env(token::mint(alice, 0u), txflags(tfTransferable));
-
6500 env.close();
-
6501 verifyNFTokenID(nftId1);
-
6502
-
6503 uint256 const nftId2{
-
6504 token::getNextID(env, alice, 0u, tfTransferable)};
-
6505 env(token::mint(alice, 0u), txflags(tfTransferable));
-
6506 env.close();
-
6507 verifyNFTokenID(nftId2);
-
6508
-
6509 // Alice creates one sell offer for each NFT
-
6510 // Verify the offer indexes are correct in the NFTokenCreateOffer tx
-
6511 // meta
-
6512 uint256 const aliceOfferIndex1 =
+
6473 env.close();
+
6474 Json::Value const meta =
+
6475 env.rpc("tx", txHash)[jss::result][jss::meta];
+
6476
+
6477 // Expect offer_id field and verify the value
+
6478 if (!BEAST_EXPECT(meta.isMember(jss::offer_id)))
+
6479 return;
+
6480
+
6481 uint256 metaOfferID;
+
6482 BEAST_EXPECT(metaOfferID.parseHex(meta[jss::offer_id].asString()));
+
6483 BEAST_EXPECT(metaOfferID == offerID);
+
6484 };
+
6485
+
6486 // Check new fields in tx meta when for all NFTtransactions
+
6487 {
+
6488 // Alice mints 2 NFTs
+
6489 // Verify the NFTokenIDs are correct in the NFTokenMint tx meta
+
6490 uint256 const nftId1{
+
6491 token::getNextID(env, alice, 0u, tfTransferable)};
+
6492 env(token::mint(alice, 0u), txflags(tfTransferable));
+
6493 env.close();
+
6494 verifyNFTokenID(nftId1);
+
6495
+
6496 uint256 const nftId2{
+
6497 token::getNextID(env, alice, 0u, tfTransferable)};
+
6498 env(token::mint(alice, 0u), txflags(tfTransferable));
+
6499 env.close();
+
6500 verifyNFTokenID(nftId2);
+
6501
+
6502 // Alice creates one sell offer for each NFT
+
6503 // Verify the offer indexes are correct in the NFTokenCreateOffer tx
+
6504 // meta
+
6505 uint256 const aliceOfferIndex1 =
+
6506 keylet::nftoffer(alice, env.seq(alice)).key;
+
6507 env(token::createOffer(alice, nftId1, drops(1)),
+
6508 txflags(tfSellNFToken));
+
6509 env.close();
+
6510 verifyNFTokenOfferID(aliceOfferIndex1);
+
6511
+
6512 uint256 const aliceOfferIndex2 =
6513 keylet::nftoffer(alice, env.seq(alice)).key;
-
6514 env(token::createOffer(alice, nftId1, drops(1)),
+
6514 env(token::createOffer(alice, nftId2, drops(1)),
6515 txflags(tfSellNFToken));
6516 env.close();
-
6517 verifyNFTokenOfferID(aliceOfferIndex1);
+
6517 verifyNFTokenOfferID(aliceOfferIndex2);
6518
-
6519 uint256 const aliceOfferIndex2 =
-
6520 keylet::nftoffer(alice, env.seq(alice)).key;
-
6521 env(token::createOffer(alice, nftId2, drops(1)),
-
6522 txflags(tfSellNFToken));
-
6523 env.close();
-
6524 verifyNFTokenOfferID(aliceOfferIndex2);
-
6525
-
6526 // Alice cancels two offers she created
-
6527 // Verify the NFTokenIDs are correct in the NFTokenCancelOffer tx
-
6528 // meta
-
6529 env(token::cancelOffer(
-
6530 alice, {aliceOfferIndex1, aliceOfferIndex2}));
-
6531 env.close();
-
6532 verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
-
6533
-
6534 // Bobs creates a buy offer for nftId1
-
6535 // Verify the offer id is correct in the NFTokenCreateOffer tx meta
-
6536 auto const bobBuyOfferIndex =
-
6537 keylet::nftoffer(bob, env.seq(bob)).key;
-
6538 env(token::createOffer(bob, nftId1, drops(1)), token::owner(alice));
-
6539 env.close();
-
6540 verifyNFTokenOfferID(bobBuyOfferIndex);
+
6519 // Alice cancels two offers she created
+
6520 // Verify the NFTokenIDs are correct in the NFTokenCancelOffer tx
+
6521 // meta
+
6522 env(token::cancelOffer(
+
6523 alice, {aliceOfferIndex1, aliceOfferIndex2}));
+
6524 env.close();
+
6525 verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
+
6526
+
6527 // Bobs creates a buy offer for nftId1
+
6528 // Verify the offer id is correct in the NFTokenCreateOffer tx meta
+
6529 auto const bobBuyOfferIndex =
+
6530 keylet::nftoffer(bob, env.seq(bob)).key;
+
6531 env(token::createOffer(bob, nftId1, drops(1)), token::owner(alice));
+
6532 env.close();
+
6533 verifyNFTokenOfferID(bobBuyOfferIndex);
+
6534
+
6535 // Alice accepts bob's buy offer
+
6536 // Verify the NFTokenID is correct in the NFTokenAcceptOffer tx meta
+
6537 env(token::acceptBuyOffer(alice, bobBuyOfferIndex));
+
6538 env.close();
+
6539 verifyNFTokenID(nftId1);
+
6540 }
6541
-
6542 // Alice accepts bob's buy offer
-
6543 // Verify the NFTokenID is correct in the NFTokenAcceptOffer tx meta
-
6544 env(token::acceptBuyOffer(alice, bobBuyOfferIndex));
-
6545 env.close();
-
6546 verifyNFTokenID(nftId1);
-
6547 }
-
6548
-
6549 // Check `nftoken_ids` in brokered mode
-
6550 {
-
6551 // Alice mints a NFT
-
6552 uint256 const nftId{
-
6553 token::getNextID(env, alice, 0u, tfTransferable)};
-
6554 env(token::mint(alice, 0u), txflags(tfTransferable));
-
6555 env.close();
-
6556 verifyNFTokenID(nftId);
-
6557
-
6558 // Alice creates sell offer and set broker as destination
-
6559 uint256 const offerAliceToBroker =
-
6560 keylet::nftoffer(alice, env.seq(alice)).key;
-
6561 env(token::createOffer(alice, nftId, drops(1)),
-
6562 token::destination(broker),
-
6563 txflags(tfSellNFToken));
+
6542 // Check `nftoken_ids` in brokered mode
+
6543 {
+
6544 // Alice mints a NFT
+
6545 uint256 const nftId{
+
6546 token::getNextID(env, alice, 0u, tfTransferable)};
+
6547 env(token::mint(alice, 0u), txflags(tfTransferable));
+
6548 env.close();
+
6549 verifyNFTokenID(nftId);
+
6550
+
6551 // Alice creates sell offer and set broker as destination
+
6552 uint256 const offerAliceToBroker =
+
6553 keylet::nftoffer(alice, env.seq(alice)).key;
+
6554 env(token::createOffer(alice, nftId, drops(1)),
+
6555 token::destination(broker),
+
6556 txflags(tfSellNFToken));
+
6557 env.close();
+
6558 verifyNFTokenOfferID(offerAliceToBroker);
+
6559
+
6560 // Bob creates buy offer
+
6561 uint256 const offerBobToBroker =
+
6562 keylet::nftoffer(bob, env.seq(bob)).key;
+
6563 env(token::createOffer(bob, nftId, drops(1)), token::owner(alice));
6564 env.close();
-
6565 verifyNFTokenOfferID(offerAliceToBroker);
+
6565 verifyNFTokenOfferID(offerBobToBroker);
6566
-
6567 // Bob creates buy offer
-
6568 uint256 const offerBobToBroker =
-
6569 keylet::nftoffer(bob, env.seq(bob)).key;
-
6570 env(token::createOffer(bob, nftId, drops(1)), token::owner(alice));
-
6571 env.close();
-
6572 verifyNFTokenOfferID(offerBobToBroker);
+
6567 // Check NFTokenID meta for NFTokenAcceptOffer in brokered mode
+
6568 env(token::brokerOffers(
+
6569 broker, offerBobToBroker, offerAliceToBroker));
+
6570 env.close();
+
6571 verifyNFTokenID(nftId);
+
6572 }
6573
-
6574 // Check NFTokenID meta for NFTokenAcceptOffer in brokered mode
-
6575 env(token::brokerOffers(
-
6576 broker, offerBobToBroker, offerAliceToBroker));
-
6577 env.close();
-
6578 verifyNFTokenID(nftId);
-
6579 }
-
6580
-
6581 // Check if there are no duplicate nft id in Cancel transactions where
-
6582 // multiple offers are cancelled for the same NFT
-
6583 {
-
6584 // Alice mints a NFT
-
6585 uint256 const nftId{
-
6586 token::getNextID(env, alice, 0u, tfTransferable)};
-
6587 env(token::mint(alice, 0u), txflags(tfTransferable));
-
6588 env.close();
-
6589 verifyNFTokenID(nftId);
-
6590
-
6591 // Alice creates 2 sell offers for the same NFT
-
6592 uint256 const aliceOfferIndex1 =
+
6574 // Check if there are no duplicate nft id in Cancel transactions where
+
6575 // multiple offers are cancelled for the same NFT
+
6576 {
+
6577 // Alice mints a NFT
+
6578 uint256 const nftId{
+
6579 token::getNextID(env, alice, 0u, tfTransferable)};
+
6580 env(token::mint(alice, 0u), txflags(tfTransferable));
+
6581 env.close();
+
6582 verifyNFTokenID(nftId);
+
6583
+
6584 // Alice creates 2 sell offers for the same NFT
+
6585 uint256 const aliceOfferIndex1 =
+
6586 keylet::nftoffer(alice, env.seq(alice)).key;
+
6587 env(token::createOffer(alice, nftId, drops(1)),
+
6588 txflags(tfSellNFToken));
+
6589 env.close();
+
6590 verifyNFTokenOfferID(aliceOfferIndex1);
+
6591
+
6592 uint256 const aliceOfferIndex2 =
6593 keylet::nftoffer(alice, env.seq(alice)).key;
6594 env(token::createOffer(alice, nftId, drops(1)),
6595 txflags(tfSellNFToken));
6596 env.close();
-
6597 verifyNFTokenOfferID(aliceOfferIndex1);
+
6597 verifyNFTokenOfferID(aliceOfferIndex2);
6598
-
6599 uint256 const aliceOfferIndex2 =
-
6600 keylet::nftoffer(alice, env.seq(alice)).key;
-
6601 env(token::createOffer(alice, nftId, drops(1)),
-
6602 txflags(tfSellNFToken));
+
6599 // Make sure the metadata only has 1 nft id, since both offers are
+
6600 // for the same nft
+
6601 env(token::cancelOffer(
+
6602 alice, {aliceOfferIndex1, aliceOfferIndex2}));
6603 env.close();
-
6604 verifyNFTokenOfferID(aliceOfferIndex2);
-
6605
-
6606 // Make sure the metadata only has 1 nft id, since both offers are
-
6607 // for the same nft
-
6608 env(token::cancelOffer(
-
6609 alice, {aliceOfferIndex1, aliceOfferIndex2}));
-
6610 env.close();
-
6611 verifyNFTokenIDsInCancelOffer({nftId});
-
6612 }
-
6613
-
6614 if (features[featureNFTokenMintOffer])
-
6615 {
-
6616 uint256 const aliceMintWithOfferIndex1 =
-
6617 keylet::nftoffer(alice, env.seq(alice)).key;
-
6618 env(token::mint(alice), token::amount(XRP(0)));
-
6619 env.close();
-
6620 verifyNFTokenOfferID(aliceMintWithOfferIndex1);
-
6621 }
-
6622 }
+
6604 verifyNFTokenIDsInCancelOffer({nftId});
+
6605 }
+
6606
+
6607 if (features[featureNFTokenMintOffer])
+
6608 {
+
6609 uint256 const aliceMintWithOfferIndex1 =
+
6610 keylet::nftoffer(alice, env.seq(alice)).key;
+
6611 env(token::mint(alice), token::amount(XRP(0)));
+
6612 env.close();
+
6613 verifyNFTokenOfferID(aliceMintWithOfferIndex1);
+
6614 }
+
6615 }
+
6616
+
6617 void
+
+ +
6619 {
+
6620 testcase("Test buyer reserve when accepting an offer");
+
6621
+
6622 using namespace test::jtx;
6623
-
6624 void
-
- -
6626 {
-
6627 testcase("Test buyer reserve when accepting an offer");
-
6628
-
6629 using namespace test::jtx;
-
6630
-
6631 // Lambda that mints an NFT and then creates a sell offer
-
6632 auto mintAndCreateSellOffer = [](test::jtx::Env& env,
-
6633 test::jtx::Account const& acct,
-
6634 STAmount const amt) -> uint256 {
-
6635 // acct mints a NFT
-
6636 uint256 const nftId{
-
6637 token::getNextID(env, acct, 0u, tfTransferable)};
-
6638 env(token::mint(acct, 0u), txflags(tfTransferable));
-
6639 env.close();
-
6640
-
6641 // acct makes an sell offer
-
6642 uint256 const sellOfferIndex =
-
6643 keylet::nftoffer(acct, env.seq(acct)).key;
-
6644 env(token::createOffer(acct, nftId, amt), txflags(tfSellNFToken));
-
6645 env.close();
-
6646
-
6647 return sellOfferIndex;
-
6648 };
+
6624 // Lambda that mints an NFT and then creates a sell offer
+
6625 auto mintAndCreateSellOffer = [](test::jtx::Env& env,
+
6626 test::jtx::Account const& acct,
+
6627 STAmount const amt) -> uint256 {
+
6628 // acct mints a NFT
+
6629 uint256 const nftId{
+
6630 token::getNextID(env, acct, 0u, tfTransferable)};
+
6631 env(token::mint(acct, 0u), txflags(tfTransferable));
+
6632 env.close();
+
6633
+
6634 // acct makes an sell offer
+
6635 uint256 const sellOfferIndex =
+
6636 keylet::nftoffer(acct, env.seq(acct)).key;
+
6637 env(token::createOffer(acct, nftId, amt), txflags(tfSellNFToken));
+
6638 env.close();
+
6639
+
6640 return sellOfferIndex;
+
6641 };
+
6642
+
6643 // Test the behaviors when the buyer makes an accept offer, both before
+
6644 // and after enabling the amendment. Exercises the precise number of
+
6645 // reserve in drops that's required to accept the offer
+
6646 {
+
6647 Account const alice{"alice"};
+
6648 Account const bob{"bob"};
6649
-
6650 // Test the behaviors when the buyer makes an accept offer, both before
-
6651 // and after enabling the amendment. Exercises the precise number of
-
6652 // reserve in drops that's required to accept the offer
-
6653 {
-
6654 Account const alice{"alice"};
-
6655 Account const bob{"bob"};
-
6656
-
6657 Env env{*this, features};
-
6658 auto const acctReserve = env.current()->fees().reserve;
-
6659 auto const incReserve = env.current()->fees().increment;
-
6660 auto const baseFee = env.current()->fees().base;
+
6650 Env env{*this, features};
+
6651 auto const acctReserve = env.current()->fees().reserve;
+
6652 auto const incReserve = env.current()->fees().increment;
+
6653 auto const baseFee = env.current()->fees().base;
+
6654
+
6655 env.fund(XRP(10000), alice);
+
6656 env.close();
+
6657
+
6658 // Bob is funded with minimum XRP reserve
+
6659 env.fund(acctReserve, bob);
+
6660 env.close();
6661
-
6662 env.fund(XRP(10000), alice);
-
6663 env.close();
-
6664
-
6665 // Bob is funded with minimum XRP reserve
-
6666 env.fund(acctReserve, bob);
-
6667 env.close();
+
6662 // alice mints an NFT and create a sell offer for 0 XRP
+
6663 auto const sellOfferIndex =
+
6664 mintAndCreateSellOffer(env, alice, XRP(0));
+
6665
+
6666 // Bob owns no object
+
6667 BEAST_EXPECT(ownerCount(env, bob) == 0);
6668
-
6669 // alice mints an NFT and create a sell offer for 0 XRP
-
6670 auto const sellOfferIndex =
-
6671 mintAndCreateSellOffer(env, alice, XRP(0));
-
6672
-
6673 // Bob owns no object
-
6674 BEAST_EXPECT(ownerCount(env, bob) == 0);
-
6675
-
6676 // Without fixNFTokenReserve amendment, when bob accepts an NFT sell
-
6677 // offer, he can get the NFT free of reserve
-
6678 if (!features[fixNFTokenReserve])
-
6679 {
-
6680 // Bob is able to accept the offer
-
6681 env(token::acceptSellOffer(bob, sellOfferIndex));
-
6682 env.close();
-
6683
-
6684 // Bob now owns an extra objects
-
6685 BEAST_EXPECT(ownerCount(env, bob) == 1);
-
6686
-
6687 // This is the wrong behavior, since Bob should need at least
-
6688 // one incremental reserve.
-
6689 }
-
6690 // With fixNFTokenReserve, bob can no longer accept the offer unless
-
6691 // there is enough reserve. A detail to note is that NFTs(sell
-
6692 // offer) will not allow one to go below the reserve requirement,
-
6693 // because buyer's balance is computed after the transaction fee is
-
6694 // deducted. This means that the reserve requirement will be `base
-
6695 // fee` drops higher than normal.
-
6696 else
-
6697 {
-
6698 // Bob is not able to accept the offer with only the account
-
6699 // reserve (200,000,000 drops)
-
6700 env(token::acceptSellOffer(bob, sellOfferIndex),
- -
6702 env.close();
-
6703
-
6704 // after prev transaction, Bob owns `200M - base fee` drops due
-
6705 // to burnt tx fee
-
6706
-
6707 BEAST_EXPECT(ownerCount(env, bob) == 0);
-
6708
-
6709 // Send bob an increment reserve and base fee (to make up for
-
6710 // the transaction fee burnt from the prev failed tx) Bob now
-
6711 // owns 250,000,000 drops
-
6712 env(pay(env.master, bob, incReserve + drops(baseFee)));
-
6713 env.close();
-
6714
-
6715 // However, this transaction will still fail because the reserve
-
6716 // requirement is `base fee` drops higher
-
6717 env(token::acceptSellOffer(bob, sellOfferIndex),
- -
6719 env.close();
-
6720
-
6721 // Send bob `base fee * 2` drops
-
6722 // Bob now owns `250M + base fee` drops
-
6723 env(pay(env.master, bob, drops(baseFee * 2)));
-
6724 env.close();
-
6725
-
6726 // Bob is now able to accept the offer
-
6727 env(token::acceptSellOffer(bob, sellOfferIndex));
-
6728 env.close();
-
6729
-
6730 BEAST_EXPECT(ownerCount(env, bob) == 1);
-
6731 }
-
6732 }
-
6733
-
6734 // Now exercise the scenario when the buyer accepts
-
6735 // many sell offers
-
6736 {
-
6737 Account const alice{"alice"};
-
6738 Account const bob{"bob"};
+
6669 // Without fixNFTokenReserve amendment, when bob accepts an NFT sell
+
6670 // offer, he can get the NFT free of reserve
+
6671 if (!features[fixNFTokenReserve])
+
6672 {
+
6673 // Bob is able to accept the offer
+
6674 env(token::acceptSellOffer(bob, sellOfferIndex));
+
6675 env.close();
+
6676
+
6677 // Bob now owns an extra objects
+
6678 BEAST_EXPECT(ownerCount(env, bob) == 1);
+
6679
+
6680 // This is the wrong behavior, since Bob should need at least
+
6681 // one incremental reserve.
+
6682 }
+
6683 // With fixNFTokenReserve, bob can no longer accept the offer unless
+
6684 // there is enough reserve. A detail to note is that NFTs(sell
+
6685 // offer) will not allow one to go below the reserve requirement,
+
6686 // because buyer's balance is computed after the transaction fee is
+
6687 // deducted. This means that the reserve requirement will be `base
+
6688 // fee` drops higher than normal.
+
6689 else
+
6690 {
+
6691 // Bob is not able to accept the offer with only the account
+
6692 // reserve (200,000,000 drops)
+
6693 env(token::acceptSellOffer(bob, sellOfferIndex),
+ +
6695 env.close();
+
6696
+
6697 // after prev transaction, Bob owns `200M - base fee` drops due
+
6698 // to burnt tx fee
+
6699
+
6700 BEAST_EXPECT(ownerCount(env, bob) == 0);
+
6701
+
6702 // Send bob an increment reserve and base fee (to make up for
+
6703 // the transaction fee burnt from the prev failed tx) Bob now
+
6704 // owns 250,000,000 drops
+
6705 env(pay(env.master, bob, incReserve + drops(baseFee)));
+
6706 env.close();
+
6707
+
6708 // However, this transaction will still fail because the reserve
+
6709 // requirement is `base fee` drops higher
+
6710 env(token::acceptSellOffer(bob, sellOfferIndex),
+ +
6712 env.close();
+
6713
+
6714 // Send bob `base fee * 2` drops
+
6715 // Bob now owns `250M + base fee` drops
+
6716 env(pay(env.master, bob, drops(baseFee * 2)));
+
6717 env.close();
+
6718
+
6719 // Bob is now able to accept the offer
+
6720 env(token::acceptSellOffer(bob, sellOfferIndex));
+
6721 env.close();
+
6722
+
6723 BEAST_EXPECT(ownerCount(env, bob) == 1);
+
6724 }
+
6725 }
+
6726
+
6727 // Now exercise the scenario when the buyer accepts
+
6728 // many sell offers
+
6729 {
+
6730 Account const alice{"alice"};
+
6731 Account const bob{"bob"};
+
6732
+
6733 Env env{*this, features};
+
6734 auto const acctReserve = env.current()->fees().reserve;
+
6735 auto const incReserve = env.current()->fees().increment;
+
6736
+
6737 env.fund(XRP(10000), alice);
+
6738 env.close();
6739
-
6740 Env env{*this, features};
-
6741 auto const acctReserve = env.current()->fees().reserve;
-
6742 auto const incReserve = env.current()->fees().increment;
-
6743
-
6744 env.fund(XRP(10000), alice);
-
6745 env.close();
-
6746
-
6747 env.fund(acctReserve + XRP(1), bob);
-
6748 env.close();
-
6749
-
6750 if (!features[fixNFTokenReserve])
-
6751 {
-
6752 // Bob can accept many NFTs without having a single reserve!
-
6753 for (size_t i = 0; i < 200; i++)
-
6754 {
-
6755 // alice mints an NFT and creates a sell offer for 0 XRP
-
6756 auto const sellOfferIndex =
-
6757 mintAndCreateSellOffer(env, alice, XRP(0));
-
6758
-
6759 // Bob is able to accept the offer
-
6760 env(token::acceptSellOffer(bob, sellOfferIndex));
-
6761 env.close();
-
6762 }
-
6763 }
-
6764 else
-
6765 {
-
6766 // alice mints the first NFT and creates a sell offer for 0 XRP
-
6767 auto const sellOfferIndex1 =
-
6768 mintAndCreateSellOffer(env, alice, XRP(0));
-
6769
-
6770 // Bob cannot accept this offer because he doesn't have the
-
6771 // reserve for the NFT
-
6772 env(token::acceptSellOffer(bob, sellOfferIndex1),
- -
6774 env.close();
-
6775
-
6776 // Give bob enough reserve
-
6777 env(pay(env.master, bob, drops(incReserve)));
-
6778 env.close();
-
6779
-
6780 BEAST_EXPECT(ownerCount(env, bob) == 0);
-
6781
-
6782 // Bob now owns his first NFT
-
6783 env(token::acceptSellOffer(bob, sellOfferIndex1));
-
6784 env.close();
-
6785
-
6786 BEAST_EXPECT(ownerCount(env, bob) == 1);
-
6787
-
6788 // alice now mints 31 more NFTs and creates an offer for each
-
6789 // NFT, then sells to bob
-
6790 for (size_t i = 0; i < 31; i++)
-
6791 {
-
6792 // alice mints an NFT and creates a sell offer for 0 XRP
-
6793 auto const sellOfferIndex =
-
6794 mintAndCreateSellOffer(env, alice, XRP(0));
-
6795
-
6796 // Bob can accept the offer because the new NFT is stored in
-
6797 // an existing NFTokenPage so no new reserve is required
-
6798 env(token::acceptSellOffer(bob, sellOfferIndex));
-
6799 env.close();
-
6800 }
+
6740 env.fund(acctReserve + XRP(1), bob);
+
6741 env.close();
+
6742
+
6743 if (!features[fixNFTokenReserve])
+
6744 {
+
6745 // Bob can accept many NFTs without having a single reserve!
+
6746 for (size_t i = 0; i < 200; i++)
+
6747 {
+
6748 // alice mints an NFT and creates a sell offer for 0 XRP
+
6749 auto const sellOfferIndex =
+
6750 mintAndCreateSellOffer(env, alice, XRP(0));
+
6751
+
6752 // Bob is able to accept the offer
+
6753 env(token::acceptSellOffer(bob, sellOfferIndex));
+
6754 env.close();
+
6755 }
+
6756 }
+
6757 else
+
6758 {
+
6759 // alice mints the first NFT and creates a sell offer for 0 XRP
+
6760 auto const sellOfferIndex1 =
+
6761 mintAndCreateSellOffer(env, alice, XRP(0));
+
6762
+
6763 // Bob cannot accept this offer because he doesn't have the
+
6764 // reserve for the NFT
+
6765 env(token::acceptSellOffer(bob, sellOfferIndex1),
+ +
6767 env.close();
+
6768
+
6769 // Give bob enough reserve
+
6770 env(pay(env.master, bob, drops(incReserve)));
+
6771 env.close();
+
6772
+
6773 BEAST_EXPECT(ownerCount(env, bob) == 0);
+
6774
+
6775 // Bob now owns his first NFT
+
6776 env(token::acceptSellOffer(bob, sellOfferIndex1));
+
6777 env.close();
+
6778
+
6779 BEAST_EXPECT(ownerCount(env, bob) == 1);
+
6780
+
6781 // alice now mints 31 more NFTs and creates an offer for each
+
6782 // NFT, then sells to bob
+
6783 for (size_t i = 0; i < 31; i++)
+
6784 {
+
6785 // alice mints an NFT and creates a sell offer for 0 XRP
+
6786 auto const sellOfferIndex =
+
6787 mintAndCreateSellOffer(env, alice, XRP(0));
+
6788
+
6789 // Bob can accept the offer because the new NFT is stored in
+
6790 // an existing NFTokenPage so no new reserve is required
+
6791 env(token::acceptSellOffer(bob, sellOfferIndex));
+
6792 env.close();
+
6793 }
+
6794
+
6795 BEAST_EXPECT(ownerCount(env, bob) == 1);
+
6796
+
6797 // alice now mints the 33rd NFT and creates an sell offer for 0
+
6798 // XRP
+
6799 auto const sellOfferIndex33 =
+
6800 mintAndCreateSellOffer(env, alice, XRP(0));
6801
-
6802 BEAST_EXPECT(ownerCount(env, bob) == 1);
-
6803
-
6804 // alice now mints the 33rd NFT and creates an sell offer for 0
-
6805 // XRP
-
6806 auto const sellOfferIndex33 =
-
6807 mintAndCreateSellOffer(env, alice, XRP(0));
-
6808
-
6809 // Bob fails to accept this NFT because he does not have enough
-
6810 // reserve for a new NFTokenPage
-
6811 env(token::acceptSellOffer(bob, sellOfferIndex33),
- -
6813 env.close();
-
6814
-
6815 // Send bob incremental reserve
-
6816 env(pay(env.master, bob, drops(incReserve)));
-
6817 env.close();
-
6818
-
6819 // Bob now has enough reserve to accept the offer and now
-
6820 // owns one more NFTokenPage
-
6821 env(token::acceptSellOffer(bob, sellOfferIndex33));
-
6822 env.close();
-
6823
-
6824 BEAST_EXPECT(ownerCount(env, bob) == 2);
-
6825 }
-
6826 }
-
6827
-
6828 // Test the behavior when the seller accepts a buy offer.
-
6829 // The behavior should not change regardless whether fixNFTokenReserve
-
6830 // is enabled or not, since the ledger is able to guard against
-
6831 // free NFTokenPages when buy offer is accepted. This is merely an
-
6832 // additional test to exercise existing offer behavior.
-
6833 {
-
6834 Account const alice{"alice"};
-
6835 Account const bob{"bob"};
-
6836
-
6837 Env env{*this, features};
-
6838 auto const acctReserve = env.current()->fees().reserve;
-
6839 auto const incReserve = env.current()->fees().increment;
-
6840 auto const baseFee = env.current()->fees().base;
-
6841
-
6842 env.fund(XRP(10000), alice);
-
6843 env.close();
-
6844
-
6845 // Bob is funded with account reserve + increment reserve + 1 XRP
-
6846 // increment reserve is for the buy offer, and 1 XRP is for offer
-
6847 // price
-
6848 env.fund(acctReserve + incReserve + XRP(1), bob);
-
6849 env.close();
-
6850
-
6851 // Alice mints a NFT
-
6852 uint256 const nftId{
-
6853 token::getNextID(env, alice, 0u, tfTransferable)};
-
6854 env(token::mint(alice, 0u), txflags(tfTransferable));
-
6855 env.close();
-
6856
-
6857 // Bob makes a buy offer for 1 XRP
-
6858 auto const buyOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key;
-
6859 env(token::createOffer(bob, nftId, XRP(1)), token::owner(alice));
+
6802 // Bob fails to accept this NFT because he does not have enough
+
6803 // reserve for a new NFTokenPage
+
6804 env(token::acceptSellOffer(bob, sellOfferIndex33),
+ +
6806 env.close();
+
6807
+
6808 // Send bob incremental reserve
+
6809 env(pay(env.master, bob, drops(incReserve)));
+
6810 env.close();
+
6811
+
6812 // Bob now has enough reserve to accept the offer and now
+
6813 // owns one more NFTokenPage
+
6814 env(token::acceptSellOffer(bob, sellOfferIndex33));
+
6815 env.close();
+
6816
+
6817 BEAST_EXPECT(ownerCount(env, bob) == 2);
+
6818 }
+
6819 }
+
6820
+
6821 // Test the behavior when the seller accepts a buy offer.
+
6822 // The behavior should not change regardless whether fixNFTokenReserve
+
6823 // is enabled or not, since the ledger is able to guard against
+
6824 // free NFTokenPages when buy offer is accepted. This is merely an
+
6825 // additional test to exercise existing offer behavior.
+
6826 {
+
6827 Account const alice{"alice"};
+
6828 Account const bob{"bob"};
+
6829
+
6830 Env env{*this, features};
+
6831 auto const acctReserve = env.current()->fees().reserve;
+
6832 auto const incReserve = env.current()->fees().increment;
+
6833 auto const baseFee = env.current()->fees().base;
+
6834
+
6835 env.fund(XRP(10000), alice);
+
6836 env.close();
+
6837
+
6838 // Bob is funded with account reserve + increment reserve + 1 XRP
+
6839 // increment reserve is for the buy offer, and 1 XRP is for offer
+
6840 // price
+
6841 env.fund(acctReserve + incReserve + XRP(1), bob);
+
6842 env.close();
+
6843
+
6844 // Alice mints a NFT
+
6845 uint256 const nftId{
+
6846 token::getNextID(env, alice, 0u, tfTransferable)};
+
6847 env(token::mint(alice, 0u), txflags(tfTransferable));
+
6848 env.close();
+
6849
+
6850 // Bob makes a buy offer for 1 XRP
+
6851 auto const buyOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key;
+
6852 env(token::createOffer(bob, nftId, XRP(1)), token::owner(alice));
+
6853 env.close();
+
6854
+
6855 // accepting the buy offer fails because bob's balance is `base fee`
+
6856 // drops lower than the required amount, since the previous tx burnt
+
6857 // drops for tx fee.
+
6858 env(token::acceptBuyOffer(alice, buyOfferIndex),
+
6860 env.close();
6861
-
6862 // accepting the buy offer fails because bob's balance is `base fee`
-
6863 // drops lower than the required amount, since the previous tx burnt
-
6864 // drops for tx fee.
-
6865 env(token::acceptBuyOffer(alice, buyOfferIndex),
- -
6867 env.close();
-
6868
-
6869 // send Bob `base fee` drops
-
6870 env(pay(env.master, bob, drops(baseFee)));
-
6871 env.close();
-
6872
-
6873 // Now bob can buy the offer
-
6874 env(token::acceptBuyOffer(alice, buyOfferIndex));
-
6875 env.close();
-
6876 }
-
6877
-
6878 // Test the reserve behavior in brokered mode.
-
6879 // The behavior should not change regardless whether fixNFTokenReserve
-
6880 // is enabled or not, since the ledger is able to guard against
-
6881 // free NFTokenPages in brokered mode. This is merely an
-
6882 // additional test to exercise existing offer behavior.
-
6883 {
-
6884 Account const alice{"alice"};
-
6885 Account const bob{"bob"};
-
6886 Account const broker{"broker"};
-
6887
-
6888 Env env{*this, features};
-
6889 auto const acctReserve = env.current()->fees().reserve;
-
6890 auto const incReserve = env.current()->fees().increment;
-
6891 auto const baseFee = env.current()->fees().base;
-
6892
-
6893 env.fund(XRP(10000), alice, broker);
-
6894 env.close();
-
6895
-
6896 // Bob is funded with account reserve + incr reserve + 1 XRP(offer
-
6897 // price)
-
6898 env.fund(acctReserve + incReserve + XRP(1), bob);
-
6899 env.close();
-
6900
-
6901 // Alice mints a NFT
-
6902 uint256 const nftId{
-
6903 token::getNextID(env, alice, 0u, tfTransferable)};
-
6904 env(token::mint(alice, 0u), txflags(tfTransferable));
-
6905 env.close();
-
6906
-
6907 // Alice creates sell offer and set broker as destination
-
6908 uint256 const offerAliceToBroker =
-
6909 keylet::nftoffer(alice, env.seq(alice)).key;
-
6910 env(token::createOffer(alice, nftId, XRP(1)),
-
6911 token::destination(broker),
-
6912 txflags(tfSellNFToken));
-
6913 env.close();
-
6914
-
6915 // Bob creates buy offer
-
6916 uint256 const offerBobToBroker =
-
6917 keylet::nftoffer(bob, env.seq(bob)).key;
-
6918 env(token::createOffer(bob, nftId, XRP(1)), token::owner(alice));
-
6919 env.close();
-
6920
-
6921 // broker offers.
-
6922 // Returns insufficient funds, because bob burnt tx fee when he
-
6923 // created his buy offer, which makes his spendable balance to be
-
6924 // less than the required amount.
-
6925 env(token::brokerOffers(
-
6926 broker, offerBobToBroker, offerAliceToBroker),
- -
6928 env.close();
-
6929
-
6930 // send Bob `base fee` drops
-
6931 env(pay(env.master, bob, drops(baseFee)));
-
6932 env.close();
-
6933
-
6934 // broker offers.
-
6935 env(token::brokerOffers(
-
6936 broker, offerBobToBroker, offerAliceToBroker));
-
6937 env.close();
-
6938 }
-
6939 }
+
6862 // send Bob `base fee` drops
+
6863 env(pay(env.master, bob, drops(baseFee)));
+
6864 env.close();
+
6865
+
6866 // Now bob can buy the offer
+
6867 env(token::acceptBuyOffer(alice, buyOfferIndex));
+
6868 env.close();
+
6869 }
+
6870
+
6871 // Test the reserve behavior in brokered mode.
+
6872 // The behavior should not change regardless whether fixNFTokenReserve
+
6873 // is enabled or not, since the ledger is able to guard against
+
6874 // free NFTokenPages in brokered mode. This is merely an
+
6875 // additional test to exercise existing offer behavior.
+
6876 {
+
6877 Account const alice{"alice"};
+
6878 Account const bob{"bob"};
+
6879 Account const broker{"broker"};
+
6880
+
6881 Env env{*this, features};
+
6882 auto const acctReserve = env.current()->fees().reserve;
+
6883 auto const incReserve = env.current()->fees().increment;
+
6884 auto const baseFee = env.current()->fees().base;
+
6885
+
6886 env.fund(XRP(10000), alice, broker);
+
6887 env.close();
+
6888
+
6889 // Bob is funded with account reserve + incr reserve + 1 XRP(offer
+
6890 // price)
+
6891 env.fund(acctReserve + incReserve + XRP(1), bob);
+
6892 env.close();
+
6893
+
6894 // Alice mints a NFT
+
6895 uint256 const nftId{
+
6896 token::getNextID(env, alice, 0u, tfTransferable)};
+
6897 env(token::mint(alice, 0u), txflags(tfTransferable));
+
6898 env.close();
+
6899
+
6900 // Alice creates sell offer and set broker as destination
+
6901 uint256 const offerAliceToBroker =
+
6902 keylet::nftoffer(alice, env.seq(alice)).key;
+
6903 env(token::createOffer(alice, nftId, XRP(1)),
+
6904 token::destination(broker),
+
6905 txflags(tfSellNFToken));
+
6906 env.close();
+
6907
+
6908 // Bob creates buy offer
+
6909 uint256 const offerBobToBroker =
+
6910 keylet::nftoffer(bob, env.seq(bob)).key;
+
6911 env(token::createOffer(bob, nftId, XRP(1)), token::owner(alice));
+
6912 env.close();
+
6913
+
6914 // broker offers.
+
6915 // Returns insufficient funds, because bob burnt tx fee when he
+
6916 // created his buy offer, which makes his spendable balance to be
+
6917 // less than the required amount.
+
6918 env(token::brokerOffers(
+
6919 broker, offerBobToBroker, offerAliceToBroker),
+ +
6921 env.close();
+
6922
+
6923 // send Bob `base fee` drops
+
6924 env(pay(env.master, bob, drops(baseFee)));
+
6925 env.close();
+
6926
+
6927 // broker offers.
+
6928 env(token::brokerOffers(
+
6929 broker, offerBobToBroker, offerAliceToBroker));
+
6930 env.close();
+
6931 }
+
6932 }
+
6933
+
6934 void
+
+ +
6936 {
+
6937 testcase("Test fix unasked for auto-trustline.");
+
6938
+
6939 using namespace test::jtx;
6940
-
6941 void
-
- -
6943 {
-
6944 testcase("Test fix unasked for auto-trustline.");
-
6945
-
6946 using namespace test::jtx;
-
6947
-
6948 Account const issuer{"issuer"};
-
6949 Account const becky{"becky"};
-
6950 Account const cheri{"cheri"};
-
6951 Account const gw("gw");
-
6952 IOU const gwAUD(gw["AUD"]);
-
6953
-
6954 // This test case covers issue...
-
6955 // https://github.com/XRPLF/rippled/issues/4925
-
6956 //
-
6957 // For an NFToken with a transfer fee, the issuer must be able to
-
6958 // accept the transfer fee or else a transfer should fail. If the
-
6959 // NFToken is transferred for a non-XRP asset, then the issuer must
-
6960 // have a trustline to that asset to receive the fee.
-
6961 //
-
6962 // This test looks at a situation where issuer would get a trustline
-
6963 // for the fee without the issuer's consent. Here are the steps:
-
6964 // 1. Issuer has a trustline (i.e., USD)
-
6965 // 2. Issuer mints NFToken with transfer fee.
-
6966 // 3. Becky acquires the NFToken, paying with XRP.
-
6967 // 4. Becky creates offer to sell NFToken for USD(100).
-
6968 // 5. Issuer deletes trustline for USD.
-
6969 // 6. Carol buys NFToken from Becky for USD(100).
-
6970 // 7. The transfer fee from Carol's purchase re-establishes issuer's
-
6971 // USD trustline.
-
6972 //
-
6973 // The fixEnforceNFTokenTrustline amendment addresses this oversight.
-
6974 //
-
6975 // We run this test case both with and without
-
6976 // fixEnforceNFTokenTrustline enabled so we can see the change
-
6977 // in behavior.
-
6978 //
-
6979 // In both cases we remove the fixRemoveNFTokenAutoTrustLine amendment.
-
6980 // Otherwise we can't create NFTokens with tfTrustLine enabled.
-
6981 FeatureBitset const localFeatures =
-
6982 features - fixRemoveNFTokenAutoTrustLine;
-
6983 for (FeatureBitset feats :
-
6984 {localFeatures - fixEnforceNFTokenTrustline,
-
6985 localFeatures | fixEnforceNFTokenTrustline})
-
6986 {
-
6987 Env env{*this, feats};
-
6988 env.fund(XRP(1000), issuer, becky, cheri, gw);
+
6941 Account const issuer{"issuer"};
+
6942 Account const becky{"becky"};
+
6943 Account const cheri{"cheri"};
+
6944 Account const gw("gw");
+
6945 IOU const gwAUD(gw["AUD"]);
+
6946
+
6947 // This test case covers issue...
+
6948 // https://github.com/XRPLF/rippled/issues/4925
+
6949 //
+
6950 // For an NFToken with a transfer fee, the issuer must be able to
+
6951 // accept the transfer fee or else a transfer should fail. If the
+
6952 // NFToken is transferred for a non-XRP asset, then the issuer must
+
6953 // have a trustline to that asset to receive the fee.
+
6954 //
+
6955 // This test looks at a situation where issuer would get a trustline
+
6956 // for the fee without the issuer's consent. Here are the steps:
+
6957 // 1. Issuer has a trustline (i.e., USD)
+
6958 // 2. Issuer mints NFToken with transfer fee.
+
6959 // 3. Becky acquires the NFToken, paying with XRP.
+
6960 // 4. Becky creates offer to sell NFToken for USD(100).
+
6961 // 5. Issuer deletes trustline for USD.
+
6962 // 6. Carol buys NFToken from Becky for USD(100).
+
6963 // 7. The transfer fee from Carol's purchase re-establishes issuer's
+
6964 // USD trustline.
+
6965 //
+
6966 // The fixEnforceNFTokenTrustline amendment addresses this oversight.
+
6967 //
+
6968 // We run this test case both with and without
+
6969 // fixEnforceNFTokenTrustline enabled so we can see the change
+
6970 // in behavior.
+
6971 //
+
6972 // In both cases we remove the fixRemoveNFTokenAutoTrustLine amendment.
+
6973 // Otherwise we can't create NFTokens with tfTrustLine enabled.
+
6974 FeatureBitset const localFeatures =
+
6975 features - fixRemoveNFTokenAutoTrustLine;
+
6976 for (FeatureBitset feats :
+
6977 {localFeatures - fixEnforceNFTokenTrustline,
+
6978 localFeatures | fixEnforceNFTokenTrustline})
+
6979 {
+
6980 Env env{*this, feats};
+
6981 env.fund(XRP(1000), issuer, becky, cheri, gw);
+
6982 env.close();
+
6983
+
6984 // Set trust lines so becky and cheri can use gw's currency.
+
6985 env(trust(becky, gwAUD(1000)));
+
6986 env(trust(cheri, gwAUD(1000)));
+
6987 env.close();
+
6988 env(pay(gw, cheri, gwAUD(500)));
6989 env.close();
6990
-
6991 // Set trust lines so becky and cheri can use gw's currency.
-
6992 env(trust(becky, gwAUD(1000)));
-
6993 env(trust(cheri, gwAUD(1000)));
-
6994 env.close();
-
6995 env(pay(gw, cheri, gwAUD(500)));
-
6996 env.close();
-
6997
-
6998 // issuer creates two NFTs: one with and one without AutoTrustLine.
-
6999 std::uint16_t xferFee = 5000; // 5%
-
7000 uint256 const nftAutoTrustID{token::getNextID(
-
7001 env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)};
+
6991 // issuer creates two NFTs: one with and one without AutoTrustLine.
+
6992 std::uint16_t xferFee = 5000; // 5%
+
6993 uint256 const nftAutoTrustID{token::getNextID(
+
6994 env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)};
+
6995 env(token::mint(issuer, 0u),
+
6996 token::xferFee(xferFee),
+
6997 txflags(tfTransferable | tfTrustLine));
+
6998 env.close();
+
6999
+
7000 uint256 const nftNoAutoTrustID{
+
7001 token::getNextID(env, issuer, 0u, tfTransferable, xferFee)};
7002 env(token::mint(issuer, 0u),
7003 token::xferFee(xferFee),
-
7004 txflags(tfTransferable | tfTrustLine));
+
7004 txflags(tfTransferable));
7005 env.close();
7006
-
7007 uint256 const nftNoAutoTrustID{
-
7008 token::getNextID(env, issuer, 0u, tfTransferable, xferFee)};
-
7009 env(token::mint(issuer, 0u),
-
7010 token::xferFee(xferFee),
-
7011 txflags(tfTransferable));
-
7012 env.close();
+
7007 // becky buys the nfts for 1 drop each.
+
7008 {
+
7009 uint256 const beckyBuyOfferIndex1 =
+
7010 keylet::nftoffer(becky, env.seq(becky)).key;
+
7011 env(token::createOffer(becky, nftAutoTrustID, drops(1)),
+
7012 token::owner(issuer));
7013
-
7014 // becky buys the nfts for 1 drop each.
-
7015 {
-
7016 uint256 const beckyBuyOfferIndex1 =
-
7017 keylet::nftoffer(becky, env.seq(becky)).key;
-
7018 env(token::createOffer(becky, nftAutoTrustID, drops(1)),
-
7019 token::owner(issuer));
-
7020
-
7021 uint256 const beckyBuyOfferIndex2 =
-
7022 keylet::nftoffer(becky, env.seq(becky)).key;
-
7023 env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
-
7024 token::owner(issuer));
-
7025
-
7026 env.close();
-
7027 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1));
-
7028 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2));
-
7029 env.close();
-
7030 }
+
7014 uint256 const beckyBuyOfferIndex2 =
+
7015 keylet::nftoffer(becky, env.seq(becky)).key;
+
7016 env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
+
7017 token::owner(issuer));
+
7018
+
7019 env.close();
+
7020 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1));
+
7021 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2));
+
7022 env.close();
+
7023 }
+
7024
+
7025 // becky creates offers to sell the nfts for AUD.
+
7026 uint256 const beckyAutoTrustOfferIndex =
+
7027 keylet::nftoffer(becky, env.seq(becky)).key;
+
7028 env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)),
+
7029 txflags(tfSellNFToken));
+
7030 env.close();
7031
-
7032 // becky creates offers to sell the nfts for AUD.
-
7033 uint256 const beckyAutoTrustOfferIndex =
-
7034 keylet::nftoffer(becky, env.seq(becky)).key;
-
7035 env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)),
-
7036 txflags(tfSellNFToken));
+
7032 // Creating an offer for the NFToken without tfTrustLine fails
+
7033 // because issuer does not have a trust line for AUD.
+
7034 env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
+
7035 txflags(tfSellNFToken),
+
7036 ter(tecNO_LINE));
7037 env.close();
7038
-
7039 // Creating an offer for the NFToken without tfTrustLine fails
-
7040 // because issuer does not have a trust line for AUD.
-
7041 env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
-
7042 txflags(tfSellNFToken),
-
7043 ter(tecNO_LINE));
-
7044 env.close();
+
7039 // issuer creates a trust line. Now the offer create for the
+
7040 // NFToken without tfTrustLine succeeds.
+
7041 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
7042 env(trust(issuer, gwAUD(1000)));
+
7043 env.close();
+
7044 BEAST_EXPECT(ownerCount(env, issuer) == 1);
7045
-
7046 // issuer creates a trust line. Now the offer create for the
-
7047 // NFToken without tfTrustLine succeeds.
-
7048 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
7049 env(trust(issuer, gwAUD(1000)));
+
7046 uint256 const beckyNoAutoTrustOfferIndex =
+
7047 keylet::nftoffer(becky, env.seq(becky)).key;
+
7048 env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
+
7049 txflags(tfSellNFToken));
7050 env.close();
-
7051 BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
7052
-
7053 uint256 const beckyNoAutoTrustOfferIndex =
-
7054 keylet::nftoffer(becky, env.seq(becky)).key;
-
7055 env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
-
7056 txflags(tfSellNFToken));
-
7057 env.close();
-
7058
-
7059 // Now that the offers are in place, issuer removes the trustline.
-
7060 BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
7061 env(trust(issuer, gwAUD(0)));
-
7062 env.close();
-
7063 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
7064
-
7065 // cheri attempts to accept becky's offers. Behavior with the
-
7066 // AutoTrustline NFT is uniform: issuer gets a new trust line.
-
7067 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
-
7068 env.close();
-
7069
-
7070 // Here's evidence that issuer got the new AUD trust line.
-
7071 BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
7072 BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5));
-
7073
-
7074 // issuer once again removes the trust line for AUD.
-
7075 env(pay(issuer, gw, gwAUD(5)));
-
7076 env.close();
-
7077 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
7078
-
7079 // cheri attempts to accept the NoAutoTrustLine NFT. Behavior
-
7080 // depends on whether fixEnforceNFTokenTrustline is enabled.
-
7081 if (feats[fixEnforceNFTokenTrustline])
-
7082 {
-
7083 // With fixEnforceNFTokenTrustline cheri can't accept the
-
7084 // offer because issuer could not get their transfer fee
-
7085 // without the appropriate trustline.
-
7086 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex),
-
7087 ter(tecNO_LINE));
-
7088 env.close();
-
7089
-
7090 // But if issuer re-establishes the trustline then the offer
-
7091 // can be accepted.
-
7092 env(trust(issuer, gwAUD(1000)));
-
7093 env.close();
-
7094 BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
7095
+
7051
+
7052 // Now that the offers are in place, issuer removes the trustline.
+
7053 BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
7054 env(trust(issuer, gwAUD(0)));
+
7055 env.close();
+
7056 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
7057
+
7058 // cheri attempts to accept becky's offers. Behavior with the
+
7059 // AutoTrustline NFT is uniform: issuer gets a new trust line.
+
7060 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
+
7061 env.close();
+
7062
+
7063 // Here's evidence that issuer got the new AUD trust line.
+
7064 BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
7065 BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5));
+
7066
+
7067 // issuer once again removes the trust line for AUD.
+
7068 env(pay(issuer, gw, gwAUD(5)));
+
7069 env.close();
+
7070 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
7071
+
7072 // cheri attempts to accept the NoAutoTrustLine NFT. Behavior
+
7073 // depends on whether fixEnforceNFTokenTrustline is enabled.
+
7074 if (feats[fixEnforceNFTokenTrustline])
+
7075 {
+
7076 // With fixEnforceNFTokenTrustline cheri can't accept the
+
7077 // offer because issuer could not get their transfer fee
+
7078 // without the appropriate trustline.
+
7079 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex),
+
7080 ter(tecNO_LINE));
+
7081 env.close();
+
7082
+
7083 // But if issuer re-establishes the trustline then the offer
+
7084 // can be accepted.
+
7085 env(trust(issuer, gwAUD(1000)));
+
7086 env.close();
+
7087 BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
7088
+
7089 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
+
7090 env.close();
+
7091 }
+
7092 else
+
7093 {
+
7094 // Without fixEnforceNFTokenTrustline the offer just works
+
7095 // and issuer gets a trustline that they did not request.
7096 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
7097 env.close();
7098 }
-
7099 else
-
7100 {
-
7101 // Without fixEnforceNFTokenTrustline the offer just works
-
7102 // and issuer gets a trustline that they did not request.
-
7103 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
-
7104 env.close();
-
7105 }
-
7106 BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
7107 BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5));
-
7108 } // for feats
-
7109 }
+
7099 BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
7100 BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5));
+
7101 } // for feats
+
7102 }
+
7103
+
7104 void
+
+ +
7106 {
+
7107 testcase("Test fix NFT issuer is IOU issuer");
+
7108
+
7109 using namespace test::jtx;
7110
-
7111 void
-
- -
7113 {
-
7114 testcase("Test fix NFT issuer is IOU issuer");
+
7111 Account const issuer{"issuer"};
+
7112 Account const becky{"becky"};
+
7113 Account const cheri{"cheri"};
+
7114 IOU const isISU(issuer["ISU"]);
7115
-
7116 using namespace test::jtx;
-
7117
-
7118 Account const issuer{"issuer"};
-
7119 Account const becky{"becky"};
-
7120 Account const cheri{"cheri"};
-
7121 IOU const isISU(issuer["ISU"]);
-
7122
-
7123 // This test case covers issue...
-
7124 // https://github.com/XRPLF/rippled/issues/4941
-
7125 //
-
7126 // If an NFToken has a transfer fee then, when an offer is accepted,
-
7127 // a portion of the sale price goes to the issuer.
-
7128 //
-
7129 // It is possible for an issuer to issue both an IOU (for remittances)
-
7130 // and NFTokens. If the issuer's IOU is used to pay for the transfer
-
7131 // of one of the issuer's NFTokens, then paying the fee for that
-
7132 // transfer will fail with a tecNO_LINE.
-
7133 //
-
7134 // The problem occurs because the NFT code looks for a trust line to
-
7135 // pay the transfer fee. However the issuer of an IOU does not need
-
7136 // a trust line to accept their own issuance and, in fact, is not
-
7137 // allowed to have a trust line to themselves.
-
7138 //
-
7139 // This test looks at a situation where transfer of an NFToken is
-
7140 // prevented by this bug:
-
7141 // 1. Issuer issues an IOU (e.g, isISU).
-
7142 // 2. Becky and Cheri get trust lines for, and acquire, some isISU.
-
7143 // 3. Issuer mints NFToken with transfer fee.
-
7144 // 4. Becky acquires the NFToken, paying with XRP.
-
7145 // 5. Becky attempts to create an offer to sell the NFToken for
-
7146 // isISU(100). The attempt fails with `tecNO_LINE`.
-
7147 //
-
7148 // The featureNFTokenMintOffer amendment addresses this oversight.
-
7149 //
-
7150 // We remove the fixRemoveNFTokenAutoTrustLine amendment. Otherwise
-
7151 // we can't create NFTokens with tfTrustLine enabled.
-
7152 FeatureBitset const localFeatures =
-
7153 features - fixRemoveNFTokenAutoTrustLine;
-
7154
-
7155 Env env{*this, localFeatures};
-
7156 env.fund(XRP(1000), issuer, becky, cheri);
+
7116 // This test case covers issue...
+
7117 // https://github.com/XRPLF/rippled/issues/4941
+
7118 //
+
7119 // If an NFToken has a transfer fee then, when an offer is accepted,
+
7120 // a portion of the sale price goes to the issuer.
+
7121 //
+
7122 // It is possible for an issuer to issue both an IOU (for remittances)
+
7123 // and NFTokens. If the issuer's IOU is used to pay for the transfer
+
7124 // of one of the issuer's NFTokens, then paying the fee for that
+
7125 // transfer will fail with a tecNO_LINE.
+
7126 //
+
7127 // The problem occurs because the NFT code looks for a trust line to
+
7128 // pay the transfer fee. However the issuer of an IOU does not need
+
7129 // a trust line to accept their own issuance and, in fact, is not
+
7130 // allowed to have a trust line to themselves.
+
7131 //
+
7132 // This test looks at a situation where transfer of an NFToken is
+
7133 // prevented by this bug:
+
7134 // 1. Issuer issues an IOU (e.g, isISU).
+
7135 // 2. Becky and Cheri get trust lines for, and acquire, some isISU.
+
7136 // 3. Issuer mints NFToken with transfer fee.
+
7137 // 4. Becky acquires the NFToken, paying with XRP.
+
7138 // 5. Becky attempts to create an offer to sell the NFToken for
+
7139 // isISU(100). The attempt fails with `tecNO_LINE`.
+
7140 //
+
7141 // The featureNFTokenMintOffer amendment addresses this oversight.
+
7142 //
+
7143 // We remove the fixRemoveNFTokenAutoTrustLine amendment. Otherwise
+
7144 // we can't create NFTokens with tfTrustLine enabled.
+
7145 FeatureBitset const localFeatures =
+
7146 features - fixRemoveNFTokenAutoTrustLine;
+
7147
+
7148 Env env{*this, localFeatures};
+
7149 env.fund(XRP(1000), issuer, becky, cheri);
+
7150 env.close();
+
7151
+
7152 // Set trust lines so becky and cheri can use isISU.
+
7153 env(trust(becky, isISU(1000)));
+
7154 env(trust(cheri, isISU(1000)));
+
7155 env.close();
+
7156 env(pay(issuer, cheri, isISU(500)));
7157 env.close();
7158
-
7159 // Set trust lines so becky and cheri can use isISU.
-
7160 env(trust(becky, isISU(1000)));
-
7161 env(trust(cheri, isISU(1000)));
-
7162 env.close();
-
7163 env(pay(issuer, cheri, isISU(500)));
-
7164 env.close();
-
7165
-
7166 // issuer creates two NFTs: one with and one without AutoTrustLine.
-
7167 std::uint16_t xferFee = 5000; // 5%
-
7168 uint256 const nftAutoTrustID{token::getNextID(
-
7169 env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)};
+
7159 // issuer creates two NFTs: one with and one without AutoTrustLine.
+
7160 std::uint16_t xferFee = 5000; // 5%
+
7161 uint256 const nftAutoTrustID{token::getNextID(
+
7162 env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)};
+
7163 env(token::mint(issuer, 0u),
+
7164 token::xferFee(xferFee),
+
7165 txflags(tfTransferable | tfTrustLine));
+
7166 env.close();
+
7167
+
7168 uint256 const nftNoAutoTrustID{
+
7169 token::getNextID(env, issuer, 0u, tfTransferable, xferFee)};
7170 env(token::mint(issuer, 0u),
7171 token::xferFee(xferFee),
-
7172 txflags(tfTransferable | tfTrustLine));
+
7172 txflags(tfTransferable));
7173 env.close();
7174
-
7175 uint256 const nftNoAutoTrustID{
-
7176 token::getNextID(env, issuer, 0u, tfTransferable, xferFee)};
-
7177 env(token::mint(issuer, 0u),
-
7178 token::xferFee(xferFee),
-
7179 txflags(tfTransferable));
-
7180 env.close();
+
7175 // becky buys the nfts for 1 drop each.
+
7176 {
+
7177 uint256 const beckyBuyOfferIndex1 =
+
7178 keylet::nftoffer(becky, env.seq(becky)).key;
+
7179 env(token::createOffer(becky, nftAutoTrustID, drops(1)),
+
7180 token::owner(issuer));
7181
-
7182 // becky buys the nfts for 1 drop each.
-
7183 {
-
7184 uint256 const beckyBuyOfferIndex1 =
-
7185 keylet::nftoffer(becky, env.seq(becky)).key;
-
7186 env(token::createOffer(becky, nftAutoTrustID, drops(1)),
-
7187 token::owner(issuer));
-
7188
-
7189 uint256 const beckyBuyOfferIndex2 =
-
7190 keylet::nftoffer(becky, env.seq(becky)).key;
-
7191 env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
-
7192 token::owner(issuer));
-
7193
-
7194 env.close();
-
7195 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1));
-
7196 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2));
-
7197 env.close();
-
7198 }
-
7199
-
7200 // Behavior from here down diverges significantly based on
-
7201 // featureNFTokenMintOffer.
-
7202 if (!localFeatures[featureNFTokenMintOffer])
-
7203 {
-
7204 // Without featureNFTokenMintOffer becky simply can't
-
7205 // create an offer for a non-tfTrustLine NFToken that would
-
7206 // pay the transfer fee in issuer's own IOU.
-
7207 env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)),
-
7208 txflags(tfSellNFToken),
-
7209 ter(tecNO_LINE));
-
7210 env.close();
-
7211
-
7212 // And issuer can't create a trust line to themselves.
-
7213 env(trust(issuer, isISU(1000)), ter(temDST_IS_SRC));
-
7214 env.close();
-
7215
-
7216 // However if the NFToken has the tfTrustLine flag set,
-
7217 // then becky can create the offer.
-
7218 uint256 const beckyAutoTrustOfferIndex =
-
7219 keylet::nftoffer(becky, env.seq(becky)).key;
-
7220 env(token::createOffer(becky, nftAutoTrustID, isISU(100)),
-
7221 txflags(tfSellNFToken));
-
7222 env.close();
-
7223
-
7224 // And cheri can accept the offer.
-
7225 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
-
7226 env.close();
-
7227
-
7228 // We verify that issuer got their transfer fee by seeing that
-
7229 // ISU(5) has disappeared out of cheri's and becky's balances.
-
7230 BEAST_EXPECT(env.balance(becky, isISU) == isISU(95));
-
7231 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400));
-
7232 }
-
7233 else
-
7234 {
-
7235 // With featureNFTokenMintOffer things go better.
-
7236 // becky creates offers to sell the nfts for ISU.
-
7237 uint256 const beckyNoAutoTrustOfferIndex =
-
7238 keylet::nftoffer(becky, env.seq(becky)).key;
-
7239 env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)),
-
7240 txflags(tfSellNFToken));
-
7241 env.close();
-
7242 uint256 const beckyAutoTrustOfferIndex =
-
7243 keylet::nftoffer(becky, env.seq(becky)).key;
-
7244 env(token::createOffer(becky, nftAutoTrustID, isISU(100)),
-
7245 txflags(tfSellNFToken));
-
7246 env.close();
-
7247
-
7248 // cheri accepts becky's offers. Behavior is uniform:
-
7249 // issuer gets paid.
-
7250 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
-
7251 env.close();
-
7252
-
7253 // We verify that issuer got their transfer fee by seeing that
-
7254 // ISU(5) has disappeared out of cheri's and becky's balances.
-
7255 BEAST_EXPECT(env.balance(becky, isISU) == isISU(95));
-
7256 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400));
-
7257
-
7258 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
-
7259 env.close();
-
7260
-
7261 // We verify that issuer got their transfer fee by seeing that
-
7262 // an additional ISU(5) has disappeared out of cheri's and
-
7263 // becky's balances.
-
7264 BEAST_EXPECT(env.balance(becky, isISU) == isISU(190));
-
7265 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(300));
-
7266 }
-
7267 }
+
7182 uint256 const beckyBuyOfferIndex2 =
+
7183 keylet::nftoffer(becky, env.seq(becky)).key;
+
7184 env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
+
7185 token::owner(issuer));
+
7186
+
7187 env.close();
+
7188 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1));
+
7189 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2));
+
7190 env.close();
+
7191 }
+
7192
+
7193 // Behavior from here down diverges significantly based on
+
7194 // featureNFTokenMintOffer.
+
7195 if (!localFeatures[featureNFTokenMintOffer])
+
7196 {
+
7197 // Without featureNFTokenMintOffer becky simply can't
+
7198 // create an offer for a non-tfTrustLine NFToken that would
+
7199 // pay the transfer fee in issuer's own IOU.
+
7200 env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)),
+
7201 txflags(tfSellNFToken),
+
7202 ter(tecNO_LINE));
+
7203 env.close();
+
7204
+
7205 // And issuer can't create a trust line to themselves.
+
7206 env(trust(issuer, isISU(1000)), ter(temDST_IS_SRC));
+
7207 env.close();
+
7208
+
7209 // However if the NFToken has the tfTrustLine flag set,
+
7210 // then becky can create the offer.
+
7211 uint256 const beckyAutoTrustOfferIndex =
+
7212 keylet::nftoffer(becky, env.seq(becky)).key;
+
7213 env(token::createOffer(becky, nftAutoTrustID, isISU(100)),
+
7214 txflags(tfSellNFToken));
+
7215 env.close();
+
7216
+
7217 // And cheri can accept the offer.
+
7218 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
+
7219 env.close();
+
7220
+
7221 // We verify that issuer got their transfer fee by seeing that
+
7222 // ISU(5) has disappeared out of cheri's and becky's balances.
+
7223 BEAST_EXPECT(env.balance(becky, isISU) == isISU(95));
+
7224 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400));
+
7225 }
+
7226 else
+
7227 {
+
7228 // With featureNFTokenMintOffer things go better.
+
7229 // becky creates offers to sell the nfts for ISU.
+
7230 uint256 const beckyNoAutoTrustOfferIndex =
+
7231 keylet::nftoffer(becky, env.seq(becky)).key;
+
7232 env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)),
+
7233 txflags(tfSellNFToken));
+
7234 env.close();
+
7235 uint256 const beckyAutoTrustOfferIndex =
+
7236 keylet::nftoffer(becky, env.seq(becky)).key;
+
7237 env(token::createOffer(becky, nftAutoTrustID, isISU(100)),
+
7238 txflags(tfSellNFToken));
+
7239 env.close();
+
7240
+
7241 // cheri accepts becky's offers. Behavior is uniform:
+
7242 // issuer gets paid.
+
7243 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
+
7244 env.close();
+
7245
+
7246 // We verify that issuer got their transfer fee by seeing that
+
7247 // ISU(5) has disappeared out of cheri's and becky's balances.
+
7248 BEAST_EXPECT(env.balance(becky, isISU) == isISU(95));
+
7249 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400));
+
7250
+
7251 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
+
7252 env.close();
+
7253
+
7254 // We verify that issuer got their transfer fee by seeing that
+
7255 // an additional ISU(5) has disappeared out of cheri's and
+
7256 // becky's balances.
+
7257 BEAST_EXPECT(env.balance(becky, isISU) == isISU(190));
+
7258 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(300));
+
7259 }
+
7260 }
+
7261
+
7262 void
+
+ +
7264 {
+
7265 testcase("Test NFTokenModify");
+
7266
+
7267 using namespace test::jtx;
7268
-
7269 void
-
- -
7271 {
-
7272 testcase("Test NFTokenModify");
-
7273
-
7274 using namespace test::jtx;
-
7275
-
7276 Account const issuer{"issuer"};
-
7277 Account const alice("alice");
-
7278 Account const bob("bob");
-
7279
-
7280 bool const modifyEnabled = features[featureDynamicNFT];
-
7281
-
7282 {
-
7283 // Mint with tfMutable
-
7284 Env env{*this, features};
-
7285 env.fund(XRP(10000), issuer);
-
7286 env.close();
-
7287
-
7288 auto const expectedTer =
-
7289 modifyEnabled ? TER{tesSUCCESS} : TER{temINVALID_FLAG};
-
7290 env(token::mint(issuer, 0u), txflags(tfMutable), ter(expectedTer));
-
7291 env.close();
-
7292 }
-
7293 {
-
7294 Env env{*this, features};
-
7295 env.fund(XRP(10000), issuer);
-
7296 env.close();
-
7297
-
7298 // Modify a nftoken
-
7299 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
-
7300 if (modifyEnabled)
-
7301 {
-
7302 env(token::mint(issuer, 0u), txflags(tfMutable));
-
7303 env.close();
-
7304 BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
7305 env(token::modify(issuer, nftId));
-
7306 BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
7269 Account const issuer{"issuer"};
+
7270 Account const alice("alice");
+
7271 Account const bob("bob");
+
7272
+
7273 bool const modifyEnabled = features[featureDynamicNFT];
+
7274
+
7275 {
+
7276 // Mint with tfMutable
+
7277 Env env{*this, features};
+
7278 env.fund(XRP(10000), issuer);
+
7279 env.close();
+
7280
+
7281 auto const expectedTer =
+
7282 modifyEnabled ? TER{tesSUCCESS} : TER{temINVALID_FLAG};
+
7283 env(token::mint(issuer, 0u), txflags(tfMutable), ter(expectedTer));
+
7284 env.close();
+
7285 }
+
7286 {
+
7287 Env env{*this, features};
+
7288 env.fund(XRP(10000), issuer);
+
7289 env.close();
+
7290
+
7291 // Modify a nftoken
+
7292 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
+
7293 if (modifyEnabled)
+
7294 {
+
7295 env(token::mint(issuer, 0u), txflags(tfMutable));
+
7296 env.close();
+
7297 BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
7298 env(token::modify(issuer, nftId));
+
7299 BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
7300 }
+
7301 else
+
7302 {
+
7303 env(token::mint(issuer, 0u));
+
7304 env.close();
+
7305 env(token::modify(issuer, nftId), ter(temDISABLED));
+
7306 env.close();
7307 }
-
7308 else
-
7309 {
-
7310 env(token::mint(issuer, 0u));
-
7311 env.close();
-
7312 env(token::modify(issuer, nftId), ter(temDISABLED));
-
7313 env.close();
-
7314 }
-
7315 }
-
7316 if (!modifyEnabled)
-
7317 return;
-
7318
-
7319 {
-
7320 Env env{*this, features};
-
7321 env.fund(XRP(10000), issuer);
-
7322 env.close();
-
7323
-
7324 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
-
7325 env(token::mint(issuer, 0u), txflags(tfMutable));
-
7326 env.close();
-
7327
-
7328 // Set a negative fee. Exercises invalid preflight1.
-
7329 env(token::modify(issuer, nftId),
-
7330 fee(STAmount(10ull, true)),
-
7331 ter(temBAD_FEE));
-
7332 env.close();
-
7333
-
7334 // Invalid Flags
-
7335 env(token::modify(issuer, nftId),
-
7336 txflags(0x00000001),
-
7337 ter(temINVALID_FLAG));
-
7338
-
7339 // Invalid Owner
-
7340 env(token::modify(issuer, nftId),
-
7341 token::owner(issuer),
-
7342 ter(temMALFORMED));
-
7343 env.close();
-
7344
-
7345 // Invalid URI length = 0
-
7346 env(token::modify(issuer, nftId),
-
7347 token::uri(""),
-
7348 ter(temMALFORMED));
-
7349 env.close();
-
7350
-
7351 // Invalid URI length > 256
-
7352 env(token::modify(issuer, nftId),
-
7353 token::uri(std::string(maxTokenURILength + 1, 'q')),
-
7354 ter(temMALFORMED));
-
7355 env.close();
-
7356 }
-
7357 {
-
7358 Env env{*this, features};
-
7359 env.fund(XRP(10000), issuer, alice, bob);
-
7360 env.close();
-
7361
-
7362 {
-
7363 // NFToken not exists
-
7364 uint256 const nftIDNotExists{
-
7365 token::getNextID(env, issuer, 0u, tfMutable)};
-
7366 env.close();
-
7367
-
7368 env(token::modify(issuer, nftIDNotExists), ter(tecNO_ENTRY));
+
7308 }
+
7309 if (!modifyEnabled)
+
7310 return;
+
7311
+
7312 {
+
7313 Env env{*this, features};
+
7314 env.fund(XRP(10000), issuer);
+
7315 env.close();
+
7316
+
7317 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
+
7318 env(token::mint(issuer, 0u), txflags(tfMutable));
+
7319 env.close();
+
7320
+
7321 // Set a negative fee. Exercises invalid preflight1.
+
7322 env(token::modify(issuer, nftId),
+
7323 fee(STAmount(10ull, true)),
+
7324 ter(temBAD_FEE));
+
7325 env.close();
+
7326
+
7327 // Invalid Flags
+
7328 env(token::modify(issuer, nftId),
+
7329 txflags(0x00000001),
+
7330 ter(temINVALID_FLAG));
+
7331
+
7332 // Invalid Owner
+
7333 env(token::modify(issuer, nftId),
+
7334 token::owner(issuer),
+
7335 ter(temMALFORMED));
+
7336 env.close();
+
7337
+
7338 // Invalid URI length = 0
+
7339 env(token::modify(issuer, nftId),
+
7340 token::uri(""),
+
7341 ter(temMALFORMED));
+
7342 env.close();
+
7343
+
7344 // Invalid URI length > 256
+
7345 env(token::modify(issuer, nftId),
+
7346 token::uri(std::string(maxTokenURILength + 1, 'q')),
+
7347 ter(temMALFORMED));
+
7348 env.close();
+
7349 }
+
7350 {
+
7351 Env env{*this, features};
+
7352 env.fund(XRP(10000), issuer, alice, bob);
+
7353 env.close();
+
7354
+
7355 {
+
7356 // NFToken not exists
+
7357 uint256 const nftIDNotExists{
+
7358 token::getNextID(env, issuer, 0u, tfMutable)};
+
7359 env.close();
+
7360
+
7361 env(token::modify(issuer, nftIDNotExists), ter(tecNO_ENTRY));
+
7362 env.close();
+
7363 }
+
7364 {
+
7365 // Invalid NFToken flag
+
7366 uint256 const nftIDNotModifiable{
+
7367 token::getNextID(env, issuer, 0u)};
+
7368 env(token::mint(issuer, 0u));
7369 env.close();
-
7370 }
-
7371 {
-
7372 // Invalid NFToken flag
-
7373 uint256 const nftIDNotModifiable{
-
7374 token::getNextID(env, issuer, 0u)};
-
7375 env(token::mint(issuer, 0u));
-
7376 env.close();
-
7377
-
7378 env(token::modify(issuer, nftIDNotModifiable),
-
7379 ter(tecNO_PERMISSION));
+
7370
+
7371 env(token::modify(issuer, nftIDNotModifiable),
+
7372 ter(tecNO_PERMISSION));
+
7373 env.close();
+
7374 }
+
7375 {
+
7376 // Unauthorized account
+
7377 uint256 const nftId{
+
7378 token::getNextID(env, issuer, 0u, tfMutable)};
+
7379 env(token::mint(issuer, 0u), txflags(tfMutable));
7380 env.close();
-
7381 }
-
7382 {
-
7383 // Unauthorized account
-
7384 uint256 const nftId{
-
7385 token::getNextID(env, issuer, 0u, tfMutable)};
-
7386 env(token::mint(issuer, 0u), txflags(tfMutable));
-
7387 env.close();
-
7388
-
7389 env(token::modify(bob, nftId),
-
7390 token::owner(issuer),
-
7391 ter(tecNO_PERMISSION));
-
7392 env.close();
-
7393
-
7394 env(token::setMinter(issuer, alice));
-
7395 env.close();
-
7396
-
7397 env(token::modify(bob, nftId),
-
7398 token::owner(issuer),
-
7399 ter(tecNO_PERMISSION));
-
7400 env.close();
-
7401 }
-
7402 }
-
7403 {
-
7404 Env env{*this, features};
-
7405 env.fund(XRP(10000), issuer, alice, bob);
-
7406 env.close();
-
7407
-
7408 // modify with tfFullyCanonicalSig should success
-
7409 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
-
7410 env(token::mint(issuer, 0u), txflags(tfMutable), token::uri("uri"));
-
7411 env.close();
-
7412
-
7413 env(token::modify(issuer, nftId), txflags(tfFullyCanonicalSig));
-
7414 env.close();
-
7415 }
-
7416 {
-
7417 Env env{*this, features};
-
7418 env.fund(XRP(10000), issuer, alice, bob);
-
7419 env.close();
-
7420
-
7421 // lambda that returns the JSON form of NFTokens held by acct
-
7422 auto accountNFTs = [&env](Account const& acct) {
-
7423 Json::Value params;
-
7424 params[jss::account] = acct.human();
-
7425 params[jss::type] = "state";
-
7426 auto response =
-
7427 env.rpc("json", "account_nfts", to_string(params));
-
7428 return response[jss::result][jss::account_nfts];
-
7429 };
-
7430
-
7431 // lambda that checks for the expected URI value of an NFToken
-
7432 auto checkURI = [&accountNFTs, this](
-
7433 Account const& acct,
-
7434 char const* uri,
-
7435 int line) {
-
7436 auto const nfts = accountNFTs(acct);
-
7437 if (nfts.size() == 1)
-
7438 pass();
-
7439 else
-
7440 {
-
7441 std::ostringstream text;
-
7442 text << "checkURI: unexpected NFT count on line " << line;
-
7443 fail(text.str(), __FILE__, line);
-
7444 return;
-
7445 }
-
7446
-
7447 if (uri == nullptr)
-
7448 {
-
7449 if (!nfts[0u].isMember(sfURI.jsonName))
-
7450 pass();
-
7451 else
-
7452 {
-
7453 std::ostringstream text;
-
7454 text << "checkURI: unexpected URI present on line "
-
7455 << line;
-
7456 fail(text.str(), __FILE__, line);
-
7457 }
-
7458 return;
-
7459 }
-
7460
-
7461 if (nfts[0u][sfURI.jsonName] == strHex(std::string(uri)))
-
7462 pass();
-
7463 else
-
7464 {
-
7465 std::ostringstream text;
-
7466 text << "checkURI: unexpected URI contents on line "
-
7467 << line;
-
7468 fail(text.str(), __FILE__, line);
-
7469 }
-
7470 };
+
7381
+
7382 env(token::modify(bob, nftId),
+
7383 token::owner(issuer),
+
7384 ter(tecNO_PERMISSION));
+
7385 env.close();
+
7386
+
7387 env(token::setMinter(issuer, alice));
+
7388 env.close();
+
7389
+
7390 env(token::modify(bob, nftId),
+
7391 token::owner(issuer),
+
7392 ter(tecNO_PERMISSION));
+
7393 env.close();
+
7394 }
+
7395 }
+
7396 {
+
7397 Env env{*this, features};
+
7398 env.fund(XRP(10000), issuer, alice, bob);
+
7399 env.close();
+
7400
+
7401 // modify with tfFullyCanonicalSig should success
+
7402 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
+
7403 env(token::mint(issuer, 0u), txflags(tfMutable), token::uri("uri"));
+
7404 env.close();
+
7405
+
7406 env(token::modify(issuer, nftId), txflags(tfFullyCanonicalSig));
+
7407 env.close();
+
7408 }
+
7409 {
+
7410 Env env{*this, features};
+
7411 env.fund(XRP(10000), issuer, alice, bob);
+
7412 env.close();
+
7413
+
7414 // lambda that returns the JSON form of NFTokens held by acct
+
7415 auto accountNFTs = [&env](Account const& acct) {
+
7416 Json::Value params;
+
7417 params[jss::account] = acct.human();
+
7418 params[jss::type] = "state";
+
7419 auto response =
+
7420 env.rpc("json", "account_nfts", to_string(params));
+
7421 return response[jss::result][jss::account_nfts];
+
7422 };
+
7423
+
7424 // lambda that checks for the expected URI value of an NFToken
+
7425 auto checkURI = [&accountNFTs, this](
+
7426 Account const& acct,
+
7427 char const* uri,
+
7428 int line) {
+
7429 auto const nfts = accountNFTs(acct);
+
7430 if (nfts.size() == 1)
+
7431 pass();
+
7432 else
+
7433 {
+
7434 std::ostringstream text;
+
7435 text << "checkURI: unexpected NFT count on line " << line;
+
7436 fail(text.str(), __FILE__, line);
+
7437 return;
+
7438 }
+
7439
+
7440 if (uri == nullptr)
+
7441 {
+
7442 if (!nfts[0u].isMember(sfURI.jsonName))
+
7443 pass();
+
7444 else
+
7445 {
+
7446 std::ostringstream text;
+
7447 text << "checkURI: unexpected URI present on line "
+
7448 << line;
+
7449 fail(text.str(), __FILE__, line);
+
7450 }
+
7451 return;
+
7452 }
+
7453
+
7454 if (nfts[0u][sfURI.jsonName] == strHex(std::string(uri)))
+
7455 pass();
+
7456 else
+
7457 {
+
7458 std::ostringstream text;
+
7459 text << "checkURI: unexpected URI contents on line "
+
7460 << line;
+
7461 fail(text.str(), __FILE__, line);
+
7462 }
+
7463 };
+
7464
+
7465 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
+
7466 env.close();
+
7467
+
7468 env(token::mint(issuer, 0u), txflags(tfMutable), token::uri("uri"));
+
7469 env.close();
+
7470 checkURI(issuer, "uri", __LINE__);
7471
-
7472 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
-
7473 env.close();
-
7474
-
7475 env(token::mint(issuer, 0u), txflags(tfMutable), token::uri("uri"));
-
7476 env.close();
-
7477 checkURI(issuer, "uri", __LINE__);
-
7478
-
7479 // set URI Field
-
7480 env(token::modify(issuer, nftId), token::uri("new_uri"));
-
7481 env.close();
-
7482 checkURI(issuer, "new_uri", __LINE__);
-
7483
-
7484 // unset URI Field
-
7485 env(token::modify(issuer, nftId));
-
7486 env.close();
-
7487 checkURI(issuer, nullptr, __LINE__);
-
7488
-
7489 // set URI Field
-
7490 env(token::modify(issuer, nftId), token::uri("uri"));
-
7491 env.close();
-
7492 checkURI(issuer, "uri", __LINE__);
-
7493
-
7494 // Account != Owner
-
7495 uint256 const offerID =
-
7496 keylet::nftoffer(issuer, env.seq(issuer)).key;
-
7497 env(token::createOffer(issuer, nftId, XRP(0)),
-
7498 txflags(tfSellNFToken));
-
7499 env.close();
-
7500 env(token::acceptSellOffer(alice, offerID));
-
7501 env.close();
-
7502 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
7503 BEAST_EXPECT(ownerCount(env, alice) == 1);
-
7504 checkURI(alice, "uri", __LINE__);
-
7505
-
7506 // Modify by owner fails.
-
7507 env(token::modify(alice, nftId),
-
7508 token::uri("new_uri"),
-
7509 ter(tecNO_PERMISSION));
-
7510 env.close();
-
7511 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
7512 BEAST_EXPECT(ownerCount(env, alice) == 1);
-
7513 checkURI(alice, "uri", __LINE__);
-
7514
-
7515 env(token::modify(issuer, nftId),
-
7516 token::owner(alice),
-
7517 token::uri("new_uri"));
-
7518 env.close();
-
7519 BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
7520 BEAST_EXPECT(ownerCount(env, alice) == 1);
-
7521 checkURI(alice, "new_uri", __LINE__);
-
7522
-
7523 env(token::modify(issuer, nftId), token::owner(alice));
-
7524 env.close();
-
7525 checkURI(alice, nullptr, __LINE__);
-
7526
-
7527 env(token::modify(issuer, nftId),
-
7528 token::owner(alice),
-
7529 token::uri("uri"));
-
7530 env.close();
-
7531 checkURI(alice, "uri", __LINE__);
-
7532
-
7533 // Modify by authorized minter
-
7534 env(token::setMinter(issuer, bob));
-
7535 env.close();
-
7536 env(token::modify(bob, nftId),
-
7537 token::owner(alice),
-
7538 token::uri("new_uri"));
-
7539 env.close();
-
7540 checkURI(alice, "new_uri", __LINE__);
-
7541
-
7542 env(token::modify(bob, nftId), token::owner(alice));
-
7543 env.close();
-
7544 checkURI(alice, nullptr, __LINE__);
-
7545
-
7546 env(token::modify(bob, nftId),
-
7547 token::owner(alice),
-
7548 token::uri("uri"));
-
7549 env.close();
-
7550 checkURI(alice, "uri", __LINE__);
-
7551 }
-
7552 }
+
7472 // set URI Field
+
7473 env(token::modify(issuer, nftId), token::uri("new_uri"));
+
7474 env.close();
+
7475 checkURI(issuer, "new_uri", __LINE__);
+
7476
+
7477 // unset URI Field
+
7478 env(token::modify(issuer, nftId));
+
7479 env.close();
+
7480 checkURI(issuer, nullptr, __LINE__);
+
7481
+
7482 // set URI Field
+
7483 env(token::modify(issuer, nftId), token::uri("uri"));
+
7484 env.close();
+
7485 checkURI(issuer, "uri", __LINE__);
+
7486
+
7487 // Account != Owner
+
7488 uint256 const offerID =
+
7489 keylet::nftoffer(issuer, env.seq(issuer)).key;
+
7490 env(token::createOffer(issuer, nftId, XRP(0)),
+
7491 txflags(tfSellNFToken));
+
7492 env.close();
+
7493 env(token::acceptSellOffer(alice, offerID));
+
7494 env.close();
+
7495 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
7496 BEAST_EXPECT(ownerCount(env, alice) == 1);
+
7497 checkURI(alice, "uri", __LINE__);
+
7498
+
7499 // Modify by owner fails.
+
7500 env(token::modify(alice, nftId),
+
7501 token::uri("new_uri"),
+
7502 ter(tecNO_PERMISSION));
+
7503 env.close();
+
7504 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
7505 BEAST_EXPECT(ownerCount(env, alice) == 1);
+
7506 checkURI(alice, "uri", __LINE__);
+
7507
+
7508 env(token::modify(issuer, nftId),
+
7509 token::owner(alice),
+
7510 token::uri("new_uri"));
+
7511 env.close();
+
7512 BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
7513 BEAST_EXPECT(ownerCount(env, alice) == 1);
+
7514 checkURI(alice, "new_uri", __LINE__);
+
7515
+
7516 env(token::modify(issuer, nftId), token::owner(alice));
+
7517 env.close();
+
7518 checkURI(alice, nullptr, __LINE__);
+
7519
+
7520 env(token::modify(issuer, nftId),
+
7521 token::owner(alice),
+
7522 token::uri("uri"));
+
7523 env.close();
+
7524 checkURI(alice, "uri", __LINE__);
+
7525
+
7526 // Modify by authorized minter
+
7527 env(token::setMinter(issuer, bob));
+
7528 env.close();
+
7529 env(token::modify(bob, nftId),
+
7530 token::owner(alice),
+
7531 token::uri("new_uri"));
+
7532 env.close();
+
7533 checkURI(alice, "new_uri", __LINE__);
+
7534
+
7535 env(token::modify(bob, nftId), token::owner(alice));
+
7536 env.close();
+
7537 checkURI(alice, nullptr, __LINE__);
+
7538
+
7539 env(token::modify(bob, nftId),
+
7540 token::owner(alice),
+
7541 token::uri("uri"));
+
7542 env.close();
+
7543 checkURI(alice, "uri", __LINE__);
+
7544 }
+
7545 }
-
7553
-
7554protected:
- -
7556
-
7557 void
-
- -
7559 {
-
7560 testEnabled(features);
-
7561 testMintReserve(features);
-
7562 testMintMaxTokens(features);
-
7563 testMintInvalid(features);
-
7564 testBurnInvalid(features);
-
7565 testCreateOfferInvalid(features);
-
7566 testCancelOfferInvalid(features);
-
7567 testAcceptOfferInvalid(features);
-
7568 testMintFlagBurnable(features);
-
7569 testMintFlagOnlyXRP(features);
- -
7571 testMintFlagTransferable(features);
-
7572 testMintTransferFee(features);
-
7573 testMintTaxon(features);
-
7574 testMintURI(features);
- - -
7577 testCreateOfferExpiration(features);
-
7578 testCancelOffers(features);
-
7579 testCancelTooManyOffers(features);
-
7580 testBrokeredAccept(features);
-
7581 testNFTokenOfferOwner(features);
-
7582 testNFTokenWithTickets(features);
-
7583 testNFTokenDeleteAccount(features);
-
7584 testNftXxxOffers(features);
-
7585 testNFTokenNegOffer(features);
-
7586 testIOUWithTransferFee(features);
-
7587 testBrokeredSaleToSelf(features);
-
7588 testNFTokenRemint(features);
-
7589 testFeatMintWithOffer(features);
-
7590 testTxJsonMetaFields(features);
- - -
7593 testNFTIssuerIsIOUIssuer(features);
-
7594 testNFTokenModify(features);
-
7595 }
+
7546
+
7547protected:
+ +
7549
+
7550 void
+
+ +
7552 {
+
7553 testEnabled(features);
+
7554 testMintReserve(features);
+
7555 testMintMaxTokens(features);
+
7556 testMintInvalid(features);
+
7557 testBurnInvalid(features);
+
7558 testCreateOfferInvalid(features);
+
7559 testCancelOfferInvalid(features);
+
7560 testAcceptOfferInvalid(features);
+
7561 testMintFlagBurnable(features);
+
7562 testMintFlagOnlyXRP(features);
+ +
7564 testMintFlagTransferable(features);
+
7565 testMintTransferFee(features);
+
7566 testMintTaxon(features);
+
7567 testMintURI(features);
+ + +
7570 testCreateOfferExpiration(features);
+
7571 testCancelOffers(features);
+
7572 testCancelTooManyOffers(features);
+
7573 testBrokeredAccept(features);
+
7574 testNFTokenOfferOwner(features);
+
7575 testNFTokenWithTickets(features);
+
7576 testNFTokenDeleteAccount(features);
+
7577 testNftXxxOffers(features);
+
7578 testNFTokenNegOffer(features);
+
7579 testIOUWithTransferFee(features);
+
7580 testBrokeredSaleToSelf(features);
+
7581 testNFTokenRemint(features);
+
7582 testFeatMintWithOffer(features);
+
7583 testTxJsonMetaFields(features);
+ + +
7586 testNFTIssuerIsIOUIssuer(features);
+
7587 testNFTokenModify(features);
+
7588 }
-
7596
-
7597public:
-
7598 void
-
-
7599 run() override
-
7600 {
- -
7602 allFeatures - fixNFTokenReserve - featureNFTokenMintOffer -
-
7603 featureDynamicNFT);
-
7604 }
+
7589
+
7590public:
+
7591 void
+
+
7592 run() override
+
7593 {
+ +
7595 allFeatures - fixNFTokenReserve - featureNFTokenMintOffer -
+
7596 featureDynamicNFT);
+
7597 }
-
7605};
+
7598};
-
7606
-
- -
7608{
-
7609 void
-
-
7610 run() override
-
7611 {
- -
7613 allFeatures - fixNFTokenReserve - featureNFTokenMintOffer -
-
7614 featureDynamicNFT);
-
7615 }
+
7599
+
+ +
7601{
+
7602 void
+
+
7603 run() override
+
7604 {
+ +
7606 allFeatures - fixNFTokenReserve - featureNFTokenMintOffer -
+
7607 featureDynamicNFT);
+
7608 }
-
7616};
+
7609};
-
7617
-
- -
7619{
-
7620 void
-
-
7621 run() override
-
7622 {
- -
7624 allFeatures - featureNFTokenMintOffer - featureDynamicNFT);
-
7625 }
+
7610
+
+ +
7612{
+
7613 void
+
+
7614 run() override
+
7615 {
+ +
7617 allFeatures - featureNFTokenMintOffer - featureDynamicNFT);
+
7618 }
-
7626};
+
7619};
-
7627
-
- -
7629{
-
7630 void
-
-
7631 run() override
-
7632 {
-
7633 testWithFeats(allFeatures - featureDynamicNFT);
-
7634 }
+
7620
+
+ +
7622{
+
7623 void
+
+
7624 run() override
+
7625 {
+
7626 testWithFeats(allFeatures - featureDynamicNFT);
+
7627 }
-
7635};
+
7628};
-
7636
-
- -
7638{
-
7639 void
-
-
7640 run() override
-
7641 {
- -
7643 }
+
7629
+
+ +
7631{
+
7632 void
+
+
7633 run() override
+
7634 {
+ +
7636 }
-
7644};
+
7637};
-
7645
-
7646BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBaseUtil, app, ripple, 2);
-
7647BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDisallowIncoming, app, ripple, 2);
-
7648BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOMintOffer, app, ripple, 2);
-
7649BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOModify, app, ripple, 2);
-
7650BEAST_DEFINE_TESTSUITE_PRIO(NFTokenAllFeatures, app, ripple, 2);
-
7651
-
7652} // namespace ripple
+
7638
+
7639BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBaseUtil, app, ripple, 2);
+
7640BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDisallowIncoming, app, ripple, 2);
+
7641BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOMintOffer, app, ripple, 2);
+
7642BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOModify, app, ripple, 2);
+
7643BEAST_DEFINE_TESTSUITE_PRIO(NFTokenAllFeatures, app, ripple, 2);
+
7644
+
7645} // namespace ripple
T back(T... args)
T back_inserter(T... args)
@@ -7854,24 +7847,24 @@ $(document).ready(function() { init_codefold(0); });
bool expect(Condition const &shouldBeTrue)
Evaluate a test condition.
Definition suite.h:226
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
- -
void run() override
Runs the suite.
+ +
void run() override
Runs the suite.
void testCreateOfferDestination(FeatureBitset features)
std::uint32_t lastClose(test::jtx::Env &env)
-
FeatureBitset const allFeatures
+
FeatureBitset const allFeatures
void testMintInvalid(FeatureBitset features)
void testAcceptOfferInvalid(FeatureBitset features)
void testMintFlagTransferable(FeatureBitset features)
void testNFTokenRemint(FeatureBitset features)
void testCancelOffers(FeatureBitset features)
-
void testNFTIssuerIsIOUIssuer(FeatureBitset features)
+
void testNFTIssuerIsIOUIssuer(FeatureBitset features)
void testMintTaxon(FeatureBitset features)
-
void testNFTokenModify(FeatureBitset features)
+
void testNFTokenModify(FeatureBitset features)
void testNFTokenDeleteAccount(FeatureBitset features)
-
void testUnaskedForAutoTrustline(FeatureBitset features)
-
void testFixNFTokenBuyerReserve(FeatureBitset features)
-
void testWithFeats(FeatureBitset features)
+
void testUnaskedForAutoTrustline(FeatureBitset features)
+
void testFixNFTokenBuyerReserve(FeatureBitset features)
+
void testWithFeats(FeatureBitset features)
void testNFTokenOfferOwner(FeatureBitset features)
void testNFTokenWithTickets(FeatureBitset features)
void testCreateOfferDestinationDisallowIncoming(FeatureBitset features)
@@ -7879,7 +7872,7 @@ $(document).ready(function() { init_codefold(0); });
void testMintMaxTokens(FeatureBitset features)
void testMintFlagCreateTrustLine(FeatureBitset features)
void testMintTransferFee(FeatureBitset features)
-
void testTxJsonMetaFields(FeatureBitset features)
+
void testTxJsonMetaFields(FeatureBitset features)
void testNftXxxOffers(FeatureBitset features)
void testEnabled(FeatureBitset features)
void testNFTokenNegOffer(FeatureBitset features)
@@ -7897,15 +7890,15 @@ $(document).ready(function() { init_codefold(0); });
static std::uint32_t mintedCount(test::jtx::Env const &env, test::jtx::Account const &issuer)
void testCancelOfferInvalid(FeatureBitset features)
void testBrokeredAccept(FeatureBitset features)
-
void testFeatMintWithOffer(FeatureBitset features)
-
void run() override
Runs the suite.
+
void testFeatMintWithOffer(FeatureBitset features)
+
void run() override
Runs the suite.
void testBurnInvalid(FeatureBitset features)
- -
void run() override
Runs the suite.
- -
void run() override
Runs the suite.
- -
void run() override
Runs the suite.
+ +
void run() override
Runs the suite.
+ +
void run() override
Runs the suite.
+ +
void run() override
Runs the suite.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:46
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:150
void rawReplace(std::shared_ptr< SLE > const &sle) override
Unconditionally replace a state item.
Definition OpenView.cpp:224
diff --git a/Payment_8cpp_source.html b/Payment_8cpp_source.html index 3dfbf89840..8272e852ad 100644 --- a/Payment_8cpp_source.html +++ b/Payment_8cpp_source.html @@ -496,273 +496,269 @@ $(document).ready(function() { init_codefold(0); });
398
399 if (!sleDst)
400 {
-
401 std::uint32_t const seqno{
-
402 view().rules().enabled(featureDeletableAccounts) ? view().seq()
-
403 : 1};
-
404
-
405 // Create the account.
-
406 sleDst = std::make_shared<SLE>(k);
-
407 sleDst->setAccountID(sfAccount, dstAccountID);
-
408 sleDst->setFieldU32(sfSequence, seqno);
-
409
-
410 view().insert(sleDst);
-
411 }
-
412 else
-
413 {
-
414 // Tell the engine that we are intending to change the destination
-
415 // account. The source account gets always charged a fee so it's always
-
416 // marked as modified.
-
417 view().update(sleDst);
-
418 }
-
419
-
420 bool const ripple =
-
421 (hasPaths || sendMax || !dstAmount.native()) && !mptDirect;
-
422
-
423 if (ripple)
-
424 {
-
425 // Ripple payment with at least one intermediate step and uses
-
426 // transitive balances.
-
427
-
428 // An account that requires authorization has two ways to get an
-
429 // IOU Payment in:
-
430 // 1. If Account == Destination, or
-
431 // 2. If Account is deposit preauthorized by destination.
-
432
-
433 if (auto err = verifyDepositPreauth(
-
434 ctx_.tx,
-
435 ctx_.view(),
-
436 account_,
-
437 dstAccountID,
-
438 sleDst,
-
439 ctx_.journal);
-
440 !isTesSuccess(err))
-
441 return err;
-
442
- -
444 rcInput.partialPaymentAllowed = partialPaymentAllowed;
-
445 rcInput.defaultPathsAllowed = defaultPathsAllowed;
-
446 rcInput.limitQuality = limitQuality;
-
447 rcInput.isLedgerOpen = view().open();
-
448
- -
450 {
-
451 PaymentSandbox pv(&view());
-
452 JLOG(j_.debug()) << "Entering RippleCalc in payment: "
- - -
455 pv,
-
456 maxSourceAmount,
-
457 dstAmount,
-
458 dstAccountID,
-
459 account_,
-
460 ctx_.tx.getFieldPathSet(sfPaths),
-
461 ctx_.tx[~sfDomainID],
-
462 ctx_.app.logs(),
-
463 &rcInput);
-
464 // VFALCO NOTE We might not need to apply, depending
-
465 // on the TER. But always applying *should*
-
466 // be safe.
-
467 pv.apply(ctx_.rawView());
-
468 }
-
469
-
470 // TODO: is this right? If the amount is the correct amount, was
-
471 // the delivered amount previously set?
-
472 if (rc.result() == tesSUCCESS && rc.actualAmountOut != dstAmount)
-
473 {
-
474 if (deliverMin && rc.actualAmountOut < *deliverMin)
- -
476 else
- -
478 }
-
479
-
480 auto terResult = rc.result();
-
481
-
482 // Because of its overhead, if RippleCalc
-
483 // fails with a retry code, claim a fee
-
484 // instead. Maybe the user will be more
-
485 // careful with their path spec next time.
-
486 if (isTerRetry(terResult))
-
487 terResult = tecPATH_DRY;
-
488 return terResult;
-
489 }
-
490 else if (mptDirect)
-
491 {
-
492 JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText();
-
493 auto const& mptIssue = dstAmount.get<MPTIssue>();
+
401 // Create the account.
+
402 sleDst = std::make_shared<SLE>(k);
+
403 sleDst->setAccountID(sfAccount, dstAccountID);
+
404 sleDst->setFieldU32(sfSequence, view().seq());
+
405
+
406 view().insert(sleDst);
+
407 }
+
408 else
+
409 {
+
410 // Tell the engine that we are intending to change the destination
+
411 // account. The source account gets always charged a fee so it's always
+
412 // marked as modified.
+
413 view().update(sleDst);
+
414 }
+
415
+
416 bool const ripple =
+
417 (hasPaths || sendMax || !dstAmount.native()) && !mptDirect;
+
418
+
419 if (ripple)
+
420 {
+
421 // Ripple payment with at least one intermediate step and uses
+
422 // transitive balances.
+
423
+
424 // An account that requires authorization has two ways to get an
+
425 // IOU Payment in:
+
426 // 1. If Account == Destination, or
+
427 // 2. If Account is deposit preauthorized by destination.
+
428
+
429 if (auto err = verifyDepositPreauth(
+
430 ctx_.tx,
+
431 ctx_.view(),
+
432 account_,
+
433 dstAccountID,
+
434 sleDst,
+
435 ctx_.journal);
+
436 !isTesSuccess(err))
+
437 return err;
+
438
+ +
440 rcInput.partialPaymentAllowed = partialPaymentAllowed;
+
441 rcInput.defaultPathsAllowed = defaultPathsAllowed;
+
442 rcInput.limitQuality = limitQuality;
+
443 rcInput.isLedgerOpen = view().open();
+
444
+ +
446 {
+
447 PaymentSandbox pv(&view());
+
448 JLOG(j_.debug()) << "Entering RippleCalc in payment: "
+ + +
451 pv,
+
452 maxSourceAmount,
+
453 dstAmount,
+
454 dstAccountID,
+
455 account_,
+
456 ctx_.tx.getFieldPathSet(sfPaths),
+
457 ctx_.tx[~sfDomainID],
+
458 ctx_.app.logs(),
+
459 &rcInput);
+
460 // VFALCO NOTE We might not need to apply, depending
+
461 // on the TER. But always applying *should*
+
462 // be safe.
+
463 pv.apply(ctx_.rawView());
+
464 }
+
465
+
466 // TODO: is this right? If the amount is the correct amount, was
+
467 // the delivered amount previously set?
+
468 if (rc.result() == tesSUCCESS && rc.actualAmountOut != dstAmount)
+
469 {
+
470 if (deliverMin && rc.actualAmountOut < *deliverMin)
+ +
472 else
+ +
474 }
+
475
+
476 auto terResult = rc.result();
+
477
+
478 // Because of its overhead, if RippleCalc
+
479 // fails with a retry code, claim a fee
+
480 // instead. Maybe the user will be more
+
481 // careful with their path spec next time.
+
482 if (isTerRetry(terResult))
+
483 terResult = tecPATH_DRY;
+
484 return terResult;
+
485 }
+
486 else if (mptDirect)
+
487 {
+
488 JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText();
+
489 auto const& mptIssue = dstAmount.get<MPTIssue>();
+
490
+
491 if (auto const ter = requireAuth(view(), mptIssue, account_);
+
492 ter != tesSUCCESS)
+
493 return ter;
494
-
495 if (auto const ter = requireAuth(view(), mptIssue, account_);
+
495 if (auto const ter = requireAuth(view(), mptIssue, dstAccountID);
496 ter != tesSUCCESS)
497 return ter;
498
-
499 if (auto const ter = requireAuth(view(), mptIssue, dstAccountID);
-
500 ter != tesSUCCESS)
-
501 return ter;
-
502
-
503 if (auto const ter =
-
504 canTransfer(view(), mptIssue, account_, dstAccountID);
-
505 ter != tesSUCCESS)
-
506 return ter;
-
507
-
508 if (auto err = verifyDepositPreauth(
-
509 ctx_.tx,
-
510 ctx_.view(),
-
511 account_,
-
512 dstAccountID,
-
513 sleDst,
-
514 ctx_.journal);
-
515 !isTesSuccess(err))
-
516 return err;
-
517
-
518 auto const& issuer = mptIssue.getIssuer();
-
519
-
520 // Transfer rate
-
521 Rate rate{QUALITY_ONE};
-
522 // Payment between the holders
-
523 if (account_ != issuer && dstAccountID != issuer)
-
524 {
-
525 // If globally/individually locked then
-
526 // - can't send between holders
-
527 // - holder can send back to issuer
-
528 // - issuer can send to holder
-
529 if (isAnyFrozen(view(), {account_, dstAccountID}, mptIssue))
-
530 return tecLOCKED;
+
499 if (auto const ter =
+
500 canTransfer(view(), mptIssue, account_, dstAccountID);
+
501 ter != tesSUCCESS)
+
502 return ter;
+
503
+
504 if (auto err = verifyDepositPreauth(
+
505 ctx_.tx,
+
506 ctx_.view(),
+
507 account_,
+
508 dstAccountID,
+
509 sleDst,
+
510 ctx_.journal);
+
511 !isTesSuccess(err))
+
512 return err;
+
513
+
514 auto const& issuer = mptIssue.getIssuer();
+
515
+
516 // Transfer rate
+
517 Rate rate{QUALITY_ONE};
+
518 // Payment between the holders
+
519 if (account_ != issuer && dstAccountID != issuer)
+
520 {
+
521 // If globally/individually locked then
+
522 // - can't send between holders
+
523 // - holder can send back to issuer
+
524 // - issuer can send to holder
+
525 if (isAnyFrozen(view(), {account_, dstAccountID}, mptIssue))
+
526 return tecLOCKED;
+
527
+
528 // Get the rate for a payment between the holders.
+
529 rate = transferRate(view(), mptIssue.getMptID());
+
530 }
531
-
532 // Get the rate for a payment between the holders.
-
533 rate = transferRate(view(), mptIssue.getMptID());
-
534 }
-
535
-
536 // Amount to deliver.
-
537 STAmount amountDeliver = dstAmount;
-
538 // Factor in the transfer rate.
-
539 // No rounding. It'll change once MPT integrated into DEX.
-
540 STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
-
541
-
542 // Send more than the account wants to pay or less than
-
543 // the account wants to deliver (if no SendMax).
-
544 // Adjust the amount to deliver.
-
545 if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
-
546 {
-
547 requiredMaxSourceAmount = maxSourceAmount;
-
548 // No rounding. It'll change once MPT integrated into DEX.
-
549 amountDeliver = divide(maxSourceAmount, rate);
-
550 }
+
532 // Amount to deliver.
+
533 STAmount amountDeliver = dstAmount;
+
534 // Factor in the transfer rate.
+
535 // No rounding. It'll change once MPT integrated into DEX.
+
536 STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
+
537
+
538 // Send more than the account wants to pay or less than
+
539 // the account wants to deliver (if no SendMax).
+
540 // Adjust the amount to deliver.
+
541 if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
+
542 {
+
543 requiredMaxSourceAmount = maxSourceAmount;
+
544 // No rounding. It'll change once MPT integrated into DEX.
+
545 amountDeliver = divide(maxSourceAmount, rate);
+
546 }
+
547
+
548 if (requiredMaxSourceAmount > maxSourceAmount ||
+
549 (deliverMin && amountDeliver < *deliverMin))
+
550 return tecPATH_PARTIAL;
551
-
552 if (requiredMaxSourceAmount > maxSourceAmount ||
-
553 (deliverMin && amountDeliver < *deliverMin))
-
554 return tecPATH_PARTIAL;
-
555
-
556 PaymentSandbox pv(&view());
-
557 auto res = accountSend(
-
558 pv, account_, dstAccountID, amountDeliver, ctx_.journal);
-
559 if (res == tesSUCCESS)
-
560 {
-
561 pv.apply(ctx_.rawView());
-
562
-
563 // If the actual amount delivered is different from the original
-
564 // amount due to partial payment or transfer fee, we need to update
-
565 // DelieveredAmount using the actual delivered amount
-
566 if (view().rules().enabled(fixMPTDeliveredAmount) &&
-
567 amountDeliver != dstAmount)
-
568 ctx_.deliver(amountDeliver);
-
569 }
-
570 else if (res == tecINSUFFICIENT_FUNDS || res == tecPATH_DRY)
-
571 res = tecPATH_PARTIAL;
-
572
-
573 return res;
-
574 }
+
552 PaymentSandbox pv(&view());
+
553 auto res = accountSend(
+
554 pv, account_, dstAccountID, amountDeliver, ctx_.journal);
+
555 if (res == tesSUCCESS)
+
556 {
+
557 pv.apply(ctx_.rawView());
+
558
+
559 // If the actual amount delivered is different from the original
+
560 // amount due to partial payment or transfer fee, we need to update
+
561 // DelieveredAmount using the actual delivered amount
+
562 if (view().rules().enabled(fixMPTDeliveredAmount) &&
+
563 amountDeliver != dstAmount)
+
564 ctx_.deliver(amountDeliver);
+
565 }
+
566 else if (res == tecINSUFFICIENT_FUNDS || res == tecPATH_DRY)
+
567 res = tecPATH_PARTIAL;
+
568
+
569 return res;
+
570 }
+
571
+
572 XRPL_ASSERT(dstAmount.native(), "ripple::Payment::doApply : amount is XRP");
+
573
+
574 // Direct XRP payment.
575
-
576 XRPL_ASSERT(dstAmount.native(), "ripple::Payment::doApply : amount is XRP");
-
577
-
578 // Direct XRP payment.
+
576 auto const sleSrc = view().peek(keylet::account(account_));
+
577 if (!sleSrc)
+
578 return tefINTERNAL; // LCOV_EXCL_LINE
579
-
580 auto const sleSrc = view().peek(keylet::account(account_));
-
581 if (!sleSrc)
-
582 return tefINTERNAL; // LCOV_EXCL_LINE
+
580 // ownerCount is the number of entries in this ledger for this
+
581 // account that require a reserve.
+
582 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
583
-
584 // ownerCount is the number of entries in this ledger for this
-
585 // account that require a reserve.
-
586 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
-
587
-
588 // This is the total reserve in drops.
-
589 auto const reserve = view().fees().accountReserve(ownerCount);
-
590
-
591 // mPriorBalance is the balance on the sending account BEFORE the
-
592 // fees were charged. We want to make sure we have enough reserve
-
593 // to send. Allow final spend to use reserve for fee.
-
594 auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp());
-
595
-
596 if (mPriorBalance < dstAmount.xrp() + mmm)
-
597 {
-
598 // Vote no. However the transaction might succeed, if applied in
-
599 // a different order.
-
600 JLOG(j_.trace()) << "Delay transaction: Insufficient funds: "
-
601 << to_string(mPriorBalance) << " / "
-
602 << to_string(dstAmount.xrp() + mmm) << " ("
-
603 << to_string(reserve) << ")";
-
604
-
605 return tecUNFUNDED_PAYMENT;
-
606 }
-
607
-
608 // Pseudo-accounts cannot receive payments, other than these native to
-
609 // their underlying ledger object - implemented in their respective
-
610 // transaction types. Note, this is not amendment-gated because all writes
-
611 // to pseudo-account discriminator fields **are** amendment gated, hence the
-
612 // behaviour of this check will always match the active amendments.
-
613 if (isPseudoAccount(sleDst))
-
614 return tecNO_PERMISSION;
-
615
-
616 // The source account does have enough money. Make sure the
-
617 // source account has authority to deposit to the destination.
-
618 // An account that requires authorization has three ways to get an XRP
-
619 // Payment in:
-
620 // 1. If Account == Destination, or
-
621 // 2. If Account is deposit preauthorized by destination, or
-
622 // 3. If the destination's XRP balance is
-
623 // a. less than or equal to the base reserve and
-
624 // b. the deposit amount is less than or equal to the base reserve,
-
625 // then we allow the deposit.
-
626 //
-
627 // Rule 3 is designed to keep an account from getting wedged
-
628 // in an unusable state if it sets the lsfDepositAuth flag and
-
629 // then consumes all of its XRP. Without the rule if an
-
630 // account with lsfDepositAuth set spent all of its XRP, it
-
631 // would be unable to acquire more XRP required to pay fees.
-
632 //
-
633 // We choose the base reserve as our bound because it is
-
634 // a small number that seldom changes but is always sufficient
-
635 // to get the account un-wedged.
-
636
-
637 // Get the base reserve.
-
638 XRPAmount const dstReserve{view().fees().reserve};
-
639
-
640 if (dstAmount > dstReserve ||
-
641 sleDst->getFieldAmount(sfBalance) > dstReserve)
-
642 {
-
643 if (auto err = verifyDepositPreauth(
-
644 ctx_.tx,
-
645 ctx_.view(),
-
646 account_,
-
647 dstAccountID,
-
648 sleDst,
-
649 ctx_.journal);
-
650 !isTesSuccess(err))
-
651 return err;
-
652 }
-
653
-
654 // Do the arithmetic for the transfer and make the ledger change.
-
655 sleSrc->setFieldAmount(sfBalance, mSourceBalance - dstAmount);
-
656 sleDst->setFieldAmount(
-
657 sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
+
584 // This is the total reserve in drops.
+
585 auto const reserve = view().fees().accountReserve(ownerCount);
+
586
+
587 // mPriorBalance is the balance on the sending account BEFORE the
+
588 // fees were charged. We want to make sure we have enough reserve
+
589 // to send. Allow final spend to use reserve for fee.
+
590 auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp());
+
591
+
592 if (mPriorBalance < dstAmount.xrp() + mmm)
+
593 {
+
594 // Vote no. However the transaction might succeed, if applied in
+
595 // a different order.
+
596 JLOG(j_.trace()) << "Delay transaction: Insufficient funds: "
+
597 << to_string(mPriorBalance) << " / "
+
598 << to_string(dstAmount.xrp() + mmm) << " ("
+
599 << to_string(reserve) << ")";
+
600
+
601 return tecUNFUNDED_PAYMENT;
+
602 }
+
603
+
604 // Pseudo-accounts cannot receive payments, other than these native to
+
605 // their underlying ledger object - implemented in their respective
+
606 // transaction types. Note, this is not amendment-gated because all writes
+
607 // to pseudo-account discriminator fields **are** amendment gated, hence the
+
608 // behaviour of this check will always match the active amendments.
+
609 if (isPseudoAccount(sleDst))
+
610 return tecNO_PERMISSION;
+
611
+
612 // The source account does have enough money. Make sure the
+
613 // source account has authority to deposit to the destination.
+
614 // An account that requires authorization has three ways to get an XRP
+
615 // Payment in:
+
616 // 1. If Account == Destination, or
+
617 // 2. If Account is deposit preauthorized by destination, or
+
618 // 3. If the destination's XRP balance is
+
619 // a. less than or equal to the base reserve and
+
620 // b. the deposit amount is less than or equal to the base reserve,
+
621 // then we allow the deposit.
+
622 //
+
623 // Rule 3 is designed to keep an account from getting wedged
+
624 // in an unusable state if it sets the lsfDepositAuth flag and
+
625 // then consumes all of its XRP. Without the rule if an
+
626 // account with lsfDepositAuth set spent all of its XRP, it
+
627 // would be unable to acquire more XRP required to pay fees.
+
628 //
+
629 // We choose the base reserve as our bound because it is
+
630 // a small number that seldom changes but is always sufficient
+
631 // to get the account un-wedged.
+
632
+
633 // Get the base reserve.
+
634 XRPAmount const dstReserve{view().fees().reserve};
+
635
+
636 if (dstAmount > dstReserve ||
+
637 sleDst->getFieldAmount(sfBalance) > dstReserve)
+
638 {
+
639 if (auto err = verifyDepositPreauth(
+
640 ctx_.tx,
+
641 ctx_.view(),
+
642 account_,
+
643 dstAccountID,
+
644 sleDst,
+
645 ctx_.journal);
+
646 !isTesSuccess(err))
+
647 return err;
+
648 }
+
649
+
650 // Do the arithmetic for the transfer and make the ledger change.
+
651 sleSrc->setFieldAmount(sfBalance, mSourceBalance - dstAmount);
+
652 sleDst->setFieldAmount(
+
653 sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
+
654
+
655 // Re-arm the password change fee if we can and need to.
+
656 if ((sleDst->getFlags() & lsfPasswordSpent))
+
657 sleDst->clearFlag(lsfPasswordSpent);
658
-
659 // Re-arm the password change fee if we can and need to.
-
660 if ((sleDst->getFlags() & lsfPasswordSpent))
-
661 sleDst->clearFlag(lsfPasswordSpent);
-
662
-
663 return tesSUCCESS;
-
664}
+
659 return tesSUCCESS;
+
660}
-
665
-
666} // namespace ripple
+
661
+
662} // namespace ripple
T any_of(T... args)
Stream debug() const
Definition Journal.h:309
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
@@ -794,8 +790,6 @@ $(document).ready(function() { init_codefold(0); });
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool open() const =0
Returns true if this reflects an open ledger.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
-
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:99
-
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
constexpr bool holds() const noexcept
Definition STAmount.h:446
diff --git a/TxQ__test_8cpp_source.html b/TxQ__test_8cpp_source.html index 93fe6a20d7..9497a1fc2a 100644 --- a/TxQ__test_8cpp_source.html +++ b/TxQ__test_8cpp_source.html @@ -4462,7 +4462,7 @@ $(document).ready(function() { init_codefold(0); });
4330 Account const ellie("ellie");
4331 Account const fiona("fiona");
4332
-
4333 constexpr int ledgersInQueue = 10;
+
4333 constexpr int ledgersInQueue = 20;
4334 auto cfg = makeConfig(
4335 {{"minimum_txn_in_ledger_standalone", "1"},
4336 {"ledgers_in_queue", std::to_string(ledgersInQueue)},
diff --git a/View_8cpp_source.html b/View_8cpp_source.html index 521921c3c9..1ea6538fcc 100644 --- a/View_8cpp_source.html +++ b/View_8cpp_source.html @@ -2057,7 +2057,7 @@ $(document).ready(function() { init_codefold(0); });
1863 // Direct send: redeeming IOUs and/or sending own IOUs.
1864 auto const ter =
1865 rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j);
-
1866 if (view.rules().enabled(featureDeletableAccounts) && ter != tesSUCCESS)
+
1866 if (ter != tesSUCCESS)
1867 return ter;
1868 saActual = saAmount;
1869 return tesSUCCESS;
diff --git a/XChainBridge_8cpp_source.html b/XChainBridge_8cpp_source.html index b73470b9dd..eab924ca93 100644 --- a/XChainBridge_8cpp_source.html +++ b/XChainBridge_8cpp_source.html @@ -494,1791 +494,1788 @@ $(document).ready(function() { init_codefold(0); });
464 }
465
466 // Create the account.
-
467 std::uint32_t const seqno{
-
468 psb.rules().enabled(featureDeletableAccounts) ? psb.seq() : 1};
-
469
-
470 sleDst = std::make_shared<SLE>(dstK);
-
471 sleDst->setAccountID(sfAccount, dst);
-
472 sleDst->setFieldU32(sfSequence, seqno);
+
467 sleDst = std::make_shared<SLE>(dstK);
+
468 sleDst->setAccountID(sfAccount, dst);
+
469 sleDst->setFieldU32(sfSequence, psb.seq());
+
470
+
471 psb.insert(sleDst);
+
472 }
473
-
474 psb.insert(sleDst);
-
475 }
-
476
-
477 (*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
-
478 (*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
-
479 psb.update(sleSrc);
-
480 psb.update(sleDst);
+
474 (*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
+
475 (*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
+
476 psb.update(sleSrc);
+
477 psb.update(sleDst);
+
478
+
479 return tesSUCCESS;
+
480 }
481
-
482 return tesSUCCESS;
-
483 }
-
484
-
485 auto const result = flow(
-
486 psb,
-
487 amt,
-
488 src,
-
489 dst,
-
490 STPathSet{},
-
491 /*default path*/ true,
-
492 /*partial payment*/ false,
-
493 /*owner pays transfer fee*/ true,
-
494 /*offer crossing*/ OfferCrossing::no,
-
495 /*limit quality*/ std::nullopt,
-
496 /*sendmax*/ std::nullopt,
-
497 /*domain id*/ std::nullopt,
-
498 j);
-
499
-
500 if (auto const r = result.result();
-
501 isTesSuccess(r) || isTecClaim(r) || isTerRetry(r))
-
502 return r;
- -
504}
-
505
-
511enum class OnTransferFail {
-
513 removeClaim,
-
515 keepClaim
-
516};
-
517
-
518struct FinalizeClaimHelperResult
-
519{
-
521 std::optional<TER> mainFundsTer;
-
522 // TER for transfering the reward funds
-
523 std::optional<TER> rewardTer;
-
524 // TER for removing the sle (if is sle is to be removed)
-
525 std::optional<TER> rmSleTer;
-
526
-
527 // Helper to check for overall success. If there wasn't overall success the
-
528 // individual ters can be used to decide what needs to be done.
-
529 bool
-
530 isTesSuccess() const
-
531 {
-
532 return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS &&
-
533 (!rmSleTer || *rmSleTer == tesSUCCESS);
-
534 }
-
535
-
536 TER
-
537 ter() const
-
538 {
-
539 if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) &&
-
540 (!rewardTer || *rewardTer == tesSUCCESS) &&
-
541 (!rmSleTer || *rmSleTer == tesSUCCESS))
-
542 return tesSUCCESS;
-
543
-
544 // if any phase return a tecINTERNAL or a tef, prefer returning those
-
545 // codes
-
546 if (mainFundsTer &&
-
547 (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
-
548 return *mainFundsTer;
-
549 if (rewardTer &&
-
550 (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
-
551 return *rewardTer;
-
552 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
-
553 return *rmSleTer;
-
554
-
555 // Only after the tecINTERNAL and tef are checked, return the first
-
556 // non-success error code.
-
557 if (mainFundsTer && mainFundsTer != tesSUCCESS)
-
558 return *mainFundsTer;
-
559 if (rewardTer && rewardTer != tesSUCCESS)
-
560 return *rewardTer;
-
561 if (rmSleTer && rmSleTer != tesSUCCESS)
-
562 return *rmSleTer;
-
563 return tesSUCCESS;
-
564 }
-
565};
-
566
-
595FinalizeClaimHelperResult
-
596finalizeClaimHelper(
-
597 PaymentSandbox& outerSb,
-
598 STXChainBridge const& bridgeSpec,
-
599 AccountID const& dst,
-
600 std::optional<std::uint32_t> const& dstTag,
-
601 AccountID const& claimOwner,
-
602 STAmount const& sendingAmount,
-
603 AccountID const& rewardPoolSrc,
-
604 STAmount const& rewardPool,
-
605 std::vector<AccountID> const& rewardAccounts,
-
606 STXChainBridge::ChainType const srcChain,
-
607 Keylet const& claimIDKeylet,
-
608 OnTransferFail onTransferFail,
-
609 DepositAuthPolicy depositAuthPolicy,
- -
611{
-
612 FinalizeClaimHelperResult result;
-
613
-
614 STXChainBridge::ChainType const dstChain =
- -
616 STAmount const thisChainAmount = [&] {
-
617 STAmount r = sendingAmount;
-
618 r.setIssue(bridgeSpec.issue(dstChain));
-
619 return r;
-
620 }();
-
621 auto const& thisDoor = bridgeSpec.door(dstChain);
-
622
-
623 {
-
624 PaymentSandbox innerSb{&outerSb};
-
625 // If distributing the reward pool fails, the mainFunds transfer should
-
626 // be rolled back
+
482 auto const result = flow(
+
483 psb,
+
484 amt,
+
485 src,
+
486 dst,
+
487 STPathSet{},
+
488 /*default path*/ true,
+
489 /*partial payment*/ false,
+
490 /*owner pays transfer fee*/ true,
+
491 /*offer crossing*/ OfferCrossing::no,
+
492 /*limit quality*/ std::nullopt,
+
493 /*sendmax*/ std::nullopt,
+
494 /*domain id*/ std::nullopt,
+
495 j);
+
496
+
497 if (auto const r = result.result();
+
498 isTesSuccess(r) || isTecClaim(r) || isTerRetry(r))
+
499 return r;
+ +
501}
+
502
+
508enum class OnTransferFail {
+
510 removeClaim,
+
512 keepClaim
+
513};
+
514
+
515struct FinalizeClaimHelperResult
+
516{
+
518 std::optional<TER> mainFundsTer;
+
519 // TER for transfering the reward funds
+
520 std::optional<TER> rewardTer;
+
521 // TER for removing the sle (if is sle is to be removed)
+
522 std::optional<TER> rmSleTer;
+
523
+
524 // Helper to check for overall success. If there wasn't overall success the
+
525 // individual ters can be used to decide what needs to be done.
+
526 bool
+
527 isTesSuccess() const
+
528 {
+
529 return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS &&
+
530 (!rmSleTer || *rmSleTer == tesSUCCESS);
+
531 }
+
532
+
533 TER
+
534 ter() const
+
535 {
+
536 if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) &&
+
537 (!rewardTer || *rewardTer == tesSUCCESS) &&
+
538 (!rmSleTer || *rmSleTer == tesSUCCESS))
+
539 return tesSUCCESS;
+
540
+
541 // if any phase return a tecINTERNAL or a tef, prefer returning those
+
542 // codes
+
543 if (mainFundsTer &&
+
544 (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
+
545 return *mainFundsTer;
+
546 if (rewardTer &&
+
547 (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
+
548 return *rewardTer;
+
549 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
+
550 return *rmSleTer;
+
551
+
552 // Only after the tecINTERNAL and tef are checked, return the first
+
553 // non-success error code.
+
554 if (mainFundsTer && mainFundsTer != tesSUCCESS)
+
555 return *mainFundsTer;
+
556 if (rewardTer && rewardTer != tesSUCCESS)
+
557 return *rewardTer;
+
558 if (rmSleTer && rmSleTer != tesSUCCESS)
+
559 return *rmSleTer;
+
560 return tesSUCCESS;
+
561 }
+
562};
+
563
+
592FinalizeClaimHelperResult
+
593finalizeClaimHelper(
+
594 PaymentSandbox& outerSb,
+
595 STXChainBridge const& bridgeSpec,
+
596 AccountID const& dst,
+
597 std::optional<std::uint32_t> const& dstTag,
+
598 AccountID const& claimOwner,
+
599 STAmount const& sendingAmount,
+
600 AccountID const& rewardPoolSrc,
+
601 STAmount const& rewardPool,
+
602 std::vector<AccountID> const& rewardAccounts,
+
603 STXChainBridge::ChainType const srcChain,
+
604 Keylet const& claimIDKeylet,
+
605 OnTransferFail onTransferFail,
+
606 DepositAuthPolicy depositAuthPolicy,
+ +
608{
+
609 FinalizeClaimHelperResult result;
+
610
+
611 STXChainBridge::ChainType const dstChain =
+ +
613 STAmount const thisChainAmount = [&] {
+
614 STAmount r = sendingAmount;
+
615 r.setIssue(bridgeSpec.issue(dstChain));
+
616 return r;
+
617 }();
+
618 auto const& thisDoor = bridgeSpec.door(dstChain);
+
619
+
620 {
+
621 PaymentSandbox innerSb{&outerSb};
+
622 // If distributing the reward pool fails, the mainFunds transfer should
+
623 // be rolled back
+
624 //
+
625 // If the claimid is removed, the rewards should be distributed
+
626 // even if the mainFunds fails.
627 //
-
628 // If the claimid is removed, the rewards should be distributed
-
629 // even if the mainFunds fails.
-
630 //
-
631 // If OnTransferFail::removeClaim, the claim should be removed even if
-
632 // the rewards cannot be distributed.
-
633
-
634 // transfer funds to the dst
-
635 result.mainFundsTer = transferHelper(
-
636 innerSb,
-
637 thisDoor,
-
638 dst,
-
639 dstTag,
-
640 claimOwner,
-
641 thisChainAmount,
-
642 CanCreateDstPolicy::yes,
-
643 depositAuthPolicy,
- -
645 j);
-
646
-
647 if (!isTesSuccess(*result.mainFundsTer) &&
-
648 onTransferFail == OnTransferFail::keepClaim)
-
649 {
-
650 return result;
-
651 }
-
652
-
653 // handle the reward pool
-
654 result.rewardTer = [&]() -> TER {
-
655 if (rewardAccounts.empty())
-
656 return tesSUCCESS;
-
657
-
658 // distribute the reward pool
-
659 // if the transfer failed, distribute the pool for "OnTransferFail"
-
660 // cases (the attesters did their job)
-
661 STAmount const share = [&] {
-
662 auto const round_mode =
-
663 innerSb.rules().enabled(fixXChainRewardRounding)
- - -
666 saveNumberRoundMode _{Number::setround(round_mode)};
-
667
-
668 STAmount const den{rewardAccounts.size()};
-
669 return divide(rewardPool, den, rewardPool.issue());
-
670 }();
-
671 STAmount distributed = rewardPool.zeroed();
-
672 for (auto const& rewardAccount : rewardAccounts)
-
673 {
-
674 auto const thTer = transferHelper(
-
675 innerSb,
-
676 rewardPoolSrc,
-
677 rewardAccount,
-
678 /*dstTag*/ std::nullopt,
-
679 // claim owner is not relevant to distributing rewards
-
680 /*claimOwner*/ std::nullopt,
-
681 share,
-
682 CanCreateDstPolicy::no,
-
683 DepositAuthPolicy::normal,
- -
685 j);
+
628 // If OnTransferFail::removeClaim, the claim should be removed even if
+
629 // the rewards cannot be distributed.
+
630
+
631 // transfer funds to the dst
+
632 result.mainFundsTer = transferHelper(
+
633 innerSb,
+
634 thisDoor,
+
635 dst,
+
636 dstTag,
+
637 claimOwner,
+
638 thisChainAmount,
+
639 CanCreateDstPolicy::yes,
+
640 depositAuthPolicy,
+ +
642 j);
+
643
+
644 if (!isTesSuccess(*result.mainFundsTer) &&
+
645 onTransferFail == OnTransferFail::keepClaim)
+
646 {
+
647 return result;
+
648 }
+
649
+
650 // handle the reward pool
+
651 result.rewardTer = [&]() -> TER {
+
652 if (rewardAccounts.empty())
+
653 return tesSUCCESS;
+
654
+
655 // distribute the reward pool
+
656 // if the transfer failed, distribute the pool for "OnTransferFail"
+
657 // cases (the attesters did their job)
+
658 STAmount const share = [&] {
+
659 auto const round_mode =
+
660 innerSb.rules().enabled(fixXChainRewardRounding)
+ + +
663 saveNumberRoundMode _{Number::setround(round_mode)};
+
664
+
665 STAmount const den{rewardAccounts.size()};
+
666 return divide(rewardPool, den, rewardPool.issue());
+
667 }();
+
668 STAmount distributed = rewardPool.zeroed();
+
669 for (auto const& rewardAccount : rewardAccounts)
+
670 {
+
671 auto const thTer = transferHelper(
+
672 innerSb,
+
673 rewardPoolSrc,
+
674 rewardAccount,
+
675 /*dstTag*/ std::nullopt,
+
676 // claim owner is not relevant to distributing rewards
+
677 /*claimOwner*/ std::nullopt,
+
678 share,
+
679 CanCreateDstPolicy::no,
+
680 DepositAuthPolicy::normal,
+ +
682 j);
+
683
+
684 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
+
685 return thTer;
686
-
687 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
-
688 return thTer;
+
687 if (isTesSuccess(thTer))
+
688 distributed += share;
689
-
690 if (isTesSuccess(thTer))
-
691 distributed += share;
-
692
-
693 // let txn succeed if error distributing rewards (other than
-
694 // inability to pay)
-
695 }
+
690 // let txn succeed if error distributing rewards (other than
+
691 // inability to pay)
+
692 }
+
693
+
694 if (distributed > rewardPool)
+
695 return tecINTERNAL; // LCOV_EXCL_LINE
696
-
697 if (distributed > rewardPool)
-
698 return tecINTERNAL; // LCOV_EXCL_LINE
+
697 return tesSUCCESS;
+
698 }();
699
-
700 return tesSUCCESS;
-
701 }();
-
702
-
703 if (!isTesSuccess(*result.rewardTer) &&
-
704 (onTransferFail == OnTransferFail::keepClaim ||
-
705 *result.rewardTer == tecINTERNAL))
-
706 {
-
707 return result;
-
708 }
-
709
-
710 if (!isTesSuccess(*result.mainFundsTer) ||
-
711 isTesSuccess(*result.rewardTer))
-
712 {
-
713 // Note: if the mainFunds transfer succeeds and the result transfer
-
714 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
-
715 // rolled back)
-
716 innerSb.apply(outerSb);
-
717 }
-
718 }
-
719
-
720 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
-
721 {
-
722 auto const cidOwner = (*sleClaimID)[sfAccount];
-
723 {
-
724 // Remove the claim id
-
725 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
-
726 auto const page = (*sleClaimID)[sfOwnerNode];
-
727 if (!outerSb.dirRemove(
-
728 keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
-
729 {
-
730 JLOG(j.fatal())
-
731 << "Unable to delete xchain seq number from owner.";
-
732 result.rmSleTer = tefBAD_LEDGER;
-
733 return result;
-
734 }
+
700 if (!isTesSuccess(*result.rewardTer) &&
+
701 (onTransferFail == OnTransferFail::keepClaim ||
+
702 *result.rewardTer == tecINTERNAL))
+
703 {
+
704 return result;
+
705 }
+
706
+
707 if (!isTesSuccess(*result.mainFundsTer) ||
+
708 isTesSuccess(*result.rewardTer))
+
709 {
+
710 // Note: if the mainFunds transfer succeeds and the result transfer
+
711 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
+
712 // rolled back)
+
713 innerSb.apply(outerSb);
+
714 }
+
715 }
+
716
+
717 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
+
718 {
+
719 auto const cidOwner = (*sleClaimID)[sfAccount];
+
720 {
+
721 // Remove the claim id
+
722 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
+
723 auto const page = (*sleClaimID)[sfOwnerNode];
+
724 if (!outerSb.dirRemove(
+
725 keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
+
726 {
+
727 JLOG(j.fatal())
+
728 << "Unable to delete xchain seq number from owner.";
+
729 result.rmSleTer = tefBAD_LEDGER;
+
730 return result;
+
731 }
+
732
+
733 // Remove the claim id from the ledger
+
734 outerSb.erase(sleClaimID);
735
-
736 // Remove the claim id from the ledger
-
737 outerSb.erase(sleClaimID);
-
738
-
739 adjustOwnerCount(outerSb, sleOwner, -1, j);
-
740 }
-
741 }
+
736 adjustOwnerCount(outerSb, sleOwner, -1, j);
+
737 }
+
738 }
+
739
+
740 return result;
+
741}
742
-
743 return result;
-
744}
-
745
- -
756getSignersListAndQuorum(
-
757 ReadView const& view,
-
758 SLE const& sleBridge,
- -
760{
- - + +
753getSignersListAndQuorum(
+
754 ReadView const& view,
+
755 SLE const& sleBridge,
+ +
757{
+ + +
760
+
761 AccountID const thisDoor = sleBridge[sfAccount];
+
762 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
763
-
764 AccountID const thisDoor = sleBridge[sfAccount];
-
765 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
-
766
-
767 if (!sleDoor)
-
768 {
-
769 return {r, q, tecINTERNAL};
-
770 }
-
771
-
772 auto const sleS = view.read(keylet::signers(sleBridge[sfAccount]));
-
773 if (!sleS)
-
774 {
-
775 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
-
776 }
-
777 q = (*sleS)[sfSignerQuorum];
-
778
-
779 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
-
780
-
781 if (!accountSigners)
-
782 {
-
783 return {r, q, tecINTERNAL};
-
784 }
-
785
-
786 for (auto const& as : *accountSigners)
-
787 {
-
788 r[as.account] = as.weight;
-
789 }
+
764 if (!sleDoor)
+
765 {
+
766 return {r, q, tecINTERNAL};
+
767 }
+
768
+
769 auto const sleS = view.read(keylet::signers(sleBridge[sfAccount]));
+
770 if (!sleS)
+
771 {
+
772 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
+
773 }
+
774 q = (*sleS)[sfSignerQuorum];
+
775
+
776 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
+
777
+
778 if (!accountSigners)
+
779 {
+
780 return {r, q, tecINTERNAL};
+
781 }
+
782
+
783 for (auto const& as : *accountSigners)
+
784 {
+
785 r[as.account] = as.weight;
+
786 }
+
787
+
788 return {std::move(r), q, tesSUCCESS};
+
789};
790
-
791 return {std::move(r), q, tesSUCCESS};
-
792};
-
793
-
794template <class R, class F>
- -
796readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
-
797{
-
798 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
-
799 if (auto r = getter(bridgeSpec, ct))
-
800 {
-
801 if ((*r)[sfXChainBridge] == bridgeSpec)
-
802 return r;
-
803 }
-
804 return nullptr;
-
805 };
-
806 if (auto r = tryGet(STXChainBridge::ChainType::locking))
-
807 return r;
- -
809}
-
810
- -
812peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
-
813{
-
814 return readOrpeekBridge<SLE>(
-
815 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
-
816 -> std::shared_ptr<SLE> { return v.peek(keylet::bridge(b, ct)); },
-
817 bridgeSpec);
-
818}
-
819
- -
821readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
-
822{
-
823 return readOrpeekBridge<SLE const>(
-
824 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
- -
826 return v.read(keylet::bridge(b, ct));
-
827 },
-
828 bridgeSpec);
-
829}
-
830
-
831// Precondition: all the claims in the range are consistent. They must sign for
-
832// the same event (amount, sending account, claim id, etc).
-
833template <class TIter>
-
834TER
-
835applyClaimAttestations(
-
836 ApplyView& view,
-
837 RawView& rawView,
-
838 TIter attBegin,
-
839 TIter attEnd,
-
840 STXChainBridge const& bridgeSpec,
-
841 STXChainBridge::ChainType const srcChain,
- -
843 std::uint32_t quorum,
- -
845{
-
846 if (attBegin == attEnd)
-
847 return tesSUCCESS;
-
848
-
849 PaymentSandbox psb(&view);
+
791template <class R, class F>
+ +
793readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
+
794{
+
795 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
+
796 if (auto r = getter(bridgeSpec, ct))
+
797 {
+
798 if ((*r)[sfXChainBridge] == bridgeSpec)
+
799 return r;
+
800 }
+
801 return nullptr;
+
802 };
+
803 if (auto r = tryGet(STXChainBridge::ChainType::locking))
+
804 return r;
+ +
806}
+
807
+ +
809peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
+
810{
+
811 return readOrpeekBridge<SLE>(
+
812 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
+
813 -> std::shared_ptr<SLE> { return v.peek(keylet::bridge(b, ct)); },
+
814 bridgeSpec);
+
815}
+
816
+ +
818readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
+
819{
+
820 return readOrpeekBridge<SLE const>(
+
821 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
+ +
823 return v.read(keylet::bridge(b, ct));
+
824 },
+
825 bridgeSpec);
+
826}
+
827
+
828// Precondition: all the claims in the range are consistent. They must sign for
+
829// the same event (amount, sending account, claim id, etc).
+
830template <class TIter>
+
831TER
+
832applyClaimAttestations(
+
833 ApplyView& view,
+
834 RawView& rawView,
+
835 TIter attBegin,
+
836 TIter attEnd,
+
837 STXChainBridge const& bridgeSpec,
+
838 STXChainBridge::ChainType const srcChain,
+ +
840 std::uint32_t quorum,
+ +
842{
+
843 if (attBegin == attEnd)
+
844 return tesSUCCESS;
+
845
+
846 PaymentSandbox psb(&view);
+
847
+
848 auto const claimIDKeylet =
+
849 keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
850
-
851 auto const claimIDKeylet =
-
852 keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
-
853
-
854 struct ScopeResult
-
855 {
-
856 OnNewAttestationResult newAttResult;
-
857 STAmount rewardAmount;
-
858 AccountID cidOwner;
-
859 };
-
860
-
861 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
-
862 // This lambda is ugly - admittedly. The purpose of this lambda is to
-
863 // limit the scope of sles so they don't overlap with
-
864 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
-
865 // views, it's important that the sle's lifetime doesn't overlap.
-
866 auto const sleClaimID = psb.peek(claimIDKeylet);
-
867 if (!sleClaimID)
- -
869
-
870 // Add claims that are part of the signer's list to the "claims" vector
- -
872 atts.reserve(std::distance(attBegin, attEnd));
-
873 for (auto att = attBegin; att != attEnd; ++att)
-
874 {
-
875 if (!signersList.contains(att->attestationSignerAccount))
-
876 continue;
-
877 atts.push_back(*att);
-
878 }
-
879
-
880 if (atts.empty())
-
881 {
- -
883 }
-
884
-
885 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
-
886 if (attBegin->sendingAccount != otherChainSource)
-
887 {
- -
889 }
-
890
-
891 {
-
892 STXChainBridge::ChainType const dstChain =
- +
851 struct ScopeResult
+
852 {
+
853 OnNewAttestationResult newAttResult;
+
854 STAmount rewardAmount;
+
855 AccountID cidOwner;
+
856 };
+
857
+
858 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
+
859 // This lambda is ugly - admittedly. The purpose of this lambda is to
+
860 // limit the scope of sles so they don't overlap with
+
861 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
+
862 // views, it's important that the sle's lifetime doesn't overlap.
+
863 auto const sleClaimID = psb.peek(claimIDKeylet);
+
864 if (!sleClaimID)
+ +
866
+
867 // Add claims that are part of the signer's list to the "claims" vector
+ +
869 atts.reserve(std::distance(attBegin, attEnd));
+
870 for (auto att = attBegin; att != attEnd; ++att)
+
871 {
+
872 if (!signersList.contains(att->attestationSignerAccount))
+
873 continue;
+
874 atts.push_back(*att);
+
875 }
+
876
+
877 if (atts.empty())
+
878 {
+ +
880 }
+
881
+
882 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
+
883 if (attBegin->sendingAccount != otherChainSource)
+
884 {
+ +
886 }
+
887
+
888 {
+
889 STXChainBridge::ChainType const dstChain =
+ +
891
+
892 STXChainBridge::ChainType const attDstChain =
+
893 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
894
-
895 STXChainBridge::ChainType const attDstChain =
-
896 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
-
897
-
898 if (attDstChain != dstChain)
-
899 {
- -
901 }
-
902 }
+
895 if (attDstChain != dstChain)
+
896 {
+ +
898 }
+
899 }
+
900
+
901 XChainClaimAttestations curAtts{
+
902 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
903
-
904 XChainClaimAttestations curAtts{
-
905 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
-
906
-
907 auto const newAttResult = onNewAttestations(
-
908 curAtts,
-
909 view,
-
910 &atts[0],
-
911 &atts[0] + atts.size(),
-
912 quorum,
-
913 signersList,
-
914 j);
-
915
-
916 // update the claim id
-
917 sleClaimID->setFieldArray(
-
918 sfXChainClaimAttestations, curAtts.toSTArray());
-
919 psb.update(sleClaimID);
-
920
-
921 return ScopeResult{
-
922 newAttResult,
-
923 (*sleClaimID)[sfSignatureReward],
-
924 (*sleClaimID)[sfAccount]};
-
925 }();
+
904 auto const newAttResult = onNewAttestations(
+
905 curAtts,
+
906 view,
+
907 &atts[0],
+
908 &atts[0] + atts.size(),
+
909 quorum,
+
910 signersList,
+
911 j);
+
912
+
913 // update the claim id
+
914 sleClaimID->setFieldArray(
+
915 sfXChainClaimAttestations, curAtts.toSTArray());
+
916 psb.update(sleClaimID);
+
917
+
918 return ScopeResult{
+
919 newAttResult,
+
920 (*sleClaimID)[sfSignatureReward],
+
921 (*sleClaimID)[sfAccount]};
+
922 }();
+
923
+
924 if (!scopeResult.has_value())
+
925 return scopeResult.error();
926
-
927 if (!scopeResult.has_value())
-
928 return scopeResult.error();
-
929
-
930 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
-
931 auto const& [rewardAccounts, attListChanged] = newAttResult;
-
932 if (rewardAccounts && attBegin->dst)
-
933 {
-
934 auto const r = finalizeClaimHelper(
-
935 psb,
-
936 bridgeSpec,
-
937 *attBegin->dst,
-
938 /*dstTag*/ std::nullopt,
-
939 cidOwner,
-
940 attBegin->sendingAmount,
-
941 cidOwner,
-
942 rewardAmount,
-
943 *rewardAccounts,
-
944 srcChain,
-
945 claimIDKeylet,
-
946 OnTransferFail::keepClaim,
-
947 DepositAuthPolicy::normal,
-
948 j);
-
949
-
950 auto const rTer = r.ter();
-
951
-
952 if (!isTesSuccess(rTer) &&
-
953 (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
-
954 return rTer;
-
955 }
-
956
-
957 psb.apply(rawView);
+
927 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
+
928 auto const& [rewardAccounts, attListChanged] = newAttResult;
+
929 if (rewardAccounts && attBegin->dst)
+
930 {
+
931 auto const r = finalizeClaimHelper(
+
932 psb,
+
933 bridgeSpec,
+
934 *attBegin->dst,
+
935 /*dstTag*/ std::nullopt,
+
936 cidOwner,
+
937 attBegin->sendingAmount,
+
938 cidOwner,
+
939 rewardAmount,
+
940 *rewardAccounts,
+
941 srcChain,
+
942 claimIDKeylet,
+
943 OnTransferFail::keepClaim,
+
944 DepositAuthPolicy::normal,
+
945 j);
+
946
+
947 auto const rTer = r.ter();
+
948
+
949 if (!isTesSuccess(rTer) &&
+
950 (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
+
951 return rTer;
+
952 }
+
953
+
954 psb.apply(rawView);
+
955
+
956 return tesSUCCESS;
+
957}
958
-
959 return tesSUCCESS;
-
960}
-
961
-
962template <class TIter>
-
963TER
-
964applyCreateAccountAttestations(
-
965 ApplyView& view,
-
966 RawView& rawView,
-
967 TIter attBegin,
-
968 TIter attEnd,
-
969 AccountID const& doorAccount,
-
970 Keylet const& doorK,
-
971 STXChainBridge const& bridgeSpec,
-
972 Keylet const& bridgeK,
-
973 STXChainBridge::ChainType const srcChain,
- -
975 std::uint32_t quorum,
- -
977{
-
978 if (attBegin == attEnd)
-
979 return tesSUCCESS;
-
980
-
981 PaymentSandbox psb(&view);
-
982
-
983 auto const claimCountResult = [&]() -> Expected<std::uint64_t, TER> {
-
984 auto const sleBridge = psb.peek(bridgeK);
-
985 if (!sleBridge)
-
986 return Unexpected(tecINTERNAL);
+
959template <class TIter>
+
960TER
+
961applyCreateAccountAttestations(
+
962 ApplyView& view,
+
963 RawView& rawView,
+
964 TIter attBegin,
+
965 TIter attEnd,
+
966 AccountID const& doorAccount,
+
967 Keylet const& doorK,
+
968 STXChainBridge const& bridgeSpec,
+
969 Keylet const& bridgeK,
+
970 STXChainBridge::ChainType const srcChain,
+ +
972 std::uint32_t quorum,
+ +
974{
+
975 if (attBegin == attEnd)
+
976 return tesSUCCESS;
+
977
+
978 PaymentSandbox psb(&view);
+
979
+
980 auto const claimCountResult = [&]() -> Expected<std::uint64_t, TER> {
+
981 auto const sleBridge = psb.peek(bridgeK);
+
982 if (!sleBridge)
+
983 return Unexpected(tecINTERNAL);
+
984
+
985 return (*sleBridge)[sfXChainAccountClaimCount];
+
986 }();
987
-
988 return (*sleBridge)[sfXChainAccountClaimCount];
-
989 }();
+
988 if (!claimCountResult.has_value())
+
989 return claimCountResult.error();
990
-
991 if (!claimCountResult.has_value())
-
992 return claimCountResult.error();
-
993
-
994 std::uint64_t const claimCount = claimCountResult.value();
-
995
-
996 if (attBegin->createCount <= claimCount)
-
997 {
- -
999 }
-
1000 if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims)
-
1001 {
-
1002 // Limit the number of claims on the account
- -
1004 }
-
1005
-
1006 {
-
1007 STXChainBridge::ChainType const dstChain =
- +
991 std::uint64_t const claimCount = claimCountResult.value();
+
992
+
993 if (attBegin->createCount <= claimCount)
+
994 {
+ +
996 }
+
997 if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims)
+
998 {
+
999 // Limit the number of claims on the account
+ +
1001 }
+
1002
+
1003 {
+
1004 STXChainBridge::ChainType const dstChain =
+ +
1006
+
1007 STXChainBridge::ChainType const attDstChain =
+
1008 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
1009
-
1010 STXChainBridge::ChainType const attDstChain =
-
1011 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
-
1012
-
1013 if (attDstChain != dstChain)
-
1014 {
-
1015 return tecXCHAIN_WRONG_CHAIN;
-
1016 }
-
1017 }
+
1010 if (attDstChain != dstChain)
+
1011 {
+
1012 return tecXCHAIN_WRONG_CHAIN;
+
1013 }
+
1014 }
+
1015
+
1016 auto const claimIDKeylet =
+
1017 keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
1018
-
1019 auto const claimIDKeylet =
-
1020 keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
-
1021
-
1022 struct ScopeResult
-
1023 {
-
1024 OnNewAttestationResult newAttResult;
-
1025 bool createCID;
-
1026 XChainCreateAccountAttestations curAtts;
-
1027 };
-
1028
-
1029 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
-
1030 // This lambda is ugly - admittedly. The purpose of this lambda is to
-
1031 // limit the scope of sles so they don't overlap with
-
1032 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
-
1033 // views, it's important that the sle's lifetime doesn't overlap.
-
1034
-
1035 // sleClaimID may be null. If it's null it isn't created until the end
-
1036 // of this function (if needed)
-
1037 auto const sleClaimID = psb.peek(claimIDKeylet);
-
1038 bool createCID = false;
-
1039 if (!sleClaimID)
-
1040 {
-
1041 createCID = true;
-
1042
-
1043 auto const sleDoor = psb.peek(doorK);
-
1044 if (!sleDoor)
-
1045 return Unexpected(tecINTERNAL);
-
1046
-
1047 // Check reserve
-
1048 auto const balance = (*sleDoor)[sfBalance];
-
1049 auto const reserve =
-
1050 psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
-
1051
-
1052 if (balance < reserve)
- -
1054 }
-
1055
- -
1057 atts.reserve(std::distance(attBegin, attEnd));
-
1058 for (auto att = attBegin; att != attEnd; ++att)
-
1059 {
-
1060 if (!signersList.contains(att->attestationSignerAccount))
-
1061 continue;
-
1062 atts.push_back(*att);
-
1063 }
-
1064 if (atts.empty())
-
1065 {
- -
1067 }
-
1068
-
1069 XChainCreateAccountAttestations curAtts = [&] {
-
1070 if (sleClaimID)
-
1071 return XChainCreateAccountAttestations{
-
1072 sleClaimID->getFieldArray(
-
1073 sfXChainCreateAccountAttestations)};
-
1074 return XChainCreateAccountAttestations{};
-
1075 }();
-
1076
-
1077 auto const newAttResult = onNewAttestations(
-
1078 curAtts,
-
1079 view,
-
1080 &atts[0],
-
1081 &atts[0] + atts.size(),
-
1082 quorum,
-
1083 signersList,
-
1084 j);
-
1085
-
1086 if (!createCID)
-
1087 {
-
1088 // Modify the object before it's potentially deleted, so the meta
-
1089 // data will include the new attestations
-
1090 if (!sleClaimID)
-
1091 return Unexpected(tecINTERNAL);
-
1092 sleClaimID->setFieldArray(
-
1093 sfXChainCreateAccountAttestations, curAtts.toSTArray());
-
1094 psb.update(sleClaimID);
-
1095 }
-
1096 return ScopeResult{newAttResult, createCID, curAtts};
-
1097 }();
+
1019 struct ScopeResult
+
1020 {
+
1021 OnNewAttestationResult newAttResult;
+
1022 bool createCID;
+
1023 XChainCreateAccountAttestations curAtts;
+
1024 };
+
1025
+
1026 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
+
1027 // This lambda is ugly - admittedly. The purpose of this lambda is to
+
1028 // limit the scope of sles so they don't overlap with
+
1029 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
+
1030 // views, it's important that the sle's lifetime doesn't overlap.
+
1031
+
1032 // sleClaimID may be null. If it's null it isn't created until the end
+
1033 // of this function (if needed)
+
1034 auto const sleClaimID = psb.peek(claimIDKeylet);
+
1035 bool createCID = false;
+
1036 if (!sleClaimID)
+
1037 {
+
1038 createCID = true;
+
1039
+
1040 auto const sleDoor = psb.peek(doorK);
+
1041 if (!sleDoor)
+
1042 return Unexpected(tecINTERNAL);
+
1043
+
1044 // Check reserve
+
1045 auto const balance = (*sleDoor)[sfBalance];
+
1046 auto const reserve =
+
1047 psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
+
1048
+
1049 if (balance < reserve)
+ +
1051 }
+
1052
+ +
1054 atts.reserve(std::distance(attBegin, attEnd));
+
1055 for (auto att = attBegin; att != attEnd; ++att)
+
1056 {
+
1057 if (!signersList.contains(att->attestationSignerAccount))
+
1058 continue;
+
1059 atts.push_back(*att);
+
1060 }
+
1061 if (atts.empty())
+
1062 {
+ +
1064 }
+
1065
+
1066 XChainCreateAccountAttestations curAtts = [&] {
+
1067 if (sleClaimID)
+
1068 return XChainCreateAccountAttestations{
+
1069 sleClaimID->getFieldArray(
+
1070 sfXChainCreateAccountAttestations)};
+
1071 return XChainCreateAccountAttestations{};
+
1072 }();
+
1073
+
1074 auto const newAttResult = onNewAttestations(
+
1075 curAtts,
+
1076 view,
+
1077 &atts[0],
+
1078 &atts[0] + atts.size(),
+
1079 quorum,
+
1080 signersList,
+
1081 j);
+
1082
+
1083 if (!createCID)
+
1084 {
+
1085 // Modify the object before it's potentially deleted, so the meta
+
1086 // data will include the new attestations
+
1087 if (!sleClaimID)
+
1088 return Unexpected(tecINTERNAL);
+
1089 sleClaimID->setFieldArray(
+
1090 sfXChainCreateAccountAttestations, curAtts.toSTArray());
+
1091 psb.update(sleClaimID);
+
1092 }
+
1093 return ScopeResult{newAttResult, createCID, curAtts};
+
1094 }();
+
1095
+
1096 if (!scopeResult.has_value())
+
1097 return scopeResult.error();
1098
-
1099 if (!scopeResult.has_value())
-
1100 return scopeResult.error();
+
1099 auto const& [attResult, createCID, curAtts] = scopeResult.value();
+
1100 auto const& [rewardAccounts, attListChanged] = attResult;
1101
-
1102 auto const& [attResult, createCID, curAtts] = scopeResult.value();
-
1103 auto const& [rewardAccounts, attListChanged] = attResult;
-
1104
-
1105 // Account create transactions must happen in order
-
1106 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
-
1107 {
-
1108 auto const r = finalizeClaimHelper(
-
1109 psb,
-
1110 bridgeSpec,
-
1111 attBegin->toCreate,
-
1112 /*dstTag*/ std::nullopt,
-
1113 doorAccount,
-
1114 attBegin->sendingAmount,
-
1115 /*rewardPoolSrc*/ doorAccount,
-
1116 attBegin->rewardAmount,
-
1117 *rewardAccounts,
-
1118 srcChain,
-
1119 claimIDKeylet,
-
1120 OnTransferFail::removeClaim,
-
1121 DepositAuthPolicy::normal,
-
1122 j);
-
1123
-
1124 auto const rTer = r.ter();
-
1125
-
1126 if (!isTesSuccess(rTer))
-
1127 {
-
1128 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT ||
-
1129 isTefFailure(rTer))
-
1130 return rTer;
-
1131 }
-
1132 // Move past this claim id even if it fails, so it doesn't block
-
1133 // subsequent claim ids
-
1134 auto const sleBridge = psb.peek(bridgeK);
-
1135 if (!sleBridge)
-
1136 return tecINTERNAL; // LCOV_EXCL_LINE
-
1137 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
-
1138 psb.update(sleBridge);
-
1139 }
-
1140 else if (createCID)
-
1141 {
-
1142 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
-
1143 (*createdSleClaimID)[sfAccount] = doorAccount;
-
1144 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
-
1145 (*createdSleClaimID)[sfXChainAccountCreateCount] =
-
1146 attBegin->createCount;
-
1147 createdSleClaimID->setFieldArray(
-
1148 sfXChainCreateAccountAttestations, curAtts.toSTArray());
-
1149
-
1150 // Add to owner directory of the door account
-
1151 auto const page = psb.dirInsert(
-
1152 keylet::ownerDir(doorAccount),
-
1153 claimIDKeylet,
-
1154 describeOwnerDir(doorAccount));
-
1155 if (!page)
-
1156 return tecDIR_FULL; // LCOV_EXCL_LINE
-
1157 (*createdSleClaimID)[sfOwnerNode] = *page;
-
1158
-
1159 auto const sleDoor = psb.peek(doorK);
-
1160 if (!sleDoor)
-
1161 return tecINTERNAL; // LCOV_EXCL_LINE
-
1162
-
1163 // Reserve was already checked
-
1164 adjustOwnerCount(psb, sleDoor, 1, j);
-
1165 psb.insert(createdSleClaimID);
-
1166 psb.update(sleDoor);
-
1167 }
-
1168
-
1169 psb.apply(rawView);
+
1102 // Account create transactions must happen in order
+
1103 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
+
1104 {
+
1105 auto const r = finalizeClaimHelper(
+
1106 psb,
+
1107 bridgeSpec,
+
1108 attBegin->toCreate,
+
1109 /*dstTag*/ std::nullopt,
+
1110 doorAccount,
+
1111 attBegin->sendingAmount,
+
1112 /*rewardPoolSrc*/ doorAccount,
+
1113 attBegin->rewardAmount,
+
1114 *rewardAccounts,
+
1115 srcChain,
+
1116 claimIDKeylet,
+
1117 OnTransferFail::removeClaim,
+
1118 DepositAuthPolicy::normal,
+
1119 j);
+
1120
+
1121 auto const rTer = r.ter();
+
1122
+
1123 if (!isTesSuccess(rTer))
+
1124 {
+
1125 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT ||
+
1126 isTefFailure(rTer))
+
1127 return rTer;
+
1128 }
+
1129 // Move past this claim id even if it fails, so it doesn't block
+
1130 // subsequent claim ids
+
1131 auto const sleBridge = psb.peek(bridgeK);
+
1132 if (!sleBridge)
+
1133 return tecINTERNAL; // LCOV_EXCL_LINE
+
1134 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
+
1135 psb.update(sleBridge);
+
1136 }
+
1137 else if (createCID)
+
1138 {
+
1139 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
+
1140 (*createdSleClaimID)[sfAccount] = doorAccount;
+
1141 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
+
1142 (*createdSleClaimID)[sfXChainAccountCreateCount] =
+
1143 attBegin->createCount;
+
1144 createdSleClaimID->setFieldArray(
+
1145 sfXChainCreateAccountAttestations, curAtts.toSTArray());
+
1146
+
1147 // Add to owner directory of the door account
+
1148 auto const page = psb.dirInsert(
+
1149 keylet::ownerDir(doorAccount),
+
1150 claimIDKeylet,
+
1151 describeOwnerDir(doorAccount));
+
1152 if (!page)
+
1153 return tecDIR_FULL; // LCOV_EXCL_LINE
+
1154 (*createdSleClaimID)[sfOwnerNode] = *page;
+
1155
+
1156 auto const sleDoor = psb.peek(doorK);
+
1157 if (!sleDoor)
+
1158 return tecINTERNAL; // LCOV_EXCL_LINE
+
1159
+
1160 // Reserve was already checked
+
1161 adjustOwnerCount(psb, sleDoor, 1, j);
+
1162 psb.insert(createdSleClaimID);
+
1163 psb.update(sleDoor);
+
1164 }
+
1165
+
1166 psb.apply(rawView);
+
1167
+
1168 return tesSUCCESS;
+
1169}
1170
-
1171 return tesSUCCESS;
-
1172}
-
1173
-
1174template <class TAttestation>
- -
1176toClaim(STTx const& tx)
-
1177{
-
1178 static_assert(
- - -
1181
-
1182 try
-
1183 {
-
1184 STObject o{tx};
-
1185 o.setAccountID(sfAccount, o[sfOtherChainSource]);
-
1186 return TAttestation(o);
+
1171template <class TAttestation>
+ +
1173toClaim(STTx const& tx)
+
1174{
+
1175 static_assert(
+ + +
1178
+
1179 try
+
1180 {
+
1181 STObject o{tx};
+
1182 o.setAccountID(sfAccount, o[sfOtherChainSource]);
+
1183 return TAttestation(o);
+
1184 }
+
1185 catch (...)
+
1186 {
1187 }
-
1188 catch (...)
-
1189 {
-
1190 }
-
1191 return std::nullopt;
-
1192}
-
1193
-
1194template <class TAttestation>
-
1195NotTEC
-
1196attestationpreflight(PreflightContext const& ctx)
-
1197{
-
1198 if (!publicKeyType(ctx.tx[sfPublicKey]))
-
1199 return temMALFORMED;
-
1200
-
1201 auto const att = toClaim<TAttestation>(ctx.tx);
-
1202 if (!att)
-
1203 return temMALFORMED;
-
1204
-
1205 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
-
1206 if (!att->verify(bridgeSpec))
-
1207 return temXCHAIN_BAD_PROOF;
-
1208 if (!att->validAmounts())
+
1188 return std::nullopt;
+
1189}
+
1190
+
1191template <class TAttestation>
+
1192NotTEC
+
1193attestationpreflight(PreflightContext const& ctx)
+
1194{
+
1195 if (!publicKeyType(ctx.tx[sfPublicKey]))
+
1196 return temMALFORMED;
+
1197
+
1198 auto const att = toClaim<TAttestation>(ctx.tx);
+
1199 if (!att)
+
1200 return temMALFORMED;
+
1201
+
1202 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
+
1203 if (!att->verify(bridgeSpec))
+
1204 return temXCHAIN_BAD_PROOF;
+
1205 if (!att->validAmounts())
+
1206 return temXCHAIN_BAD_PROOF;
+
1207
+
1208 if (att->sendingAmount.signum() <= 0)
1209 return temXCHAIN_BAD_PROOF;
-
1210
-
1211 if (att->sendingAmount.signum() <= 0)
-
1212 return temXCHAIN_BAD_PROOF;
-
1213 auto const expectedIssue =
-
1214 bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
-
1215 if (att->sendingAmount.issue() != expectedIssue)
-
1216 return temXCHAIN_BAD_PROOF;
+
1210 auto const expectedIssue =
+
1211 bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
+
1212 if (att->sendingAmount.issue() != expectedIssue)
+
1213 return temXCHAIN_BAD_PROOF;
+
1214
+
1215 return tesSUCCESS;
+
1216}
1217
-
1218 return tesSUCCESS;
-
1219}
-
1220
-
1221template <class TAttestation>
-
1222TER
-
1223attestationPreclaim(PreclaimContext const& ctx)
-
1224{
-
1225 auto const att = toClaim<TAttestation>(ctx.tx);
-
1226 // checked in preflight
-
1227 if (!att)
-
1228 return tecINTERNAL; // LCOV_EXCL_LINE
-
1229
-
1230 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
-
1231 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
-
1232 if (!sleBridge)
-
1233 {
-
1234 return tecNO_ENTRY;
-
1235 }
-
1236
-
1237 AccountID const attestationSignerAccount{
-
1238 ctx.tx[sfAttestationSignerAccount]};
-
1239 PublicKey const pk{ctx.tx[sfPublicKey]};
-
1240
-
1241 // signersList is a map from account id to weights
-
1242 auto const [signersList, quorum, slTer] =
-
1243 getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
+
1218template <class TAttestation>
+
1219TER
+
1220attestationPreclaim(PreclaimContext const& ctx)
+
1221{
+
1222 auto const att = toClaim<TAttestation>(ctx.tx);
+
1223 // checked in preflight
+
1224 if (!att)
+
1225 return tecINTERNAL; // LCOV_EXCL_LINE
+
1226
+
1227 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
+
1228 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
+
1229 if (!sleBridge)
+
1230 {
+
1231 return tecNO_ENTRY;
+
1232 }
+
1233
+
1234 AccountID const attestationSignerAccount{
+
1235 ctx.tx[sfAttestationSignerAccount]};
+
1236 PublicKey const pk{ctx.tx[sfPublicKey]};
+
1237
+
1238 // signersList is a map from account id to weights
+
1239 auto const [signersList, quorum, slTer] =
+
1240 getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
+
1241
+
1242 if (!isTesSuccess(slTer))
+
1243 return slTer;
1244
-
1245 if (!isTesSuccess(slTer))
-
1246 return slTer;
-
1247
-
1248 return checkAttestationPublicKey(
-
1249 ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
-
1250}
-
1251
-
1252template <class TAttestation>
-
1253TER
-
1254attestationDoApply(ApplyContext& ctx)
-
1255{
-
1256 auto const att = toClaim<TAttestation>(ctx.tx);
-
1257 if (!att)
-
1258 // Should already be checked in preflight
-
1259 return tecINTERNAL; // LCOV_EXCL_LINE
-
1260
-
1261 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
-
1262
-
1263 struct ScopeResult
-
1264 {
-
1265 STXChainBridge::ChainType srcChain;
- -
1267 std::uint32_t quorum;
-
1268 AccountID thisDoor;
-
1269 Keylet bridgeK;
-
1270 };
-
1271
-
1272 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
-
1273 // This lambda is ugly - admittedly. The purpose of this lambda is to
-
1274 // limit the scope of sles so they don't overlap with
-
1275 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
-
1276 // views, it's important that the sle's lifetime doesn't overlap.
-
1277 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
-
1278 if (!sleBridge)
-
1279 {
-
1280 return Unexpected(tecNO_ENTRY);
-
1281 }
-
1282 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
-
1283 AccountID const thisDoor = (*sleBridge)[sfAccount];
-
1284
- -
1286 {
-
1287 if (thisDoor == bridgeSpec.lockingChainDoor())
- -
1289 else if (thisDoor == bridgeSpec.issuingChainDoor())
- -
1291 else
-
1292 return Unexpected(tecINTERNAL);
-
1293 }
-
1294 STXChainBridge::ChainType const srcChain =
- -
1296
-
1297 // signersList is a map from account id to weights
-
1298 auto [signersList, quorum, slTer] =
-
1299 getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
+
1245 return checkAttestationPublicKey(
+
1246 ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
+
1247}
+
1248
+
1249template <class TAttestation>
+
1250TER
+
1251attestationDoApply(ApplyContext& ctx)
+
1252{
+
1253 auto const att = toClaim<TAttestation>(ctx.tx);
+
1254 if (!att)
+
1255 // Should already be checked in preflight
+
1256 return tecINTERNAL; // LCOV_EXCL_LINE
+
1257
+
1258 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
+
1259
+
1260 struct ScopeResult
+
1261 {
+
1262 STXChainBridge::ChainType srcChain;
+ +
1264 std::uint32_t quorum;
+
1265 AccountID thisDoor;
+
1266 Keylet bridgeK;
+
1267 };
+
1268
+
1269 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
+
1270 // This lambda is ugly - admittedly. The purpose of this lambda is to
+
1271 // limit the scope of sles so they don't overlap with
+
1272 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
+
1273 // views, it's important that the sle's lifetime doesn't overlap.
+
1274 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
+
1275 if (!sleBridge)
+
1276 {
+
1277 return Unexpected(tecNO_ENTRY);
+
1278 }
+
1279 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
+
1280 AccountID const thisDoor = (*sleBridge)[sfAccount];
+
1281
+ +
1283 {
+
1284 if (thisDoor == bridgeSpec.lockingChainDoor())
+ +
1286 else if (thisDoor == bridgeSpec.issuingChainDoor())
+ +
1288 else
+
1289 return Unexpected(tecINTERNAL);
+
1290 }
+
1291 STXChainBridge::ChainType const srcChain =
+ +
1293
+
1294 // signersList is a map from account id to weights
+
1295 auto [signersList, quorum, slTer] =
+
1296 getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
+
1297
+
1298 if (!isTesSuccess(slTer))
+
1299 return Unexpected(slTer);
1300
-
1301 if (!isTesSuccess(slTer))
-
1302 return Unexpected(slTer);
-
1303
-
1304 return ScopeResult{
-
1305 srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
-
1306 }();
+
1301 return ScopeResult{
+
1302 srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
+
1303 }();
+
1304
+
1305 if (!scopeResult.has_value())
+
1306 return scopeResult.error();
1307
-
1308 if (!scopeResult.has_value())
-
1309 return scopeResult.error();
+
1308 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] =
+
1309 scopeResult.value();
1310
-
1311 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] =
-
1312 scopeResult.value();
-
1313
-
1314 static_assert(
- - -
1317
- -
1319 {
-
1320 return applyClaimAttestations(
-
1321 ctx.view(),
-
1322 ctx.rawView(),
-
1323 &*att,
-
1324 &*att + 1,
-
1325 bridgeSpec,
-
1326 srcChain,
-
1327 signersList,
-
1328 quorum,
-
1329 ctx.journal);
-
1330 }
-
1331 else if constexpr (std::is_same_v<
-
1332 TAttestation,
-
1333 Attestations::AttestationCreateAccount>)
-
1334 {
-
1335 return applyCreateAccountAttestations(
-
1336 ctx.view(),
-
1337 ctx.rawView(),
-
1338 &*att,
-
1339 &*att + 1,
-
1340 thisDoor,
-
1341 keylet::account(thisDoor),
-
1342 bridgeSpec,
-
1343 bridgeK,
-
1344 srcChain,
-
1345 signersList,
-
1346 quorum,
-
1347 ctx.journal);
-
1348 }
-
1349}
+
1311 static_assert(
+ + +
1314
+ +
1316 {
+
1317 return applyClaimAttestations(
+
1318 ctx.view(),
+
1319 ctx.rawView(),
+
1320 &*att,
+
1321 &*att + 1,
+
1322 bridgeSpec,
+
1323 srcChain,
+
1324 signersList,
+
1325 quorum,
+
1326 ctx.journal);
+
1327 }
+
1328 else if constexpr (std::is_same_v<
+
1329 TAttestation,
+
1330 Attestations::AttestationCreateAccount>)
+
1331 {
+
1332 return applyCreateAccountAttestations(
+
1333 ctx.view(),
+
1334 ctx.rawView(),
+
1335 &*att,
+
1336 &*att + 1,
+
1337 thisDoor,
+
1338 keylet::account(thisDoor),
+
1339 bridgeSpec,
+
1340 bridgeK,
+
1341 srcChain,
+
1342 signersList,
+
1343 quorum,
+
1344 ctx.journal);
+
1345 }
+
1346}
+
1347
+
1348} // namespace
+
1349//------------------------------------------------------------------------------
1350
-
1351} // namespace
-
1352//------------------------------------------------------------------------------
-
1353
-
1354NotTEC
-
- -
1356{
-
1357 auto const account = ctx.tx[sfAccount];
-
1358 auto const reward = ctx.tx[sfSignatureReward];
-
1359 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
-
1360 auto const bridgeSpec = ctx.tx[sfXChainBridge];
-
1361 // Doors must be distinct to help prevent transaction replay attacks
-
1362 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
-
1363 {
- -
1365 }
-
1366
-
1367 if (bridgeSpec.lockingChainDoor() != account &&
-
1368 bridgeSpec.issuingChainDoor() != account)
-
1369 {
- -
1371 }
-
1372
-
1373 if (isXRP(bridgeSpec.lockingChainIssue()) !=
-
1374 isXRP(bridgeSpec.issuingChainIssue()))
-
1375 {
-
1376 // Because ious and xrp have different numeric ranges, both the src and
-
1377 // dst issues must be both XRP or both IOU.
- -
1379 }
-
1380
-
1381 if (!isXRP(reward) || reward.signum() < 0)
-
1382 {
- -
1384 }
-
1385
-
1386 if (minAccountCreate &&
-
1387 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
-
1388 !isXRP(bridgeSpec.lockingChainIssue()) ||
-
1389 !isXRP(bridgeSpec.issuingChainIssue())))
-
1390 {
- -
1392 }
-
1393
-
1394 if (isXRP(bridgeSpec.issuingChainIssue()))
-
1395 {
-
1396 // Issuing account must be the root account for XRP (which presumably
-
1397 // owns all the XRP). This is done so the issuing account can't "run
-
1398 // out" of wrapped tokens.
-
1399 static auto const rootAccount = calcAccountID(
- -
1401 KeyType::secp256k1, generateSeed("masterpassphrase"))
-
1402 .first);
-
1403 if (bridgeSpec.issuingChainDoor() != rootAccount)
-
1404 {
- -
1406 }
-
1407 }
-
1408 else
-
1409 {
-
1410 // Issuing account must be the issuer for non-XRP. This is done so the
-
1411 // issuing account can't "run out" of wrapped tokens.
-
1412 if (bridgeSpec.issuingChainDoor() !=
-
1413 bridgeSpec.issuingChainIssue().account)
-
1414 {
- -
1416 }
-
1417 }
-
1418
-
1419 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
-
1420 {
-
1421 // If the locking chain door is locking their own asset, in some sense
-
1422 // nothing is being locked. Disallow this.
- -
1424 }
+
1351NotTEC
+
+ +
1353{
+
1354 auto const account = ctx.tx[sfAccount];
+
1355 auto const reward = ctx.tx[sfSignatureReward];
+
1356 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
+
1357 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1358 // Doors must be distinct to help prevent transaction replay attacks
+
1359 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
+
1360 {
+ +
1362 }
+
1363
+
1364 if (bridgeSpec.lockingChainDoor() != account &&
+
1365 bridgeSpec.issuingChainDoor() != account)
+
1366 {
+ +
1368 }
+
1369
+
1370 if (isXRP(bridgeSpec.lockingChainIssue()) !=
+
1371 isXRP(bridgeSpec.issuingChainIssue()))
+
1372 {
+
1373 // Because ious and xrp have different numeric ranges, both the src and
+
1374 // dst issues must be both XRP or both IOU.
+ +
1376 }
+
1377
+
1378 if (!isXRP(reward) || reward.signum() < 0)
+
1379 {
+ +
1381 }
+
1382
+
1383 if (minAccountCreate &&
+
1384 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
+
1385 !isXRP(bridgeSpec.lockingChainIssue()) ||
+
1386 !isXRP(bridgeSpec.issuingChainIssue())))
+
1387 {
+ +
1389 }
+
1390
+
1391 if (isXRP(bridgeSpec.issuingChainIssue()))
+
1392 {
+
1393 // Issuing account must be the root account for XRP (which presumably
+
1394 // owns all the XRP). This is done so the issuing account can't "run
+
1395 // out" of wrapped tokens.
+
1396 static auto const rootAccount = calcAccountID(
+ +
1398 KeyType::secp256k1, generateSeed("masterpassphrase"))
+
1399 .first);
+
1400 if (bridgeSpec.issuingChainDoor() != rootAccount)
+
1401 {
+ +
1403 }
+
1404 }
+
1405 else
+
1406 {
+
1407 // Issuing account must be the issuer for non-XRP. This is done so the
+
1408 // issuing account can't "run out" of wrapped tokens.
+
1409 if (bridgeSpec.issuingChainDoor() !=
+
1410 bridgeSpec.issuingChainIssue().account)
+
1411 {
+ +
1413 }
+
1414 }
+
1415
+
1416 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
+
1417 {
+
1418 // If the locking chain door is locking their own asset, in some sense
+
1419 // nothing is being locked. Disallow this.
+ +
1421 }
+
1422
+
1423 return tesSUCCESS;
+
1424}
+
1425
-
1426 return tesSUCCESS;
-
1427}
-
-
1428
-
1429TER
-
- -
1431{
-
1432 auto const account = ctx.tx[sfAccount];
-
1433 auto const bridgeSpec = ctx.tx[sfXChainBridge];
-
1434 STXChainBridge::ChainType const chainType =
-
1435 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
-
1436
-
1437 {
-
1438 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
-
1439 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
-
1440 };
-
1441
-
1442 if (hasBridge(STXChainBridge::ChainType::issuing) ||
- -
1444 {
-
1445 return tecDUPLICATE;
-
1446 }
-
1447 }
-
1448
-
1449 if (!isXRP(bridgeSpec.issue(chainType)))
-
1450 {
-
1451 auto const sleIssuer =
-
1452 ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
+
1426TER
+
+ +
1428{
+
1429 auto const account = ctx.tx[sfAccount];
+
1430 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1431 STXChainBridge::ChainType const chainType =
+
1432 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
+
1433
+
1434 {
+
1435 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
+
1436 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
+
1437 };
+
1438
+
1439 if (hasBridge(STXChainBridge::ChainType::issuing) ||
+ +
1441 {
+
1442 return tecDUPLICATE;
+
1443 }
+
1444 }
+
1445
+
1446 if (!isXRP(bridgeSpec.issue(chainType)))
+
1447 {
+
1448 auto const sleIssuer =
+
1449 ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
+
1450
+
1451 if (!sleIssuer)
+
1452 return tecNO_ISSUER;
1453
-
1454 if (!sleIssuer)
-
1455 return tecNO_ISSUER;
-
1456
-
1457 // Allowing clawing back funds would break the bridge's invariant that
-
1458 // wrapped funds are always backed by locked funds
-
1459 if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
-
1460 return tecNO_PERMISSION;
-
1461 }
-
1462
-
1463 {
-
1464 // Check reserve
-
1465 auto const sleAcc = ctx.view.read(keylet::account(account));
-
1466 if (!sleAcc)
-
1467 return terNO_ACCOUNT;
-
1468
-
1469 auto const balance = (*sleAcc)[sfBalance];
-
1470 auto const reserve =
-
1471 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
-
1472
-
1473 if (balance < reserve)
- -
1475 }
+
1454 // Allowing clawing back funds would break the bridge's invariant that
+
1455 // wrapped funds are always backed by locked funds
+
1456 if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
+
1457 return tecNO_PERMISSION;
+
1458 }
+
1459
+
1460 {
+
1461 // Check reserve
+
1462 auto const sleAcc = ctx.view.read(keylet::account(account));
+
1463 if (!sleAcc)
+
1464 return terNO_ACCOUNT;
+
1465
+
1466 auto const balance = (*sleAcc)[sfBalance];
+
1467 auto const reserve =
+
1468 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
+
1469
+
1470 if (balance < reserve)
+ +
1472 }
+
1473
+
1474 return tesSUCCESS;
+
1475}
+
1476
-
1477 return tesSUCCESS;
-
1478}
-
-
1479
-
1480TER
-
- -
1482{
-
1483 auto const account = ctx_.tx[sfAccount];
-
1484 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
-
1485 auto const reward = ctx_.tx[sfSignatureReward];
-
1486 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
-
1487
-
1488 auto const sleAcct = ctx_.view().peek(keylet::account(account));
-
1489 if (!sleAcct)
-
1490 return tecINTERNAL; // LCOV_EXCL_LINE
+
1477TER
+
+ +
1479{
+
1480 auto const account = ctx_.tx[sfAccount];
+
1481 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
+
1482 auto const reward = ctx_.tx[sfSignatureReward];
+
1483 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
+
1484
+
1485 auto const sleAcct = ctx_.view().peek(keylet::account(account));
+
1486 if (!sleAcct)
+
1487 return tecINTERNAL; // LCOV_EXCL_LINE
+
1488
+
1489 STXChainBridge::ChainType const chainType =
+
1490 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1491
-
1492 STXChainBridge::ChainType const chainType =
-
1493 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
+
1492 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
+
1493 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1494
-
1495 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
-
1496 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
-
1497
-
1498 (*sleBridge)[sfAccount] = account;
-
1499 (*sleBridge)[sfSignatureReward] = reward;
-
1500 if (minAccountCreate)
-
1501 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
-
1502 (*sleBridge)[sfXChainBridge] = bridgeSpec;
-
1503 (*sleBridge)[sfXChainClaimID] = 0;
-
1504 (*sleBridge)[sfXChainAccountCreateCount] = 0;
-
1505 (*sleBridge)[sfXChainAccountClaimCount] = 0;
-
1506
-
1507 // Add to owner directory
-
1508 {
-
1509 auto const page = ctx_.view().dirInsert(
-
1510 keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
-
1511 if (!page)
-
1512 return tecDIR_FULL; // LCOV_EXCL_LINE
-
1513 (*sleBridge)[sfOwnerNode] = *page;
-
1514 }
-
1515
-
1516 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
+
1495 (*sleBridge)[sfAccount] = account;
+
1496 (*sleBridge)[sfSignatureReward] = reward;
+
1497 if (minAccountCreate)
+
1498 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
+
1499 (*sleBridge)[sfXChainBridge] = bridgeSpec;
+
1500 (*sleBridge)[sfXChainClaimID] = 0;
+
1501 (*sleBridge)[sfXChainAccountCreateCount] = 0;
+
1502 (*sleBridge)[sfXChainAccountClaimCount] = 0;
+
1503
+
1504 // Add to owner directory
+
1505 {
+
1506 auto const page = ctx_.view().dirInsert(
+
1507 keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
+
1508 if (!page)
+
1509 return tecDIR_FULL; // LCOV_EXCL_LINE
+
1510 (*sleBridge)[sfOwnerNode] = *page;
+
1511 }
+
1512
+
1513 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
+
1514
+
1515 ctx_.view().insert(sleBridge);
+
1516 ctx_.view().update(sleAcct);
1517
-
1518 ctx_.view().insert(sleBridge);
-
1519 ctx_.view().update(sleAcct);
+
1518 return tesSUCCESS;
+
1519}
+
1520
-
1521 return tesSUCCESS;
-
1522}
+
1521//------------------------------------------------------------------------------
+
1522
+ +
+ +
1525{
+
1526 return tfBridgeModifyMask;
+
1527}
-
1523
-
1524//------------------------------------------------------------------------------
-
1525
- -
- -
1528{
-
1529 return tfBridgeModifyMask;
-
1530}
+
1528
+
1529NotTEC
+
+ +
1531{
+
1532 auto const account = ctx.tx[sfAccount];
+
1533 auto const reward = ctx.tx[~sfSignatureReward];
+
1534 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
+
1535 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1536 bool const clearAccountCreate =
+ +
1538
+
1539 if (!reward && !minAccountCreate && !clearAccountCreate)
+
1540 {
+
1541 // Must change something
+
1542 return temMALFORMED;
+
1543 }
+
1544
+
1545 if (minAccountCreate && clearAccountCreate)
+
1546 {
+
1547 // Can't both clear and set account create in the same txn
+
1548 return temMALFORMED;
+
1549 }
+
1550
+
1551 if (bridgeSpec.lockingChainDoor() != account &&
+
1552 bridgeSpec.issuingChainDoor() != account)
+
1553 {
+ +
1555 }
+
1556
+
1557 if (reward && (!isXRP(*reward) || reward->signum() < 0))
+
1558 {
+ +
1560 }
+
1561
+
1562 if (minAccountCreate &&
+
1563 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
+
1564 !isXRP(bridgeSpec.lockingChainIssue()) ||
+
1565 !isXRP(bridgeSpec.issuingChainIssue())))
+
1566 {
+ +
1568 }
+
1569
+
1570 return tesSUCCESS;
+
1571}
-
1531
-
1532NotTEC
-
- -
1534{
-
1535 auto const account = ctx.tx[sfAccount];
-
1536 auto const reward = ctx.tx[~sfSignatureReward];
-
1537 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
-
1538 auto const bridgeSpec = ctx.tx[sfXChainBridge];
-
1539 bool const clearAccountCreate =
- -
1541
-
1542 if (!reward && !minAccountCreate && !clearAccountCreate)
-
1543 {
-
1544 // Must change something
-
1545 return temMALFORMED;
-
1546 }
-
1547
-
1548 if (minAccountCreate && clearAccountCreate)
-
1549 {
-
1550 // Can't both clear and set account create in the same txn
-
1551 return temMALFORMED;
-
1552 }
-
1553
-
1554 if (bridgeSpec.lockingChainDoor() != account &&
-
1555 bridgeSpec.issuingChainDoor() != account)
-
1556 {
- -
1558 }
-
1559
-
1560 if (reward && (!isXRP(*reward) || reward->signum() < 0))
-
1561 {
- -
1563 }
-
1564
-
1565 if (minAccountCreate &&
-
1566 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
-
1567 !isXRP(bridgeSpec.lockingChainIssue()) ||
-
1568 !isXRP(bridgeSpec.issuingChainIssue())))
-
1569 {
- -
1571 }
1572
-
1573 return tesSUCCESS;
-
1574}
-
-
1575
-
1576TER
-
- -
1578{
-
1579 auto const account = ctx.tx[sfAccount];
-
1580 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1573TER
+
+ +
1575{
+
1576 auto const account = ctx.tx[sfAccount];
+
1577 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1578
+
1579 STXChainBridge::ChainType const chainType =
+
1580 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1581
-
1582 STXChainBridge::ChainType const chainType =
-
1583 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
-
1584
-
1585 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
-
1586 {
-
1587 return tecNO_ENTRY;
-
1588 }
+
1582 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
+
1583 {
+
1584 return tecNO_ENTRY;
+
1585 }
+
1586
+
1587 return tesSUCCESS;
+
1588}
+
1589
-
1590 return tesSUCCESS;
-
1591}
-
-
1592
-
1593TER
-
- -
1595{
-
1596 auto const account = ctx_.tx[sfAccount];
-
1597 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
-
1598 auto const reward = ctx_.tx[~sfSignatureReward];
-
1599 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
-
1600 bool const clearAccountCreate =
- -
1602
-
1603 auto const sleAcct = ctx_.view().peek(keylet::account(account));
-
1604 if (!sleAcct)
-
1605 return tecINTERNAL; // LCOV_EXCL_LINE
+
1590TER
+
+ +
1592{
+
1593 auto const account = ctx_.tx[sfAccount];
+
1594 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
+
1595 auto const reward = ctx_.tx[~sfSignatureReward];
+
1596 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
+
1597 bool const clearAccountCreate =
+ +
1599
+
1600 auto const sleAcct = ctx_.view().peek(keylet::account(account));
+
1601 if (!sleAcct)
+
1602 return tecINTERNAL; // LCOV_EXCL_LINE
+
1603
+
1604 STXChainBridge::ChainType const chainType =
+
1605 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1606
-
1607 STXChainBridge::ChainType const chainType =
-
1608 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
-
1609
-
1610 auto const sleBridge =
-
1611 ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
-
1612 if (!sleBridge)
-
1613 return tecINTERNAL; // LCOV_EXCL_LINE
-
1614
-
1615 if (reward)
-
1616 (*sleBridge)[sfSignatureReward] = *reward;
-
1617 if (minAccountCreate)
-
1618 {
-
1619 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
-
1620 }
-
1621 if (clearAccountCreate &&
-
1622 sleBridge->isFieldPresent(sfMinAccountCreateAmount))
-
1623 {
-
1624 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
-
1625 }
-
1626 ctx_.view().update(sleBridge);
+
1607 auto const sleBridge =
+
1608 ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
+
1609 if (!sleBridge)
+
1610 return tecINTERNAL; // LCOV_EXCL_LINE
+
1611
+
1612 if (reward)
+
1613 (*sleBridge)[sfSignatureReward] = *reward;
+
1614 if (minAccountCreate)
+
1615 {
+
1616 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
+
1617 }
+
1618 if (clearAccountCreate &&
+
1619 sleBridge->isFieldPresent(sfMinAccountCreateAmount))
+
1620 {
+
1621 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
+
1622 }
+
1623 ctx_.view().update(sleBridge);
+
1624
+
1625 return tesSUCCESS;
+
1626}
+
1627
-
1628 return tesSUCCESS;
-
1629}
+
1628//------------------------------------------------------------------------------
+
1629
+
1630NotTEC
+
+ +
1632{
+
1633 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
+
1634 auto const amount = ctx.tx[sfAmount];
+
1635
+
1636 if (amount.signum() <= 0 ||
+
1637 (amount.issue() != bridgeSpec.lockingChainIssue() &&
+
1638 amount.issue() != bridgeSpec.issuingChainIssue()))
+
1639 {
+
1640 return temBAD_AMOUNT;
+
1641 }
+
1642
+
1643 return tesSUCCESS;
+
1644}
-
1630
-
1631//------------------------------------------------------------------------------
-
1632
-
1633NotTEC
-
- -
1635{
-
1636 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
-
1637 auto const amount = ctx.tx[sfAmount];
-
1638
-
1639 if (amount.signum() <= 0 ||
-
1640 (amount.issue() != bridgeSpec.lockingChainIssue() &&
-
1641 amount.issue() != bridgeSpec.issuingChainIssue()))
-
1642 {
-
1643 return temBAD_AMOUNT;
-
1644 }
1645
-
1646 return tesSUCCESS;
-
1647}
-
-
1648
-
1649TER
-
- -
1651{
-
1652 AccountID const account = ctx.tx[sfAccount];
-
1653 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
-
1654 STAmount const& thisChainAmount = ctx.tx[sfAmount];
-
1655 auto const claimID = ctx.tx[sfXChainClaimID];
-
1656
-
1657 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
-
1658 if (!sleBridge)
-
1659 {
-
1660 return tecNO_ENTRY;
-
1661 }
-
1662
-
1663 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
-
1664 {
-
1665 return tecNO_DST;
-
1666 }
-
1667
-
1668 auto const thisDoor = (*sleBridge)[sfAccount];
-
1669 bool isLockingChain = false;
-
1670 {
-
1671 if (thisDoor == bridgeSpec.lockingChainDoor())
-
1672 isLockingChain = true;
-
1673 else if (thisDoor == bridgeSpec.issuingChainDoor())
-
1674 isLockingChain = false;
-
1675 else
-
1676 return tecINTERNAL; // LCOV_EXCL_LINE
-
1677 }
+
1646TER
+
+ +
1648{
+
1649 AccountID const account = ctx.tx[sfAccount];
+
1650 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
+
1651 STAmount const& thisChainAmount = ctx.tx[sfAmount];
+
1652 auto const claimID = ctx.tx[sfXChainClaimID];
+
1653
+
1654 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
+
1655 if (!sleBridge)
+
1656 {
+
1657 return tecNO_ENTRY;
+
1658 }
+
1659
+
1660 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
+
1661 {
+
1662 return tecNO_DST;
+
1663 }
+
1664
+
1665 auto const thisDoor = (*sleBridge)[sfAccount];
+
1666 bool isLockingChain = false;
+
1667 {
+
1668 if (thisDoor == bridgeSpec.lockingChainDoor())
+
1669 isLockingChain = true;
+
1670 else if (thisDoor == bridgeSpec.issuingChainDoor())
+
1671 isLockingChain = false;
+
1672 else
+
1673 return tecINTERNAL; // LCOV_EXCL_LINE
+
1674 }
+
1675
+
1676 {
+
1677 // Check that the amount specified matches the expected issue
1678
-
1679 {
-
1680 // Check that the amount specified matches the expected issue
-
1681
-
1682 if (isLockingChain)
-
1683 {
-
1684 if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
- -
1686 }
-
1687 else
-
1688 {
-
1689 if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
- -
1691 }
-
1692 }
-
1693
-
1694 if (isXRP(bridgeSpec.lockingChainIssue()) !=
-
1695 isXRP(bridgeSpec.issuingChainIssue()))
-
1696 {
-
1697 // Should have been caught when creating the bridge
-
1698 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
-
1699 // and the numeric issues that need to be addressed with that.
-
1700 return tecINTERNAL; // LCOV_EXCL_LINE
-
1701 }
-
1702
-
1703 auto const otherChainAmount = [&]() -> STAmount {
-
1704 STAmount r(thisChainAmount);
-
1705 if (isLockingChain)
-
1706 r.setIssue(bridgeSpec.issuingChainIssue());
-
1707 else
-
1708 r.setIssue(bridgeSpec.lockingChainIssue());
-
1709 return r;
-
1710 }();
-
1711
-
1712 auto const sleClaimID =
-
1713 ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
-
1714 {
-
1715 // Check that the sequence number is owned by the sender of this
-
1716 // transaction
-
1717 if (!sleClaimID)
-
1718 {
-
1719 return tecXCHAIN_NO_CLAIM_ID;
-
1720 }
-
1721
-
1722 if ((*sleClaimID)[sfAccount] != account)
-
1723 {
-
1724 // Sequence number isn't owned by the sender of this transaction
- -
1726 }
-
1727 }
-
1728
-
1729 // quorum is checked in `doApply`
-
1730 return tesSUCCESS;
-
1731}
+
1679 if (isLockingChain)
+
1680 {
+
1681 if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
+ +
1683 }
+
1684 else
+
1685 {
+
1686 if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
+ +
1688 }
+
1689 }
+
1690
+
1691 if (isXRP(bridgeSpec.lockingChainIssue()) !=
+
1692 isXRP(bridgeSpec.issuingChainIssue()))
+
1693 {
+
1694 // Should have been caught when creating the bridge
+
1695 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
+
1696 // and the numeric issues that need to be addressed with that.
+
1697 return tecINTERNAL; // LCOV_EXCL_LINE
+
1698 }
+
1699
+
1700 auto const otherChainAmount = [&]() -> STAmount {
+
1701 STAmount r(thisChainAmount);
+
1702 if (isLockingChain)
+
1703 r.setIssue(bridgeSpec.issuingChainIssue());
+
1704 else
+
1705 r.setIssue(bridgeSpec.lockingChainIssue());
+
1706 return r;
+
1707 }();
+
1708
+
1709 auto const sleClaimID =
+
1710 ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
+
1711 {
+
1712 // Check that the sequence number is owned by the sender of this
+
1713 // transaction
+
1714 if (!sleClaimID)
+
1715 {
+
1716 return tecXCHAIN_NO_CLAIM_ID;
+
1717 }
+
1718
+
1719 if ((*sleClaimID)[sfAccount] != account)
+
1720 {
+
1721 // Sequence number isn't owned by the sender of this transaction
+ +
1723 }
+
1724 }
+
1725
+
1726 // quorum is checked in `doApply`
+
1727 return tesSUCCESS;
+
1728}
-
1732
-
1733TER
-
- -
1735{
-
1736 PaymentSandbox psb(&ctx_.view());
-
1737
-
1738 AccountID const account = ctx_.tx[sfAccount];
-
1739 auto const dst = ctx_.tx[sfDestination];
-
1740 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
-
1741 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
-
1742 auto const claimID = ctx_.tx[sfXChainClaimID];
-
1743 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
-
1744
-
1745 struct ScopeResult
-
1746 {
-
1747 std::vector<AccountID> rewardAccounts;
-
1748 AccountID rewardPoolSrc;
-
1749 STAmount sendingAmount;
- -
1751 STAmount signatureReward;
-
1752 };
-
1753
-
1754 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
-
1755 // This lambda is ugly - admittedly. The purpose of this lambda is to
-
1756 // limit the scope of sles so they don't overlap with
-
1757 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
-
1758 // views, it's important that the sle's lifetime doesn't overlap.
-
1759
-
1760 auto const sleAcct = psb.peek(keylet::account(account));
-
1761 auto const sleBridge = peekBridge(psb, bridgeSpec);
-
1762 auto const sleClaimID = psb.peek(claimIDKeylet);
+
1729
+
1730TER
+
+ +
1732{
+
1733 PaymentSandbox psb(&ctx_.view());
+
1734
+
1735 AccountID const account = ctx_.tx[sfAccount];
+
1736 auto const dst = ctx_.tx[sfDestination];
+
1737 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
+
1738 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
+
1739 auto const claimID = ctx_.tx[sfXChainClaimID];
+
1740 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
+
1741
+
1742 struct ScopeResult
+
1743 {
+
1744 std::vector<AccountID> rewardAccounts;
+
1745 AccountID rewardPoolSrc;
+
1746 STAmount sendingAmount;
+ +
1748 STAmount signatureReward;
+
1749 };
+
1750
+
1751 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
+
1752 // This lambda is ugly - admittedly. The purpose of this lambda is to
+
1753 // limit the scope of sles so they don't overlap with
+
1754 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
+
1755 // views, it's important that the sle's lifetime doesn't overlap.
+
1756
+
1757 auto const sleAcct = psb.peek(keylet::account(account));
+
1758 auto const sleBridge = peekBridge(psb, bridgeSpec);
+
1759 auto const sleClaimID = psb.peek(claimIDKeylet);
+
1760
+
1761 if (!(sleBridge && sleClaimID && sleAcct))
+
1762 return Unexpected(tecINTERNAL);
1763
-
1764 if (!(sleBridge && sleClaimID && sleAcct))
-
1765 return Unexpected(tecINTERNAL);
-
1766
-
1767 AccountID const thisDoor = (*sleBridge)[sfAccount];
-
1768
- -
1770 {
-
1771 if (thisDoor == bridgeSpec.lockingChainDoor())
- -
1773 else if (thisDoor == bridgeSpec.issuingChainDoor())
- -
1775 else
-
1776 return Unexpected(tecINTERNAL);
-
1777 }
-
1778 STXChainBridge::ChainType const srcChain =
- -
1780
-
1781 auto const sendingAmount = [&]() -> STAmount {
-
1782 STAmount r(thisChainAmount);
-
1783 r.setIssue(bridgeSpec.issue(srcChain));
-
1784 return r;
-
1785 }();
+
1764 AccountID const thisDoor = (*sleBridge)[sfAccount];
+
1765
+ +
1767 {
+
1768 if (thisDoor == bridgeSpec.lockingChainDoor())
+ +
1770 else if (thisDoor == bridgeSpec.issuingChainDoor())
+ +
1772 else
+
1773 return Unexpected(tecINTERNAL);
+
1774 }
+
1775 STXChainBridge::ChainType const srcChain =
+ +
1777
+
1778 auto const sendingAmount = [&]() -> STAmount {
+
1779 STAmount r(thisChainAmount);
+
1780 r.setIssue(bridgeSpec.issue(srcChain));
+
1781 return r;
+
1782 }();
+
1783
+
1784 auto const [signersList, quorum, slTer] =
+
1785 getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1786
-
1787 auto const [signersList, quorum, slTer] =
-
1788 getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
+
1787 if (!isTesSuccess(slTer))
+
1788 return Unexpected(slTer);
1789
-
1790 if (!isTesSuccess(slTer))
-
1791 return Unexpected(slTer);
+ +
1791 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1792
- -
1794 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
-
1795
-
1796 auto const claimR = onClaim(
-
1797 curAtts,
-
1798 psb,
-
1799 sendingAmount,
-
1800 /*wasLockingChainSend*/ srcChain ==
- -
1802 quorum,
-
1803 signersList,
-
1804 ctx_.journal);
-
1805 if (!claimR.has_value())
-
1806 return Unexpected(claimR.error());
-
1807
-
1808 return ScopeResult{
-
1809 claimR.value(),
-
1810 (*sleClaimID)[sfAccount],
-
1811 sendingAmount,
-
1812 srcChain,
-
1813 (*sleClaimID)[sfSignatureReward],
-
1814 };
-
1815 }();
+
1793 auto const claimR = onClaim(
+
1794 curAtts,
+
1795 psb,
+
1796 sendingAmount,
+
1797 /*wasLockingChainSend*/ srcChain ==
+ +
1799 quorum,
+
1800 signersList,
+
1801 ctx_.journal);
+
1802 if (!claimR.has_value())
+
1803 return Unexpected(claimR.error());
+
1804
+
1805 return ScopeResult{
+
1806 claimR.value(),
+
1807 (*sleClaimID)[sfAccount],
+
1808 sendingAmount,
+
1809 srcChain,
+
1810 (*sleClaimID)[sfSignatureReward],
+
1811 };
+
1812 }();
+
1813
+
1814 if (!scopeResult.has_value())
+
1815 return scopeResult.error();
1816
-
1817 if (!scopeResult.has_value())
-
1818 return scopeResult.error();
-
1819
-
1820 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
-
1821 scopeResult.value();
-
1822 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
-
1823
-
1824 auto const r = finalizeClaimHelper(
-
1825 psb,
-
1826 bridgeSpec,
-
1827 dst,
-
1828 dstTag,
-
1829 /*claimOwner*/ account,
-
1830 sendingAmount,
-
1831 rewardPoolSrc,
-
1832 signatureReward,
-
1833 rewardAccounts,
-
1834 srcChain,
-
1835 claimIDKeylet,
-
1836 OnTransferFail::keepClaim,
-
1837 DepositAuthPolicy::dstCanBypass,
-
1838 ctx_.journal);
-
1839 if (!r.isTesSuccess())
-
1840 return r.ter();
-
1841
-
1842 psb.apply(ctx_.rawView());
+
1817 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
+
1818 scopeResult.value();
+
1819 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
+
1820
+
1821 auto const r = finalizeClaimHelper(
+
1822 psb,
+
1823 bridgeSpec,
+
1824 dst,
+
1825 dstTag,
+
1826 /*claimOwner*/ account,
+
1827 sendingAmount,
+
1828 rewardPoolSrc,
+
1829 signatureReward,
+
1830 rewardAccounts,
+
1831 srcChain,
+
1832 claimIDKeylet,
+
1833 OnTransferFail::keepClaim,
+
1834 DepositAuthPolicy::dstCanBypass,
+
1835 ctx_.journal);
+
1836 if (!r.isTesSuccess())
+
1837 return r.ter();
+
1838
+
1839 psb.apply(ctx_.rawView());
+
1840
+
1841 return tesSUCCESS;
+
1842}
+
1843
-
1844 return tesSUCCESS;
-
1845}
+
1844//------------------------------------------------------------------------------
+
1845
+ +
+ +
1848{
+
1849 auto const maxSpend = [&] {
+
1850 auto const amount = ctx.tx[sfAmount];
+
1851 if (amount.native() && amount.signum() > 0)
+
1852 return amount.xrp();
+
1853 return XRPAmount{beast::zero};
+
1854 }();
+
1855
+
1856 return TxConsequences{ctx.tx, maxSpend};
+
1857}
-
1846
-
1847//------------------------------------------------------------------------------
-
1848
- -
- -
1851{
-
1852 auto const maxSpend = [&] {
-
1853 auto const amount = ctx.tx[sfAmount];
-
1854 if (amount.native() && amount.signum() > 0)
-
1855 return amount.xrp();
-
1856 return XRPAmount{beast::zero};
-
1857 }();
1858
-
1859 return TxConsequences{ctx.tx, maxSpend};
-
1860}
-
-
1861
-
1862NotTEC
-
- -
1864{
-
1865 auto const amount = ctx.tx[sfAmount];
-
1866 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1859NotTEC
+
+ +
1861{
+
1862 auto const amount = ctx.tx[sfAmount];
+
1863 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1864
+
1865 if (amount.signum() <= 0 || !isLegalNet(amount))
+
1866 return temBAD_AMOUNT;
1867
-
1868 if (amount.signum() <= 0 || !isLegalNet(amount))
-
1869 return temBAD_AMOUNT;
-
1870
-
1871 if (amount.issue() != bridgeSpec.lockingChainIssue() &&
-
1872 amount.issue() != bridgeSpec.issuingChainIssue())
-
1873 return temBAD_ISSUER;
+
1868 if (amount.issue() != bridgeSpec.lockingChainIssue() &&
+
1869 amount.issue() != bridgeSpec.issuingChainIssue())
+
1870 return temBAD_ISSUER;
+
1871
+
1872 return tesSUCCESS;
+
1873}
+
1874
-
1875 return tesSUCCESS;
-
1876}
-
-
1877
-
1878TER
-
- -
1880{
-
1881 auto const bridgeSpec = ctx.tx[sfXChainBridge];
-
1882 auto const amount = ctx.tx[sfAmount];
-
1883
-
1884 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
-
1885 if (!sleBridge)
-
1886 {
-
1887 return tecNO_ENTRY;
-
1888 }
+
1875TER
+
+ +
1877{
+
1878 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1879 auto const amount = ctx.tx[sfAmount];
+
1880
+
1881 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
+
1882 if (!sleBridge)
+
1883 {
+
1884 return tecNO_ENTRY;
+
1885 }
+
1886
+
1887 AccountID const thisDoor = (*sleBridge)[sfAccount];
+
1888 AccountID const account = ctx.tx[sfAccount];
1889
-
1890 AccountID const thisDoor = (*sleBridge)[sfAccount];
-
1891 AccountID const account = ctx.tx[sfAccount];
-
1892
-
1893 if (thisDoor == account)
-
1894 {
-
1895 // Door account can't lock funds onto itself
-
1896 return tecXCHAIN_SELF_COMMIT;
-
1897 }
-
1898
-
1899 bool isLockingChain = false;
-
1900 {
-
1901 if (thisDoor == bridgeSpec.lockingChainDoor())
-
1902 isLockingChain = true;
-
1903 else if (thisDoor == bridgeSpec.issuingChainDoor())
-
1904 isLockingChain = false;
-
1905 else
-
1906 return tecINTERNAL; // LCOV_EXCL_LINE
-
1907 }
-
1908
-
1909 if (isLockingChain)
-
1910 {
-
1911 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
- -
1913 }
-
1914 else
-
1915 {
-
1916 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
- -
1918 }
+
1890 if (thisDoor == account)
+
1891 {
+
1892 // Door account can't lock funds onto itself
+
1893 return tecXCHAIN_SELF_COMMIT;
+
1894 }
+
1895
+
1896 bool isLockingChain = false;
+
1897 {
+
1898 if (thisDoor == bridgeSpec.lockingChainDoor())
+
1899 isLockingChain = true;
+
1900 else if (thisDoor == bridgeSpec.issuingChainDoor())
+
1901 isLockingChain = false;
+
1902 else
+
1903 return tecINTERNAL; // LCOV_EXCL_LINE
+
1904 }
+
1905
+
1906 if (isLockingChain)
+
1907 {
+
1908 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
+ +
1910 }
+
1911 else
+
1912 {
+
1913 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
+ +
1915 }
+
1916
+
1917 return tesSUCCESS;
+
1918}
+
1919
-
1920 return tesSUCCESS;
-
1921}
-
-
1922
-
1923TER
-
- -
1925{
-
1926 PaymentSandbox psb(&ctx_.view());
-
1927
-
1928 auto const account = ctx_.tx[sfAccount];
-
1929 auto const amount = ctx_.tx[sfAmount];
-
1930 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
+
1920TER
+
+ +
1922{
+
1923 PaymentSandbox psb(&ctx_.view());
+
1924
+
1925 auto const account = ctx_.tx[sfAccount];
+
1926 auto const amount = ctx_.tx[sfAmount];
+
1927 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
+
1928
+
1929 if (!psb.read(keylet::account(account)))
+
1930 return tecINTERNAL; // LCOV_EXCL_LINE
1931
-
1932 if (!psb.read(keylet::account(account)))
-
1933 return tecINTERNAL; // LCOV_EXCL_LINE
-
1934
-
1935 auto const sleBridge = readBridge(psb, bridgeSpec);
-
1936 if (!sleBridge)
-
1937 return tecINTERNAL; // LCOV_EXCL_LINE
-
1938
-
1939 auto const dst = (*sleBridge)[sfAccount];
-
1940
-
1941 // Support dipping into reserves to pay the fee
-
1942 TransferHelperSubmittingAccountInfo submittingAccountInfo{
- -
1944
-
1945 auto const thTer = transferHelper(
-
1946 psb,
-
1947 account,
-
1948 dst,
-
1949 /*dstTag*/ std::nullopt,
-
1950 /*claimOwner*/ std::nullopt,
-
1951 amount,
-
1952 CanCreateDstPolicy::no,
-
1953 DepositAuthPolicy::normal,
-
1954 submittingAccountInfo,
-
1955 ctx_.journal);
+
1932 auto const sleBridge = readBridge(psb, bridgeSpec);
+
1933 if (!sleBridge)
+
1934 return tecINTERNAL; // LCOV_EXCL_LINE
+
1935
+
1936 auto const dst = (*sleBridge)[sfAccount];
+
1937
+
1938 // Support dipping into reserves to pay the fee
+
1939 TransferHelperSubmittingAccountInfo submittingAccountInfo{
+ +
1941
+
1942 auto const thTer = transferHelper(
+
1943 psb,
+
1944 account,
+
1945 dst,
+
1946 /*dstTag*/ std::nullopt,
+
1947 /*claimOwner*/ std::nullopt,
+
1948 amount,
+
1949 CanCreateDstPolicy::no,
+
1950 DepositAuthPolicy::normal,
+
1951 submittingAccountInfo,
+
1952 ctx_.journal);
+
1953
+
1954 if (!isTesSuccess(thTer))
+
1955 return thTer;
1956
-
1957 if (!isTesSuccess(thTer))
-
1958 return thTer;
-
1959
-
1960 psb.apply(ctx_.rawView());
+
1957 psb.apply(ctx_.rawView());
+
1958
+
1959 return tesSUCCESS;
+
1960}
+
1961
-
1962 return tesSUCCESS;
-
1963}
-
-
1964
-
1965//------------------------------------------------------------------------------
-
1966
-
1967NotTEC
-
- -
1969{
-
1970 auto const reward = ctx.tx[sfSignatureReward];
+
1962//------------------------------------------------------------------------------
+
1963
+
1964NotTEC
+
+ +
1966{
+
1967 auto const reward = ctx.tx[sfSignatureReward];
+
1968
+
1969 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
+
1971
-
1972 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
- +
1972 return tesSUCCESS;
+
1973}
+
1974
-
1975 return tesSUCCESS;
-
1976}
-
-
1977
-
1978TER
-
- -
1980{
-
1981 auto const account = ctx.tx[sfAccount];
-
1982 auto const bridgeSpec = ctx.tx[sfXChainBridge];
-
1983 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
-
1984
-
1985 if (!sleBridge)
-
1986 {
-
1987 return tecNO_ENTRY;
-
1988 }
+
1975TER
+
+ +
1977{
+
1978 auto const account = ctx.tx[sfAccount];
+
1979 auto const bridgeSpec = ctx.tx[sfXChainBridge];
+
1980 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
+
1981
+
1982 if (!sleBridge)
+
1983 {
+
1984 return tecNO_ENTRY;
+
1985 }
+
1986
+
1987 // Check that the reward matches
+
1988 auto const reward = ctx.tx[sfSignatureReward];
1989
-
1990 // Check that the reward matches
-
1991 auto const reward = ctx.tx[sfSignatureReward];
-
1992
-
1993 if (reward != (*sleBridge)[sfSignatureReward])
-
1994 {
- -
1996 }
-
1997
-
1998 {
-
1999 // Check reserve
-
2000 auto const sleAcc = ctx.view.read(keylet::account(account));
-
2001 if (!sleAcc)
-
2002 return terNO_ACCOUNT;
-
2003
-
2004 auto const balance = (*sleAcc)[sfBalance];
-
2005 auto const reserve =
-
2006 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
-
2007
-
2008 if (balance < reserve)
- -
2010 }
+
1990 if (reward != (*sleBridge)[sfSignatureReward])
+
1991 {
+ +
1993 }
+
1994
+
1995 {
+
1996 // Check reserve
+
1997 auto const sleAcc = ctx.view.read(keylet::account(account));
+
1998 if (!sleAcc)
+
1999 return terNO_ACCOUNT;
+
2000
+
2001 auto const balance = (*sleAcc)[sfBalance];
+
2002 auto const reserve =
+
2003 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
+
2004
+
2005 if (balance < reserve)
+ +
2007 }
+
2008
+
2009 return tesSUCCESS;
+
2010}
+
2011
-
2012 return tesSUCCESS;
-
2013}
+
2012TER
+
+ +
2014{
+
2015 auto const account = ctx_.tx[sfAccount];
+
2016 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
+
2017 auto const reward = ctx_.tx[sfSignatureReward];
+
2018 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
+
2019
+
2020 auto const sleAcct = ctx_.view().peek(keylet::account(account));
+
2021 if (!sleAcct)
+
2022 return tecINTERNAL; // LCOV_EXCL_LINE
+
2023
+
2024 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
+
2025 if (!sleBridge)
+
2026 return tecINTERNAL; // LCOV_EXCL_LINE
+
2027
+
2028 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
+
2029 if (claimID == 0)
+
2030 {
+
2031 // overflow
+
2032 return tecINTERNAL; // LCOV_EXCL_LINE
+
2033 }
+
2034
+
2035 (*sleBridge)[sfXChainClaimID] = claimID;
+
2036
+
2037 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
+
2038 if (ctx_.view().exists(claimIDKeylet))
+
2039 {
+
2040 // already checked out!?!
+
2041 return tecINTERNAL; // LCOV_EXCL_LINE
+
2042 }
+
2043
+
2044 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
+
2045
+
2046 (*sleClaimID)[sfAccount] = account;
+
2047 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
+
2048 (*sleClaimID)[sfXChainClaimID] = claimID;
+
2049 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
+
2050 (*sleClaimID)[sfSignatureReward] = reward;
+
2051 sleClaimID->setFieldArray(
+
2052 sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
+
2053
+
2054 // Add to owner directory
+
2055 {
+
2056 auto const page = ctx_.view().dirInsert(
+
2057 keylet::ownerDir(account),
+
2058 claimIDKeylet,
+
2059 describeOwnerDir(account));
+
2060 if (!page)
+
2061 return tecDIR_FULL; // LCOV_EXCL_LINE
+
2062 (*sleClaimID)[sfOwnerNode] = *page;
+
2063 }
+
2064
+
2065 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
+
2066
+
2067 ctx_.view().insert(sleClaimID);
+
2068 ctx_.view().update(sleBridge);
+
2069 ctx_.view().update(sleAcct);
+
2070
+
2071 return tesSUCCESS;
+
2072}
-
2014
-
2015TER
-
- -
2017{
-
2018 auto const account = ctx_.tx[sfAccount];
-
2019 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
-
2020 auto const reward = ctx_.tx[sfSignatureReward];
-
2021 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
-
2022
-
2023 auto const sleAcct = ctx_.view().peek(keylet::account(account));
-
2024 if (!sleAcct)
-
2025 return tecINTERNAL; // LCOV_EXCL_LINE
-
2026
-
2027 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
-
2028 if (!sleBridge)
-
2029 return tecINTERNAL; // LCOV_EXCL_LINE
-
2030
-
2031 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
-
2032 if (claimID == 0)
-
2033 {
-
2034 // overflow
-
2035 return tecINTERNAL; // LCOV_EXCL_LINE
-
2036 }
-
2037
-
2038 (*sleBridge)[sfXChainClaimID] = claimID;
-
2039
-
2040 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
-
2041 if (ctx_.view().exists(claimIDKeylet))
-
2042 {
-
2043 // already checked out!?!
-
2044 return tecINTERNAL; // LCOV_EXCL_LINE
-
2045 }
-
2046
-
2047 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
-
2048
-
2049 (*sleClaimID)[sfAccount] = account;
-
2050 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
-
2051 (*sleClaimID)[sfXChainClaimID] = claimID;
-
2052 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
-
2053 (*sleClaimID)[sfSignatureReward] = reward;
-
2054 sleClaimID->setFieldArray(
-
2055 sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
-
2056
-
2057 // Add to owner directory
-
2058 {
-
2059 auto const page = ctx_.view().dirInsert(
-
2060 keylet::ownerDir(account),
-
2061 claimIDKeylet,
-
2062 describeOwnerDir(account));
-
2063 if (!page)
-
2064 return tecDIR_FULL; // LCOV_EXCL_LINE
-
2065 (*sleClaimID)[sfOwnerNode] = *page;
-
2066 }
-
2067
-
2068 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
-
2069
-
2070 ctx_.view().insert(sleClaimID);
-
2071 ctx_.view().update(sleBridge);
-
2072 ctx_.view().update(sleAcct);
2073
-
2074 return tesSUCCESS;
-
2075}
+
2074//------------------------------------------------------------------------------
+
2075
+
2076NotTEC
+
+ +
2078{
+
2079 return attestationpreflight<Attestations::AttestationClaim>(ctx);
+
2080}
-
2076
-
2077//------------------------------------------------------------------------------
-
2078
-
2079NotTEC
-
- -
2081{
-
2082 return attestationpreflight<Attestations::AttestationClaim>(ctx);
-
2083}
+
2081
+
2082TER
+
+ +
2084{
+
2085 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
+
2086}
-
2084
-
2085TER
-
- -
2087{
-
2088 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
-
2089}
+
2087
+
2088TER
+
+ +
2090{
+
2091 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
+
2092}
-
2090
-
2091TER
-
- -
2093{
-
2094 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
-
2095}
+
2093
+
2094//------------------------------------------------------------------------------
+
2095
+
2096NotTEC
+
+ +
2098{
+
2099 return attestationpreflight<Attestations::AttestationCreateAccount>(ctx);
+
2100}
-
2096
-
2097//------------------------------------------------------------------------------
-
2098
-
2099NotTEC
-
- -
2101{
-
2102 return attestationpreflight<Attestations::AttestationCreateAccount>(ctx);
-
2103}
+
2101
+
2102TER
+
+ +
2104{
+
2105 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
+
2106}
-
2104
-
2105TER
-
- -
2107{
-
2108 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
-
2109}
+
2107
+
2108TER
+
+ +
2110{
+
2111 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
+
2112}
-
2110
-
2111TER
-
- -
2113{
-
2114 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
-
2115}
-
-
2116
-
2117//------------------------------------------------------------------------------
-
2118
-
2119NotTEC
-
- -
2121{
-
2122 auto const amount = ctx.tx[sfAmount];
+
2113
+
2114//------------------------------------------------------------------------------
+
2115
+
2116NotTEC
+
+ +
2118{
+
2119 auto const amount = ctx.tx[sfAmount];
+
2120
+
2121 if (amount.signum() <= 0 || !amount.native())
+
2122 return temBAD_AMOUNT;
2123
-
2124 if (amount.signum() <= 0 || !amount.native())
-
2125 return temBAD_AMOUNT;
-
2126
-
2127 auto const reward = ctx.tx[sfSignatureReward];
-
2128 if (reward.signum() < 0 || !reward.native())
+
2124 auto const reward = ctx.tx[sfSignatureReward];
+
2125 if (reward.signum() < 0 || !reward.native())
+
2126 return temBAD_AMOUNT;
+
2127
+
2128 if (reward.issue() != amount.issue())
2129 return temBAD_AMOUNT;
2130
-
2131 if (reward.issue() != amount.issue())
-
2132 return temBAD_AMOUNT;
+
2131 return tesSUCCESS;
+
2132}
+
2133
-
2134 return tesSUCCESS;
-
2135}
-
-
2136
-
2137TER
-
- -
2139{
-
2140 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
-
2141 STAmount const amount = ctx.tx[sfAmount];
-
2142 STAmount const reward = ctx.tx[sfSignatureReward];
-
2143
-
2144 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
-
2145 if (!sleBridge)
-
2146 {
-
2147 return tecNO_ENTRY;
-
2148 }
-
2149
-
2150 if (reward != (*sleBridge)[sfSignatureReward])
-
2151 {
- -
2153 }
+
2134TER
+
+ +
2136{
+
2137 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
+
2138 STAmount const amount = ctx.tx[sfAmount];
+
2139 STAmount const reward = ctx.tx[sfSignatureReward];
+
2140
+
2141 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
+
2142 if (!sleBridge)
+
2143 {
+
2144 return tecNO_ENTRY;
+
2145 }
+
2146
+
2147 if (reward != (*sleBridge)[sfSignatureReward])
+
2148 {
+ +
2150 }
+
2151
+
2152 std::optional<STAmount> const minCreateAmount =
+
2153 (*sleBridge)[~sfMinAccountCreateAmount];
2154
-
2155 std::optional<STAmount> const minCreateAmount =
-
2156 (*sleBridge)[~sfMinAccountCreateAmount];
+
2155 if (!minCreateAmount)
+
2157
-
2158 if (!minCreateAmount)
- +
2158 if (amount < *minCreateAmount)
+
2160
-
2161 if (amount < *minCreateAmount)
- +
2161 if (minCreateAmount->issue() != amount.issue())
+
2163
-
2164 if (minCreateAmount->issue() != amount.issue())
- -
2166
-
2167 AccountID const thisDoor = (*sleBridge)[sfAccount];
-
2168 AccountID const account = ctx.tx[sfAccount];
-
2169 if (thisDoor == account)
-
2170 {
-
2171 // Door account can't lock funds onto itself
-
2172 return tecXCHAIN_SELF_COMMIT;
-
2173 }
-
2174
- -
2176 {
-
2177 if (thisDoor == bridgeSpec.lockingChainDoor())
- -
2179 else if (thisDoor == bridgeSpec.issuingChainDoor())
- -
2181 else
-
2182 return tecINTERNAL; // LCOV_EXCL_LINE
-
2183 }
-
2184 STXChainBridge::ChainType const dstChain =
- +
2164 AccountID const thisDoor = (*sleBridge)[sfAccount];
+
2165 AccountID const account = ctx.tx[sfAccount];
+
2166 if (thisDoor == account)
+
2167 {
+
2168 // Door account can't lock funds onto itself
+
2169 return tecXCHAIN_SELF_COMMIT;
+
2170 }
+
2171
+ +
2173 {
+
2174 if (thisDoor == bridgeSpec.lockingChainDoor())
+ +
2176 else if (thisDoor == bridgeSpec.issuingChainDoor())
+ +
2178 else
+
2179 return tecINTERNAL; // LCOV_EXCL_LINE
+
2180 }
+
2181 STXChainBridge::ChainType const dstChain =
+ +
2183
+
2184 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
+
2186
-
2187 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
- +
2187 if (!isXRP(bridgeSpec.issue(dstChain)))
+
2189
-
2190 if (!isXRP(bridgeSpec.issue(dstChain)))
- +
2190 return tesSUCCESS;
+
2191}
+
2192
-
2193 return tesSUCCESS;
-
2194}
-
-
2195
-
2196TER
-
- -
2198{
-
2199 PaymentSandbox psb(&ctx_.view());
-
2200
-
2201 AccountID const account = ctx_.tx[sfAccount];
-
2202 STAmount const amount = ctx_.tx[sfAmount];
-
2203 STAmount const reward = ctx_.tx[sfSignatureReward];
-
2204 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
-
2205
-
2206 auto const sle = psb.peek(keylet::account(account));
-
2207 if (!sle)
-
2208 return tecINTERNAL; // LCOV_EXCL_LINE
-
2209
-
2210 auto const sleBridge = peekBridge(psb, bridge);
-
2211 if (!sleBridge)
-
2212 return tecINTERNAL; // LCOV_EXCL_LINE
-
2213
-
2214 auto const dst = (*sleBridge)[sfAccount];
-
2215
-
2216 // Support dipping into reserves to pay the fee
-
2217 TransferHelperSubmittingAccountInfo submittingAccountInfo{
- -
2219 STAmount const toTransfer = amount + reward;
-
2220 auto const thTer = transferHelper(
-
2221 psb,
-
2222 account,
-
2223 dst,
-
2224 /*dstTag*/ std::nullopt,
-
2225 /*claimOwner*/ std::nullopt,
-
2226 toTransfer,
-
2227 CanCreateDstPolicy::yes,
-
2228 DepositAuthPolicy::normal,
-
2229 submittingAccountInfo,
-
2230 ctx_.journal);
+
2193TER
+
+ +
2195{
+
2196 PaymentSandbox psb(&ctx_.view());
+
2197
+
2198 AccountID const account = ctx_.tx[sfAccount];
+
2199 STAmount const amount = ctx_.tx[sfAmount];
+
2200 STAmount const reward = ctx_.tx[sfSignatureReward];
+
2201 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
+
2202
+
2203 auto const sle = psb.peek(keylet::account(account));
+
2204 if (!sle)
+
2205 return tecINTERNAL; // LCOV_EXCL_LINE
+
2206
+
2207 auto const sleBridge = peekBridge(psb, bridge);
+
2208 if (!sleBridge)
+
2209 return tecINTERNAL; // LCOV_EXCL_LINE
+
2210
+
2211 auto const dst = (*sleBridge)[sfAccount];
+
2212
+
2213 // Support dipping into reserves to pay the fee
+
2214 TransferHelperSubmittingAccountInfo submittingAccountInfo{
+ +
2216 STAmount const toTransfer = amount + reward;
+
2217 auto const thTer = transferHelper(
+
2218 psb,
+
2219 account,
+
2220 dst,
+
2221 /*dstTag*/ std::nullopt,
+
2222 /*claimOwner*/ std::nullopt,
+
2223 toTransfer,
+
2224 CanCreateDstPolicy::yes,
+
2225 DepositAuthPolicy::normal,
+
2226 submittingAccountInfo,
+
2227 ctx_.journal);
+
2228
+
2229 if (!isTesSuccess(thTer))
+
2230 return thTer;
2231
-
2232 if (!isTesSuccess(thTer))
-
2233 return thTer;
-
2234
-
2235 (*sleBridge)[sfXChainAccountCreateCount] =
-
2236 (*sleBridge)[sfXChainAccountCreateCount] + 1;
-
2237 psb.update(sleBridge);
-
2238
-
2239 psb.apply(ctx_.rawView());
-
2240
-
2241 return tesSUCCESS;
-
2242}
+
2232 (*sleBridge)[sfXChainAccountCreateCount] =
+
2233 (*sleBridge)[sfXChainAccountCreateCount] + 1;
+
2234 psb.update(sleBridge);
+
2235
+
2236 psb.apply(ctx_.rawView());
+
2237
+
2238 return tesSUCCESS;
+
2239}
-
2243
-
2244} // namespace ripple
+
2240
+
2241} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:333
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
@@ -2290,10 +2287,10 @@ $(document).ready(function() { init_codefold(0); });
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:300
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
-
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
-
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
- +
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
+
static rounding_mode getround()
Definition Number.cpp:28
@@ -2328,29 +2325,29 @@ $(document).ready(function() { init_codefold(0); });
ApplyContext & ctx_
Definition Transactor.h:124
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:39
-
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
- -
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
- +
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+ +
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+ -
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
-
TER doApply() override
- -
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
-
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
- -
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
- -
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
- -
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+
TER doApply() override
+ +
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+ +
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+ +
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+ +
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
diff --git a/XChainBridge_8h_source.html b/XChainBridge_8h_source.html index 8efd4144f3..f921e9ccd7 100644 --- a/XChainBridge_8h_source.html +++ b/XChainBridge_8h_source.html @@ -361,10 +361,10 @@ $(document).ready(function() { init_codefold(0); });
BridgeModify(ApplyContext &ctx)
static constexpr ConsequencesFactoryType ConsequencesFactory
-
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
-
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
- +
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
+
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
+ @@ -374,47 +374,47 @@ $(document).ready(function() { init_codefold(0); });
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:39
static constexpr ConsequencesFactoryType ConsequencesFactory
-
static TER preclaim(PreclaimContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
XChainAddAccountCreateAttestation(ApplyContext &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
- +
static NotTEC preflight(PreflightContext const &ctx)
+ -
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
XChainAddClaimAttestation(ApplyContext &ctx)
static constexpr ConsequencesFactoryType ConsequencesFactory
- +
static constexpr ConsequencesFactoryType ConsequencesFactory
-
static TER preclaim(PreclaimContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
XChainClaim(ApplyContext &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
-
TER doApply() override
+
static NotTEC preflight(PreflightContext const &ctx)
+
TER doApply() override
XChainCommit(ApplyContext &ctx)
- -
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
+ +
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static constexpr ConsequencesFactoryType ConsequencesFactory
-
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
- +
static constexpr ConsequencesFactoryType ConsequencesFactory
-
static TER preclaim(PreclaimContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
XChainCreateAccountCommit(ApplyContext &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
- -
static TER preclaim(PreclaimContext const &ctx)
+ +
static TER preclaim(PreclaimContext const &ctx)
static constexpr ConsequencesFactoryType ConsequencesFactory
-
static NotTEC preflight(PreflightContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
XChainCreateBridge(ApplyContext &ctx)
static constexpr ConsequencesFactoryType ConsequencesFactory
- +
XChainCreateClaimID(ApplyContext &ctx)
-
static TER preclaim(PreclaimContext const &ctx)
-
static NotTEC preflight(PreflightContext const &ctx)
+
static TER preclaim(PreclaimContext const &ctx)
+
static NotTEC preflight(PreflightContext const &ctx)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr size_t xbridgeMaxAccountCreateClaims
diff --git a/classripple_1_1BridgeModify.html b/classripple_1_1BridgeModify.html index 8e5f6cee97..fbce082fe3 100644 --- a/classripple_1_1BridgeModify.html +++ b/classripple_1_1BridgeModify.html @@ -442,7 +442,7 @@ Static Private Member Functions
-

Definition at line 1527 of file XChainBridge.cpp.

+

Definition at line 1524 of file XChainBridge.cpp.

@@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 1533 of file XChainBridge.cpp.

+

Definition at line 1530 of file XChainBridge.cpp.

@@ -498,7 +498,7 @@ Static Private Member Functions
-

Definition at line 1577 of file XChainBridge.cpp.

+

Definition at line 1574 of file XChainBridge.cpp.

@@ -527,7 +527,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 1594 of file XChainBridge.cpp.

+

Definition at line 1591 of file XChainBridge.cpp.

diff --git a/classripple_1_1DeleteAccount.html b/classripple_1_1DeleteAccount.html index 1a1ed11e6a..a15b4525ad 100644 --- a/classripple_1_1DeleteAccount.html +++ b/classripple_1_1DeleteAccount.html @@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 36 of file DeleteAccount.cpp.

+

Definition at line 33 of file DeleteAccount.cpp.

@@ -508,7 +508,7 @@ Static Private Member Functions
-

Definition at line 50 of file DeleteAccount.cpp.

+

Definition at line 47 of file DeleteAccount.cpp.

@@ -536,7 +536,7 @@ Static Private Member Functions
-

Definition at line 212 of file DeleteAccount.cpp.

+

Definition at line 209 of file DeleteAccount.cpp.

@@ -565,7 +565,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 341 of file DeleteAccount.cpp.

+

Definition at line 338 of file DeleteAccount.cpp.

diff --git a/classripple_1_1Feature__test.html b/classripple_1_1Feature__test.html index 5ac83a4317..13c32495e4 100644 --- a/classripple_1_1Feature__test.html +++ b/classripple_1_1Feature__test.html @@ -339,7 +339,7 @@ Private Attributes
-

Definition at line 135 of file Feature_test.cpp.

+

Definition at line 134 of file Feature_test.cpp.

@@ -366,7 +366,7 @@ Private Attributes
-

Definition at line 180 of file Feature_test.cpp.

+

Definition at line 179 of file Feature_test.cpp.

@@ -393,7 +393,7 @@ Private Attributes
-

Definition at line 210 of file Feature_test.cpp.

+

Definition at line 209 of file Feature_test.cpp.

@@ -420,7 +420,7 @@ Private Attributes
-

Definition at line 242 of file Feature_test.cpp.

+

Definition at line 241 of file Feature_test.cpp.

@@ -447,7 +447,7 @@ Private Attributes
-

Definition at line 321 of file Feature_test.cpp.

+

Definition at line 320 of file Feature_test.cpp.

@@ -474,7 +474,7 @@ Private Attributes
-

Definition at line 376 of file Feature_test.cpp.

+

Definition at line 375 of file Feature_test.cpp.

@@ -501,7 +501,7 @@ Private Attributes
-

Definition at line 475 of file Feature_test.cpp.

+

Definition at line 474 of file Feature_test.cpp.

@@ -528,7 +528,7 @@ Private Attributes
-

Definition at line 526 of file Feature_test.cpp.

+

Definition at line 525 of file Feature_test.cpp.

@@ -559,7 +559,7 @@ Private Attributes

Implements beast::unit_test::suite.

-

Definition at line 596 of file Feature_test.cpp.

+

Definition at line 595 of file Feature_test.cpp.

diff --git a/classripple_1_1NFTokenAllFeatures__test.html b/classripple_1_1NFTokenAllFeatures__test.html index 214ca7a0f2..178f450e25 100644 --- a/classripple_1_1NFTokenAllFeatures__test.html +++ b/classripple_1_1NFTokenAllFeatures__test.html @@ -340,7 +340,7 @@ Private Attributes

Detailed Description

-

Definition at line 7637 of file NFToken_test.cpp.

+

Definition at line 7630 of file NFToken_test.cpp.

Member Function Documentation

◆ run()

@@ -369,7 +369,7 @@ Private Attributes

Implements beast::unit_test::suite.

-

Definition at line 7640 of file NFToken_test.cpp.

+

Definition at line 7633 of file NFToken_test.cpp.

@@ -1389,7 +1389,7 @@ Private Attributes
-

Definition at line 6120 of file NFToken_test.cpp.

+

Definition at line 6113 of file NFToken_test.cpp.

@@ -1417,7 +1417,7 @@ Private Attributes
-

Definition at line 6384 of file NFToken_test.cpp.

+

Definition at line 6377 of file NFToken_test.cpp.

@@ -1445,7 +1445,7 @@ Private Attributes
-

Definition at line 6625 of file NFToken_test.cpp.

+

Definition at line 6618 of file NFToken_test.cpp.

@@ -1473,7 +1473,7 @@ Private Attributes
-

Definition at line 6942 of file NFToken_test.cpp.

+

Definition at line 6935 of file NFToken_test.cpp.

@@ -1501,7 +1501,7 @@ Private Attributes
-

Definition at line 7112 of file NFToken_test.cpp.

+

Definition at line 7105 of file NFToken_test.cpp.

@@ -1529,7 +1529,7 @@ Private Attributes
-

Definition at line 7270 of file NFToken_test.cpp.

+

Definition at line 7263 of file NFToken_test.cpp.

@@ -1557,7 +1557,7 @@ Private Attributes
-

Definition at line 7558 of file NFToken_test.cpp.

+

Definition at line 7551 of file NFToken_test.cpp.

@@ -2306,7 +2306,7 @@ template<class Condition >
-

Definition at line 7555 of file NFToken_test.cpp.

+

Definition at line 7548 of file NFToken_test.cpp.

diff --git a/classripple_1_1NFTokenBaseUtil__test.html b/classripple_1_1NFTokenBaseUtil__test.html index 0800de24aa..a1b7c7af13 100644 --- a/classripple_1_1NFTokenBaseUtil__test.html +++ b/classripple_1_1NFTokenBaseUtil__test.html @@ -1362,7 +1362,7 @@ Private Attributes
-

Definition at line 6120 of file NFToken_test.cpp.

+

Definition at line 6113 of file NFToken_test.cpp.

@@ -1390,7 +1390,7 @@ Private Attributes
-

Definition at line 6384 of file NFToken_test.cpp.

+

Definition at line 6377 of file NFToken_test.cpp.

@@ -1418,7 +1418,7 @@ Private Attributes
-

Definition at line 6625 of file NFToken_test.cpp.

+

Definition at line 6618 of file NFToken_test.cpp.

@@ -1446,7 +1446,7 @@ Private Attributes
-

Definition at line 6942 of file NFToken_test.cpp.

+

Definition at line 6935 of file NFToken_test.cpp.

@@ -1474,7 +1474,7 @@ Private Attributes
-

Definition at line 7112 of file NFToken_test.cpp.

+

Definition at line 7105 of file NFToken_test.cpp.

@@ -1502,7 +1502,7 @@ Private Attributes
-

Definition at line 7270 of file NFToken_test.cpp.

+

Definition at line 7263 of file NFToken_test.cpp.

@@ -1530,7 +1530,7 @@ Private Attributes
-

Definition at line 7558 of file NFToken_test.cpp.

+

Definition at line 7551 of file NFToken_test.cpp.

@@ -1563,7 +1563,7 @@ Private Attributes

Reimplemented in ripple::NFTokenDisallowIncoming_test, ripple::NFTokenWOMintOffer_test, and ripple::NFTokenWOModify_test.

-

Definition at line 7599 of file NFToken_test.cpp.

+

Definition at line 7592 of file NFToken_test.cpp.

@@ -2312,7 +2312,7 @@ template<class Condition >
-

Definition at line 7555 of file NFToken_test.cpp.

+

Definition at line 7548 of file NFToken_test.cpp.

diff --git a/classripple_1_1NFTokenCountTracking.html b/classripple_1_1NFTokenCountTracking.html index 18a699df28..97ca51fc3b 100644 --- a/classripple_1_1NFTokenCountTracking.html +++ b/classripple_1_1NFTokenCountTracking.html @@ -160,7 +160,7 @@ Private Attributes
-

Definition at line 1232 of file InvariantCheck.cpp.

+

Definition at line 1227 of file InvariantCheck.cpp.

@@ -208,7 +208,7 @@ Private Attributes
-

Definition at line 1251 of file InvariantCheck.cpp.

+

Definition at line 1246 of file InvariantCheck.cpp.

diff --git a/classripple_1_1NFTokenDisallowIncoming__test.html b/classripple_1_1NFTokenDisallowIncoming__test.html index 6e0a897dae..eb7fb5e21b 100644 --- a/classripple_1_1NFTokenDisallowIncoming__test.html +++ b/classripple_1_1NFTokenDisallowIncoming__test.html @@ -340,7 +340,7 @@ Private Attributes

Detailed Description

-

Definition at line 7607 of file NFToken_test.cpp.

+

Definition at line 7600 of file NFToken_test.cpp.

Member Function Documentation

◆ run()

@@ -369,7 +369,7 @@ Private Attributes

Reimplemented from ripple::NFTokenBaseUtil_test.

-

Definition at line 7610 of file NFToken_test.cpp.

+

Definition at line 7603 of file NFToken_test.cpp.

@@ -1389,7 +1389,7 @@ Private Attributes
-

Definition at line 6120 of file NFToken_test.cpp.

+

Definition at line 6113 of file NFToken_test.cpp.

@@ -1417,7 +1417,7 @@ Private Attributes
-

Definition at line 6384 of file NFToken_test.cpp.

+

Definition at line 6377 of file NFToken_test.cpp.

@@ -1445,7 +1445,7 @@ Private Attributes
-

Definition at line 6625 of file NFToken_test.cpp.

+

Definition at line 6618 of file NFToken_test.cpp.

@@ -1473,7 +1473,7 @@ Private Attributes
-

Definition at line 6942 of file NFToken_test.cpp.

+

Definition at line 6935 of file NFToken_test.cpp.

@@ -1501,7 +1501,7 @@ Private Attributes
-

Definition at line 7112 of file NFToken_test.cpp.

+

Definition at line 7105 of file NFToken_test.cpp.

@@ -1529,7 +1529,7 @@ Private Attributes
-

Definition at line 7270 of file NFToken_test.cpp.

+

Definition at line 7263 of file NFToken_test.cpp.

@@ -1557,7 +1557,7 @@ Private Attributes
-

Definition at line 7558 of file NFToken_test.cpp.

+

Definition at line 7551 of file NFToken_test.cpp.

@@ -2306,7 +2306,7 @@ template<class Condition >
-

Definition at line 7555 of file NFToken_test.cpp.

+

Definition at line 7548 of file NFToken_test.cpp.

diff --git a/classripple_1_1NFTokenWOMintOffer__test.html b/classripple_1_1NFTokenWOMintOffer__test.html index b6793cd274..09d6b953a3 100644 --- a/classripple_1_1NFTokenWOMintOffer__test.html +++ b/classripple_1_1NFTokenWOMintOffer__test.html @@ -340,7 +340,7 @@ Private Attributes

Detailed Description

-

Definition at line 7618 of file NFToken_test.cpp.

+

Definition at line 7611 of file NFToken_test.cpp.

Member Function Documentation

◆ run()

@@ -369,7 +369,7 @@ Private Attributes

Reimplemented from ripple::NFTokenBaseUtil_test.

-

Definition at line 7621 of file NFToken_test.cpp.

+

Definition at line 7614 of file NFToken_test.cpp.

@@ -1389,7 +1389,7 @@ Private Attributes
-

Definition at line 6120 of file NFToken_test.cpp.

+

Definition at line 6113 of file NFToken_test.cpp.

@@ -1417,7 +1417,7 @@ Private Attributes
-

Definition at line 6384 of file NFToken_test.cpp.

+

Definition at line 6377 of file NFToken_test.cpp.

@@ -1445,7 +1445,7 @@ Private Attributes
-

Definition at line 6625 of file NFToken_test.cpp.

+

Definition at line 6618 of file NFToken_test.cpp.

@@ -1473,7 +1473,7 @@ Private Attributes
-

Definition at line 6942 of file NFToken_test.cpp.

+

Definition at line 6935 of file NFToken_test.cpp.

@@ -1501,7 +1501,7 @@ Private Attributes
-

Definition at line 7112 of file NFToken_test.cpp.

+

Definition at line 7105 of file NFToken_test.cpp.

@@ -1529,7 +1529,7 @@ Private Attributes
-

Definition at line 7270 of file NFToken_test.cpp.

+

Definition at line 7263 of file NFToken_test.cpp.

@@ -1557,7 +1557,7 @@ Private Attributes
-

Definition at line 7558 of file NFToken_test.cpp.

+

Definition at line 7551 of file NFToken_test.cpp.

@@ -2306,7 +2306,7 @@ template<class Condition >
-

Definition at line 7555 of file NFToken_test.cpp.

+

Definition at line 7548 of file NFToken_test.cpp.

diff --git a/classripple_1_1NFTokenWOModify__test.html b/classripple_1_1NFTokenWOModify__test.html index 646f1303c0..d9fde8397d 100644 --- a/classripple_1_1NFTokenWOModify__test.html +++ b/classripple_1_1NFTokenWOModify__test.html @@ -340,7 +340,7 @@ Private Attributes

Detailed Description

-

Definition at line 7628 of file NFToken_test.cpp.

+

Definition at line 7621 of file NFToken_test.cpp.

Member Function Documentation

◆ run()

@@ -369,7 +369,7 @@ Private Attributes

Reimplemented from ripple::NFTokenBaseUtil_test.

-

Definition at line 7631 of file NFToken_test.cpp.

+

Definition at line 7624 of file NFToken_test.cpp.

@@ -1389,7 +1389,7 @@ Private Attributes
-

Definition at line 6120 of file NFToken_test.cpp.

+

Definition at line 6113 of file NFToken_test.cpp.

@@ -1417,7 +1417,7 @@ Private Attributes
-

Definition at line 6384 of file NFToken_test.cpp.

+

Definition at line 6377 of file NFToken_test.cpp.

@@ -1445,7 +1445,7 @@ Private Attributes
-

Definition at line 6625 of file NFToken_test.cpp.

+

Definition at line 6618 of file NFToken_test.cpp.

@@ -1473,7 +1473,7 @@ Private Attributes
-

Definition at line 6942 of file NFToken_test.cpp.

+

Definition at line 6935 of file NFToken_test.cpp.

@@ -1501,7 +1501,7 @@ Private Attributes
-

Definition at line 7112 of file NFToken_test.cpp.

+

Definition at line 7105 of file NFToken_test.cpp.

@@ -1529,7 +1529,7 @@ Private Attributes
-

Definition at line 7270 of file NFToken_test.cpp.

+

Definition at line 7263 of file NFToken_test.cpp.

@@ -1557,7 +1557,7 @@ Private Attributes
-

Definition at line 7558 of file NFToken_test.cpp.

+

Definition at line 7551 of file NFToken_test.cpp.

@@ -2306,7 +2306,7 @@ template<class Condition >
-

Definition at line 7555 of file NFToken_test.cpp.

+

Definition at line 7548 of file NFToken_test.cpp.

diff --git a/classripple_1_1ValidAMM.html b/classripple_1_1ValidAMM.html index 87b25505fa..0a2892d53d 100644 --- a/classripple_1_1ValidAMM.html +++ b/classripple_1_1ValidAMM.html @@ -228,7 +228,7 @@ Private Attributes
-

Definition at line 1861 of file InvariantCheck.cpp.

+

Definition at line 1856 of file InvariantCheck.cpp.

@@ -276,7 +276,7 @@ Private Attributes
-

Definition at line 2125 of file InvariantCheck.cpp.

+

Definition at line 2120 of file InvariantCheck.cpp.

@@ -314,7 +314,7 @@ Private Attributes
-

Definition at line 1933 of file InvariantCheck.cpp.

+

Definition at line 1928 of file InvariantCheck.cpp.

@@ -352,7 +352,7 @@ Private Attributes
-

Definition at line 1914 of file InvariantCheck.cpp.

+

Definition at line 1909 of file InvariantCheck.cpp.

@@ -402,7 +402,7 @@ Private Attributes
-

Definition at line 1962 of file InvariantCheck.cpp.

+

Definition at line 1957 of file InvariantCheck.cpp.

@@ -446,7 +446,7 @@ Private Attributes
-

Definition at line 2005 of file InvariantCheck.cpp.

+

Definition at line 2000 of file InvariantCheck.cpp.

@@ -496,7 +496,7 @@ Private Attributes
-

Definition at line 2084 of file InvariantCheck.cpp.

+

Definition at line 2079 of file InvariantCheck.cpp.

@@ -546,7 +546,7 @@ Private Attributes
-

Definition at line 2105 of file InvariantCheck.cpp.

+

Definition at line 2100 of file InvariantCheck.cpp.

@@ -584,7 +584,7 @@ Private Attributes
-

Definition at line 2023 of file InvariantCheck.cpp.

+

Definition at line 2018 of file InvariantCheck.cpp.

@@ -634,7 +634,7 @@ Private Attributes
-

Definition at line 2038 of file InvariantCheck.cpp.

+

Definition at line 2033 of file InvariantCheck.cpp.

diff --git a/classripple_1_1ValidClawback.html b/classripple_1_1ValidClawback.html index 81982e179d..d49ee88808 100644 --- a/classripple_1_1ValidClawback.html +++ b/classripple_1_1ValidClawback.html @@ -148,7 +148,7 @@ Private Attributes
-

Definition at line 1338 of file InvariantCheck.cpp.

+

Definition at line 1333 of file InvariantCheck.cpp.

@@ -196,7 +196,7 @@ Private Attributes
-

Definition at line 1351 of file InvariantCheck.cpp.

+

Definition at line 1346 of file InvariantCheck.cpp.

diff --git a/classripple_1_1ValidMPTIssuance.html b/classripple_1_1ValidMPTIssuance.html index 920801fd02..5f80918b73 100644 --- a/classripple_1_1ValidMPTIssuance.html +++ b/classripple_1_1ValidMPTIssuance.html @@ -147,7 +147,7 @@ Private Attributes
-

Definition at line 1416 of file InvariantCheck.cpp.

+

Definition at line 1411 of file InvariantCheck.cpp.

@@ -195,7 +195,7 @@ Private Attributes
-

Definition at line 1439 of file InvariantCheck.cpp.

+

Definition at line 1434 of file InvariantCheck.cpp.

diff --git a/classripple_1_1ValidNFTokenPage.html b/classripple_1_1ValidNFTokenPage.html index 5f7de74b20..e318d453ec 100644 --- a/classripple_1_1ValidNFTokenPage.html +++ b/classripple_1_1ValidNFTokenPage.html @@ -155,7 +155,7 @@ Private Attributes
-

Definition at line 1068 of file InvariantCheck.cpp.

+

Definition at line 1063 of file InvariantCheck.cpp.

@@ -203,7 +203,7 @@ Private Attributes
-

Definition at line 1175 of file InvariantCheck.cpp.

+

Definition at line 1170 of file InvariantCheck.cpp.

diff --git a/classripple_1_1ValidPermissionedDEX.html b/classripple_1_1ValidPermissionedDEX.html index bc83bb9acd..1bcc252035 100644 --- a/classripple_1_1ValidPermissionedDEX.html +++ b/classripple_1_1ValidPermissionedDEX.html @@ -153,7 +153,7 @@ Private Attributes
-

Definition at line 1778 of file InvariantCheck.cpp.

+

Definition at line 1773 of file InvariantCheck.cpp.

@@ -201,7 +201,7 @@ Private Attributes
-

Definition at line 1807 of file InvariantCheck.cpp.

+

Definition at line 1802 of file InvariantCheck.cpp.

diff --git a/classripple_1_1ValidPermissionedDomain.html b/classripple_1_1ValidPermissionedDomain.html index 802457a9d0..cc77691225 100644 --- a/classripple_1_1ValidPermissionedDomain.html +++ b/classripple_1_1ValidPermissionedDomain.html @@ -153,7 +153,7 @@ Private Attributes
-

Definition at line 1581 of file InvariantCheck.cpp.

+

Definition at line 1576 of file InvariantCheck.cpp.

@@ -201,7 +201,7 @@ Private Attributes
-

Definition at line 1629 of file InvariantCheck.cpp.

+

Definition at line 1624 of file InvariantCheck.cpp.

diff --git a/classripple_1_1ValidPseudoAccounts.html b/classripple_1_1ValidPseudoAccounts.html index c804d0c96c..02c6f64650 100644 --- a/classripple_1_1ValidPseudoAccounts.html +++ b/classripple_1_1ValidPseudoAccounts.html @@ -150,7 +150,7 @@ Private Attributes
-

Definition at line 1682 of file InvariantCheck.cpp.

+

Definition at line 1677 of file InvariantCheck.cpp.

@@ -198,7 +198,7 @@ Private Attributes
-

Definition at line 1749 of file InvariantCheck.cpp.

+

Definition at line 1744 of file InvariantCheck.cpp.

diff --git a/classripple_1_1ValidVault.html b/classripple_1_1ValidVault.html index 057956f390..374e5db325 100644 --- a/classripple_1_1ValidVault.html +++ b/classripple_1_1ValidVault.html @@ -222,7 +222,7 @@ Static Private Attributes
-

Definition at line 2202 of file InvariantCheck.cpp.

+

Definition at line 2197 of file InvariantCheck.cpp.

@@ -270,7 +270,7 @@ Static Private Attributes
-

Definition at line 2290 of file InvariantCheck.cpp.

+

Definition at line 2285 of file InvariantCheck.cpp.

diff --git a/classripple_1_1XChainAddAccountCreateAttestation.html b/classripple_1_1XChainAddAccountCreateAttestation.html index defe727c53..46e4413892 100644 --- a/classripple_1_1XChainAddAccountCreateAttestation.html +++ b/classripple_1_1XChainAddAccountCreateAttestation.html @@ -442,7 +442,7 @@ Static Private Member Functions
-

Definition at line 2100 of file XChainBridge.cpp.

+

Definition at line 2097 of file XChainBridge.cpp.

@@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 2106 of file XChainBridge.cpp.

+

Definition at line 2103 of file XChainBridge.cpp.

@@ -499,7 +499,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 2112 of file XChainBridge.cpp.

+

Definition at line 2109 of file XChainBridge.cpp.

diff --git a/classripple_1_1XChainAddClaimAttestation.html b/classripple_1_1XChainAddClaimAttestation.html index 0e8239cffb..d5db687397 100644 --- a/classripple_1_1XChainAddClaimAttestation.html +++ b/classripple_1_1XChainAddClaimAttestation.html @@ -442,7 +442,7 @@ Static Private Member Functions
-

Definition at line 2080 of file XChainBridge.cpp.

+

Definition at line 2077 of file XChainBridge.cpp.

@@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 2086 of file XChainBridge.cpp.

+

Definition at line 2083 of file XChainBridge.cpp.

@@ -499,7 +499,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 2092 of file XChainBridge.cpp.

+

Definition at line 2089 of file XChainBridge.cpp.

diff --git a/classripple_1_1XChainClaim.html b/classripple_1_1XChainClaim.html index b2a5339c86..d6f1d08fdf 100644 --- a/classripple_1_1XChainClaim.html +++ b/classripple_1_1XChainClaim.html @@ -442,7 +442,7 @@ Static Private Member Functions
-

Definition at line 1634 of file XChainBridge.cpp.

+

Definition at line 1631 of file XChainBridge.cpp.

@@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 1650 of file XChainBridge.cpp.

+

Definition at line 1647 of file XChainBridge.cpp.

@@ -499,7 +499,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 1734 of file XChainBridge.cpp.

+

Definition at line 1731 of file XChainBridge.cpp.

diff --git a/classripple_1_1XChainCommit.html b/classripple_1_1XChainCommit.html index ea2d91aedf..95dd71cb5a 100644 --- a/classripple_1_1XChainCommit.html +++ b/classripple_1_1XChainCommit.html @@ -444,7 +444,7 @@ Static Private Member Functions
-

Definition at line 1850 of file XChainBridge.cpp.

+

Definition at line 1847 of file XChainBridge.cpp.

@@ -472,7 +472,7 @@ Static Private Member Functions
-

Definition at line 1863 of file XChainBridge.cpp.

+

Definition at line 1860 of file XChainBridge.cpp.

@@ -500,7 +500,7 @@ Static Private Member Functions
-

Definition at line 1879 of file XChainBridge.cpp.

+

Definition at line 1876 of file XChainBridge.cpp.

@@ -529,7 +529,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 1924 of file XChainBridge.cpp.

+

Definition at line 1921 of file XChainBridge.cpp.

diff --git a/classripple_1_1XChainCreateAccountCommit.html b/classripple_1_1XChainCreateAccountCommit.html index c4885c7ed4..243868bdce 100644 --- a/classripple_1_1XChainCreateAccountCommit.html +++ b/classripple_1_1XChainCreateAccountCommit.html @@ -442,7 +442,7 @@ Static Private Member Functions
-

Definition at line 2120 of file XChainBridge.cpp.

+

Definition at line 2117 of file XChainBridge.cpp.

@@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 2138 of file XChainBridge.cpp.

+

Definition at line 2135 of file XChainBridge.cpp.

@@ -499,7 +499,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 2197 of file XChainBridge.cpp.

+

Definition at line 2194 of file XChainBridge.cpp.

diff --git a/classripple_1_1XChainCreateBridge.html b/classripple_1_1XChainCreateBridge.html index 158ff00661..3b00c98290 100644 --- a/classripple_1_1XChainCreateBridge.html +++ b/classripple_1_1XChainCreateBridge.html @@ -442,7 +442,7 @@ Static Private Member Functions
-

Definition at line 1355 of file XChainBridge.cpp.

+

Definition at line 1352 of file XChainBridge.cpp.

@@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 1430 of file XChainBridge.cpp.

+

Definition at line 1427 of file XChainBridge.cpp.

@@ -499,7 +499,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 1481 of file XChainBridge.cpp.

+

Definition at line 1478 of file XChainBridge.cpp.

diff --git a/classripple_1_1XChainCreateClaimID.html b/classripple_1_1XChainCreateClaimID.html index a650240669..46cd360536 100644 --- a/classripple_1_1XChainCreateClaimID.html +++ b/classripple_1_1XChainCreateClaimID.html @@ -442,7 +442,7 @@ Static Private Member Functions
-

Definition at line 1968 of file XChainBridge.cpp.

+

Definition at line 1965 of file XChainBridge.cpp.

@@ -470,7 +470,7 @@ Static Private Member Functions
-

Definition at line 1979 of file XChainBridge.cpp.

+

Definition at line 1976 of file XChainBridge.cpp.

@@ -499,7 +499,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 2016 of file XChainBridge.cpp.

+

Definition at line 2013 of file XChainBridge.cpp.

diff --git a/classripple_1_1test_1_1AccountDelete__test-members.html b/classripple_1_1test_1_1AccountDelete__test-members.html index 23e7502324..4aabee87cd 100644 --- a/classripple_1_1test_1_1AccountDelete__test-members.html +++ b/classripple_1_1test_1_1AccountDelete__test-members.html @@ -103,25 +103,24 @@ $(function() { runner_beast::unit_test::suiteprivate suite()beast::unit_test::suite suite(suite const &)=deletebeast::unit_test::suite - testAmendmentEnable()ripple::test::AccountDelete_test - testBalanceTooSmallForFee()ripple::test::AccountDelete_test - testBasics()ripple::test::AccountDelete_test - testcasebeast::unit_test::suite - testDeleteCredentialsOwner()ripple::test::AccountDelete_test - testDest()ripple::test::AccountDelete_test - testDestinationDepositAuthCredentials()ripple::test::AccountDelete_test - testDirectories()ripple::test::AccountDelete_test - testImplicitlyCreatedTrustline()ripple::test::AccountDelete_test - testOwnedTypes()ripple::test::AccountDelete_test - testTooManyOffers()ripple::test::AccountDelete_test - testWithTickets()ripple::test::AccountDelete_test - this_suite()beast::unit_test::suitestatic - unexcept(F &&f, String const &reason)beast::unit_test::suite - unexcept(F &&f)beast::unit_test::suite - unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite - unexpected(Condition shouldBeFalse)beast::unit_test::suite - verifyDeliveredAmount(jtx::Env &env, STAmount const &amount)ripple::test::AccountDelete_testprivate - ~suite()=defaultbeast::unit_test::suitevirtual + testBalanceTooSmallForFee()ripple::test::AccountDelete_test + testBasics()ripple::test::AccountDelete_test + testcasebeast::unit_test::suite + testDeleteCredentialsOwner()ripple::test::AccountDelete_test + testDest()ripple::test::AccountDelete_test + testDestinationDepositAuthCredentials()ripple::test::AccountDelete_test + testDirectories()ripple::test::AccountDelete_test + testImplicitlyCreatedTrustline()ripple::test::AccountDelete_test + testOwnedTypes()ripple::test::AccountDelete_test + testTooManyOffers()ripple::test::AccountDelete_test + testWithTickets()ripple::test::AccountDelete_test + this_suite()beast::unit_test::suitestatic + unexcept(F &&f, String const &reason)beast::unit_test::suite + unexcept(F &&f)beast::unit_test::suite + unexpected(Condition shouldBeFalse, String const &reason)beast::unit_test::suite + unexpected(Condition shouldBeFalse)beast::unit_test::suite + verifyDeliveredAmount(jtx::Env &env, STAmount const &amount)ripple::test::AccountDelete_testprivate + ~suite()=defaultbeast::unit_test::suitevirtual