Skip to content

Latest commit

 

History

History
126 lines (87 loc) · 4.74 KB

078.md

File metadata and controls

126 lines (87 loc) · 4.74 KB

Savory Glossy Pike

High

Stale Oracle Data Leading to Missed or Incorrect Order Execution (Profit loss || missed Stop Limit)

Summary

The Bracket contract relies on an external oracle to fetch token exchange rates for order execution. However, there are no mechanisms to validate the freshness of the oracle data. If the oracle provides outdated or stale data, the checkInRange function will fail to identify valid orders or execute them at incorrect prices, leading to user losses and missed positions.

Likelihood

High Probability:

  • Oracle delays or network congestion are common in blockchain environments, especially during periods of high activity.

No Validation Mechanism:

  • Without data freshness checks, there is no safeguard against this issue occurring.

Root Cause

https://github.com/sherlock-audit/2024-11-oku/blob/main/oku-custom-order-types/contracts/automatedTrigger/Bracket.sol#L587-L614

Reliance on Oracle Data:

  • The function fetches the exchangeRate from the oracle using MASTER.getExchangeRate.
  • There is no validation of whether the fetched rate is current.

Potential for Stale Data:

  • If the oracle provides outdated data (e.g., due to network delays, lags, or disruptions) which is very common due to common occurrence of stale oracle data in blockchain systems., valid orders may not execute or execute at incorrect rates.

Internal pre-conditions

No response

External pre-conditions

No response

Attack Path

Scenario: Missed Stop-Loss Execution Due to Stale Data 1- Setup:

  • A user sets a stop-loss order with a stop price of $950 for a token pair.
  • The oracle updates exchange rates every 60 seconds.

2-Market Movement:

  • The market price of the token pair rapidly drops from $1000 to $900 within 30 seconds.

3-Impact:

  • The oracle still reports the exchange rate as $1000, as it hasn't been updated.
  • The checkInRange function evaluates the stop-loss condition based on the stale rate of $1000, failing to trigger the stop-loss order.
  • The user's position continues to lose value, and they miss the opportunity to limit their losses.

Scenario: Incorrect Take-Profit Execution 1-Setup:

  • A user sets a take-profit order at $1050.
  • The oracle is delayed due to network congestion.

2-Market Movement:

  • The market price briefly spikes to $1100 before dropping back to $1000.
  • The oracle updates the exchange rate to $1050 after the market price has already fallen back.

3-Impact:

  • The checkInRange function triggers the take-profit order based on outdated data.
  • The order executes at a less favorable rate than the user expected, leading to reduced profits.

Impact

1- Missed Opportunities:

  • Orders that should have been triggered may remain unprocessed, resulting in missed opportunities for profit or risk mitigation.

2- Financial Losses:

  • Executing orders based on outdated prices may cause users to sell or buy assets at unfavorable rates.

3- User Distrust:

  • Users may lose confidence in the platform's reliability and accuracy, affecting its reputation.

PoC

Severity Classification: High

Why High?

  • The impact directly affects user funds and the platform's operational integrity.
  • The likelihood is non-negligible due to the absence of validation and the common occurrence of stale oracle data in blockchain systems.

Mitigation

Introduce a freshness validation mechanism to ensure that the oracle data is up-to-date before executing orders.

Updated Code: Include Data Freshness Checks

function checkInRange(
    Order memory order
) internal view returns (bool inRange, bool takeProfit, uint256 exchangeRate) {
    (exchangeRate, uint256 lastUpdated) = MASTER.getExchangeRateWithTimestamp(order.tokenIn, order.tokenOut);

    // Validate data freshness
    require(block.timestamp - lastUpdated <= MAX_ORACLE_AGE, "Stale oracle data");

    if (order.direction) {
        if (exchangeRate <= order.takeProfit) {
            return (true, true, exchangeRate);
        }
        if (exchangeRate >= order.stopPrice) {
            return (true, false, exchangeRate);
        }
    } else {
        if (exchangeRate >= order.takeProfit) {
            return (true, true, exchangeRate);
        }
        if (exchangeRate <= order.stopPrice) {
            return (true, false, exchangeRate);
        }
    }
}

Key Changes:

  • Fetch both the exchange rate and its last update timestamp.
  • Add a require statement to validate that the data is not older than a predefined threshold (MAX_ORACLE_AGE).

Definitions:

  • MAX_ORACLE_AGE: Maximum allowable age for oracle data, e.g., 30 seconds.
  • getExchangeRateWithTimestamp: Updated oracle function to return both the rate and the timestamp of its last update.