← back to lessons

Lesson: ERC4626 convertToAssets consumed as an oracle price - the bounds + monotonicity seam

Banked 2026-06-11 (SEAMS lane). Source: Morpho Blue MorphoChainlinkOracleV2 x MetaMorpho.

The seam shape (reusable across targets)

Protocol A reads B.convertToAssets(sample) (or pricePerShare / getRate / exchangeRate) and uses it DIRECTLY as a price, with no clamp/staleness/deviation/monotonicity guard. A's auditor sees "reads an ERC4626 rate" and trusts the standard; B's auditor sees "we are a compliant ERC4626" and never sees A's usage. The boundary contract that should assert "B's rate must be hard-to-move AND within [min,max] AND not assumed monotonic" exists in neither audit. This is the 3F residual.

What A almost always assumes (and usually does NOT enforce)

  1. The rate is hard to manipulate - false at low totalSupply. OZ virtual offset (+1, +10**offset) DAMPENS but does not ELIMINATE inflation; a 1-wei-seeded vault is fully movable (tested: ~5e11x with one donation). Defense is the vault being seeded, which is operational, not code-enforced and frequently not even documented in A's README.
  2. The rate is roughly monotonic ("rate only goes up", LST mental model) - FALSE for any vault whose totalAssets is a live sum over markets that can socialize bad debt. The rate drops 1:1 with bad debt the SAME BLOCK (tested: 30% bad debt -> 30% price drop, no lag).
  3. The rate is fresh and never reverts - check whether B can be paused / put in limbo / have a reverting underlying while A still calls convertToAssets.

Hunting checklist (apply to any "vault-rate-as-oracle" integration)

Meta-rule

For vault-rate-as-oracle seams, the math seam is almost always real but defended-in-practice on a correctly-configured target. The yield is NOT in the math - it is in finding the ONE deployed instance where the operational defense (seeding, sane LLTV, trustworthy underlying) was skipped. "Clean WALK on the reference deployment" + "enumerate-deployments follow-up" is the correct output, not "no bug." Bank the seam-map; the live instance is a separate, schedulable hunt.

Cross-refs

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