refactor: extract amendmentCombinations; document vault-share freeze checks

- Move amendmentCombinations algorithm to jtx::amendmentCombinations in
  test/jtx/utility.h+cpp; it was copy-pasted into both AMM_test and
  AMMMPT_test. AMM_test retains a one-liner wrapper that seeds from its
  local testableAmendments() (strips SAV/Lending).
- Document the isVaultPseudoAccountFrozen sub-step in the Doxygen for
  checkWithdrawFreeze and checkDepositFreeze; the MPT-only transitive
  freeze check occurs between the listed steps 1 and 2 in each function.
- Expand the IgnoreFreeze comment in VaultWithdraw::doApply to explain
  why preclaim validation makes the flag safe to use post-fix330.
This commit is contained in:
Vito
2026-06-24 11:12:17 +02:00
parent f3d15671ec
commit b2b240e9d5
5 changed files with 43 additions and 14 deletions

View File

@@ -72,9 +72,6 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const&
[[nodiscard]] TER
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] TER
checkIndividualDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
@@ -149,6 +146,9 @@ checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& ass
* Otherwise checks, in order:
* 1. checkFrozen(sourceAcct, asset) — the pseudo-account's trustline /
* global freeze must not block sending.
* 1a. For MPT assets: isVaultPseudoAccountFrozen(sourceAcct, asset) —
* the pseudo-account's vault share must not be transitively frozen
* via its underlying asset.
* 2. checkFrozen(submitterAcct, asset) — skipped when submitter == dst
* (self-withdrawal); a regular freeze should not prevent recovering
* one's own funds.
@@ -176,6 +176,9 @@ checkWithdrawFreeze(
* Checks, in order:
* 1. checkFrozen(srcAcct, asset) — the depositor must not be frozen
* (global or individual) for the asset.
* 1a. For MPT assets: isVaultPseudoAccountFrozen(dstAcct, asset) —
* the pseudo-account's vault share must not be transitively frozen
* via its underlying asset.
* 2. checkFrozen(dstAcct, asset) — the pseudo-account must not be
* frozen for the asset. Unlike regular accounts, pseudo-accounts
* cannot receive deposits under a regular freeze because the

View File

@@ -272,7 +272,10 @@ VaultWithdraw::doApply()
return tecPATH_DRY;
}
// Post-fixCleanup3_3_0: preclaim already handles freeze checks
// Post-fixCleanup3_3_0: preclaim already validated all freeze conditions
// (checkWithdrawFreeze), so IgnoreFreeze avoids a redundant check that
// would incorrectly return zero for vault pseudo-accounts whose shares
// are frozen via a transitively frozen underlying asset.
auto const freezeHandling = view().rules().enabled(fixCleanup3_3_0)
? FreezeHandling::IgnoreFreeze
: FreezeHandling::ZeroIfFrozen;

View File

@@ -19,6 +19,7 @@
#include <test/jtx/ter.h>
#include <test/jtx/trust.h>
#include <test/jtx/txflags.h>
#include <test/jtx/utility.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
@@ -86,19 +87,11 @@ private:
return jtx::testableAmendments() - featureSingleAssetVault - featureLendingProtocol;
}
// All 2^N permutations of testableAmendments() with each subset of the
// given features excluded.
// Seed from the local testableAmendments() which strips SAV and Lending.
static std::vector<FeatureBitset>
amendmentCombinations(std::initializer_list<uint256> features)
{
std::vector<FeatureBitset> result{testableAmendments()};
for (auto const& f : features)
{
auto const n = result.size();
for (std::size_t i = 0; i < n; ++i)
result.push_back(result[i] - f);
}
return result;
return jtx::amendmentCombinations(features, testableAmendments());
}
void

View File

@@ -1,6 +1,7 @@
#include <test/jtx/utility.h>
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <xrpld/rpc/RPCCall.h>
@@ -101,4 +102,17 @@ cmdToJSONRPC(std::vector<std::string> const& args, beast::Journal j, unsigned in
return jv;
}
std::vector<FeatureBitset>
amendmentCombinations(std::initializer_list<uint256> features, FeatureBitset seed)
{
std::vector<FeatureBitset> result{seed};
for (auto const& f : features)
{
auto const n = result.size();
for (std::size_t i = 0; i < n; ++i)
result.push_back(result[i] - f);
}
return result;
}
} // namespace xrpl::test::jtx

View File

@@ -1,12 +1,16 @@
#pragma once
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <xrpl/json/json_value.h>
#include <xrpl/ledger/Ledger.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STObject.h>
#include <initializer_list>
#include <stdexcept>
#include <vector>
namespace xrpl::test::jtx {
@@ -51,4 +55,16 @@ fillSeq(json::Value& jv, ReadView const& view);
json::Value
cmdToJSONRPC(std::vector<std::string> const& args, beast::Journal j, unsigned int apiVersion);
/**
* Returns all 2^N permutations of a seed FeatureBitset with each subset of
* the given features excluded. The seed is included as the first element.
*
* Useful for running a test over every combination of optional amendments
* so that each case is exercised both with and without each feature.
*/
std::vector<FeatureBitset>
amendmentCombinations(
std::initializer_list<uint256> features,
FeatureBitset seed = testableAmendments());
} // namespace xrpl::test::jtx