Lesson: Pendle FIXED-LINEAR-DISCOUNT PT oracle seam (no-AMM valuation)
Banked: 2026-06-11 (SEAMS lane, Morpho PT-USD3-17DEC2026/USDC instance; 24h-rule follow-up to the PT-apxUSD WALK-CLEAN)
Status: methodology + scoping-boundary calibration. WALK-CLEAN (protocol) / CURATOR-RISK (real mismatch).
TL;DR
PendleSparkLinearDiscountOracle prices a PT as 1e18 - timeLeft * baseDiscountPerYear / 365d. It NEVER reads the AMM, SY.exchangeRate, or any market state - the price is a pure function of block.timestamp. baseDiscountPerYear is IMMUTABLE (constructor-set, no setter, no governance path). So the oracle structurally cannot track the real PT market price. The seam is real and quantifiable; whether it is a FINDING depends entirely on the CONSUMER's program scope (permissionless vs permissioned) and on whether the consumer added an underlying-depeg feed.
The two over-valuation directions (and which is live)
- Rate-move over-valuation: the fixed discount under-values while real implied APY < fixed rate (the common, safe case), and over-values once real implied APY exceeds the rate that solves
(1+apy)^-T = 1 - r*T. For a 30%/yr fixed rate at ~0.5yr tenor, the crossover is ~38.5% real implied APY. Big conservative buffer; not attacker-induced. Usually NOT the live risk.
- Underlying-depeg over-valuation (THE LIVE ONE): the linear oracle prices PT-in-UNDERLYING. The consuming Morpho oracle then needs an independent UNDERLYING/USD feed (baseFeed2) to catch underlying depeg. If baseFeed2 = address(0), the underlying is hardcoded to $1 and any depeg flows through UNHAIRCUT into bad debt. Onset of bad debt for fully-drawn positions =
1 - LLTV depeg (e.g. 14% at 86% LLTV).
Fast recipe to map this seam
- From the Morpho oracle, read
BASE_FEED_1(). If description() reverts / is absent and decimals()==18, suspect a linear-discount feed or its wrapper.
- Unwrap:
innerOracle() on the wrapper -> the real PendleSparkLinearDiscountOracle. Read PT(), maturity(), baseDiscountPerYear() (3e17 = 30%/yr). It has NO market(), NO twapDuration(), NO baseOracleType() (that distinguishes it from the recommended PendleChainlinkOracle).
- CRITICAL: read
BASE_FEED_2(), QUOTE_FEED_1(), QUOTE_FEED_2() on the Morpho oracle. If baseFeed2 == address(0) the underlying has NO depeg protection. This is the whole finding's hinge.
- Get the real PT price + implied APY from Pendle API:
GET api-v2.pendle.finance/core/v1/1/markets?pt=<ptAddr> -> pt_price_usd, implied_apy. Compare to the oracle's 1 - r*T. Compute the crossover APY and the depeg-to-bad-debt threshold (1 - LLTV).
- Identify the UNDERLYING's risk class. A 1:1 stable wrapper (apxUSD) makes depeg inert; a credit-pool / uncollateralized-lending stablecoin (3Jane USD3, senior tranche of a USDC credit pool) makes the depeg path REALISTIC. The size of
baseDiscountPerYear is a tell: 30%/yr signals the curator knows the asset is risky.
Scoping boundary (the decisive WALK-vs-STAGE rule)
- Morpho Blue is PERMISSIONLESS. Immunefi/Morpho docs explicitly state market-creator + oracle choice is the lender's/curator's responsibility and "no oracle is immune." A bad oracle config (missing depeg feed) on a Morpho market is OUT OF SCOPE for the Morpho protocol program. -> WALK-CLEAN for Morpho, output a curator advisory instead.
- Pendle's linear oracle does exactly what it documents (conservative, AMM-independent). It is not over-valuing by construction. -> Not a Pendle bug.
- The same config inside a PERMISSIONED, governance-listed protocol (SparkLend Aave-fork, Sky) WOULD be a protocol-config finding because governance, not an anonymous market creator, chose the unsafe oracle. THAT is in-scope. -> If you find the linear oracle wired without an underlying-depeg feed inside Spark/Sky governance-listed collateral, STAGE it.
So: linear-discount + missing depeg feed on Morpho = advisory; same on Spark/Sky = candidate. Always resolve "who chose this oracle - anonymous creator or governance?" before deciding WALK vs STAGE.
Negative-result calibration
- A real, large (10% current) oracle-vs-market price gap is NOT automatically a finding. Direction matters: if the fixed discount is ABOVE the real implied APY, the oracle UNDER-values (conservative, safe). Confirm the direction before getting excited.
- "Immutable misconfigurable parameter" is not exploitable absent an attacker-triggerable state. The linear oracle reads no manipulable state -> no flash-loan / donation / within-block path. The dangerous state (underlying depeg) is exogenous. This caps severity to "latent valuation risk," not "exploit."
- The hinge is ALWAYS the consumer's missing depeg feed + the underlying's credit risk, NOT the linear oracle itself. Don't write the finding against Pendle; write it against the market config (and only stage where governance owns that config).
Reusable numbers / heuristics
- Crossover implied APY for over-valuation: solve
(1+apy)^-T = 1 - r*T, T = tenor in years, r = baseDiscountPerYear.
- Bad-debt-onset depeg =
1 - LLTV (for fully-drawn positions). 86% LLTV -> 14% depeg. 91.5% LLTV -> 8.5% depeg (tighter, more dangerous).
- Tooling: same as prior PT lesson - Sourcify +
cast against ethereum-rpc.publicnode.com, Morpho blue-api.morpho.org/graphql (no uniqueKey), Pendle api-v2.pendle.finance/core/v1/1/markets?pt=.