The StrataxOracle.getPrice() function retrieves prices from Chainlink without validating freshness. It calls latestRoundData() but discards the updatedAt, roundId, and answeredInRound return values, checking only that answer > 0. This allows arbitrarily stale prices to be consumed by all price-dependent calculations in Stratax.
In StrataxOracle.sol:64-74:
Missing validations:
updatedAt timestamp: No check that the price was recently updated — prices could be hours, days, or weeks old
answeredInRound >= roundId: No check that the answer was computed in the current round
This affects three price-dependent operations in Stratax:
calculateOpenParams() (Stratax.sol:393-402) — Fetches oracle prices when collateralTokenPrice or borrowTokenPrice is zero. A stale price distorts the flash loan and borrow amount calculations, producing incorrect leverage parameters. Note: Aave's own health factor check at L526 (require(healthFactor > 1e18)) uses Aave's independent oracle as a backstop, so over-leveraged positions would still be rejected by Aave. The impact here is suboptimal parameter estimation that either reverts inside Aave or produces unintended leverage ratios.
calculateUnwindParams() (Stratax.sol:461-462) — Directly calls getPrice() for both debt and collateral tokens. A stale price produces an incorrect collateralToWithdraw value that flows into the unwind operation.
_executeUnwindOperation() (Stratax.sol:570-571) — This is the most critical usage. Inside the flash loan callback, the stale oracle price directly determines how much collateral to withdraw from Aave at L575-577:
If the stale price overvalues collateral (e.g., reports $4000 when actual is$2000), too little collateral is withdrawn. The subsequent swap produces insufficient debt tokens, and the flash loan repayment fails at L588 (require(returnAmount >= totalDebt, "Insufficient funds to repay flash loan")). This results in a denial of service — the user cannot unwind their position until the oracle updates.
If the stale price undervalues collateral, excess collateral is withdrawn, reducing the remaining position's health factor more than necessary.
Likelihood:
Chainlink feeds can become stale during network congestion, aggregator node failures, or extreme market volatility (e.g., feeds experienced delays during the LUNA/UST collapse)
These events are uncommon on Ethereum mainnet but do occur during the exact high-volatility conditions when accurate pricing matters most
No special attacker role required — the vulnerability exists in normal operation whenever a feed goes stale
The protocol has no circuit breaker or fallback mechanism for stale oracle data
Impact:
DoS on unwind operations: When the stale Stratax oracle price diverges from the actual price, the collateral withdrawal calculation in _executeUnwindOperation() produces an incorrect amount. If the stale price overvalues collateral, too little is withdrawn, the swap can't cover the flash loan, and the transaction reverts — the user is unable to unwind their position until the oracle resumes updates
Suboptimal position parameters: calculateOpenParams() returns incorrect flash loan and borrow amounts, causing positions to be created with unintended leverage ratios. Aave's health factor check (L526) acts as a safety net against positions that are outright unsafe, but cannot correct the parameter distortion itself
Excess collateral withdrawal during unwind: If the stale price undervalues collateral, more collateral is withdrawn from Aave than necessary, unnecessarily degrading the position's health factor
The PoC demonstrates three things:
A week-old price is silently accepted by getPrice()
A year-old price is silently accepted by getPrice()
Stale prices distort the collateral-to-debt ratio calculation used by _executeUnwindOperation(), causing 50% less collateral to be withdrawn — which would cause the flash loan repayment to revert (DoS)
Verified: All 3 tests pass with forge test --match-path test/OracleStaleness.t.sol -vvv.
Add freshness checks to validate Chainlink data before use. The fix validates updatedAt is recent and answeredInRound >= roundId:
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.