The StrataxOracle contract serves as the price oracle for the Stratax protocol, providing token prices used in all leverage calculations. The getPrice function retrieves the latest price from Chainlink's latestRoundData() call. A properly implemented oracle consumer should validate multiple fields from the response: updatedAt for staleness, roundId for round completeness, and answeredInRound to ensure the answer was finalized.
The getPrice function only checks that answer > 0, discarding roundId, startedAt, updatedAt, and answeredInRound entirely. This means the oracle will return prices that are hours or days old during periods of Chainlink downtime, network congestion, or feed deprecation. Stale prices directly feed into calculateOpenParams and calculateUnwindParams, causing incorrect leverage and collateral calculations.
Likelihood:
Chainlink price feeds have historically experienced downtime and delayed updates during periods of extreme market volatility (e.g., LUNA crash, FTX collapse). During these events, latestRoundData() continues to return the last successfully reported price while updatedAt lags far behind block.timestamp.
The oracle also accepts data from incomplete rounds where answeredInRound < roundId, which occurs when Chainlink nodes have not yet reached consensus on a new price.
Impact:
Leveraged positions created during oracle staleness use an incorrect price, resulting in positions that are immediately under-collateralized or over-collateralized relative to the actual market price.
During a rapid price drop, a user can create a position using a stale (higher) price, effectively borrowing more than the collateral supports, leaving bad debt in the Aave pool.
This Foundry test deploys a mock Chainlink price feed that returns a valid price (2500e8) but with an updatedAt timestamp set 24 hours in the past. Despite the data being a full day stale, getPrice returns it without revert because the only check performed is answer > 0.
Add three additional validation checks after retrieving latestRoundData: ensure the round is complete (updatedAt > 0), the data is fresh (block.timestamp - updatedAt <= MAX_STALENESS), and the answer was finalized in the current round (answeredInRound >= roundId). Define MAX_STALENESS as a configurable constant (e.g., 3600 seconds for a 1-hour heartbeat).
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.