How Morpho V2 picks up adapter losses automatically, where the mechanism can lag economic reality, and what the curator does about it.
There is no realizeLoss function. Earlier drafts of this page documented one — that was incorrect. Morpho V2 realises losses automatically inside accrueInterest() by summing every active adapter's realAssets() view. The curator's job isn't to call a missing button; it's to keep adapter realAssets() truthful and use the right lever when it isn't.
Every Morpho V2 adapter implements a view called realAssets() that returns its position's current on-chain value. MorphoMarketV1Adapter sums expectedSupplyAssets(marketId) across each Morpho Blue market it holds; the generic ERC-4626 adapter calls target.convertToAssets(target.balanceOf(this)); future adapters follow the same shape.
The vault's accrueInterest() function (in VaultV2.sol, the canonical Morpho V2 vault) loops over every active adapter, sums those views, and compares the new total to the stored _totalAssets. Any drop writes through to _totalAssets immediately, the share price drops, and the next previewRedeem(...) reflects the new value.
accrueInterest() runs at the head of every state-changing user-facing entry point — deposits, withdrawals, allocate/deallocate, fee accrual, role setters — so a loss surfaces on the very first interaction with the vault after the adapter's underlying market value drops. There is no separate curator-triggered step to "mark" it.
On the LayerCover wrapper, _syncAccounting() runs at the head of every meaningful entry point too (depositSenior, depositJunior, both request*, both withdraw*, syncAccounting), reads the wrapped vault's previewRedeem(totalVaultShares) at fresh prices, and pushes the delta through the senior/junior loss waterfall via _applyLoss(...). If the wrapped vault has accrued in this block, the wrapper sees the realised value; if not, calling vault.accrueInterest() first (or any state-changing entry) forces it.
The "automatic" guarantee is only as good as each adapter's realAssets() view. Two staleness patterns to know:
1. The adapter's view depends on an underlying that hasn't accrued.
A Morpho Blue market's expectedSupplyAssets doesn't recognise the interest accrued since the last touch on that market. If nobody has interacted with the market for several blocks, realAssets() underestimates a healthy market and overestimates one that's bleeding into bad debt. Touching morpho.accrueInterest(marketParams) on the underlying market forces it to fresh.
2. The adapter's view depends on an oracle or market the underlying doesn't yet reflect. A frozen oracle, an unliquidated bad-debt position, or an off-chain event (e.g. a real-world default the on-chain market hasn't priced yet) sits outside the chain's view. The adapter still reports its stale pre-event value. Until liquidations cascade through or the underlying takes a write-down, the LayerCover share price overstates the vault.
In both cases the symptom is identical: depositors can previewRedeem at an inflated price ahead of the true loss landing.
| Lever | Effect | Who can call |
|---|---|---|
morpho.accrueInterest(marketParams) on the underlying Morpho Blue market | Forces the market's own interest accrual. Picks up rate-driven drifts but doesn't conjure a write-down the market hasn't priced. | Anyone (permissionless) |
vault.accrueInterest() on the V2 vault | Re-sums every adapter's realAssets() and writes the delta into _totalAssets. Use this after touching the underlying market to push the new value through. | Anyone (permissionless) |
forceDeallocate(adapter, data, assets, onBehalf) | Pulls assets back to idle at the adapter's current real value. The strongest tool — it crystallises the position out of the bad market, so any further deterioration only affects the smaller residual. | Anyone (permissionless; pays the per-adapter penalty) |
decreaseAbsoluteCap / decreaseRelativeCap | Lowers the affected adapter's cap so no further deposits route into it. Doesn't change current holdings. | Curator OR sentinel (instant, no timelock) |
setIsAllocator(addr, false) | Locks out an allocator whose judgement is no longer trusted. | Curator (timelocked) |
removeAdapter(adapter) | Retires the adapter entirely once its allocation is zero. | Curator (timelocked) |
The first two are read-the-truth tools: they don't change anyone's exposure, they just make the chain catch up to it.
The remaining four are containment: they limit damage to depositors who haven't realised the loss yet by either crystallising the position or refusing further allocations into the affected market.
The LayerCover wrapper's senior/junior waterfall (TranchedMetaVault._applyLoss) handles whatever the underlying vault tells it. If previewRedeem is honest, the waterfall is honest. If previewRedeem is overstating, the waterfall delays — junior absorbs less than it should, senior takes a stale-priced exit if it withdraws first.
The risk to senior LPs is therefore not a missed admin call. It's the staleness gap above. The defences against it are:
realAssets() through the vault before any senior LP exits at a stale price.accrueInterest on affected markets + the vault when off-chain information indicates a loss is incoming, and forceDeallocates out of markets they've lost confidence in.There is no "5-day SLA to call realizeLoss" — that was the wrong framing. The right framing is: the senior notice window is the slack the protocol gives a curator to keep adapter views honest before any senior LP can exit on a stale price.