Stratax Contracts

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

Unguarded External Calls In Oracle Functions Propagate Reverts To Critical Paths

Author Revealed upon completion

StrataxOracle is a thin wrapper around Chainlink's AggregatorV3Interface. Its getPrice(), getPriceDecimals(), and getRoundData() functions each make external calls to the configured Chainlink price feed without any try/catch error handling or fallback mechanism.

The getPrice() function calls priceFeed.latestRoundData(), and if that external call reverts for any reason, the revert propagates unconditionally to all callers:

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);
// @audit external call with no try/catch; reverts propagate to all callers
(, int256 answer,,,) = priceFeed.latestRoundData();
require(answer > 0, "Invalid price from oracle");
price = uint256(answer);
}

The same pattern exists in getPriceDecimals() (calling priceFeed.decimals()) and getRoundData() (calling priceFeed.latestRoundData()). None of these functions implement try/catch, cached fallback values, or alternative feed sources.

The critical impact is on position unwinding. When a user calls unwindPosition(), the flow proceeds as follows:

  1. unwindPosition() calls aavePool.flashLoanSimple().

  2. Aave calls back executeOperation().

  3. executeOperation() routes to _executeUnwindOperation().

  4. _executeUnwindOperation() calls IStrataxOracle(strataxOracle).getPrice() for both the debt token and collateral token with no try/catch and no bypass:

// @audit both calls revert if the Chainlink feed is unavailable
uint256 debtTokenPrice = IStrataxOracle(strataxOracle).getPrice(_asset);
uint256 collateralTokenPrice = IStrataxOracle(strataxOracle).getPrice(unwindParams.collateralToken);

If either getPrice() call reverts, the entire flash loan callback reverts, causing the flash loan to fail and blocking the unwind operation entirely. Since the Stratax contract holds the Aave position (collateral and debt belong to address(this)), users cannot directly interact with Aave to unwind their positions and are fully dependent on the Stratax contract's unwindPosition() flow.

The view helper calculateUnwindParams() is similarly affected, as it also calls getPrice() for both the borrow and collateral tokens. The view helper calculateOpenParams() calls getPrice() only when token prices are passed as zero, but createLeveragedPosition() itself does not call the oracle and can still be invoked with externally-calculated parameters, so position opening is not blocked.

The protocol has no fallback mechanism: no cached prices, no alternative feeds, and no circuit breaker that allows emergency operations. The only mitigation is for the owner to manually call setPriceFeed() to point to a working alternative feed, which requires the owner to be online and responsive, and for a working alternative feed to exist.

Chainlink feed outages are documented real-world events. During the Arbitrum sequencer outage in June 2023, Chainlink feeds on Arbitrum were returning stale data or reverting, causing DeFi protocols without fallback mechanisms to experience disruptions. The LUNA crash in May 2022 similarly caused Chainlink's LUNA/USD feed to become unreliable, blocking protocols that had no fallback mechanism.

This issue has a medium impact as users with active leveraged positions genuinely cannot unwind through the protocol during an oracle outage, potentially leading to Aave liquidation losses if the market moves against them. However, the impact is bounded because Aave liquidations still function independently, and the owner can update the price feed address to a working feed.

This issue has a low likelihood as Chainlink feed outages are rare on mainnet Ethereum but more common on L2s during sequencer downtime. Feed deprecation is announced in advance. The scenario requires a specific Chainlink infrastructure failure, which is an external dependency outside the protocol's control.

recommendation

Implement a fallback mechanism in StrataxOracle for handling Chainlink feed failures. Wrap the latestRoundData() call in a try/catch block and maintain a cached price storage that records the last known good price and timestamp for each token. On success, update the cached price. On failure, return the cached price if it is within an acceptable staleness threshold, or revert with a more descriptive error if the cached price is too stale. Note that getPrice() must be changed from a view function to a non-view function to support writing cached values to storage.

Additionally, add a staleness check on the updatedAt value returned from latestRoundData() to detect feeds that return stale data without reverting. Implementing a secondary oracle feed (such as a Uniswap TWAP oracle) as a fallback when the primary Chainlink feed is unavailable would further improve resilience.

Support

FAQs

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

Give us feedback!