Use Case

This assertion monitors oracle price updates to detect and prevent price manipulation within a single transaction. Price oracles are critical security components that many DeFi protocols rely on for valuation, liquidations, and trading decisions. Intra-transaction price manipulations can lead to:

  • Flash loan attacks that temporarily manipulate oracle prices
  • Malicious arbitrage exploiting large price deviations
  • Theft of funds through manipulated liquidations or swaps
  • Protocol insolvency due to incorrect asset valuations

This assertion is particularly important for:

  • Preventing price manipulation attacks that exploit multiple price updates
  • Ensuring price stability across oracle updates within a transaction
  • Protecting protocols from flash-loan based oracle attacks
  • Maintaining protocol security by enforcing price deviation thresholds

Applicable Protocols

  • AMM-based DEXs that use oracle prices for various operations
  • Lending protocols that rely on oracles for collateral valuation
  • Synthetic asset platforms that need stable and accurate price feeds
  • Derivatives protocols that use oracles for settlement prices
  • Yield farming platforms that calculate rewards based on asset prices

These protocols need this assertion because:

  • DEXs can be exploited through price manipulation to extract value
  • Lending protocols need accurate price data to maintain proper collateralization
  • Synthetic asset platforms require stable oracle prices to maintain peg
  • Derivatives protocols depend on reliable pricing for fair settlement
  • Yield platforms may miscalculate rewards with manipulated prices

Explanation

The assertion implements a simple but effective approach to verify oracle price updates:

  1. Pre/Post State Comparison:

    • Uses forkPreState() to capture the price before the transaction
    • Uses forkPostState() to capture the price after the transaction
    • Compares pre and post states to detect price deviations (reverts early if deviation is detected)
  2. Intra-Transaction Inspection:

    • Uses getCallInputs() to capture all price update calls within the transaction
    • Verifies each update’s price parameter against the initial price
    • Enforces that no price update can deviate more than the maximum allowed percentage from the initial price

The assertion uses the following cheatcodes:

  • forkPreState(): Captures the oracle price before any updates
  • forkPostState(): Captures the oracle price after all updates
  • getCallInputs(): Monitors all price update function calls within the transaction
  • registerCallTrigger(): Triggers the assertion when an oracle price update is detected

This approach ensures that:

  1. Oracle prices remain within an acceptable deviation range throughout a transaction
  2. Flash loan attacks attempting to manipulate prices are detected and prevented
  3. Both the final price and any intermediate price updates are validated against the initial price

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 updatePrice(uint256 price) external;
    function price() external view returns (uint256);
}

contract IntraTxOracleDeviationAssertion is Assertion {
    // The oracle contract to monitor
    IOracle public oracle;

    // Maximum allowed price deviation (10% by default)
    uint256 public constant MAX_DEVIATION_PERCENTAGE = 10;

    constructor(address oracle_) {
        oracle = IOracle(oracle_);
    }

    function triggers() external view override {
        // Register trigger for oracle price update calls
        registerCallTrigger(this.assertOracleDeviation.selector, oracle.updatePrice.selector);
    }

    // Check that price updates don't deviate more than the allowed percentage
    // from the initial price at any point during the transaction
    function assertOracleDeviation() external {
        // Start with a simple check comparing pre and post state
        ph.forkPreState();
        uint256 prePrice = oracle.price();

        // Calculate allowed deviation thresholds (10% by default)
        uint256 maxAllowedPrice = (prePrice * (100 + MAX_DEVIATION_PERCENTAGE)) / 100;
        uint256 minAllowedPrice = (prePrice * (100 - MAX_DEVIATION_PERCENTAGE)) / 100;

        // First check post-state price
        ph.forkPostState();
        uint256 postPrice = oracle.price();

        // Verify post-state price is within allowed deviation from initial price
        require(
            postPrice >= minAllowedPrice && postPrice <= maxAllowedPrice,
            "Oracle post-state price deviation exceeds threshold"
        );

        // Get all price update calls in this transaction
        // Since this assertion is triggered by updatePrice calls, we know there's at least one
        PhEvm.CallInputs[] memory priceUpdates = ph.getCallInputs(address(oracle), oracle.updatePrice.selector);

        // Check each price update to ensure none deviate more than allowed from initial price
        for (uint256 i = 0; i < priceUpdates.length; i++) {
            // Decode the price from the function call data
            uint256 updatedPrice = abi.decode(priceUpdates[i].input, (uint256));

            // Verify each update is within allowed deviation from initial pre-state price
            require(
                updatedPrice >= minAllowedPrice && updatedPrice <= maxAllowedPrice,
                "Oracle intra-tx price deviation exceeds threshold"
            );
        }
    }
}

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:

  1. Deploy a test oracle contract with price update functionality
  2. Create test scenarios with varying price update patterns:
    • Single price updates with different deviation amounts
    • Multiple price updates within acceptable ranges
    • Multiple price updates with excessive deviations
    • Flash loan-style attacks with price manipulation
  3. Verify the assertion correctly:
    • Allows updates within acceptable deviation thresholds
    • Blocks updates exceeding deviation thresholds
    • Detects suspicious patterns of sequential updates

Assertion Best Practices

  • Adjust the MAX_DEVIATION_PERCENTAGE based on the specific asset’s volatility
  • Combine with Oracle Validation for comprehensive oracle protection
  • Consider implementing time-weighted average prices (TWAP) in volatile markets
  • Add specific detection for sandwich patterns (price up then down, or vice versa)
  • Monitor transaction origin to detect potential flash loan sources
  • Consider different thresholds for different assets or market conditions