The StrataxOracle contract acts as a price oracle wrapper around Chainlink price feeds. The getPrice() function calls Chainlink's latestRoundData() but discards the updatedAt, roundId, startedAt, and answeredInRound return values. The only validation performed is that answer > 0. There is no staleness threshold parameter defined anywhere in the contract, meaning the oracle has no concept of stale data and will accept a price that is hours, days, or even weeks old as valid.
The getRoundData() function similarly performs zero validation on returned data:
The stale prices returned by getPrice() are consumed by multiple critical financial operations in Stratax.sol. The calculateOpenParams() function calls IStrataxOracle(strataxOracle).getPrice() for both the collateral token and borrow token prices when their prices are not explicitly provided. The calculateUnwindParams() function calls getPrice() for both the debt token and collateral token prices. The _executeUnwindOperation() function similarly calls getPrice() for both the debt token and collateral token prices. In each case, the returned price is used directly in arithmetic operations that determine flash loan amounts, borrow amounts, and collateral withdrawal amounts, with no freshness check at any level of the call chain.
When a Chainlink aggregator experiences downtime or delayed updates (for example, during network congestion, L2 sequencer outages, or aggregator contract issues), the last reported price becomes stale. If the contract owner (or automated systems acting on their behalf) creates a leveraged position or unwinds a position during such an event, the stale price data causes the collateral to be overvalued or undervalued. An overvalued collateral assessment results in a position that appears healthy but is actually under-collateralised relative to true market prices. When the Chainlink feed resumes and reports the correct price, the position may already be below the liquidation threshold on Aave, resulting in liquidation and loss of funds. During an unwind operation, stale prices cause incorrect calculation of collateralToWithdraw in _executeUnwindOperation(), potentially withdrawing too little or too much collateral.
It should be noted that all state-changing functions that use oracle prices (createLeveragedPosition() and unwindPosition()) are restricted to onlyOwner. This means external attackers cannot directly trigger the vulnerable code paths. However, the owner or automated systems acting on the owner's behalf can still be affected by stale prices during a feed downtime event. The calculateOpenParams() and calculateUnwindParams() view functions used for parameter computation also consume stale prices without any warning.
This issue has a high impact as stale price data flows directly into leverage calculations, collateral valuations, and unwind operations. Incorrect prices can lead to under-collateralised positions that face immediate liquidation on Aave, or unfavourable unwind operations that lose funds. The oracle serves as the single source of truth for all financial calculations in the protocol.
This issue has a medium likelihood as Chainlink aggregator delays are not frequent but do occur, particularly during high network congestion, L2 sequencer outages, or extreme market volatility -- precisely when accurate prices are most critical. The onlyOwner access restriction reduces the exploitability surface, as only the owner can trigger state-changing operations. However, the complete absence of any staleness parameter means there is no defence from deployment, and no post-deployment configuration step that could address it.
Add a staleness threshold parameter to StrataxOracle and validate the updatedAt timestamp returned by latestRoundData() against this threshold in the getPrice() function. Validate that updatedAt > 0 to ensure the round is complete, and validate that block.timestamp - updatedAt does not exceed the configured threshold to ensure price freshness. Additionally, validate that answeredInRound >= roundId to guard against stale rounds. Apply the same validation to getRoundData() or remove that function if it is not required.
Allow per-feed staleness thresholds via a mapping, since different Chainlink feeds have different heartbeat intervals (for example, ETH/USD has a 1-hour heartbeat while some feeds have 24-hour heartbeats).
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.