fixing Freeze tests

This commit is contained in:
Valentin Balaschenko
2026-01-05 13:50:59 +00:00
parent d58d49a754
commit 59f5dfa9f1
5 changed files with 70 additions and 23 deletions

View File

@@ -299,6 +299,22 @@ checkDeepFrozen(
asset.value());
}
[[nodiscard]] inline bool
isAnyDeepOrIndividuallyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
int depth = 0)
{
for (auto const& account : accounts)
{
if (isDeepFrozen(view, account, asset, depth) ||
isIndividualFrozen(view, account, asset))
return true;
}
return false;
}
[[nodiscard]] bool
isLPTokenFrozen(
ReadView const& view,

View File

@@ -331,7 +331,8 @@ isVaultPseudoAccountFrozen(
// LCOV_EXCL_STOP
}
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
return isAnyDeepOrIndividuallyFrozen(
view, {issuer, account}, vault->at(sfAsset), depth + 1);
}
bool
@@ -1435,6 +1436,16 @@ doWithdraw(
// LCOV_EXCL_STOP
}
if (dstAcct != senderAcct && dstAcct != amount.getIssuer() &&
senderAcct != amount.getIssuer())
{
if (isGlobalFrozen(view, amount.getIssuer()))
{
return view.rules().enabled(featureLendingProtocol) ? tecPATH_DRY
: tecFROZEN;
}
}
// Move the funds directly from the broker's pseudo-account to the
// dstAcct
return accountSend(
@@ -1456,7 +1467,10 @@ addEmptyHolding(
auto const& issuerId = issue.getIssuer();
auto const& currency = issue.currency;
if (isGlobalFrozen(view, issuerId))
return tecFROZEN; // LCOV_EXCL_LINE
{
return view.rules().enabled(featureLendingProtocol) ? tecPATH_DRY
: tecFROZEN;
}
auto const& srcId = issuerId;
auto const& dstId = accountID;

View File

@@ -2210,8 +2210,15 @@ class Freeze_test : public beast::unit_test::suite
env_post(coverDeposit(issuer, brokerKeylet.key, USD(20)));
env_post.close();
// Withdrawal to holder should fail due to global freeze
tx = coverWithdraw(owner, brokerKeylet.key, USD(15));
tx[sfDestination] = holder.human();
env_post(tx, ter(tecPATH_DRY));
// Withdraw to issuer instead (issuer is exempt from global freeze)
tx = coverWithdraw(owner, brokerKeylet.key, USD(15));
tx[sfDestination] = issuer.human();
env_post(tx);
env_post.close();
@@ -2223,6 +2230,24 @@ class Freeze_test : public beast::unit_test::suite
env_post(del(owner, brokerKeylet.key));
env_post.close();
// Withdraw remaining vault balances before deleting
// Holder has 40 shares (deposited 50, withdrew 10)
tx = vault.withdraw(
{.depositor = holder,
.id = vaultKeylet.key,
.amount = USD(40)});
tx[sfDestination] = issuer.human();
env_post(tx);
env_post.close();
// Issuer has 20 shares (deposited 20)
env_post(vault.withdraw(
{.depositor = issuer,
.id = vaultKeylet.key,
.amount = USD(20)}));
env_post.close();
env_post(vault.del({.owner = owner, .id = vaultKeylet.key}));
}
}

View File

@@ -96,12 +96,12 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType))
return ter;
// Check for freezes, unless sending directly to the issuer
// Skip source freeze check here - let doWithdraw handle it.
// This allows global freeze cases to properly return tecPATH_DRY instead of
// tecFROZEN. Check for destination deep freeze, unless sending directly to
// the issuer
if (dstAcct != vaultAsset.getIssuer())
{
// Cannot send a frozen Asset
if (auto const ret = checkFrozen(ctx.view, pseudoAccountID, vaultAsset))
return ret;
// Destination account cannot receive if asset is deep frozen
if (auto const ret = checkDeepFrozen(ctx.view, dstAcct, vaultAsset))
return ret;
@@ -126,19 +126,16 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
if ((coverAvail - amount) < minimumCover)
return tecINSUFFICIENT_FUNDS;
// Allow returning frozen/locked assets to issuer for both IOUs and MPTs.
// When withdrawing to the issuer, ignore freeze since frozen IOUs and
// locked MPTs can both be sent back to their issuer.
FreezeHandling const freezeHandling =
(dstAcct == vaultAsset.getIssuer() || account == vaultAsset.getIssuer())
? FreezeHandling::fhIGNORE_FREEZE
: FreezeHandling::fhZERO_IF_FROZEN;
// Check if the pseudo-account has sufficient funds.
// Use fhIGNORE_FREEZE because we want to allow the transaction to proceed
// to doApply where doWithdraw will perform the proper global freeze check
// and return tecPATH_DRY if appropriate. If we used fhZERO_IF_FROZEN here,
// we would return tecINSUFFICIENT_FUNDS prematurely.
if (accountHolds(
ctx.view,
pseudoAccountID,
vaultAsset,
freezeHandling,
FreezeHandling::fhIGNORE_FREEZE,
AuthHandling::ahZERO_IF_UNAUTHORIZED,
ctx.j) < amount)
return tecINSUFFICIENT_FUNDS;

View File

@@ -79,14 +79,9 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
!isTesSuccess(ter))
return ter;
// Cannot withdraw from a Vault an Asset frozen for the destination account.
// Skip freeze check if either party is the issuer (applies to both IOUs and
// MPTs).
if (dstAcct != vaultAsset.getIssuer() && account != vaultAsset.getIssuer())
{
if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
return ret;
}
// Skip destination asset freeze check here - let doWithdraw handle it.
// This allows global freeze cases to properly return tecPATH_DRY instead
// of tecFROZEN.
// Cannot return shares to the vault, if the underlying asset was frozen for
// the submitter. Skip freeze check if either party is the issuer (applies