diff --git a/include/xrpl/tx/invariants/VaultInvariantData.h b/include/xrpl/tx/invariants/VaultInvariantData.h index e20456f74b..4ec5873ae5 100644 --- a/include/xrpl/tx/invariants/VaultInvariantData.h +++ b/include/xrpl/tx/invariants/VaultInvariantData.h @@ -67,10 +67,15 @@ public: [[nodiscard]] std::optional findShares(uint192 const& mptID) const; + /** Find deleted shares in beforeMPTs_ whose mptID matches. */ + [[nodiscard]] std::optional + findDeletedShares(uint192 const& mptID) const; + private: std::vector afterVault_; std::vector beforeVault_; std::vector afterMPTs_; + std::vector beforeMPTs_; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/vault/VaultDelete.h b/include/xrpl/tx/transactors/vault/VaultDelete.h index b8bb3c4096..40d4fecc48 100644 --- a/include/xrpl/tx/transactors/vault/VaultDelete.h +++ b/include/xrpl/tx/transactors/vault/VaultDelete.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace xrpl { @@ -32,6 +33,9 @@ public: XRPAmount fee, ReadView const& view, beast::Journal const& j) override; + +private: + VaultInvariantData data_; }; } // namespace xrpl diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp index af1c722254..104099970d 100644 --- a/src/libxrpl/tx/invariants/VaultInvariant.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -339,51 +338,8 @@ ValidVault::finalize( return !enforce; // That's all we can do here } - // Note, if afterVault_ is empty then we know that beforeVault_ is not - // empty, as enforced at the top of this function - auto const& beforeVault = beforeVault_[0]; - - // At this moment we only know a vault is being deleted and there - // might be some MPTokenIssuance objects which are deleted in the - // same transaction. Find the one matching this vault. - auto const deletedShares = [&]() -> std::optional { - for (auto const& e : beforeMPTs_) - { - if (e.share.getMptID() == beforeVault.shareMPTID) - return e; - } - return std::nullopt; - }(); - - if (!deletedShares) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must also " - "delete shares"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares deletion invariant"); - return !enforce; // That's all we can do here - } - - bool result = true; - if (deletedShares->sharesTotal != 0) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " - "shares outstanding"; - result = false; - } - if (beforeVault.assetsTotal != kZero) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " - "assets outstanding"; - result = false; - } - if (beforeVault.assetsAvailable != kZero) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " - "assets available"; - result = false; - } - - return result; + // Delete-specific checks now live in VaultDelete::finalizeInvariants. + return true; } if (txnType == ttVAULT_DELETE) { diff --git a/src/libxrpl/tx/invariants/VaultInvariantData.cpp b/src/libxrpl/tx/invariants/VaultInvariantData.cpp index ddc2807579..a7e27af841 100644 --- a/src/libxrpl/tx/invariants/VaultInvariantData.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariantData.cpp @@ -5,8 +5,11 @@ #include #include #include +#include #include // IWYU pragma: keep +#include + namespace xrpl { VaultInvariantData::Vault @@ -51,6 +54,9 @@ VaultInvariantData::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ if (before && before->getType() == ltVAULT) beforeVault_.push_back(Vault::make(*before)); + if (before && before->getType() == ltMPTOKEN_ISSUANCE) + beforeMPTs_.push_back(Shares::make(*before)); + if (!isDelete && after) { switch (after->getType()) @@ -77,4 +83,15 @@ VaultInvariantData::findShares(uint192 const& mptID) const return std::nullopt; } +std::optional +VaultInvariantData::findDeletedShares(uint192 const& mptID) const +{ + for (auto const& s : beforeMPTs_) + { + if (s.share.getMptID() == mptID) + return s; + } + return std::nullopt; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index 030a7e971c..0ef1a59060 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -1,12 +1,16 @@ #include #include +#include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -211,15 +215,76 @@ VaultDelete::doApply() } void -VaultDelete::visitInvariantEntry(bool, SLE::const_ref, SLE::const_ref) +VaultDelete::visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { - // No transaction-specific invariants yet (future work). + data_.visitEntry(isDelete, before, after); } bool -VaultDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +VaultDelete::finalizeInvariants( + STTx const&, + TER result, + XRPAmount, + ReadView const& view, + beast::Journal const& j) { - // No transaction-specific invariants yet (future work). + bool const enforce = view.rules().enabled(featureSingleAssetVault); + + if (!isTesSuccess(result)) + return true; + + static constexpr Number kZero{}; + + // VaultDelete must have deleted the vault; if it still exists, something + // went wrong. + if (!data_.afterVaults().empty()) + { + JLOG(j.fatal()) << "Invariant failed: vault not deleted by VaultDelete"; + XRPL_ASSERT(enforce, "xrpl::VaultDelete::finalizeInvariants : vault not deleted invariant"); + return !enforce; + } + + // Nothing to check if the vault was never in the before-set (e.g. a + // test that omits the vault entry entirely). + if (data_.beforeVaults().empty()) + return true; + + auto const& beforeVault = data_.beforeVaults()[0]; + + auto const deletedShares = data_.findDeletedShares(beforeVault.shareMPTID); + if (!deletedShares) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must also delete shares"; + XRPL_ASSERT(enforce, "xrpl::VaultDelete::finalizeInvariants : shares deletion invariant"); + return !enforce; + } + + bool result2 = true; + + if (deletedShares->sharesTotal != 0) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must have no shares outstanding"; + result2 = false; + } + + if (beforeVault.assetsTotal != kZero) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must have no assets outstanding"; + result2 = false; + } + + if (beforeVault.assetsAvailable != kZero) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must have no assets available"; + result2 = false; + } + + if (!result2) + { + XRPL_ASSERT(enforce, "xrpl::VaultDelete::finalizeInvariants : vault delete invariants"); + return !enforce; + } + return true; }