fix: removes redundant global freeze checks

This commit is contained in:
Vito
2026-06-23 14:14:29 +02:00
parent 1a3aed2b9f
commit 884d1591a7
3 changed files with 66 additions and 24 deletions

View File

@@ -72,6 +72,9 @@ 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

View File

@@ -79,6 +79,14 @@ checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset cons
return tesSUCCESS;
}
TER
checkIndividualDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
if (isDeepFrozen(view, account, asset))
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
return tesSUCCESS;
}
bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth)
{
@@ -176,25 +184,29 @@ checkWithdrawFreeze(
isPseudoAccount(view, srcAcct),
"xrpl::checkWithdrawFreeze : source must be a pseudo-account");
// No matter what, funds can be sent to the issuer
// Funds can always be sent to the issuer
if (dstAcct == asset.getIssuer())
return tesSUCCESS;
// If the asset is globally frozen, other checks are redundant
if (auto const ret = checkGlobalFrozen(view, asset))
return ret;
// The transfer is from Submitter to Destination via Source (pseudo-account)
// Both Source and Submitter must not be frozen to allow sending funds
if (auto const ret = checkFrozen(view, srcAcct, asset))
if (auto const ret = checkIndividualFrozen(view, srcAcct, asset))
return ret;
// Check submitter's individual freeze only when Submitter != Destination (a regular freeze
// should not block self-withdrawal).
if (submitterAcct != dstAcct)
{
if (auto const ret = checkFrozen(view, submitterAcct, asset))
if (auto const ret = checkIndividualFrozen(view, submitterAcct, asset))
return ret;
}
// The destination account must not be deep frozen to receive the funds
return checkDeepFrozen(view, dstAcct, asset);
return checkIndividualDeepFrozen(view, dstAcct, asset);
}
[[nodiscard]] TER
@@ -208,12 +220,22 @@ checkDepositFreeze(
isPseudoAccount(view, dstAcct),
"xrpl::checkDepositFreeze : destination must be a pseudo-account");
if (auto const ret = checkFrozen(view, srcAcct, asset))
// An Issuer cannot deposit when:
// 1. Asset is globally frozen
// 2. The trustline of the pseudo-account is frozen
if (auto const ret = checkGlobalFrozen(view, asset))
return ret;
if (srcAcct != asset.getIssuer())
{
if (auto const ret = checkIndividualFrozen(view, srcAcct, asset))
return ret;
}
// Unlike regular accounts, pseudo-accounts cannot receive deposits under a regular freeze
// because those funds cannot be later withdrawn
return checkFrozen(view, dstAcct, asset);
return checkIndividualFrozen(view, dstAcct, asset);
}
//------------------------------------------------------------------------------

View File

@@ -7462,24 +7462,41 @@ class MPToken_test : public beast::unit_test::Suite
// MPTLock is set
usd.set({.flags = tfMPTLock});
auto const fix330 = env.current()->rules().enabled(fixCleanup3_3_0);
// carol can't withdraw the locked token; post-fixCleanup3_3_0 the
// issuer can still redeem its own locked token.
for (auto const& account : {carol, gw})
{
auto const lockErr =
(fix330 && account == gw) ? Ter(tesSUCCESS) : Ter(tecLOCKED);
amm.withdraw(
{.account = account,
.asset1Out = usd(1),
.asset2Out = eur(1),
.err = lockErr});
amm.withdraw({.account = account, .tokens = 1'000, .err = lockErr});
// can single withdraw another asset
amm.withdraw(
{.account = account,
.asset1Out = eur(1),
.assets = std::make_pair(eur, usd)});
}
// carol can't withdraw the locked token (any withdrawal type)
amm.withdraw(
{.account = carol,
.asset1Out = usd(1),
.asset2Out = eur(1),
.err = Ter(tecLOCKED)});
amm.withdraw({.account = carol, .tokens = 1'000, .err = Ter(tecLOCKED)});
// can single withdraw the non-locked asset
amm.withdraw(
{.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)});
// post-fixCleanup3_3_0 the issuer can redeem even when locked.
// Each successful withdrawal burns LP tokens, so replenish between
// each type to keep gw's LP balance stable.
auto const gwLockErr = fix330 ? Ter(tesSUCCESS) : Ter(tecLOCKED);
auto const replenish = [&](IOUAmount tokens) {
usd.set({.flags = tfMPTUnlock});
amm.deposit({.account = gw, .tokens = tokens});
usd.set({.flags = tfMPTLock});
};
amm.withdraw(
{.account = gw, .asset1Out = usd(1), .asset2Out = eur(1), .err = gwLockErr});
if (fix330)
replenish(IOUAmount{1});
amm.withdraw({.account = gw, .tokens = 1'000, .err = gwLockErr});
if (fix330)
replenish(IOUAmount{1'000});
// can single withdraw the non-locked asset
amm.withdraw(
{.account = gw, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)});
usd.set({.flags = tfMPTUnlock});
// MPTRequireAuth is set