58035 sc high killswitch early return in strategy causes vault to adapter asset leakage mis accounting and deallocation dos

Submitted on Oct 30th 2025 at 06:43:50 UTC by @IronsideSec for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58035

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/MYTStrategy.sol

  • Impacts:

    • Permanent freezing of funds

Description

Brief/Intro

When killSwitch is enabled on strategies inheriting MYTStrategy, allocate() returns early without refunding tokens. Because the vault transfers assets to the adapter before invoking allocate(), assets are stranded on the adapter, realAssets() excludes them, allocations remain unchanged, and subsequent deallocations revert (no approvals). This creates an immediate asset mismatch, eventual realized loss on next accrual, and a denial-of-service on exiting via deallocate.

Vulnerability Details

  • The vault sends assets to the adapter before calling allocate():

572:589:lib/vault-v2/src/VaultV2.sol
        SafeERC20Lib.safeTransfer(asset, adapter, assets);
        (bytes32[] memory ids, int256 change) = IAdapter(adapter).allocate(data, assets, msg.sig, msg.sender);
        // ... caps checks using 'change' ...
  • Strategy allocate() returns early under killSwitch, but does not refund:

  • Strategy deallocate() also early-returns under killSwitch, never setting approvals required by the vault’s subsequent transferFrom:

  • The example strategy reports only ERC4626 position as realAssets() and ignores idle tokens stranded on the adapter:

  • Proof-of-concept (non-reverting) shows:

    • Vault calls allocate() → assets move to adapter.

    • killSwitch early-return → allocation unchanged.

    • Adapter holds raw USDC; realAssets() remains 0.

    • Vault’s token balance decreases exactly by the allocated amount.

Why this is dangerous:

  • Funds are effectively stuck on the adapter until owner action, and vault exits via deallocate() revert due to missing approvals.

  • realAssets() excludes the stranded tokens, so the next accrueInterest() realizes a loss (share price drop) despite funds not actually being deployed.

  • Allocations and caps remain inconsistent (no increase in allocation despite asset movement), undermining vault risk controls.

Impact Details

  • Loss realization: Next accrual sets totalAssets to a value excluding stranded tokens → share price decreases, harming depositors.

  • Exit DOS: Vault deallocate() reverts (adapter didn’t approve), preventing liquidity retrieval during emergency.

  • Allocation/cap inconsistency: Allocations unchanged while assets moved, weakening risk caps and accounting invariants.

  • Governance/safety friction: Enabling killSwitch (intended for emergencies) paradoxically leaks funds to the adapter and blocks emergency exits.

Severity: High.

  • In allocate() when killSwitch == true, refund immediately to the vault and return zero change:

  • In deallocate() when killSwitch == true, approve the vault to pull and return the correct negative change (or avoid early-return and perform approvals first):

  • Count idle tokens in realAssets() to avoid loss realization when funds are temporarily held idle on the adapter (strategy-specific):

References

  • Vault transfers then calls allocate:

  • Strategy allocate() killSwitch early-return:

  • Strategy deallocate() killSwitch early-return:

  • Strategy realAssets() excludes idle:

  • PoC test (non-reverting, calls VaultV2.allocate()):

https://gist.github.com/IronsideSec/52387980717c94bdd092cfa847883fce

Proof of Concept

Proof of Concept

  • create a file src/test/PermissionedProxy.t.sol and paste the POC from gist : https://gist.github.com/IronsideSec/52387980717c94bdd092cfa847883fce

  • then run forge t --mt test_killSwitch_allocate_leaks_assets_and_misaccounts_no_revert -vvvv

Was this helpful?