Compare commits

...

3 Commits

Author SHA1 Message Date
Vito
696351e41e chore: Apply clang-tidy include-cleaner and pass-by-value on InvariantEntry
Give checkers a direct InvariantEntry include and take SLE pointers by
value so clang-tidy -Werror matches CI.
2026-08-27 14:23:24 +02:00
Vito
4f2eaeedbc refactor: Validate visitEntry arguments via InvariantEntry
Own before/after as shared_ptrs so a malformed create/delete cannot
reach checkers, and throw std::logic_error so the runner can fail
the transaction in all builds.
2026-08-27 14:21:51 +02:00
Vito
d3bf7a73fd refactor: Enforce visitEntry SLE nullability in checkInvariants
Throw if after is null or a deletion lacks before, then drop the
duplicate checker-level guards that assumed those pointers might be missing.
2026-08-27 13:39:29 +02:00
31 changed files with 428 additions and 322 deletions

View File

@@ -20,6 +20,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/applySteps.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <xrpl/tx/invariants/InvariantRunner.h>
#include <cstddef>
@@ -578,9 +579,9 @@ private:
* ordering is enforced.
*/
void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) final
visitEntry(InvariantEntry const& entry) final
{
visitInvariantEntry(isDelete, before, after);
visitInvariantEntry(entry.isDelete(), entry.before(), entry.after());
}
[[nodiscard]] bool

View File

@@ -4,10 +4,10 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <optional>
@@ -27,7 +27,7 @@ public:
ValidAMM() = default;
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -4,12 +4,10 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <memory>
#include <xrpl/tx/invariants/InvariantEntry.h>
namespace xrpl {
@@ -20,7 +18,7 @@ class ValidBookDirectory
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <map>
#include <optional>
@@ -44,7 +45,7 @@ class TransfersNotFrozen
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -9,6 +9,7 @@
#include <xrpl/tx/invariants/AMMInvariant.h>
#include <xrpl/tx/invariants/DirectoryInvariant.h>
#include <xrpl/tx/invariants/FreezeInvariant.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
#include <xrpl/tx/invariants/LoanInvariant.h>
#include <xrpl/tx/invariants/MPTInvariant.h>
@@ -71,21 +72,13 @@ public:
/**
* @brief called for each ledger entry in the current transaction.
*
* @param isDelete true if the SLE is being deleted.
* @param before ledger entry before modification by the transaction. `before` will be null if
* the entry is new.
* @param after ledger entry after modification by the transaction. Always non-null. When
* deleting, `after` may differ from `before`. Whether that is important is up to the
* individual invariant check.
* @param entry validated, non-owning view of the modified ledger entry.
*
* @note `after` IS NEVER NULL. `isDelete` is the only correct way to check for deletions.
* Do not make logic or branching decisions on whether on `after` is set, because it will
* always be set. Treat a null `after` as a programming error (with XRPL_ASSERT). An
* invariant MAY check for null defensively, if it makes more sense, but an assertion is
* preferred for new invariants.
* @note `entry.after()` IS NEVER NULL. `entry.isDelete()` is the only
* correct way to check for deletions.
*/
void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after);
visitEntry(InvariantEntry const& entry);
/**
* @brief called after all ledger entries have been visited to determine
@@ -123,7 +116,7 @@ class TransactionFeeCheck
{
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
static bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
@@ -143,7 +136,7 @@ class XRPNotCreated
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -163,7 +156,7 @@ class AccountRootsNotDeleted
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -190,7 +183,7 @@ class AccountRootsDeletedClean
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
@@ -209,7 +202,7 @@ class XRPBalanceChecks
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -226,7 +219,7 @@ class LedgerEntryTypesMatch
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -244,7 +237,7 @@ class NoXRPTrustLines
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -263,7 +256,7 @@ class NoDeepFreezeTrustLinesWithoutFreeze
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -282,7 +275,7 @@ class NoBadOffers
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -298,7 +291,7 @@ class NoZeroEscrow
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -318,7 +311,7 @@ class ValidNewAccountRoot
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -348,7 +341,7 @@ class ValidClawback
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -368,7 +361,7 @@ class ValidPseudoAccounts
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
@@ -388,7 +381,7 @@ class NoModifiedUnmodifiableFields
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
@@ -404,7 +397,7 @@ class ValidAmounts
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -421,7 +414,7 @@ class ObjectHasPseudoAccount
{
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;

View File

@@ -0,0 +1,53 @@
#pragma once
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <stdexcept>
#include <utility>
namespace xrpl {
/**
* A validated ledger entry visited by invariants.
*/
class InvariantEntry
{
bool isDelete_;
SLE::const_pointer before_;
SLE::const_pointer after_;
public:
InvariantEntry(bool isDelete, SLE::const_pointer before, SLE::const_pointer after)
: isDelete_(isDelete), before_(std::move(before)), after_(std::move(after))
{
if (after_ == nullptr)
Throw<std::logic_error>("InvariantEntry: after is never null");
if (isDelete_ && before_ == nullptr)
Throw<std::logic_error>("InvariantEntry: deleted entry missing before state");
}
InvariantEntry(InvariantEntry const&) = delete;
InvariantEntry&
operator=(InvariantEntry const&) = delete;
[[nodiscard]] bool
isDelete() const
{
return isDelete_;
}
[[nodiscard]] SLE::const_ref
before() const
{
return before_;
}
[[nodiscard]] SLE::const_ref
after() const
{
return after_;
}
};
} // namespace xrpl

