Stratax Contracts

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

Stale Chainlink Oracle Price Data Used in Leverage Calculations Due to Missing Staleness and Validity Checks in `StrataxOracle::getPrice()`

Root + Impact

Description

  • The StrataxOracle::getPrice() function is used throughout the protocol to fetch token prices for leverage calculations, position opening, and unwinding. It calls Chainlink's latestRoundData() but only validates that answer > 0, discarding all other return values that are critical for detecting stale or invalid price data.

  • The function ignores updatedAt, roundId, answeredInRound, and startedAt — all of which are necessary to determine whether the price feed is current and complete. When a Chainlink oracle goes stale (due to network congestion, oracle node failures, or feed deprecation), the last known price continues to be returned indefinitely.

// Root cause in the codebase with @> marks to highlight the relevant sectionfunction 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(); // All staleness fields discarded
@> require(answer > 0, "Invalid price from oracle"); // Only check: answer > 0
price = uint256(answer);
}

Risk

Likelihood:

  • Chainlink oracle staleness events occur during periods of network congestion, gas spikes, or oracle infrastructure issues — these are documented real-world events that have affected DeFi protocols on Ethereum mainnet

  • The protocol targets Ethereum mainnet and "all EVM-compatible chains with Aave V3, 1inch, and Chainlink deployed," and some L2 chains have experienced extended oracle delays

    Impact:

  • Stale prices fed into calculateOpenParams() produce incorrect flash loan amounts and borrow amounts, resulting in positions with wrong leverage that may be immediately liquidatable on Aave

  • Stale prices fed into calculateUnwindParams() and _executeUnwindOperation() produce incorrect collateral withdrawal amounts, potentially leaving users with less collateral than expected or causing the unwind to fail

  • An attacker aware of a stale oracle can exploit the price discrepancy to extract value — e.g., opening a leveraged position using an outdated favorable price, then unwinding at the real (current) price

Proof of Concept

// The getPrice function is called in these critical paths:
// 1. calculateOpenParams() — Lines 395-396, 401-402
details.collateralTokenPrice = IStrataxOracle(strataxOracle).getPrice(details.collateralToken);
details.borrowTokenPrice = IStrataxOracle(strataxOracle).getPrice(details.borrowToken);
// 2. calculateUnwindParams() — Lines 461-462
uint256 debtTokenPrice = IStrataxOracle(strataxOracle).getPrice(_borrowToken);
uint256 collateralTokenPrice = IStrataxOracle(strataxOracle).getPrice(_collateralToken);
// 3. _executeUnwindOperation() — Lines 570-571
uint256 debtTokenPrice = IStrataxOracle(strataxOracle).getPrice(_asset);
uint256 collateralTokenPrice = IStrataxOracle(strataxOracle).getPrice(unwindParams.collateralToken);
// If the Chainlink feed for WETH/USD returns a stale price of $3,000 when the real
// price is $2,500, the protocol will calculate incorrect leverage parameters:
// - Flash loan amount will be based on inflated collateral value
// - Borrow amount will be too high relative to actual collateral worth
// - The Aave position will have a lower real health factor than calculated
// - The position may be immediately liquidated by Aave at the real price

Recommended Mitigation

- remove this code
+ add this code+ uint256 public constant MAX_STALENESS = 3600; // 1 hour
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();
+ (uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) =
+ priceFeed.latestRoundData();
require(answer > 0, "Invalid price from oracle");
+ require(updatedAt > 0, "Round not complete");
+ require(answeredInRound >= roundId, "Stale price data");
+ require(block.timestamp - updatedAt <= MAX_STALENESS, "Price feed is stale");
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!