# Incorrect assumption that Chainlink always increments roundId by 1

Bima's PriceFeed assumes Chainlink roundId increments by 1, so after an aggregator version upgrade the previous-round lookup fails and fetchPrice reverts, DoSing troves and liquidations.

- Payout: Contest reward
- Severity: medium
- Program: Bima
- Credited: T1MOH
- Disclosed: 2025-02-05
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_bima_december2024.pdf#3.2.7
- Library page: https://bugbountylibrary.com/r/cantina%3Abima_december2024%3A3.2.7
- Tags: domain:web3 impact:dos kind:contest platform:cantina sev:medium tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:dos vuln:oracle-manipulation

**Target:** Chainlink price feed consumer (PriceFeed / TroveManager)
**Root cause:** Chainlink roundId packs aggregator version in high bits plus the actual round id; subtracting 1 to get the previous round is wrong once the aggregator version changes, so getRoundData(_currentRoundId - 1) returns a stale/empty round.
**Steps:**
1. Deploy MockOracle_PoC with roundId 12345 and a valid price; fetchPrice() succeeds.
2. Call upgradeAggregatorVersionAndUpdatePrice() which sets roundId = (1<<64) + roundId + 1.
3. Skip past heartbeat window; call sbtc2TroveManager.fetchPrice().
4. Observe _fetchPrevFeedResponse queries roundId-1 which no longer maps to the prior round, prevResponse is stale, _isFeedWorking returns false, and fetchPrice reverts with PriceFeed__FeedFrozenError.
**How found:** Reviewer read Chainlink docs noting roundId packs aggregator version and actual round id, then spotted the unchecked `_currentRoundId - 1` in _fetchPrevFeedResponse and reasoned it breaks across aggregator upgrades; confirmed with a Foundry PoC simulating a version bump.
**Exploitation:** Not attacker-triggered directly; occurs naturally after any Chainlink aggregator version upgrade, causing fetchPrice to revert and blocking openTrove, redeemTrove, closeTrove and liquidations until manual intervention.
**Insight:** When consuming Chainlink getRoundData, never derive the previous round as roundId-1; the high 64 bits are the aggregator version, so a version upgrade jumps the roundId. Track prior rounds explicitly or use phase-aware decoding.
