Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: medium
Valid

Oracle Staleness: `getPrice()` Never Validates `updatedAt` Timestamp

Root + Impact

Description

  • StrataxOracle.getPrice() calls latestRoundData() but discards all return values except answer, never comparing updatedAt against block.timestamp.

  • Chainlink prices can become stale during network congestion, sequencer downtime on L2s, or feed deprecation. The stale price flows directly into calculateOpenParams and calculateUnwindParams, which determine borrow amounts, flash loan sizes, and collateral withdrawal amounts.

// StrataxOracle.sol - getPrice()
function getPrice(address _token) public view returns (uint256 price) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(priceFeeds[_token]);
@> (, int256 answer,,,) = priceFeed.latestRoundData(); // updatedAt silently discarded
require(answer > 0, "Invalid price from oracle");
price = uint256(answer);
}

Risk

Likelihood:

  • Chainlink feeds become stale during network congestion, L2 sequencer downtime, or feed deprecation — this is a documented, recurring event

  • No heartbeat or deviation threshold check exists, so any lag between Chainlink updates goes undetected

Impact:

  • Stale price causes incorrect collateral valuation in calculateOpenParams, leading to over-borrowing and positions that are immediately liquidatable at the real market price

  • During unwind, stale price causes under-withdrawal of collateral, resulting in direct fund loss for the user

  • All positions share one Aave account, so a stale price degrading one position's health factor affects all positions simultaneously

Proof of Concept

```solidity
function testStalePriceExploit() public {
// Mock Chainlink returning stale price (updatedAt = 2 hours ago)
mockFeed.setRoundData(1, 3000e8, block.timestamp - 2 hours, block.timestamp - 2 hours, 1);
// Real market price has dropped to $2,000
// getPrice() still returns $3,000 — no staleness check
@> uint256 price = oracle.getPrice(WETH);
assertEq(price, 3000e8); // Stale price accepted without revert
// Position created with inflated collateral value
// Immediately liquidatable at real market price
}
```

Recommended Mitigation

```diff
- (, int256 answer,,,) = priceFeed.latestRoundData();
+ (uint80 roundId, int256 answer,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(answer > 0, "Invalid price from oracle");
+ require(updatedAt > 0, "Round not complete");
+ require(block.timestamp - updatedAt <= MAX_STALENESS, "Stale price");
+ require(roundId > 0, "Invalid round");
```
Add a configurable `MAX_STALENESS` constant per feed (e.g. 3600 for ETH/USD, 86400 for less volatile pairs).
Updates

Lead Judging Commences

izuman Lead Judge 16 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Price feed has no staleness check

StrataxOracle contract fails to check if the price is stale, which can mess up swap calculations.

Support

FAQs

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

Give us feedback!