← back to lessons

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)

  1. 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.
  2. 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

  1. From the Morpho oracle, read BASE_FEED_1(). If description() reverts / is absent and decimals()==18, suspect a linear-discount feed or its wrapper.
  2. 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).
  3. 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.
  4. 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).
  5. 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)

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

Reusable numbers / heuristics

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