Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: high

Stale Oracle Prices Accepted Without Timestamp Validation

Author Revealed upon completion

Description

The oracle should only return fresh, recently updated prices to ensure accurate position valuations and prevent liquidations based on outdated data. The getPrice() function fetches price data from Chainlink but only validates that the price is non-zero, ignoring the updatedAt timestamp that indicates when the price was last updated.

function getPrice(address _token) public view returns (uint256 price) {
address priceFeedAddress = priceFeeds[_token];
require(priceFeedAddress != address(0), "Price feed not set for token");
AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);
@> (, int256 answer,,,) = priceFeed.latestRoundData(); // updatedAt ignored
require(answer > 0, "Invalid price from oracle");
price = uint256(answer);
}

Risk

Likelihood:

During network congestion or oracle downtime, Chainlink feeds can become stale for hours/days
Market crashes can trigger oracle pauses, leaving old prices active
This happens regularly during extreme volatility

Impact:

Users get liquidated based on outdated prices when their actual position is healthy
Protocol calculates incorrect leverage ratios using stale prices
Flash loan repayment calculations fail when swap prices differ from stale oracle prices
Loss of user funds through unfair liquidations

PoC

// Scenario: Oracle price is 3 days old
// 1. User opens 3x ETH position when ETH = $3000 (current price)
// 2. Oracle returns stale price of $2500 (from 3 days ago)
// 3. calculateOpenParams uses $2500, calculates wrong borrow amount
// 4. User gets less leverage than expected OR transaction fails
// 5. No one notices because getPrice() doesn't validate freshness

Recommended Mitigation

difffunction getPrice(address _token) public view returns (uint256 price) {
address priceFeedAddress = priceFeeds[_token];
require(priceFeedAddress != address(0), "Price feed not set for token");
AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);
- (, int256 answer,,,) = priceFeed.latestRoundData();
+ (, int256 answer,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(answer > 0, "Invalid price from oracle");
+ require(block.timestamp - updatedAt <= 3600, "Stale price"); // 1 hour max
price = uint256(answer);
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!