View File

@@ -2,11 +2,11 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <functional>
#include <optional>
@@ -66,16 +66,10 @@ public:
/**
* @brief Called for each ledger entry modified by the transaction.
*
* @param isDelete true if the SLE is being deleted.
* @param before the entry's state before the transaction (nullptr for
* newly created entries).
* @param after the entry's state after the transaction. For deletions
* this is the SLE being erased; use @p isDelete rather than
* a null @p after to detect deletions. @p after is
* never null.
* @param entry a validated, non-owning view of the modified entry.
*/
virtual void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) = 0;
visitEntry(InvariantEntry const& entry) = 0;
/**
* @brief Called after all entries have been visited.

View File

@@ -7,6 +7,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <map>
#include <vector>
@@ -48,7 +49,7 @@ class ValidLoanBroker
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -6,6 +6,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <utility>
#include <vector>
@@ -28,7 +29,7 @@ class ValidLoan
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <array>
#include <cstdint>
@@ -54,12 +55,10 @@ public:
* @brief Track MPT issuance and holding creations, deletions, and
* mutations.
*
* @param isDelete Whether the ledger entry is being deleted.
* @param before The ledger entry before transaction application.
* @param after The ledger entry after transaction application.
* @param entry The ledger entry change being checked.
*/
void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after);
visitEntry(InvariantEntry const& entry);
/**
* @brief Verify MPT issuance invariants after transaction application.
@@ -106,12 +105,10 @@ public:
/**
* @brief Track MPT amount and outstanding amount changes.
*
* @param isDelete Whether the ledger entry is being deleted.
* @param before The ledger entry before transaction application.
* @param after The ledger entry after transaction application.
* @param entry The ledger entry change being checked.
*/
void
visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after);
visitEntry(InvariantEntry const& entry);
/**
* @brief Verify public MPT payment accounting invariants.
@@ -173,15 +170,10 @@ public:
/**
* @brief Track confidential MPT balance, issuance, and version changes.
*
* @param isDelete Whether the ledger entry is being deleted.
* @param before The ledger entry before transaction application.
* @param after The ledger entry after transaction application.
* @param entry The ledger entry change being checked.
*/
void
visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after);
visitEntry(InvariantEntry const& entry);
/**
* @brief Verify confidential MPT accounting and encrypted-field
@@ -220,15 +212,10 @@ public:
/**
* @brief Track MPT balance changes and deleted authorization state.
*
* @param isDelete Whether the ledger entry is being deleted.
* @param before The ledger entry before transaction application.
* @param after The ledger entry after transaction application.
* @param entry The ledger entry change being checked.
*/
void
visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after);
visitEntry(InvariantEntry const& entry);
/**
* @brief Verify MPT transfer authorization invariants.

View File

@@ -2,10 +2,10 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <cstdint>
@@ -34,7 +34,7 @@ class ValidNFTokenPage
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -62,7 +62,7 @@ class NFTokenCountTracking
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;

View File

@@ -4,10 +4,10 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
namespace xrpl {
@@ -22,7 +22,7 @@ class ValidPermissionedDEX
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -2,10 +2,10 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <cstddef>
#include <vector>
@@ -35,7 +35,7 @@ class ValidPermissionedDomain
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -2,10 +2,10 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <cstdint>
@@ -31,7 +31,7 @@ class SponsorshipOwnerCountsMatch
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
@@ -52,7 +52,7 @@ class SponsorshipAccountCountMatchesField
public:
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;

View File

@@ -13,6 +13,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <cstdint>
#include <optional>
@@ -183,7 +184,7 @@ public:
computeCoarsestScale(std::vector<DeltaInfo> const& numbers);
void
visitEntry(bool, SLE::const_ref, SLE::const_ref);
visitEntry(InvariantEntry const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);

View File

@@ -18,14 +18,19 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <string>
namespace xrpl {
void
ValidAMM::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidAMM::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
if (isDelete)
{
if (before && before->getType() == ltAMM)
@@ -36,23 +41,20 @@ ValidAMM::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
return;
}
if (after)
auto const type = after->getType();
// AMM object changed
if (type == ltAMM)
{
auto const type = after->getType();
// AMM object changed
if (type == ltAMM)
{
ammAccount_ = after->getAccountID(sfAccount);
lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
}
// AMM pool changed
else if (
(type == ltRIPPLE_STATE && after->isFlag(lsfAMMNode)) ||
(type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)) ||
(type == ltMPTOKEN && after->isFlag(lsfMPTAMM)))
{
ammPoolChanged_ = true;
}
ammAccount_ = after->getAccountID(sfAccount);
lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance);
}
// AMM pool changed
else if (
(type == ltRIPPLE_STATE && after->isFlag(lsfAMMNode)) ||
(type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)) ||
(type == ltMPTOKEN && after->isFlag(lsfMPTAMM)))
{
ammPoolChanged_ = true;
}
if (before)

View File

@@ -12,9 +12,9 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <algorithm>
#include <memory>
namespace xrpl {
@@ -41,19 +41,19 @@ badExchangeRate(SLE const& dir)
} // namespace
void
ValidBookDirectory::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
ValidBookDirectory::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
// New root directories must have matching exchange-rate metadata. New
// child directories, and modified directories that change sfRootIndex, must
// point to an existing root.
// Only validate newly-created directories and sfRootIndex changes;
// LedgerStateFix handles legacy bad exchange-rate metadata. Skip deletions
// because `after` is not guaranteed to be null.
if (badBookDirectory_ || isDelete || !after || after->getType() != ltDIR_NODE)
// LedgerStateFix handles legacy bad exchange-rate metadata. Skip deletions.
if (badBookDirectory_ || isDelete || after->getType() != ltDIR_NODE)
return;
auto const rootIndex = after->getFieldH256(sfRootIndex);

View File

@@ -17,6 +17,7 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <algorithm>
#include <optional>
@@ -25,8 +26,12 @@
namespace xrpl {
void
TransfersNotFrozen::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
TransfersNotFrozen::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
/*
* A trust line freeze state alone doesn't determine if a transfer is
* frozen. The transfer must be examined "end-to-end" because both sides of
@@ -116,13 +121,6 @@ TransfersNotFrozen::finalize(
bool
TransfersNotFrozen::isValidEntry(SLE::const_ref before, SLE::const_ref after)
{
// `after` can never be null, even if the trust line is deleted.
XRPL_ASSERT(after, "xrpl::TransfersNotFrozen::isValidEntry : valid after.");
if (!after)
{
return false;
}
if (after->getType() == ltACCOUNT_ROOT)
{
possibleIssuers_.emplace(after->at(sfAccount), after);

View File

@@ -29,11 +29,11 @@
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
@@ -88,7 +88,7 @@ ledgerEntryTypeName(SLE const& sle)
}
void
TransactionFeeCheck::visitEntry(bool, SLE::const_ref, SLE::const_ref)
TransactionFeeCheck::visitEntry(InvariantEntry const&)
{
// nothing to do
}
@@ -131,8 +131,12 @@ TransactionFeeCheck::finalize(
//------------------------------------------------------------------------------
void
XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
XRPNotCreated::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
/* We go through all modified ledger entries, looking only at account roots,
* escrow payments, and payment channels. We remove from the total any
* previous XRP values and add to the total any new XRP values. The net
@@ -168,13 +172,6 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
}
}
if (!after)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::XRPNotCreated::visitEntry : after can't be null");
return;
// LCOV_EXCL_STOP
}
switch (after->getType())
{
case ltACCOUNT_ROOT:
@@ -232,8 +229,11 @@ XRPNotCreated::finalize(
//------------------------------------------------------------------------------
void
XRPBalanceChecks::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
XRPBalanceChecks::visitEntry(InvariantEntry const& entry)
{
auto const& before = entry.before();
auto const& after = entry.after();
auto isBad = [](STAmount const& balance) {
if (!balance.native())
return true;
@@ -255,7 +255,7 @@ XRPBalanceChecks::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
if (before && before->getType() == ltACCOUNT_ROOT)
bad_ |= isBad((*before)[sfBalance]);
if (after && after->getType() == ltACCOUNT_ROOT)
if (after->getType() == ltACCOUNT_ROOT)
bad_ |= isBad((*after)[sfBalance]);
}
@@ -279,8 +279,11 @@ XRPBalanceChecks::finalize(
//------------------------------------------------------------------------------
void
NoBadOffers::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
NoBadOffers::visitEntry(InvariantEntry const& entry)
{
auto const& before = entry.before();
auto const& after = entry.after();
auto isBad = [](STAmount const& pays, STAmount const& gets) {
// An offer should never be negative
if (pays < beast::kZero)
@@ -296,7 +299,7 @@ NoBadOffers::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref aft
if (before && before->getType() == ltOFFER)
bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
if (after && after->getType() == ltOFFER)
if (after->getType() == ltOFFER)
bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
}
@@ -320,8 +323,11 @@ NoBadOffers::finalize(
//------------------------------------------------------------------------------
void
NoZeroEscrow::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
NoZeroEscrow::visitEntry(InvariantEntry const& entry)
{
auto const& before = entry.before();
auto const& after = entry.after();
auto isBad = [](STAmount const& amount) {
// XRP case
if (amount.native())
@@ -364,7 +370,7 @@ NoZeroEscrow::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref af
if (before && before->getType() == ltESCROW)
bad_ |= isBad((*before)[sfAmount]);
if (after && after->getType() == ltESCROW)
if (after->getType() == ltESCROW)
bad_ |= isBad((*after)[sfAmount]);
auto checkAmount = [this](std::int64_t amount) {
@@ -374,7 +380,7 @@ NoZeroEscrow::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref af
bool const overwriteFixEnabled = isFeatureEnabled(fixCleanup3_1_3, true);
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
if (after->getType() == ltMPTOKEN_ISSUANCE)
{
auto const outstanding = (*after)[sfOutstandingAmount];
checkAmount(outstanding);
@@ -393,7 +399,7 @@ NoZeroEscrow::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref af
}
}
if (after && after->getType() == ltMPTOKEN)
if (after->getType() == ltMPTOKEN)
{
auto const mptAmount = (*after)[sfMPTAmount];
checkAmount(mptAmount);
@@ -424,8 +430,11 @@ NoZeroEscrow::finalize(
//------------------------------------------------------------------------------
void
AccountRootsNotDeleted::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref)
AccountRootsNotDeleted::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
accountsDeleted_++;
}
@@ -476,8 +485,12 @@ AccountRootsNotDeleted::finalize(
//------------------------------------------------------------------------------
void
AccountRootsDeletedClean::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
AccountRootsDeletedClean::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
accountsDeleted_.emplace_back(before, after);
}
@@ -599,31 +612,31 @@ AccountRootsDeletedClean::finalize(
//------------------------------------------------------------------------------
void
LedgerEntryTypesMatch::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
LedgerEntryTypesMatch::visitEntry(InvariantEntry const& entry)
{
if (before && after && before->getType() != after->getType())
auto const& before = entry.before();
auto const& after = entry.after();
if (before && before->getType() != after->getType())
typeMismatch_ = true;
if (after)
{
#pragma push_macro("LEDGER_ENTRY")
#undef LEDGER_ENTRY
#define LEDGER_ENTRY(tag, ...) case tag:
switch (after->getType())
{
switch (after->getType())
{
#include <xrpl/protocol/detail/ledger_entries.macro>
break;
default:
invalidTypeAdded_ = true;
break;
default:
invalidTypeAdded_ = true;
break;
}
}
#undef LEDGER_ENTRY
#pragma pop_macro("LEDGER_ENTRY")
}
}
bool
@@ -653,11 +666,13 @@ LedgerEntryTypesMatch::finalize(
//------------------------------------------------------------------------------
void
NoXRPTrustLines::visitEntry(bool, SLE::const_ref, SLE::const_ref after)
NoXRPTrustLines::visitEntry(InvariantEntry const& entry)
{
auto const& after = entry.after();
bool const overwriteFixEnabled = isFeatureEnabled(fixCleanup3_1_3, true);
if (after && after->getType() == ltRIPPLE_STATE)
if (after->getType() == ltRIPPLE_STATE)
{
// checking the issue directly here instead of
// relying on .native() just in case native somehow
@@ -693,9 +708,11 @@ NoXRPTrustLines::finalize(
//------------------------------------------------------------------------------
void
NoDeepFreezeTrustLinesWithoutFreeze::visitEntry(bool, SLE::const_ref, SLE::const_ref after)
NoDeepFreezeTrustLinesWithoutFreeze::visitEntry(InvariantEntry const& entry)
{
if (after && after->getType() == ltRIPPLE_STATE)
auto const& after = entry.after();
if (after->getType() == ltRIPPLE_STATE)
{
bool const overwriteFixEnabled = isFeatureEnabled(fixCleanup3_1_3, true);
@@ -736,8 +753,11 @@ NoDeepFreezeTrustLinesWithoutFreeze::finalize(
//------------------------------------------------------------------------------
void
ValidNewAccountRoot::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
ValidNewAccountRoot::visitEntry(InvariantEntry const& entry)
{
auto const& before = entry.before();
auto const& after = entry.after();
if (!before && after->getType() == ltACCOUNT_ROOT)
{
accountsCreated_++;
@@ -834,15 +854,19 @@ clawbackTrustLineBalanceInHolderTerms(
}
void
ValidClawback::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidClawback::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
if (before && before->getType() == ltRIPPLE_STATE)
{
trustlinesChanged_++;
iou_.before = before;
}
if (!isDelete && after && after->getType() == ltRIPPLE_STATE)
if (!isDelete && after->getType() == ltRIPPLE_STATE)
iou_.after = after;
if (before && before->getType() == ltMPTOKEN)
@@ -851,7 +875,7 @@ ValidClawback::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
mpt_.before = before;
}
if (!isDelete && after && after->getType() == ltMPTOKEN)
if (!isDelete && after->getType() == ltMPTOKEN)
mpt_.after = after;
}
@@ -1009,15 +1033,19 @@ ValidClawback::finalize(
//------------------------------------------------------------------------------
void
ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidPseudoAccounts::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
if (isDelete)
{
// Deletion is ignored
return;
}
if (after && after->getType() == ltACCOUNT_ROOT)
if (after->getType() == ltACCOUNT_ROOT)
{
bool const isPseudo = [&]() {
// isPseudoAccount checks that any of the pseudo-account fields are
@@ -1104,8 +1132,12 @@ ValidPseudoAccounts::finalize(
//------------------------------------------------------------------------------
void
NoModifiedUnmodifiableFields::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
NoModifiedUnmodifiableFields::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
if (isDelete || !before)
{
// Creation and deletion are ignored
@@ -1212,12 +1244,12 @@ NoModifiedUnmodifiableFields::finalize(
}
void
ValidAmounts::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const& after)
ValidAmounts::visitEntry(InvariantEntry const& entry)
{
if (!isDelete && after)
auto const isDelete = entry.isDelete();
auto const& after = entry.after();
if (!isDelete)
afterEntries_.push_back(after);
}
@@ -1243,21 +1275,14 @@ ValidAmounts::finalize(
}
void
ObjectHasPseudoAccount::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ObjectHasPseudoAccount::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
if (!isDelete)
return;
// Before should never be null when isDelete = true
if (!before)
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::ObjectHasPseudoAccount::visitEntry : deleted ledger entry missing before state");
return;
// LCOV_EXCL_STOP
}
switch (before->getType())
{
case ltAMM:

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/invariants/InvariantCheck.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <algorithm>
#include <array>
@@ -48,9 +49,11 @@ checkInvariantsHelper(
auto checkers = getInvariantChecks();
ctx.visit([&](uint256 const&, bool isDelete, SLE::const_ref before, SLE::const_ref after) {
InvariantEntry const entry{isDelete, before, after};
if (txCheck)
txCheck->get().visitEntry(isDelete, before, after);
(..., std::get<Is>(checkers).visitEntry(isDelete, before, after));
txCheck->get().visitEntry(entry);
(..., std::get<Is>(checkers).visitEntry(entry));
});
if (txCheck)

View File

@@ -14,36 +14,37 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <algorithm>
namespace xrpl {
void
ValidLoanBroker::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidLoanBroker::visitEntry(InvariantEntry const& entry)
{
if (after)
auto const& before = entry.before();
auto const& after = entry.after();
if (after->getType() == ltLOAN_BROKER)
{
if (after->getType() == ltLOAN_BROKER)
{
auto& broker = brokers_[after->key()];
broker.brokerBefore = before;
broker.brokerAfter = after;
}
else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID))
{
auto const& loanBrokerID = after->at(sfLoanBrokerID);
// create an entry if one doesn't already exist
brokers_.emplace(loanBrokerID, BrokerInfo{});
}
else if (after->getType() == ltRIPPLE_STATE)
{
lines_.emplace_back(after);
}
else if (after->getType() == ltMPTOKEN)
{
mpts_.emplace_back(after);
}
auto& broker = brokers_[after->key()];
broker.brokerBefore = before;
broker.brokerAfter = after;
}
else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID))
{
auto const& loanBrokerID = after->at(sfLoanBrokerID);
// create an entry if one doesn't already exist
brokers_.emplace(loanBrokerID, BrokerInfo{});
}
else if (after->getType() == ltRIPPLE_STATE)
{
lines_.emplace_back(after);
}
else if (after->getType() == ltMPTOKEN)
{
mpts_.emplace_back(after);
}
}

View File

@@ -9,20 +9,23 @@
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <cstdint>
namespace xrpl {
void
ValidLoan::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidLoan::visitEntry(InvariantEntry const& entry)
{
if (after && after->getType() == ltLOAN)
auto const& before = entry.before();
auto const& after = entry.after();
if (after->getType() == ltLOAN)
{
loans_.emplace_back(before, after);
}

View File

@@ -24,6 +24,7 @@
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <algorithm>
#include <array>
@@ -66,8 +67,12 @@ subtractMPTAmountDelta(std::int64_t delta, std::uint64_t amount)
} // namespace
void
ValidMPTIssuance::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidMPTIssuance::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
// The sfReferenceHolding tracking and the deleted-holding capture are
// only meaningful post-fixCleanup3_2_0 (the field is never set
// pre-amendment, and the holding-deletion rule does not apply).
@@ -75,7 +80,7 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_re
// on the hot path.
bool const fix320Enabled = isFeatureEnabled(fixCleanup3_2_0);
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
if (after->getType() == ltMPTOKEN_ISSUANCE)
{
if (isDelete)
{
@@ -102,7 +107,7 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_re
}
}
if (after && after->getType() == ltMPTOKEN)
if (after->getType() == ltMPTOKEN)
{
if (isDelete)
{
@@ -121,7 +126,7 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_re
// Capture deleted RippleState SLEs so finalize() can verify none of
// them were owned by a vault pseudo-account outside VaultDelete.
if (fix320Enabled && isDelete && after && after->getType() == ltRIPPLE_STATE)
if (fix320Enabled && isDelete && after->getType() == ltRIPPLE_STATE)
deletedHoldings_.push_back(after);
}
@@ -416,8 +421,11 @@ ValidMPTIssuance::finalize(
}
void
ValidMPTBalanceChanges::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
ValidMPTBalanceChanges::visitEntry(InvariantEntry const& entry)
{
auto const& before = entry.before();
auto const& after = entry.after();
if (overflow_)
return;
@@ -466,15 +474,11 @@ ValidMPTBalanceChanges::visitEntry(bool, SLE::const_ref before, SLE::const_ref a
if (before && !update(*before, Order::Before))
return;
if (after)
if (after->getType() == ltMPTOKEN_ISSUANCE)
{
if (after->getType() == ltMPTOKEN_ISSUANCE)
{
overflow_ = (*after)[sfOutstandingAmount] > maxMPTAmount(*after);
}
if (!update(*after, Order::After))
return;
overflow_ = (*after)[sfOutstandingAmount] > maxMPTAmount(*after);
}
update(*after, Order::After);
}
bool
@@ -544,11 +548,12 @@ ValidMPTBalanceChanges::finalize(
}
void
ValidConfidentialMPToken::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
ValidConfidentialMPToken::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
// Helper to get MPToken Issuance ID safely
auto const getMptID = [](std::shared_ptr<SLE const> const& sle) -> uint192 {
if (!sle)
@@ -581,7 +586,7 @@ ValidConfidentialMPToken::visitEntry(
}
}
if (after && after->getType() == ltMPTOKEN)
if (after->getType() == ltMPTOKEN)
{
uint192 const id = getMptID(after);
auto& change = changes_[id];
@@ -633,7 +638,7 @@ ValidConfidentialMPToken::visitEntry(
change.outstandingDelta, before->getFieldU64(sfOutstandingAmount));
}
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
if (after->getType() == ltMPTOKEN_ISSUANCE)
{
uint192 const id = getMptID(after);
auto& change = changes_[id];
@@ -653,7 +658,7 @@ ValidConfidentialMPToken::visitEntry(
change.badCOA = true;
}
if (before && after && before->getType() == ltMPTOKEN && after->getType() == ltMPTOKEN)
if (before && before->getType() == ltMPTOKEN && after->getType() == ltMPTOKEN)
{
uint192 const id = getMptID(after);
@@ -799,11 +804,12 @@ ValidConfidentialMPToken::finalize(
}
void
ValidMPTTransfer::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
ValidMPTTransfer::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
// Record the before/after MPTAmount for each (issuanceID, account) pair
// so finalize() can determine whether a transfer actually occurred.
auto update = [&](SLE const& sle, bool isBefore) {
@@ -830,8 +836,7 @@ ValidMPTTransfer::visitEntry(
if (before)
update(*before, true);
if (after)
update(*after, false);
update(*after, false);
}
bool

View File

@@ -18,6 +18,7 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/nftPageMask.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <cstddef>
#include <optional>
@@ -25,13 +26,16 @@
namespace xrpl {
void
ValidNFTokenPage::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidNFTokenPage::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
static constexpr uint256 const& kPageBits = nft::kPageMask;
static constexpr uint256 kAccountBits = ~kPageBits;
if ((before && before->getType() != ltNFTOKEN_PAGE) ||
(after && after->getType() != ltNFTOKEN_PAGE))
if ((before && before->getType() != ltNFTOKEN_PAGE) || after->getType() != ltNFTOKEN_PAGE)
return;
auto check = [this, isDelete](SLE::const_ref sle) {
@@ -107,10 +111,9 @@ ValidNFTokenPage::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_re
}
}
if (after)
check(after);
check(after);
if (!isDelete && before && after)
if (!isDelete && before)
{
// If the NFTokenPage
// 1. Has a NextMinPage field in before, but loses it in after, and
@@ -183,15 +186,18 @@ ValidNFTokenPage::finalize(
//------------------------------------------------------------------------------
void
NFTokenCountTracking::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
NFTokenCountTracking::visitEntry(InvariantEntry const& entry)
{
auto const& before = entry.before();
auto const& after = entry.after();
if (before && before->getType() == ltACCOUNT_ROOT)
{
beforeMintedTotal_ += (*before)[~sfMintedNFTokens].value_or(0);
beforeBurnedTotal_ += (*before)[~sfBurnedNFTokens].value_or(0);
}
if (after && after->getType() == ltACCOUNT_ROOT)
if (after->getType() == ltACCOUNT_ROOT)
{
afterMintedTotal_ += (*after)[~sfMintedNFTokens].value_or(0);
afterBurnedTotal_ += (*after)[~sfBurnedNFTokens].value_or(0);

View File

@@ -15,16 +15,15 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
namespace xrpl {
void
ValidPermissionedDEX::visitEntry(bool isDelete, SLE::const_ref, SLE::const_ref after)
ValidPermissionedDEX::visitEntry(InvariantEntry const& entry)
{
// Post-fixCleanup3_4_0: skip when after is null (defensive).
// Pre-amendment: original after-only path via the `if (after && ...)` checks below.
if (isFeatureEnabled(fixCleanup3_4_0) && !after)
return;
auto const isDelete = entry.isDelete();
auto const& after = entry.after();
auto trackDomain = [this, isDelete](uint256 const& domain) {
domainsOld_.insert(domain);
@@ -32,13 +31,13 @@ ValidPermissionedDEX::visitEntry(bool isDelete, SLE::const_ref, SLE::const_ref a
domains_.insert(domain);
};
if (after && after->getType() == ltDIR_NODE)
if (after->getType() == ltDIR_NODE)
{
if (after->isFieldPresent(sfDomainID))
trackDomain(after->getFieldH256(sfDomainID));
}
if (after && after->getType() == ltOFFER)
if (after->getType() == ltOFFER)
{
if (after->isFieldPresent(sfDomainID))
{

View File

@@ -15,17 +15,22 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <vector>
namespace xrpl {
void
ValidPermissionedDomain::visitEntry(bool isDel, SLE::const_ref before, SLE::const_ref after)
ValidPermissionedDomain::visitEntry(InvariantEntry const& entry)
{
auto const isDel = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
if (before && before->getType() != ltPERMISSIONED_DOMAIN)
return;
if (after && after->getType() != ltPERMISSIONED_DOMAIN)
if (after->getType() != ltPERMISSIONED_DOMAIN)
return;
auto check = [isDel](std::vector<SleStatus>& sleStatus, SLE::const_ref sle) {
@@ -54,8 +59,7 @@ ValidPermissionedDomain::visitEntry(bool isDel, SLE::const_ref before, SLE::cons
sleStatus.emplace_back(ss);
};
if (after)
check(sleStatus_, after);
check(sleStatus_, after);
}
bool

View File

@@ -10,6 +10,7 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <cstdint>
@@ -17,8 +18,12 @@ namespace xrpl {
// Add new sponsorship-related invariants implementations
void
SponsorshipOwnerCountsMatch::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
SponsorshipOwnerCountsMatch::visitEntry(InvariantEntry const& entry)
{
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
auto getSponsored = [](SLE::const_ref sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoredOwnerCount);
@@ -116,8 +121,11 @@ SponsorshipOwnerCountsMatch::finalize(
}
void
SponsorshipAccountCountMatchesField::visitEntry(bool, SLE::const_ref before, SLE::const_ref after)
SponsorshipAccountCountMatchesField::visitEntry(InvariantEntry const& entry)
{
auto const& before = entry.before();
auto const& after = entry.after();
auto getSponsoringAccountCount = [](SLE::const_ref sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringAccountCount);

View File

@@ -22,6 +22,7 @@
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <algorithm>
#include <cstdint>
@@ -83,14 +84,11 @@ ValidVault::Shares::make(SLE const& from)
}
void
ValidVault::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after)
ValidVault::visitEntry(InvariantEntry const& entry)
{
// If `before` is empty, this means an object is being created, in which
// case `isDelete` must be false. Otherwise `before` and `after` are set and
// `isDelete` indicates whether an object is being deleted or modified.
XRPL_ASSERT(
after != nullptr && (before != nullptr || !isDelete),
"xrpl::ValidVault::visitEntry : some object is available");
auto const isDelete = entry.isDelete();
auto const& before = entry.before();
auto const& after = entry.after();
// Number balanceDelta will capture the difference (delta) between "before"
// state (zero if created) and "after" state (zero if destroyed), and
@@ -144,7 +142,7 @@ ValidVault::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref afte
}
}
if (!isDelete && after)
if (!isDelete)
{
switch (after->getType())
{

View File

@@ -71,7 +71,10 @@ class InvariantsAMM_test : public InvariantsBase
ValidAMM invariant;
if (deletedLPBalance)
invariant.visitEntry(true, makeAMM(*deletedLPBalance), nullptr);
{
auto const sleAMM = makeAMM(*deletedLPBalance);
invariant.visitEntry(InvariantEntry{true, sleAMM, sleAMM});
}
bool const actual = invariant.finalize(
STTx{txType, [](STObject&) {}}, result, XRPAmount{}, *env.current(), jlog);

View File

@@ -40,6 +40,7 @@
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/applySteps.h>
#include <xrpl/tx/invariants/InvariantEntry.h>
#include <xrpl/tx/invariants/InvariantRunner.h>
#include <array>
@@ -49,6 +50,7 @@
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -1183,6 +1185,39 @@ class InvariantsMisc_test : public InvariantsBase
}
}
void
testInvariantEntry()
{
testcase << "invariant entry validation";
SLE::const_pointer const null;
SLE::const_pointer const sle = std::make_shared<SLE>(keylet::amendments());
InvariantEntry const created{false, null, sle};
BEAST_EXPECT(!created.isDelete());
BEAST_EXPECT(!created.before());
BEAST_EXPECT(created.after() == sle);
auto const expectLogicError = [&](bool isDelete,
SLE::const_ref before,
SLE::const_ref after,
std::string_view message) {
try
{
InvariantEntry const entry{isDelete, before, after};
(void)entry;
BEAST_EXPECT(false);
}
catch (std::logic_error const& ex)
{
BEAST_EXPECT(std::string_view{ex.what()}.contains(message));
}
};
expectLogicError(false, null, null, "after is never null");
expectLogicError(true, null, sle, "deleted entry missing before state");
}
void
testTxCheckException()
{
@@ -1205,7 +1240,7 @@ class InvariantsMisc_test : public InvariantsBase
}
void
visitEntry(bool, SLE::const_ref, SLE::const_ref) override
visitEntry(InvariantEntry const&) override
{
if (throwFrom == ThrowFrom::VisitEntry)
throw std::runtime_error("test-injected visitEntry exception");
@@ -1269,7 +1304,7 @@ class InvariantsMisc_test : public InvariantsBase
struct FailingTxInvariantCheck : TxInvariantCheck
{
void
visitEntry(bool, SLE::const_ref, SLE::const_ref) override
visitEntry(InvariantEntry const&) override
{
}
@@ -1323,6 +1358,7 @@ class InvariantsMisc_test : public InvariantsBase
testInvariantOverwrite(all_ - fixCleanup3_1_3);
testObjectHasPseudoAccount();
testSponsorship();
testInvariantEntry();
testTxCheckException();
testTxCheckFinalizeFalse();
}

View File

@@ -640,83 +640,67 @@ class InvariantsPermissioned_test : public InvariantsBase
{
using namespace test::jtx;
testcase << "PermissionedDEX null after";
testcase << "PermissionedDEX deleted offer after";
// Tx is OfferCreate on pd2. Tracking pd1 fails the invariant iff that
// domain lands in the set finalize consults. after == null is never
// tracked (pre-340: after-only; post-340: early return) — same result,
// both sides are coverage/regression that we do not fall back to before.
auto const check = [this](
FeatureBitset features,
bool const afterIsNull,
bool const isDelete,
bool const expectInvariantFailure) {
Env env(*this, features);
// domain lands in the set finalize consults.
auto const check =
[this](FeatureBitset features, bool const isDelete, bool const expectInvariantFailure) {
Env env(*this, features);
Account const a1{"A1"};
Account const a2{"A2"};
env.fund(XRP(1000), a1, a2);
env.close();
Account const a1{"A1"};
Account const a2{"A2"};
env.fund(XRP(1000), a1, a2);
env.close();
[[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env, a1, a2);
[[maybe_unused]] auto [seq2, pd2] = createPermissionedDomainEnv(env, a1, a2);
env.close();
[[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env, a1, a2);
[[maybe_unused]] auto [seq2, pd2] = createPermissionedDomainEnv(env, a1, a2);
env.close();
auto sleOffer =
std::make_shared<SLE>(keylet::offer(a2.id(), SeqProxy::rawSequence(10)));
sleOffer->setAccountID(sfAccount, a2);
sleOffer->setFieldAmount(sfTakerPays, a1["USD"](10));
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
sleOffer->setFieldH256(sfDomainID, pd1);
auto sleOffer =
std::make_shared<SLE>(keylet::offer(a2.id(), SeqProxy::rawSequence(10)));
sleOffer->setAccountID(sfAccount, a2);
sleOffer->setFieldAmount(sfTakerPays, a1["USD"](10));
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
sleOffer->setFieldH256(sfDomainID, pd1);
CurrentTransactionRulesGuard const rulesGuard(env.current()->rules());
CurrentTransactionRulesGuard const rulesGuard(env.current()->rules());
ValidPermissionedDEX invariant;
if (afterIsNull)
{
// Defensive path: after is null. Must not fall back to before.
invariant.visitEntry(isDelete, sleOffer, nullptr);
}
else
{
// Normal / real-erase path: after is the offer on pd1.
invariant.visitEntry(isDelete, nullptr, sleOffer);
}
ValidPermissionedDEX invariant;
SLE::const_pointer const before = isDelete ? sleOffer : nullptr;
invariant.visitEntry(InvariantEntry{isDelete, before, sleOffer});
STTx const tx{ttOFFER_CREATE, [&pd2, &a1](STObject& tx) {
tx.setFieldH256(sfDomainID, pd2);
tx.setFieldAmount(sfTakerPays, a1["USD"](10));
tx.setFieldAmount(sfTakerGets, XRP(1));
}};
STTx const tx{ttOFFER_CREATE, [&pd2, &a1](STObject& tx) {
tx.setFieldH256(sfDomainID, pd2);
tx.setFieldAmount(sfTakerPays, a1["USD"](10));
tx.setFieldAmount(sfTakerGets, XRP(1));
}};
test::StreamSink sink{beast::Severity::Warning};
beast::Journal const jlog{sink};
bool const passed =
invariant.finalize(tx, tesSUCCESS, XRPAmount{}, *env.current(), jlog);
BEAST_EXPECT(passed != expectInvariantFailure);
if (expectInvariantFailure)
{
BEAST_EXPECT(sink.messages().str().contains("transaction consumed wrong domains"));
}
else
{
BEAST_EXPECT(sink.messages().str().empty());
}
};
test::StreamSink sink{beast::Severity::Warning};
beast::Journal const jlog{sink};
bool const passed =
invariant.finalize(tx, tesSUCCESS, XRPAmount{}, *env.current(), jlog);
BEAST_EXPECT(passed != expectInvariantFailure);
if (expectInvariantFailure)
{
BEAST_EXPECT(
sink.messages().str().contains("transaction consumed wrong domains"));
}
else
{
BEAST_EXPECT(sink.messages().str().empty());
}
};
auto const pre = all_ - fixCleanup3_4_0;
auto const post = all_;
// after == null: not tracked
check(pre, true, true, false);
check(post, true, true, false);
// after == offer on pd1
// pre-340: domainsOld_ (delete still inserted) → fail
check(pre, false, true, true);
check(pre, true, true);
// post-340: isDelete → only domainsOld_ → pass; !isDelete → domains_ → fail
check(post, false, true, false);
check(post, false, false, true);
check(post, true, false);
check(post, false, true);
}
void
@@ -810,7 +794,8 @@ class InvariantsPermissioned_test : public InvariantsBase
view.rawInsert(makeRootPage(rootDir, directoryQuality + 1));
ValidBookDirectory invariant;
invariant.visitEntry(false, nullptr, makeChildPage(rootDir));
auto const childPage = makeChildPage(rootDir);
invariant.visitEntry(InvariantEntry{false, nullptr, childPage});
test::StreamSink sink{beast::Severity::Warning};
beast::Journal const jlog{sink};
@@ -840,7 +825,7 @@ class InvariantsPermissioned_test : public InvariantsBase
{
// add
ValidBookDirectory invariant;
invariant.visitEntry(false, nullptr, badRoot);
invariant.visitEntry(InvariantEntry{false, nullptr, badRoot});
BEAST_EXPECT(
!invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog));
@@ -848,7 +833,7 @@ class InvariantsPermissioned_test : public InvariantsBase
{
// modify (without changing the sfRootIndex)
ValidBookDirectory invariant;
invariant.visitEntry(false, badRoot, badRoot);
invariant.visitEntry(InvariantEntry{false, badRoot, badRoot});
BEAST_EXPECT(
invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog));
@@ -860,7 +845,7 @@ class InvariantsPermissioned_test : public InvariantsBase
childAfter->setFieldH256(sfRootIndex, missingRootDir.key);
ValidBookDirectory invariant;
invariant.visitEntry(false, childBefore, childAfter);
invariant.visitEntry(InvariantEntry{false, childBefore, childAfter});
test::StreamSink missingRootSink{beast::Severity::Warning};
beast::Journal const missingRootJlog{missingRootSink};
@@ -875,7 +860,7 @@ class InvariantsPermissioned_test : public InvariantsBase
BEAST_EXPECT(!view.exists(rootDir));
ValidBookDirectory invariant;
invariant.visitEntry(true, badRoot, badRoot);
invariant.visitEntry(InvariantEntry{true, badRoot, badRoot});
BEAST_EXPECT(
invariant.finalize(makeOfferCreateTx(), tesSUCCESS, XRPAmount{}, view, jlog));
}