57041 sc high deallocation accounting mismatch between vault and adapter
Submitted on Oct 22nd 2025 at 22:40:22 UTC by @nem0thefinder for Audit Comp | Alchemix V3
Report ID: #57041
Report Type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/MYTStrategy.sol
Impacts:
Smart contract unable to operate due to lack of token funds
Permanent freezing of funds
Description
Summary
The adapter's deallocate function returns the requested deallocation amount instead of the actual amount received from the external strategy, causing the vault to pull more funds than the adapter actually withdrew. This creates an accounting mismatch where the allocation cap decreases by the requested amount, but the adapter must cover slippage losses from its own balance.
Description
Note!!
This apply for all strategies When deallocating funds from an external strategy:
The vault calls
deallocateInternalrequesting withdrawal ofassetsamount (e.g., 1000 USDC)The adapter withdraws from the external strategy via
_deallocate(amount)The external strategy may return less than requested due to slippage (e.g., request 1000 USDC, receive 980 USDC)
The vault correctly updates the allocation cap by the requested amount (decreases by 1000 USDC)
The adapter emits a loss event but still returns
withdrawReturn = amount(the full 1000 USDC)Then there are two Execution Paths
The require statement that check the adapter balance will revert if there's no balance cover the amount (DoS deallocation)
the require statement pass since the adapter balance cover the amount (there was prev.balance or team manual intervention)
The adapter approves the full requested amount for transfer (1000 USDC)
The vault pulls the full requested amount via
SafeERC20Lib.safeTransferFrom(1000 USDC)
The Problem:
Allocation cap update is CORRECT: Decreases by 1000 USDC (the amount requested from the strategy)
Transfer amount is WRONG: Vault pulls 1000 USDC but adapter only received 980 USDC from the external strategy
The adapter must have sufficient USDC balance to cover the slippage loss (20 USDC in the example), otherwise the
require(TokenUtils.safeBalanceOf(address(usdc), address(this)) >= amount, ...)check will failExpected behavior: Vault should update allocation cap by requested amount (1000 USDC) but pull only the actual amount received (980 USDC) VaultV2::_dealocateInternal
AaveV3ARBUSDCStrategy::_deallocate
The protocol acknowledges slippage is expected (via slippageBPS and _previewAdjustedWithdraw), but the implementation forces the adapter to cover losses rather than properly accounting for them.
Impact
Fragile Dependency: Adapter requires spare USDC balance to cover slippage, creating an implicit requirement that's not guaranteed
Incorrect Loss Attribution: Slippage losses are absorbed by the adapter's balance rather than properly attributed to vault depositors. The vault's accounting is correct (allocation decreases by requested amount), but the actual transfer forces the adapter to make up the difference
Potential DoS: If adapter doesn't have sufficient balance to cover slippage, deallocations will revert
Mitigation:
Strategies/Adapters
Calculate slippage-adjusted minimum:
minAcceptable = amount - (amount * slippageBPS / 10000)Validate received amount meets minimum:
require(redeemedAmount >= minAcceptable, "Slippage exceeded")Return allocation change based on requested amount: Keep current logic where
change = int256(newAllocation) - int256(oldAllocation)(based onamountrequested, notredeemedAmount)Approve only the actual redeemed amount:
TokenUtils.safeApprove(address(usdc), msg.sender, redeemedAmount)Communicate actual redeemed amount to vault: The adapter needs a way to return
redeemedAmountseparately from the allocationchange
MorpheusVault
Update allocation cap by the change (already implemented correctly): Based on requested
assetsamountTransfer only the actual redeemed amount:
SafeERC20Lib.safeTransferFrom(asset, adapter, address(this), redeemedAmount)instead ofassets
Proof of Concept
Proof of Concept
1. Paste the following interfact in FluidARBUSDCStrategy.t.sol
2. Paste the following test in FluidARBUSDCStrategy.t.sol
3.Run it via forge test --mc FluidARBUSDCStrategyTest --mt test_POC_DeallocateAccountingMismatch -vvv
Logs
Was this helpful?