diff --git a/include/xrpl/ledger/helpers/NFTokenHelpers.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h index 3af81eff16..d8dac4caaf 100644 --- a/include/xrpl/ledger/helpers/NFTokenHelpers.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -20,6 +20,10 @@ removeTokenOffersWithLimit( Keylet const& directory, std::size_t maxDeletableOffers); +/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */ +TER +notTooManyOffers(ReadView const& view, uint256 const& nftokenID); + /** Finds the specified token in the owner's token directory. */ std::optional findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID); diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index ad4c5e16c4..5fbd0b2f5c 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -372,6 +372,22 @@ public: finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); }; +/** + * @brief Invariants: An account's directory should never be empty + * + */ +class NoEmptyDirectory +{ + bool bad_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + // additional invariant checks can be declared above and then added to this // tuple using InvariantChecks = std::tuple< @@ -399,7 +415,8 @@ using InvariantChecks = std::tuple< ValidLoanBroker, ValidLoan, ValidVault, - ValidMPTPayment>; + ValidMPTPayment, + NoEmptyDirectory>; /** * @brief get a tuple of all invariant checks diff --git a/src/libxrpl/ledger/ApplyView.cpp b/src/libxrpl/ledger/ApplyView.cpp index 476b635511..964495f77b 100644 --- a/src/libxrpl/ledger/ApplyView.cpp +++ b/src/libxrpl/ledger/ApplyView.cpp @@ -244,6 +244,7 @@ ApplyView::emptyDirDelete(Keylet const& directory) bool ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot) { + keepRoot = false; auto node = peek(keylet::page(directory, page)); if (!node) diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index 7e7335232f..4652bccca8 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -607,6 +607,33 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t return deletedOffersCount; } +TER +notTooManyOffers(ReadView const& view, uint256 const& nftokenID) +{ + std::size_t totalOffers = 0; + + { + Dir const buys(view, keylet::nft_buys(nftokenID)); + for (auto iter = buys.begin(); iter != buys.end(); iter.next_page()) + { + totalOffers += iter.page_size(); + if (totalOffers > maxDeletableTokenOfferEntries) + return tefTOO_BIG; + } + } + + { + Dir const sells(view, keylet::nft_sells(nftokenID)); + for (auto iter = sells.begin(); iter != sells.end(); iter.next_page()) + { + totalOffers += iter.page_size(); + if (totalOffers > maxDeletableTokenOfferEntries) + return tefTOO_BIG; + } + } + return tesSUCCESS; +} + bool deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer) { diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 1d6756eaca..d077f556bd 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -1025,4 +1025,42 @@ NoModifiedUnmodifiableFields::finalize( return true; } +//------------------------------------------------------------------------------ + +void +NoEmptyDirectory::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete) + return; + if (before && before->getType() != ltDIR_NODE) + return; + if (after && after->getType() != ltDIR_NODE) + return; + if (!after->isFieldPresent(sfOwner)) + // Not an account dir + return; + + bad_ = after->at(sfIndexes).empty(); +} + +bool +NoEmptyDirectory::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (bad_) + { + JLOG(j.fatal()) << "Invariant failed: empty owner directory."; + return false; + } + + return true; +} + } // namespace xrpl