# fetchPrice can return different prices in the same transaction

eBTC PriceFeed state machine returns last-good price then current Chainlink price within one tx when CL deviates >50% and no fallback oracle is set, enabling CDP arbitrage.

- Payout: Contest reward
- Severity: medium
- Program: eBTC Protocol
- Credited: shaka
- Disclosed: 2024-02-13
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-10-badger-findings/issues/310
- Library page: https://bugbountylibrary.com/r/c4%3A2023-10-badger%3AM-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:chained technique:source-review vuln:business-logic vuln:oracle-manipulation

**Target:** PriceFeed.sol oracle state machine (fetchPrice)
**Root cause:** When status is usingChainlinkFallbackUntrusted and CL deviates >50%, fetchPrice returns lastGoodPrice and sets bothOraclesUntrusted; a second call in the same tx sees bothOraclesUntrusted with no fallback set, CL not broken/frozen, and returns the current CL price.
**Steps:**
1. Set status to usingChainlinkFallbackUntrusted with fallbackCaller == address(0).
2. Mock Chainlink price to >2x previous round.
3. Call `fetchPrice()` -> returns lastGoodPrice, status becomes bothOraclesUntrusted.
4. Call `fetchPrice()` again same block -> returns current Chainlink price, status back to usingChainlinkFallbackUntrusted.
5. Exploit: flash-loan eBTC, `redeemCollateral` at lastGoodPrice, then `openCdp` at new CL price to capture collateral surplus.
**How found:** Code-reading of the PriceFeed state machine: noticed the two branches (CASE 5 line 340 and CASE 3 line 220) handle the >50% deviation and missing fallback inconsistently, so consecutive calls in one tx flip status and return different prices.
**Exploitation:** Attacker flash-borrows eBTC via BorrowerOperations.flashLoan, redeems collateral at the stale last-good price, opens a CDP with the redeemed collateral at the higher current Chainlink price to mint more eBTC than borrowed, repays flash loan and withdraws surplus collateral at protocol expense.
**Insight:** In oracle state machines, check that every status transition is idempotent within a transaction: a branch that returns lastGoodPrice and downgrades status can be undone by the next call if the downgraded status has a permissive path (here, missing-fallback shortcut). Trace all status->status edges for same-block price divergence.
