# Wrong accounting enables attackers to prevent users from withdrawing their funds

Donating 1 USDC to Opal's Omnipool inflates totalUnderlying_, skewing per-pool target allocations so withdrawals can never fully drain pools and revert.

- Payout: Contest reward
- Severity: high
- Program: Opal
- Credited: 0xadrii
- Disclosed: 2024-03-30
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_opal_feb2024.pdf#3.2.20
- Library page: https://bugbountylibrary.com/r/cantina%3Aopal_feb2024%3A3.2.20
- Tags: domain:web3 impact:dos kind:contest platform:cantina sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:dos

**Target:** Omnipool.sol withdraw/_withdrawFromAura/_getWithdrawPool
**Root cause:** totalUnderlying_ passed to _withdrawFromAura includes idle underlying balance, so targetAllocation per pool is inflated by any donated tokens; pools then never release enough to satisfy withdrawalsRemaining.
**Steps:**
1. Deploy Omnipool with three Aura pools (weights 40/30/30) and price feeds.
2. user and user2 each deposit 50 USDC via `omnipool.deposit(50e6, 0)`.
3. Attacker transfers 1 USDC directly: `token.transfer(address(omnipool), 1e6)`.
4. User calls `omnipool.withdraw(lpToken.balanceOf(user), 0)` — reverts `error retrieving withdraw pool`.
5. User calls `omnipool.withdraw(2e6, 0)` — also reverts, since idle 1 USDC inflates targetAllocation for every pool.
**How found:** Reading withdraw() flow: noticed allocatedUnderlying_ (deployed + idle balanceOf) is passed as totalUnderlying_ into _withdrawFromAura, then traced how _getWithdrawPool derives targetAllocation from that inflated total, hypothesizing a donation could make pools unreachable.
**Exploitation:** Attacker needs only 1 USDC and no special role: a direct ERC20 transfer to the Omnipool raises the idle balance counted in totalUnderlying_. Every subsequent withdraw computes inflated targetAllocations, so _getWithdrawPool eventually skips all pools (currentAlloc <= targetAllocation_) and reverts, blocking all users from withdrawing.
**Insight:** When a share/weight accounting function mixes deployed funds with a live balanceOf of idle tokens, any direct transfer shifts the denominator; test donation attacks against every function that reads totalUnderlying_ or targetAllocation.
