# Chainlink Oracles may return stale prices or may be unusable when aggregator roundId is less than 50

Wise Lending's oracle heartbeat calibration subtracts from a proxy roundId combining phaseId and aggregator round, causing reverts or stale prices when aggregator roundId < 50.

- Payout: Contest reward
- Severity: medium
- Program: Wise Lending
- Credited: nonseodion
- Disclosed: 2024-05-03
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-02-wise-lending-findings/issues/276
- Library page: https://bugbountylibrary.com/r/c4%3A2024-02-wise-lending%3AM-02
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:dos vuln:oracle-manipulation

**Target:** Chainlink oracle integration in WiseOracleHub/OracleHelper
**Root cause:** The proxy roundId packs phaseId in the upper bits and aggregator roundId in the lower uint64; subtracting a flat iteration count from the full uint80 can cross into a nonexistent phase/round, reverting getRoundData. The iteration count check compares the full uint80 (always >50) instead of the trimmed uint64 aggregator round.
**Steps:**
1. Read `_getIterationCount()`: `res = _latestAggregatorRoundId < MAX_ROUND_COUNT ? _latestAggregatorRoundId : MAX_ROUND_COUNT` where `_latestAggregatorRoundId` is the full proxy roundId (uint80).
2. Observe proxy roundId = (phaseId << 64) | aggregatorRoundId, so it is always >= 2^64 > 50; iterationCount is always 50.
3. In `_recalibratePreview()`, loop `while (i < iterationCount)` calling `_getRoundTimestamp(token, latestRoundId - i)`.
4. With proxy roundId `0x40000000000000010` (phase 4, aggregator round 16), after 16 iterations `latestRoundId - i` = `0x40000000000000000`, which does not exist; `getRoundData` reverts.
5. Caller must wait until aggregator roundId > 50 (up to 50x heartbeat, e.g. 50 days for BTC/ETH) before calibration succeeds.
**How found:** Code-reading of the Chainlink proxy roundId encoding (phaseId in high bits, uint64 counter in low bits) against the flat subtraction in the calibration loop; noticed the uint80 comparison makes the <50 branch unreachable.
**Insight:** When integrating Chainlink, remember proxy roundIds pack phaseId in the upper 64 bits; any arithmetic like `latestRoundId - i` can silently cross phase boundaries. Trim to uint64 before comparing or iterating historical rounds.
