Stratax Contracts

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

StrataxOracle fails to validate Chainlink data freshness, risking stale price usage during unwinds

Author Revealed upon completion

Root + Impact

Description

The Oracle should return the most recent and valid price data from Chainlink to ensure accurate collateral and debt calculations.

The getRoundData function retrieves the price from latestRoundData but ignores the updatedAt timestamp and answeredInRound ID, allowing the system to accept stale or frozen prices during oracle outages.

// src/StrataxOracle.sol
function getRoundData(address _token)
public
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
address priceFeedAddress = priceFeeds[_token];
require(priceFeedAddress != address(0), "Price feed not set for token");
AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeedAddress);
@> (roundId, answer, startedAt, updatedAt, answeredInRound) = priceFeed.latestRoundData();
}

Risk

Likelihood:

  • The Chainlink feed heartbeat passes without an update due to network congestion (L2 sequencer downtime) or node operator issues.

Impact:

  • Users are unable to unwind positions because stale prices cause the debt repayment calculation to mismatch reality, leading to reverts.

  • The protocol miscalculates position health, preventing necessary liquidations or allowing under-collateralized borrowing.

Proof of Concept

Add this test to test/StrataxPoC.t.sol:

function test_OracleAcceptsStalePrice() public {
// 1. Setup a mock feed that hasn't updated in 365 days
MockAggregator staleFeed = new MockAggregator(2000e8, block.timestamp - 365 days);
oracle.setPriceFeed(address(tokenA), address(staleFeed));
// 2. Call getRoundData
(,, uint256 retrievedUpdatedAt,,) = oracle.getRoundData(address(tokenA));
// 3. Assert that the stale data is returned without revert
assertEq(retrievedUpdatedAt, block.timestamp - 365 days);
// Vulnerability: Contract accepted 1 year old price
}

Recommended Mitigation

function getRoundData(address _token) ... {
// ... previous code
(roundId, answer, startedAt, updatedAt, answeredInRound) = priceFeed.latestRoundData();
+ require(answer > 0, "Invalid price");
+ require(updatedAt != 0, "Incomplete round");
+ require(answeredInRound >= roundId, "Stale round");
+ require(block.timestamp - updatedAt <= 86400, "Stale price"); // Configurable heartbeat
}

Support

FAQs

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

Give us feedback!