Description
On September 2, 2025, Bunni was exploited for $8.4M through a rounding error in the protocol’s withdrawal mechanism. The attack affected two pools: weETH/ETH on Unichain and USDC/USDT on Ethereum.Core Vulnerability Mechanism
The attack exploited a rounding error in Bunni’s idle balance update mechanism during withdrawals. The vulnerability occurred inBunniHubLogic::withdraw()
:
mulDiv
function was intentionally rounded down during development, with the assumption that this would round up the idle balance and round down the active balance. The developers considered this “safe” since lower liquidity meant more price impact during swaps, favoring the pool.
Why This Assumption Failed:
While the rounding direction was safe for individual operations, it became exploitable when combined with multiple operations in sequence.
Attack Execution
The exploit consisted of three steps:Step 1: Swap with Flashloaned Funds
- Attacker flashborrowed 3M USDT from external venues (Uniswap v3 and Morpho)
- Made multiple swaps from USDT (token1) to USDC (token0)
- Pushed the pool’s spot price tick to 5000 (1 USDC = 1.68 USDT)
- Pool’s active balance in USDC decreased to 28 wei
Step 2: Exploiting Rounding Errors
- Attacker made 44 tiny withdrawals in a single transaction that exploited the rounding error
- USDC active balance decreased from 28 wei to 4 wei (85.7% decrease)
- This decrease was disproportionate to the amount of liquidity shares being burned
- Total liquidity decreased by 84.4% from 5.83e16 to 9.114e15
Step 3: Sandwich Attack
- With liquidity artificially decreased, attacker made a large swap from USDT to USDC
- Spot price tick pushed to 839189 (1 USDC = 2.77e36 USDT)
- The swap caused total liquidity to increase by 16.8% from 9.114e15 to 1.065e16
- Attacker made a second swap from USDC to USDT at inflated prices
- After repaying flashloan, attacker gained 1.33M USDC and 1M USDT
Why Unichain USDC/USD₮0 Pool Wasn’t Exploited
The largest Bunni pool (Unichain USDC/USD₮0) was spared due to insufficient flashloan liquidity:- Required 17M flashloan but only 11M was available on Euler
- Uniswap v4 couldn’t be used due to conflicts with step 2 withdrawals
- Pool assets were rehypothecated to Euler, making them unavailable for exploitation
Root Cause Analysis
The Issue: A rounding direction that’s safe for individual operations may not be safe for multiple operations in sequence. Technical Details:- Bunni uses two estimates of total liquidity and takes the smaller one
- The series of withdrawals disproportionately reduced
totalLiquidityEstimate0
(USDC-based) - The first swap in step 3 caused
totalDensity0X96
to become miniscule (1 wei) - This made
totalLiquidityEstimate1
(USDT-based) the chosen estimate, which was larger than the artificially decreased value from step 2 - The liquidity increase was then sandwiched by the attacker
Proposed Solution
A withdrawal proportionality assertion could have prevented this attack by ensuring that active balance decreases are proportional to shares burned:How This Assertion Prevents the Attack
What it does:- Monitors all withdrawals to the Bunni protocol
- Calculates expected balance decreases based on shares burned and total supply
- Uses dynamic tolerance that scales with transaction size (1% of expected decrease + 1 wei minimum)
- Reverts on disproportionate changes that exceed the tolerance
- Expected decrease: Proportional to shares burned (small amount, e.g., 0.1 wei)
- Actual decrease: 24 wei (28 - 4)
- Dynamic tolerance: 1% of expected + 1 wei (e.g., 1 wei for tiny withdrawals)
- Result: The 24 wei actual decrease vs 0.1 wei expected would exceed the 1 wei tolerance, triggering the assertion