Stratax Contracts

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

StrataxOracle accepts stale or invalid Chainlink rounds without freshness checks

Author Revealed upon completion

Description

  • Normal behavior: oracle reads should validate round freshness and consistency (updatedAt, answeredInRound, etc.) before returning prices.

  • Issue: getPrice only checks answer > 0 and does not validate freshness or round integrity. Any positive stale round is accepted and consumed by leverage/unwind calculations.

// src/StrataxOracle.sol
function getPrice(address _token) public view returns (uint256 price) {
...
@> (, int256 answer,,,) = priceFeed.latestRoundData();
@> require(answer > 0, "Invalid price from oracle");
@> price = uint256(answer);
}

Risk

Likelihood:

  • Reason 1 // Chainlink feeds can lag updates during market stress or data-source degradation.

  • Reason 2 // This oracle function is directly used by position sizing paths and executes frequently.

Impact:

  • Impact 1 // Borrow/withdraw sizing may be based on stale prices and become unsafe.

  • Impact 2 // Users can face higher revert rate, worse execution, or liquidation exposure.

Proof of Concept

This PoC deploys a mock Chainlink feed with valid decimals but intentionally stale/inconsistent round metadata (updatedAt = 1, answeredInRound < roundId). getPrice still returns the value, demonstrating that freshness and round-integrity are not enforced.

// test/poc/StrataxVulnerabilities.t.sol
function testPoC_StaleOracleRoundIsAcceptedByStrataxOracle() public {
StrataxOracle strataxOracle = new StrataxOracle();
MockAggregator agg = new MockAggregator(8);
// stale/inconsistent round accepted by current implementation
agg.setRoundData(200, 2_000e8, 1, 1, 199);
strataxOracle.setPriceFeed(address(collateralToken), address(agg));
uint256 price = strataxOracle.getPrice(address(collateralToken));
assertEq(price, 2_000e8);
}

Recommended Mitigation

The mitigation adds standard Chainlink guardrails: positive answer, non-zero/fresh updatedAt, and round consistency (answeredInRound >= roundId). This blocks stale rounds from propagating into leverage and unwind sizing.

- (, int256 answer,,,) = priceFeed.latestRoundData();
- require(answer > 0, "Invalid price from oracle");
+ (uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData();
+ require(answer > 0, "Invalid price from oracle");
+ require(updatedAt != 0 && updatedAt + MAX_STALENESS >= block.timestamp, "Stale price");
+ require(answeredInRound >= roundId, "Stale round");

Support

FAQs

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

Give us feedback!