Stratax Contracts

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

Missing staleness validation in getPrice() allows use of frozen/outdated oracle prices

Author Revealed upon completion

Normal behavior:
A secure oracle wrapper should validate that price data is fresh before returning it to downstream protocols, rejecting stale values that could misrepresent current market conditions

problem

The getPrice() function returns price data from Chainlink feeds without validating the updatedAt timestamp. This allows the contract to serve arbitrarily old prices if the aggregator stops updating (due to network issues, node failures, or deliberate manipulation), causing downstream protocols to make critical decisions on outdated market data.

https://github.com/CodeHawks-Contests/2026-02-stratax-contracts/blob/f6525334ddeb7910733432a992daecb0a8041430/src/StrataxOracle.sol#L64-74
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(); // Missing timestamp validation
require(answer > 0, "Invalid price from oracle");
price = uint256(answer);
}

Risk

Likelihood: High

  • Chainlink feeds have frozen multiple times in production

  • Aggregators can stop updating during extreme market volatility when accurate pricing is most critical

  • No validation means any frozen feed automatically becomes exploitable

impact

  • Downstream lending protocols may execute incorrect liquidations using stale prices (e.g., liquidating healthy positions during flash crashes)

  • AMMs could suffer value extraction via stale-price arbitrage

Proof of Concept

// Simulated attack scenario after Chainlink feed freezes for 24h during market crash
// Current market: ETH = $1,500
// Frozen feed still reports: ETH = $3,000 (from 24h ago)
contract VulnerableLendingProtocol {
StrataxOracle public oracle;
mapping(address => uint256) public collateral;
function liquidate(address user) external {
uint256 ethPrice = oracle.getPrice(ETH_TOKEN); // Returns stale $3,000 price
uint256 collateralValue = collateral[user] * ethPrice / 1e18;
// Healthy position incorrectly flagged as undercollateralized
require(collateralValue < debt[user] * 1.5e18, "Not liquidatable");
// Attacker liquidates solvent user at 50% discount
seizeCollateral(user);
}
}

Recommended Mitigation

-
+
- (, int256 answer,,,) = priceFeed.latestRoundData();
- require(answer > 0, "Invalid price from oracle");
+ (, int256 answer, , uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();
+ require(answer > 0, "Invalid price");
+ require(updatedAt != 0, "Never updated");
+ require(block.timestamp - updatedAt <= maxStaleness, "Stale price");
+ require(answeredInRound >= answeredInRound, "Incomplete round"); // Defense-in-depth
price = uint256(answer);
}
}

Support

FAQs

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

Give us feedback!