← back to lessons

Lesson: MetaMorpho totalAssets is share-accounted, so it is NOT donation-inflatable as an oracle price source

Banked 2026-06-11. Follow-up to the SEAMS finding on MorphoChainlinkOracleV2.price() reading a vault's convertToAssets raw.

The lesson (one line)

When an ERC4626 share is used as a Morpho oracle price source, do NOT assume the generic ERC4626 donation/inflation attack applies - first check how the vault computes totalAssets. A MetaMorpho vault computes totalAssets = SUM(MORPHO.expectedSupplyAssets(market, vault)), which is share-accounted on Morpho Blue, not a raw token balance. A raw ERC20 donation does not move it. The donation lever the SEAMS Foundry model assumed does not exist for MetaMorpho vaults.

Why this matters / what it corrects

The SEAMS finding modeled the vault as a generic ERC4626 where donating tokens inflates the share price (shares * (totalAssets+1)/(totalSupply+offset)), and concluded "defended only by social seeding, fragile." That framing is too pessimistic for MetaMorpho:

The residual real seam is the OTHER direction: non-monotonic instant DOWNSIDE when an underlying Morpho market realizes bad debt. totalAssets drops the same block, price drops 1:1, no oracle lag. That is by-design borrower risk transfer, not a free-extraction exploit.

The generalizable rule

For "ERC4626-rate-as-oracle" seams, classify the vault's totalAssets source BEFORE assuming a donation attack: 1. Raw token balance (asset.balanceOf(this)) -> donation-inflatable. Real seam if thin. 2. Share/position accounting (Morpho expectedSupplyAssets, Aave aToken scaled balance, Compound) -> donation does NOT move it. Donation sub-seam is defended structurally. 3. Either way the DOWNSIDE seam (rate can drop, no monotonicity guard in the consumer) remains and is the higher-value lead when the collateral is used at high LLTV.

Enumeration method that worked (reusable)

Morpho public GraphQL https://blue-api.morpho.org/graphql (no auth, complexity-limited): - Paginate markets(first:1000, skip:N); pageInfo.countTotal gives the universe (6951). - Filter collateralAsset.vault != null -> collateral is a vault share. - Confirm the seam path with oracle.data.MorphoChainlinkOracleV2Data.baseOracleVault.address == collateralAsset.vault.address (distinguishes "vault priced by its own convertToAssets" from "vault token priced by a feed"). - Rank by: low vault TVL + high LLTV + meaningful state.borrowAssetsUsd + deposit-enabled. - Economic gate: cost-to-move-price (= pct * vaultTotalAssetsUsd) vs extractable (= min position size, state.liquidityAssetsUsd). On a seeded vault this is wildly negative.

Live result (2026-06-11)

39 vault-collateral markets across all chains; 34 on the seam path; only 1 (steakUSDC/XSGD on Base, $808k borrow) had non-trivial borrow, sitting on a $322M vault (1% price move costs $3.22M to unlock $99k -> ~32x cost/extract). WALK-CLEAN-confirmed. The math seam is real; no live instance is economically exploitable.

Anti-pattern to avoid

Do not stop the SEAMS analysis at the Foundry model of an idealized ERC4626. The model is a hypothesis about the provider's totalAssets; the actual deployed provider's accounting can invalidate the donation vector entirely. Always read the REAL totalAssets implementation of the specific vaults that are live on the seam, not a generic stand-in.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min