Oracle with Validation
Oracle with Validation is a Morpho-compliant oracle type that improves the robustness of the price oracle by mitigating oracle provider failures such as compromise 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.
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 deviation check 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 . The maximum allowed deviation is
If , the price call reverts and the primary value is withheld.
Price Validation
When price() is called, the contract runs these checks in order:
- Fetches the price from the primary oracle.
- If
validationCheckPausedistrue, returns the primary price immediately. - Otherwise, fetches the price from the validation oracle and computes the absolute deviation. Reverts with
ExcessiveOracleDeviationif it exceeds . - Returns the primary price.
If the primary oracle returns zero and the validation oracle returns nonzero, the deviation check reverts with ExcessiveOracleDeviation. If both return zero, price() returns zero to Morpho (Morpho handles the zero-price case).
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, the deployer will typically want the deviation threshold to stay below for the check to be useful: 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 , so for tight-LLTV markets prefer
to keep the effective overshoot within .
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
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 entireprice()call reverts withValidationOracleFailure. Use this when you want a malfunctioning validation oracle to halt all operations that depend on the price. In this mode, pausing the validation check is the only wrapper-side way to keepprice()working if the validation oracle breaks permanently — see Owner Controls for how this interacts with renouncing ownership.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.
A validation oracle that signals failure by returning 0 (instead of reverting) is not caught by the try/catch path. Against a nonzero primary price, the deviation check fails and price() reverts with ExcessiveOracleDeviation even when REVERT_ON_VALIDATION_ORACLE_FAILURE is false.
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.pauseValidationCheck()reverts withNotAllowedif already paused.unpauseValidationCheck()re-enables it.unpauseValidationCheck()reverts withNotAllowedif not paused.
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 is risky: if the temporary owner has paused validation first, the oracle is permanently primary-only with no way to re-enable validation.
The renunciation state interacts with the pause state:
- Pausing validation and then renouncing ownership permanently locks the oracle into primary-only mode.
- Renouncing while unpaused makes the validated configuration permanent — validation can never be paused.
When REVERT_ON_VALIDATION_ORACLE_FAILURE = true, pausing the validation check is the only wrapper-side way to keep price() working if the validation oracle later breaks permanently. Renouncing ownership removes that option: with no owner, validation can never be paused, so price() reverts until the validation oracle recovers on its own. While price() reverts, dependent health checks and liquidations stall, and interest keeps accruing on positions that cannot be liquidated.
For strict-failure markets (REVERT_ON_VALIDATION_ORACLE_FAILURE = true), retain an owner or another documented recovery process so wrapper-side liveness can be restored. Do not make "strict mode plus immediate renounce" the default deployment path. If immutability is required, prefer REVERT_ON_VALIDATION_ORACLE_FAILURE = false, where a failing validation oracle passes through to the primary price instead of halting it.
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). Iffalse, 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
primaryOraclenorvalidationOraclecan be the zero address. - Primary and validation oracle addresses must be different.
MAX_ORACLE_DEVIATIONmust be strictly less than 100% (1e18).- The factory tracks all deployed oracles via the
isDeployedOraclemapping.
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: A zero primary price is not rejected by a dedicated guard. If the validation oracle returns nonzero, the deviation check reverts with
ExcessiveOracleDeviation; if both feeds return zero,price()returns zero to Morpho, which handles the zero-price case.
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).