Skip to main content

Oracle with Validation

Oracle with Validation is a Morpho-compliant oracle type that improves the robustness of the price oracle to mitigate against oracle provider failures such as oracle compromises or manipulation. The Oracle with Validation cross-validates a primary oracle price against a validation oracle price before allowing any take to proceed, always returning the primary oracle's price but reverting if the two feeds diverge beyond a configured deviation threshold.

For example, a cbBTC collateral market with USDC loan token can use Chainlink BTC/USD as the primary oracle price and Redstone BTC/USD as the validation oracle price, with a 10% deviation threshold. As long as the two feeds stay within 10% of each other, the market behaves normally using the Chainlink oracle price. If one feed is manipulated or suffers a compromise, the deviation check trips and all borrow, withdraw collateral, and liquidation actions are effectively paused until the discrepancy resolves.

This design is intended for liquid markets where multiple oracle providers or onchain sources track prices with high fidelity.

Trade-off

Price calls revert whenever the two feeds are outside the deviation limit, which also prevents liquidations from being executed during that time. A threshold that is too narrow can be dangerous: it trades direct manipulation risk for liquidation-availability risk. See Sizing MAX_ORACLE_DEVIATION for the bounds to respect.

Oracle Architecture

The contract uses two independent oracle sources.

Primary oracle:

  • The main price source, such as Chainlink.
  • Its price is always returned to callers unless the price validation against the validation oracle triggers.
  • Must return a non-zero price.

Validation oracle:

  • Secondary price source used purely for validation, such as a TWAP, Chronicle, or Redstone feed.
  • Used to detect price manipulation or primary oracle failures.
  • Deviation is checked against the primary oracle.
  • Its price is never returned to callers and is never read by the Morpho market. It only gates whether the primary price is allowed through.

The absolute deviation is computed as pprimarypvalidation|p_{\text{primary}} - p_{\text{validation}}|. The maximum allowed deviation is

Δmax=pprimaryMAX_ORACLE_DEVIATION1018\Delta_{\max} = \frac{p_{\text{primary}} \cdot \text{MAX\_ORACLE\_DEVIATION}}{10^{18}}

If pprimarypvalidation>Δmax|p_{\text{primary}} - p_{\text{validation}}| > \Delta_{\max}, the price call reverts and the primary value is withheld.

Price Validation

When price() is called, the contract runs these checks in order:

  1. Fetches the price from the primary oracle.
  2. Reverts with ZeroPrimaryPrice if the primary price is zero.
  3. If validationCheckPaused is true, returns the primary price immediately.
  4. Otherwise, fetches the price from the validation oracle and computes the absolute deviation. Reverts with ExcessiveOracleDeviation if it exceeds Δmax\Delta_{\max}.
  5. Returns the primary price.

Sizing MAX_ORACLE_DEVIATION

MAX_ORACLE_DEVIATION is expressed in 18 decimals, for example, 5e16 for 5%. It is immutable after deployment.

For Morpho markets, for the check to be useful the deployer will typically want the deviation to stay below (1LLTV)(1 - \text{LLTV}): any larger overestimate by the primary oracle would let a borrower open a position whose true LTV exceeds 100%, making bad debt unavoidable. Because deviation is measured as a fraction of primaryPrice (not validationPrice), the effective overshoot when primary is the high side is d1d\frac{d}{1 - d}, so for tight-LLTV markets prefer

MAX_ORACLE_DEVIATION1LLTV2LLTV\text{MAX\_ORACLE\_DEVIATION} \leq \frac{1 - \text{LLTV}}{2 - \text{LLTV}}

to keep the effective overshoot within (1LLTV)(1 - \text{LLTV}).

The threshold also cannot be too tight: the validation oracle is not ground truth and can drift independently from the primary due to feed update cadence, decimal rounding, sequencer lag, TWAP smoothing, and so on. A threshold that triggers on honest deviation halts price() and blocks liquidations of unhealthy positions while interest accrues. A reasonable starting point is

MAX_ORACLE_DEVIATION(1LLTV)expected_normal_deviation\text{MAX\_ORACLE\_DEVIATION} \leq (1 - \text{LLTV}) - \text{expected\_normal\_deviation}

calibrated against the historical spread between the two feeds.

Validation Oracle Failure Behavior

The validation oracle call uses try/catch. At deployment, the deployer configures the immutable REVERT_ON_VALIDATION_ORACLE_FAILURE flag, which controls what happens when the validation oracle reverts:

  • true: The entire price() call reverts with ValidationOraclePriceFailure. Use this when you want a malfunctioning validation oracle to halt all operations that depend on the price.
  • false: The primary price is returned without validation. Use this when availability is more important than validation, and the owner can pause/unpause validation manually if needed.

Typically for Tenor markets, a revert of the validation oracle is ignored (REVERT_ON_VALIDATION_ORACLE_FAILURE = false) so that the primary oracle price is returned. This is to avoid relying on the validation oracle for liveness.

Owner Controls

The contract owner can pause and unpause the validation check. When paused, only the primary oracle is used and no deviation check is performed, which is useful if the validation oracle becomes unreliable.

  • pauseValidationCheck() disables the deviation check. Reverts if already paused.
  • unpauseValidationCheck() re-enables it. Reverts if already unpaused.

The contract uses OpenZeppelin's Ownable2Step for safe ownership transfers. Calling renounceOwnership() makes the oracle immutable: pause/unpause and ownership transfer are permanently disabled. The constructor does not accept address(0) as the initial owner, so to deploy an immutable oracle, deploy with a temporary owner, verify that the validation check is unpaused and price() returns successfully, then renounce. Renouncing before verifying allows a temporary owner to pause validation first, leaving the oracle permanently primary-only with no way to re-enable validation.

Key Parameters

  • PRIMARY_ORACLE: Address of the primary oracle (immutable).
  • VALIDATION_ORACLE: Address of the validation oracle (immutable).
  • MAX_ORACLE_DEVIATION: Maximum allowed deviation between oracles in 18 decimals (immutable, must be less than 100%).
  • REVERT_ON_VALIDATION_ORACLE_FAILURE: Whether the oracle reverts when the validation oracle's price() call reverts (immutable). If false, a failing validation oracle is treated as a pass-through and the primary price is still returned.

Factory Deployment

Oracle with Validation instances are deployed via the OracleWithValidationFactory using CREATE2 for deterministic addresses.

Factory constraints:

  • Neither primaryOracle nor validationOracle can be the zero address.
  • Primary and validation oracle addresses must be different.
  • MAX_ORACLE_DEVIATION must be strictly less than 100% (1e18).
  • The factory tracks all deployed oracles via the isDeployedOracle mapping.

Security Considerations

  • Both oracles must be trusted: A compromised validation oracle can be used to block legitimate primary prices and halt liquidations, even though it cannot directly fabricate the price returned to callers.
  • No staleness check: This contract does not enforce a maximum age on either feed; staleness handling is delegated to the underlying oracle implementations.
  • Zero primary price always reverts: ZeroPrimaryPrice fires regardless of pause state. Pausing the validation check does not bypass this guard.

Composing Morpho-compatible oracles

The Oracle with Validation type can be paired with other Morpho-compliant oracles as its primary or validation source. For example, one could use the Steakhouse metadeviation oracle, which itself wraps two underlying feeds and uses a timelock-gated challenge mechanism to switch between them: while both feeds agree it reports its primary, and once a deviation between them is challenged and the challenge timelock expires, anyone can call acceptChallenge to switch its reported price to the backup feed (with a symmetric heal path to switch back).