diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 90679c2dcf..dd31f02ca2 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -497,6 +497,20 @@ AccountRootsDeletedClean::finalize( if (enforce) return false; } + // An account should not be deleted with sponsorship fields + if (after->isFieldPresent(sfSponsoredOwnerCount) || + after->isFieldPresent(sfSponsoringOwnerCount) || + after->isFieldPresent(sfSponsoringAccountCount) || after->isFieldPresent(sfSponsor)) + { + JLOG(j.fatal()) << "Invariant failed: account deletion left " + "behind a sponsorship field"; + XRPL_ASSERT( + enforce, + "xrpl::AccountRootsDeletedClean::finalize : " + "deleted account has no sponsorship fields"); + if (enforce) + return false; + } // Simple types for (auto const& [keyletfunc, _1, _2] : directAccountKeylets) { @@ -849,10 +863,10 @@ ValidPseudoAccounts::visitEntry( // 1. Exactly one of the pseudo-account fields is set. // 2. The sequence number is not changed. // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth - // flags are set. + // flags are set. // 4. The RegularKey is not set. - // 5. The SponsoredOwnerCount, SponsoringOwnerCount, and - // SponsorAccount fields are not set. + // 5. The SponsoredOwnerCount, SponsoringOwnerCount, SponsoringAccountCount, Sponsor + // fields are not set. { std::vector const& fields = getPseudoAccountFields(); @@ -880,7 +894,8 @@ ValidPseudoAccounts::visitEntry( errors_.emplace_back("pseudo-account has a regular key"); } if (after->isFieldPresent(sfSponsoredOwnerCount) || - after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor)) + after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor) || + after->isFieldPresent(sfSponsoringAccountCount)) { errors_.emplace_back("pseudo-account has a sponsorship field"); } diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index ee85244506..3f5a9aa08a 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -277,6 +277,74 @@ class Invariants_test : public beast::unit_test::suite XRPAmount{}, STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const a1 = A1.id(); + auto const sleA1 = ac.view().peek(keylet::account(a1)); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::zero; + sleA1->setFieldU32(sfSponsoredOwnerCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const a1 = A1.id(); + auto const sleA1 = ac.view().peek(keylet::account(a1)); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::zero; + sleA1->setFieldU32(sfSponsoringOwnerCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const a1 = A1.id(); + auto const sleA1 = ac.view().peek(keylet::account(a1)); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::zero; + sleA1->setFieldU32(sfSponsoringAccountCount, 1); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + + doInvariantCheck( + {{"account deletion left behind a sponsorship field"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const a1 = A1.id(); + auto const sleA1 = ac.view().peek(keylet::account(a1)); + if (!sleA1) + return false; + sleA1->at(sfBalance) = beast::zero; + sleA1->setAccountID(sfSponsor, A2.id()); + + ac.view().erase(sleA1); + + return true; + }, + XRPAmount{}, + STTx{ttACCOUNT_DELETE, [](STObject& tx) {}}); + for (auto const& keyletInfo : directAccountKeylets) { // TODO: Use structured binding once LLVM 16 is the minimum @@ -1650,6 +1718,10 @@ class Invariants_test : public beast::unit_test::suite "pseudo-account has a sponsorship field", [](SLE::pointer& sle) { sle->at(sfSponsoringOwnerCount) = 1; }, }, + { + "pseudo-account has a sponsorship field", + [](SLE::pointer& sle) { sle->at(sfSponsoringAccountCount) = 1; }, + }, { "pseudo-account has a sponsorship field", [](SLE::pointer& sle) { sle->at(sfSponsor) = Account("sponsor").id(); },