← back to lessons

Lesson: LRT-collateral oracle - the decisive bit is feed TYPE (market vs redemption), read it from dataFeedId/value-comparison, not from the wrapper

Banked 2026-06-11 (SEAMS lane, Morpho Blue ezETH/WETH WALK-CLEAN)

The lesson

When auditing an LRT-as-collateral oracle, the make-or-break question is NOT "does the wrapper have a cap/staleness guard" - it is "what does the underlying feed actually MEASURE: secondary-market price (depeg-visible) or fundamental/redemption rate (depeg-invisible, donation-inflatable)?"

The wrapper (e.g. MorphoChainlinkOracleV2) is a thin passthrough; its price() just multiplies feed answers. The trust gap lives ENTIRELY in the feed semantics. A "no staleness guard, no cap" wrapper is SAFE if the feed is a defended market feed, and DANGEROUS if the feed is a self-reported redemption rate.

How to classify the feed cheaply (passive cast reads)

  1. cast call FEED "getDataFeedId()(bytes32)" then cast --to-ascii -> RedStone feeds self-label: "ezETH/ETH" (market) vs "ezETH_FUNDAMENTAL"/"ezETH/ETH-exchange-rate" (redemption). Decisive in one call.
  2. Triangulate the VALUE: pull the LRT's own redemption rate (provider getRate(), 1e18) AND a known Chainlink MARKET feed, then see which the consumer feed matches. If consumer == Chainlink-market and < redemption by a small % => market feed. If consumer == provider getRate() => redemption feed. - Worked here: consumer 1.07722 == Chainlink-market 1.07688, both ~0.26% BELOW Renzo redemption 1.08003 -> market feed, defended.
  3. description() ("Redstone Price Feed") and decimals() (8 = Chainlink-style) confirm interface, not semantics - do not stop there.

Decision tree (for next LRT-collateral instance)

Scope-gate corollary (reinforces banked rule)

Morpho Blue oracle choice = permissionless curator accepted-risk (immutable oracle, no governance vetting) -> OUT of scope even if the wrapper ignores staleness, because the code behaves exactly as documented. Aave/Spark governance-listed reserve oracle = permissioned config -> a bad oracle choice IS in-scope. Always resolve permissioned-vs-permissionless BEFORE judging severity.

Next-target pointer (24h queue)

Hunt a lending market whose ezETH (or rsETH/pufETH) adapter reads a Renzo-style getRate() / LRT-self exchange-rate feed rather than a DEX-market feed. Renzo BalancerRateProvider 0x387dBc0fB00b26fb085aa658527D5BE98302c84C is documented donation-vulnerable (reads address(eigenPod).balance). Candidates to grep: Zerolend mainnet-lrt pool oracles, Euler ezETH vaults, smaller Morpho markets curated off the FUNDAMENTAL feed.

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