Allocators are responsible for ensuring users can withdraw assets at any time. This is achieved through managing idle liquidity, an optional liquidity adapter, and the permissionless forceDeallocate mechanism.
Liquidity sources
The vault has three sources of liquidity for withdrawals:
Idle assets - Tokens held directly by the vault
Liquidity adapter - Optional adapter for primary liquidity
Force deallocation - Permissionless withdrawal from any liquid adapter
Idle assets
Idle assets are tokens held directly in the vault contract:
uint256 idleAssets = IERC20 (asset). balanceOf ( address ( this ));
These assets:
Are used first during withdrawals
Don’t earn yield from underlying markets
Provide instant liquidity without gas overhead
Maintaining some idle assets reduces withdrawal gas costs and ensures instant liquidity for small withdrawals.
Liquidity adapter
The liquidity adapter is an optional adapter used for deposits and withdrawals:
address public liquidityAdapter;
bytes public liquidityData;
Setting the liquidity adapter
Allocators can set the liquidity adapter:
function setLiquidityAdapterAndData ( address newLiquidityAdapter , bytes memory newLiquidityData ) external {
require (isAllocator[ msg.sender ], ErrorsLib. Unauthorized ());
liquidityAdapter = newLiquidityAdapter;
liquidityData = newLiquidityData;
emit EventsLib. SetLiquidityAdapterAndData ( msg.sender , newLiquidityAdapter, newLiquidityData);
}
Whether the liquidity adapter is valid is checked during allocate/deallocate operations, not when setting it.
Liquidity adapter behavior
The same adapter and data are used for both deposits and withdrawals to maintain allocation consistency:
Why use the same adapter for entry and exit?
Using the same adapter for both entry and exit ensures that looping supply-withdraw or withdraw-supply generally doesn’t change the allocation. This provides predictable behavior and prevents allocation drift from repeated deposit/withdrawal cycles.
Deposit flow
When users deposit assets:
function enter ( uint256 assets , uint256 shares , address onBehalf ) internal {
require ( canReceiveShares (onBehalf), ErrorsLib. CannotReceiveShares ());
require ( canSendAssets ( msg.sender ), ErrorsLib. CannotSendAssets ());
SafeERC20Lib. safeTransferFrom (asset, msg.sender , address ( this ), assets);
createShares (onBehalf, shares);
_totalAssets += assets. toUint128 ();
emit EventsLib. Deposit ( msg.sender , onBehalf, assets, shares);
if (liquidityAdapter != address ( 0 )) allocateInternal (liquidityAdapter, liquidityData, assets);
}
Transfer assets
User transfers assets to the vault
Mint shares
Vault mints shares to the recipient
Allocate to liquidity adapter
If liquidity adapter is set, assets are automatically allocated to it
If a cap (absolute or relative) associated with the liquidity adapter’s IDs is reached, deposits will revert. This is particularly likely when the vault is empty or almost empty due to relative cap checks.
Withdrawal flow
When users withdraw assets:
function exit ( uint256 assets , uint256 shares , address receiver , address onBehalf ) internal {
require ( canSendShares (onBehalf), ErrorsLib. CannotSendShares ());
require ( canReceiveAssets (receiver), ErrorsLib. CannotReceiveAssets ());
uint256 idleAssets = IERC20 (asset). balanceOf ( address ( this ));
if (assets > idleAssets && liquidityAdapter != address ( 0 )) {
deallocateInternal (liquidityAdapter, liquidityData, assets - idleAssets);
}
if ( msg.sender != onBehalf) {
uint256 _allowance = allowance[onBehalf][ msg.sender ];
if (_allowance != type ( uint256 ).max) allowance[onBehalf][ msg.sender ] = _allowance - shares;
}
deleteShares (onBehalf, shares);
_totalAssets -= assets. toUint128 ();
SafeERC20Lib. safeTransfer (asset, receiver, assets);
emit EventsLib. Withdraw ( msg.sender , receiver, onBehalf, assets, shares);
}
Check idle assets
Vault checks if idle assets can cover the withdrawal
Deallocate if needed
If insufficient idle assets, deallocate from liquidity adapter
Burn shares and transfer
Burn user shares and transfer assets to receiver
Withdrawal priority
First: Use idle assets up to the withdrawal amount
Second: Deallocate remaining amount from liquidity adapter (if set)
Last resort: User must use forceDeallocate if liquidity adapter insufficient
Force deallocation
The permissionless forceDeallocate function allows anyone to withdraw from any adapter:
function forceDeallocate ( address adapter , bytes memory data , uint256 assets , address onBehalf )
external
returns ( uint256 )
{
bytes32 [] memory ids = deallocateInternal (adapter, data, assets);
uint256 penaltyAssets = assets. mulDivUp (forceDeallocatePenalty[adapter], WAD);
uint256 penaltyShares = withdraw (penaltyAssets, address ( this ), onBehalf);
emit EventsLib. ForceDeallocate ( msg.sender , adapter, assets, onBehalf, ids, penaltyAssets);
return penaltyShares;
}
How force deallocation works
Deallocate from adapter
Assets are moved from adapter to vault’s idle balance
Calculate penalty
Penalty is calculated based on adapter’s forceDeallocatePenalty
Withdraw penalty
Penalty shares are withdrawn from user and sent to vault
Force deallocate penalties
Curator sets penalties per adapter (capped at 2%):
function setForceDeallocatePenalty ( address adapter , uint256 newForceDeallocatePenalty ) external {
timelocked ();
require (newForceDeallocatePenalty <= MAX_FORCE_DEALLOCATE_PENALTY, ErrorsLib. PenaltyTooHigh ());
forceDeallocatePenalty[adapter] = newForceDeallocatePenalty;
emit EventsLib. SetForceDeallocatePenalty (adapter, newForceDeallocatePenalty);
}
MAX_FORCE_DEALLOCATE_PENALTY is capped at 2% to prevent excessive penalties while still discouraging allocation manipulation.
Penalty mechanics
// Penalty calculation
uint256 penaltyAssets = assets. mulDivUp (forceDeallocatePenalty[adapter], WAD);
uint256 penaltyShares = withdraw (penaltyAssets, address ( this ), onBehalf);
The penalty:
Is taken as a withdrawal from onBehalf
Returns assets to the vault (increasing share price for remaining users)
Doesn’t change total assets significantly (only rounding effects)
Discourages manipulation of relative caps
The only friction to deallocating an adapter with a 0% penalty is the associated gas cost.
In-kind redemptions
In-kind redemption allows users to exit with underlying market positions instead of assets:
Strategy for in-kind redemption
Flashloan liquidity
Borrow assets via flashloan
Supply to adapter's market
Supply assets directly to the underlying market
Force deallocate
Call forceDeallocate to withdraw the supplied liquidity
Repay flashloan
Repay flashloan with deallocated assets
In-kind redemptions allow exiting the vault even when no underlying markets are liquid, as long as the user can accept the market position directly.
Optimal force deallocate amount
If a user has A assets in the vault and the vault is fully illiquid:
optimalAmount = min (
liquidity_of_market,
A / ( 1 + penalty)
)
This ensures either:
The market is emptied, or
The user exits completely with no shares or liquidity remaining
Liquidity management best practices
For allocators
Set appropriate liquidity adapter
Choose a highly liquid market (e.g., large Morpho Market V1)
Monitor withdrawal patterns
Adjust idle assets based on typical withdrawal sizes
Maintain liquidity buffer
Ensure liquidity adapter has sufficient depth for expected withdrawals
Balance yield and liquidity
Higher idle assets = lower yield but better UX
Liquidity adapter selection
A typical liquidity adapter should:
Have deep liquidity
Have low or zero force deallocate penalty
Support instant withdrawals
Minimize entry/exit losses
Example: A very liquid Morpho Market V1 with:
High total supply
Low utilization
Trusted oracle and collateral
Adaptive curve IRM for predictable rates
Liquidity failures
What happens if all liquidity sources are exhausted:
Scenario: User wants to withdraw but:
Idle assets = 0
Liquidity adapter exhausted or not set
Other adapters illiquid
Options:
Wait for liquidity to be added by allocator
Use forceDeallocate with penalty (if markets have any liquidity)
Execute in-kind redemption via flashloan
Wait for other users to deposit
Non-custodial guarantees ensure users can always exit via forceDeallocate and in-kind redemptions, even if it requires paying penalties or accepting market positions.
Monitoring liquidity health
Key metrics to monitor:
// Idle liquidity
uint256 idle = IERC20 (asset). balanceOf ( address (vault));
// Liquidity adapter depth (if set)
if (vault. liquidityAdapter () != address ( 0 )) {
uint256 adapterLiquidity = IAdapter (vault. liquidityAdapter ()). realAssets ();
}
// Total vault liquidity (idle + liquidity adapter)
uint256 totalLiquidity = idle + adapterLiquidity;
// Liquidity ratio
uint256 liquidityRatio = totalLiquidity * 1e18 / vault. totalAssets ();
Recommended liquidity ratio depends on:
Vault size and user base
Historical withdrawal patterns
Yield differential between liquid and illiquid markets
Depositor sophistication