# Pumps are not updated in the shift() and sync() functions, allowing oracle manipulation

Basin Well's shift() and sync() change reserves without updating Pumps, letting an attacker shift then swap back to feed manipulated reserves into the oracle.

- Payout: Contest reward
- Severity: high
- Program: Basin
- Credited: eeyore
- Disclosed: 2023-10-05
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-07-basin-findings/issues/136
- Library page: https://bugbountylibrary.com/r/c4%3A2023-07-basin%3AH-01
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:oracle-manipulation

**Target:** Well.sol AMM shift()/sync() and MultiFlowPump oracle
**Root cause:** shift() and sync() allocate a fresh reserves array instead of calling _updatePumps(), so reserve changes in the current block are not pushed to the Pumps before being overwritten by subsequent swaps.
**Steps:**
1. Deploy Well with MultiFlowPump (cap 50%/block, alpha 0.9).
2. increaseTime(120) to advance block.
3. Transfer amountIn of token0 to the Well and call `well.shift(tokens[1], 0, user)` to skew reserves in current block.
4. Call `well.swapFrom(tokens[1], tokens[0], amountOut, 0, user, max)` to restore reserves to original state.
5. swapFrom triggers _updatePumps with the manipulated reserves, poisoning the Pump oracle while attacker recovers funds.
**How found:** Code review of Well.sol: noticed swap functions call _updatePumps() but shift() and sync() build a fresh reserves array, violating the stated invariant that Pumps must be updated whenever reserves change in a block.
**Exploitation:** Attacker uses shift() to move reserves to a desired ratio within one block, then swaps back to recover capital; the swap's Pump update records the manipulated reserves, corrupting the oracle for any protocol consuming it.
**Insight:** When an AMM documents an invariant that oracles are updated on every reserve change, grep every state-mutating entry point (shift, sync, donate, skim) for the update call—missing one path lets you move reserves and restore them via a swap that writes the poisoned snapshot.
