Use Case

Oracles are components in DeFi protocols that provide external data (like asset prices) to on-chain contracts. Oracle liveness is a security parameter that ensures the data provided by oracles is current and reliable. Stale oracle data can lead to significant financial losses and protocol vulnerabilities.

This assertion is important for:

  • Preventing the use of stale price data that could lead to incorrect valuations
  • Ensuring oracle services remain operational and responsive
  • Detecting oracle service disruptions before they impact protocol operations
  • Maintaining protocol security by enforcing time-based data freshness requirements

For example, if an oracle hasn’t been updated for an extended period, it could indicate a service disruption or attack on the oracle infrastructure, potentially leading to incorrect pricing and financial losses.

Applicable Protocols

  • Lending protocols that rely on price oracles for collateral valuation and liquidation thresholds
  • DEXs that use oracles for price discovery and arbitrage detection
  • Yield farming protocols that depend on accurate price data for reward calculations
  • Insurance protocols that need current market data for premium calculations
  • Options and derivatives protocols that require precise price feeds for settlement

Hacks that are related to this assertion and that most likely could have been prevented by using an assertion like this:

  • Loopscale: Hack caused by a stale oracle. It was on Solana, so not relevant to Credible Layer, but the same idea applies.

Explanation

The assertion monitors the timestamp of the last update to an oracle contract when critical protocol functions like swaps are called.

The assertion uses the following cheatcodes and functions:

  • forkPostState(): Creates a fork of the state after the transaction to check the current oracle state
  • registerCallTrigger(): Triggers the assertion when specific protocol functions that rely on oracle data are called

The implementation performs the following check:

  1. Upon a function call that relies on oracle data (like a DEX swap), it verifies that the oracle’s last update time is within the defined maximum time window

This ensures that:

  • Critical operations only use fresh oracle data
  • Transactions revert when oracles are stale
  • Protocol functions that rely on price data remain secure

For more information about cheatcodes, see the Cheatcodes Documentation.

Code Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Assertion} from "credible-std/Assertion.sol";
import {PhEvm} from "credible-std/PhEvm.sol";

interface IOracle {
    function lastUpdated() external view returns (uint256);
}

interface IDex {
    function swap(address tokenIn, address tokenOut, uint256 amountIn) external returns (uint256);
}

contract OracleLivenessAssertion is Assertion {
    IOracle public oracle;
    IDex public dex;

    // Maximum time window (in seconds) that oracle data can be considered fresh
    // This is a constant that should be adjusted based on the protocol's requirements
    uint256 public constant MAX_UPDATE_WINDOW = 10 minutes;

    constructor(address _oracle, address _dex) {
        oracle = IOracle(_oracle);
        dex = IDex(_dex);
    }

    function triggers() external view override {
        // Register trigger for the swap function which relies on oracle data
        registerCallTrigger(this.assertionOracleLiveness.selector, dex.swap.selector);
    }

    // Assert that the oracle has been updated within the specified time window
    function assertionOracleLiveness() external {
        // Get the current state to check the oracle's last update time
        ph.forkPostState();

        // Check if the oracle has been updated within the maximum allowed window
        uint256 lastUpdateTime = oracle.lastUpdated();
        uint256 currentTime = block.timestamp;

        // Verify the oracle data is fresh (updated within the time window)
        require(currentTime - lastUpdateTime <= MAX_UPDATE_WINDOW, "Oracle not updated within the allowed time window");
    }
}

Note: This code example is maintained in the Phylax Assertion Examples Repository. For a full examples with mock protocol code and tests please refer to the repository.

Testing

To test this assertion, you would typically:

  1. Deploy a mock oracle contract that implements the IOracle interface
  2. Deploy a mock DEX contract that implements the IDex interface
  3. Set up the assertion contract with the mock oracle and DEX addresses
  4. Test scenarios where the oracle is updated within the time window (should pass)
  5. Test scenarios where the oracle hasn’t been updated for longer than the time window (should fail)
  6. Test edge cases like exactly at the time window boundary

Assertion Best Practices

  • Adjust the MAX_UPDATE_WINDOW based on the specific requirements of your protocol and the oracle’s update frequency
  • Add checks for other critical functions that rely on oracle data
  • Consider combining this assertion with other oracle-related assertions like Oracle Price Deviation for comprehensive security