# ConstantProductPool.burnSingle swap amount computations should use balance

ConstantProductPool.burnSingle computed swap output from reserves instead of balances, letting excess tokens in the pool cause underpaid swap amounts and fund loss.

- Payout: Audit contest
- Severity: high
- Program: Sushi
- Found by: cmichel
- Disclosed: 2021-11-22
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2021-09-sushitrident-findings/issues/96
- Library page: https://bugbountylibrary.com/r/c4%3A2021-09-sushitrident%3AH-11
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error

**Target:** ConstantProductPool.burnSingle / HybridPool.burnSingle AMM contract function
**Root cause:** burnSingle redeems amounts based on balance but computes the swap output via _getAmountOut using _reserve0/_reserve1 instead of balance0/balance1, so when balance > reserve the swap amount is understated.
**Steps:**
1. Deploy/observe a ConstantProductPool where balance0 > _reserve0 (tokens sent directly to contract).
2. Call `burnSingle` with liquidity that redeems amount0/amount1.
3. Observe swap output computed as `_getAmountOut(amount0, _reserve0 - amount0, _reserve1 - amount1)` using stale reserves.
4. Compare to sequential `burn` then `swap`, which would use updated balances, and note the shortfall returned to the caller.
**How found:** Reasoned that burnSingle is semantically burn-then-swap, so it must match sequential behavior; noticed redemption amounts used balance while swap pricing used reserves, an inconsistency that only manifests when balance > reserve.
**Exploitation:** Attacker (or any LP) calls burnSingle on a pool whose balance exceeds reserves; the swap leg is priced against lower reserves, returning less than the fair swap amount, leaving value in the pool that can be extracted by others — Sushi confirmed this can lead to loss of funds from the pool.
**Insight:** When a function composes two operations (burn+swap), diff its internal accounting against actually calling them sequentially; look for mixed use of cached reserves vs live balances, and check the balance>reserve case where they diverge.

