On September 2, 2025, the Bunni V2 protocol suffered a sophisticated exploit [1]. An attacker leveraged a critical vulnerability in its liquidity accounting mechanism to extract approximately 8.4 million USD from two liquidity pools: the USDC/USDT pool on Ethereum [2] and the weETH/ETH pool on Unichain [3].
The root cause was a rounding error in the protocol's update of idle pool balances during liquidity removal. This error led to a significant undervaluation of the total liquidity in the contract, creating an exploitable discrepancy between the theoretical and actual liquidity. The attacker then executed a precise sandwich attack to profit from this disparity.
This incident directly resulted in severe financial loss for the Bunni protocol, which subsequently declared bankruptcy on October 23, 2025 [4].
Background
Bunni V2 is an Automated Market Maker (AMM) protocol built on Uniswap V4. It implements its core logic through the hook mechanism and introduces innovations upon the concentrated liquidity algorithm of Uniswap V3, aiming to provide improved capital efficiency for Liquidity Providers (LPs) [5].
Specifically, the protocol enhances LP returns primarily through a Rehypothecation feature and a Rebalancing mechanism. The former allocates liquidity to external yield-generating protocols, securing base liquidity while capturing additional external yield. The latter continuously optimizes the distribution of liquidity across price ranges, increasing the active utilization of capital to boost fee income. These two mechanisms form the core innovations of the protocol upon the foundational concentrated liquidity model.
Rehypothecation
To enhance returns for Liquidity Providers, Bunni V2 employs a Rehypothecation strategy. This strategy allocates funds to different positions:
- rawBalance: A portion of the pool's reserves for a token is stored directly within Uniswap V4's
contract PoolManager. This serves as the instantly available liquidity for facilitating swaps. - reserves: The remainder is deposited into a specified ERC4626 vault. This allows users to earn additional external yield on these assets.
Therefore, the total assets for a pool are defined as: Pool Assets = rawBalance + underlying amount of reserves.
Rebalancing
To increase fee revenue, Bunni V2 implements a rebalance mechanism, which monitors the time-weighted average price. When the price change exceeds a threshold, liquidity is redistributed across different price ranges according to the Liquidity Distribution Function (LDF).
This reallocation can modify the token ratio required by the LDF, leaving a surplus in one token. This surplus is defined as the idle balance.
Thus, liquidity is divided into two parts:
- Active Balance: The portion allocated by LDF that participate in liquidity calculation.
- Idle Balance: The surplus not used for active liquidity.
Therefore, Pool Assets = Active Balance + Idle Balance.
Key functions: liquidity calculation and removals
This attack exploits two critical functions: queryLDF() and withdraw(). The function queryLDF() calculates the pool's liquidity for swaps, while the function withdraw() allows users to remove a proportional liquidity.
Functions `queryLDF()`
Due to the Rehypothecation strategy, the quantity of underlying assets is dynamic, and Bunni V2 does not store a fixed "total liquidity" value. Instead, the protocol provides the function queryLDF() to retrieve real-time liquidity when a swap occurs [6]. The execution process of this function consists of the following four steps:
-
Querying Liquidity Density:
-
Invoke the liquidity density function
ldf.query()which obtains the liquidity density outside the current price tick range. -
Invoke
LiquidityAmounts.getAmountsForLiquidity()to get the density within the current tick range. -
Calculate the total liquidity density of token0 and token1 in both directions, denoted as
totalDensity0andtotalDensity1.
Notably, the function
LiquidityAmounts.getAmountsForLiquidity()uses rounding up to ensure that the calculated token quantities are conservatively no less than the theoretical values.
-
-
Calculate Available Balance
The available balances used for liquidity calculations are denoted as
balance0andbalance1. The idle balance is deducted from the total balance of the corresponding token, excluding funds that do not participate in liquidity calculations.In this attack, where the pool's idle funds consisted of
token0, the calculation formulas are: -
-
Estimate Effective Liquidity
-
Estimate the liquidity each token can support based on its actual available balance (
balance0orbalance1) and calculated total density (totalDensity0ortotalDensity1). -
Select the smaller of the two estimates as the final effective total liquidity.
The formula is as follows:
-
-
Calculate Active Balances
Based on the determined total liquidity, the protocol calculates the actual quantity of tokens available for trading. This is defined as the Active Balance.
Function `withdraw()`
Bunni V2 provides function withdraw() for removing liquidity. Users remove liquidity proportional to their share of the pool's total funds. The protocol updates the rawBalance, reserves, and idleBalance in the same proportion. The adjustment formula is as follows:
Where:
sharesis the number of liquidity shares the user removes;totalSupplyis the total supply of liquidity tokens for that pool.
Vulnerability Analysis
The vulnerability originates in the function withdraw()'s calculation of the adjustment amount for the idle balance, which uses floor rounding (i.e., rounding down). This results in an overestimation of the idle balance.
Recalling the available balance formula**,**. An overestimated idle balance directly causes the available balance (balance0) used for liquidity calculations to be underestimated. Consequently, the estimated effective total liquidity is also undervalued. According to the Bunni Exploit Post Mortem [7], this rounding direction in liquidity calculations was intentionally employed. A lower calculated liquidity value leads to a higher price impact during swaps.
This design relies on a critical assumption: the balance ratio between the two tokens remains relatively balanced. Under normal conditions with adequate liquidity, the total liquidity values estimated separately for each token are typically close. The impact of the rounding error is therefore limited. However, when the available balance of the token carrying an idle balance becomes extremely low, the flaw emerges. In this scenario, the floor rounding error is significantly amplified.
The attacker exploited this vulnerability by performing a series of small withdrawals, rounding down the available balance of token0 from 28 wei to 4 wei. This drop far exceeded the proportion of liquidity shares actually burned. Meanwhile, the available balance of token1 remained at a relatively normal level. This imbalance created a significant arbitrage window. The next chapter provides a detailed numerical analysis.
Attack Analysis
Taking the Ethereum transaction [2] as an example, the attacker executed a three-stage attack:
- In the first stage, the attacker performed price manipulation to significantly deplete USDC's available balance (token0). This created the initial conditions necessary to amplify the subsequent rounding error.
- In the second stage, the core exploit was performed via a series of small withdrawals, causing the protocol to underestimate the pool's actual liquidity.
- In the third stage, the attacker executed two directional swaps to arbitrage the discrepancy between the protocol's underestimated liquidity and the pool's actual liquidity, ultimately extracting profit.
Stage1: Manipulating the price and reducing the target token balance
The attacker executed three swap transactions, manipulating the price of USDC (token0) relative to USDT (token1), driving it from an initial tick = -1 to tick = 5000. The main purpose was to deplete the pool's active balance of USDC, reducing it to an extremely low level of 28 wei. This created the initial conditions necessary to amplify the subsequent rounding error in the following phase.
Stage2: Exploiting withdrawals to amplify liquidity discrepancies
The attacker initiated 44 small withdrawals via function withdraw(). Due to the floor rounding used by this function when updating the idleBalance, the protocol's idle balance became overestimated. This further underestimated the USDC available balance in function queryLDF(). After these repeated operations, the USDC available balance was abnormally suppressed from 28 wei down to 4 wei. This represented an actual reduction of 85.7%, far exceeding the theoretical proportion corresponding to the removed liquidity shares (i.e., 8.998105442969973e-07%). At this point, the estimated liquidity from USDC in the pool was severely underestimated.
Stage3: Executing arbitrage and realizing profits
The attacker then executed two directional swaps, constituting an operation similar to a sandwich attack.
Step1: The attacker used a large amount of USDT to swap for USDC. At this time, the internal liquidity calculation was severely undervalued based on the underestimated USDC balance. This large swap pushed the price to an extreme, moving the tick from 5,000 to 839,189.
Step2: After the extreme price was formed, the attacker immediately reversed the operation, swapping a portion of the USDC back for USDT. Since the pool's price was now severely misaligned, the return value of the queryLDF() function for USDC liquidity density dropped to 1. That made the liquidity value estimated based on USDC greater than the estimated value based on USDT.
According to the protocol's logic of selecting the smaller value, the total liquidity is determined by the USDT balance. This caused the calculated liquidity to immediately revert from an undervalued state to a normal level, resulting in a sudden increase. The attacker exploited this shift, exchanging a minimal amount of USDC for a large quantity of USDT, thereby completing the arbitrage and realizing a profit.
Summary
This incident was ultimately caused by rounding errors in adjusting idle balances during liquidity removal. While this floor function design was intended as a security strategy in liquidity calculations, it failed to adequately consider critical boundary conditions. Specifically, rounding errors are non-linearly amplified when token balances are severely imbalanced.
This incident reveals the coupling risks between multiple modules in complex DeFi protocols. Even if the rounding rules of individual components are designed conservatively, a lack of consistent security validation across the entire system can lead to critical vulnerabilities that can be exploited under specific circumstances.
Reference
-
https://etherscan.io/tx/0x1c27c4d625429acfc0f97e466eda725fd09ebdc77550e529ba4cbdbc33beb97b
-
https://uniscan.xyz/tx/0x4776f31156501dd456664cd3c91662ac8acc78358b9d4fd79337211eb6a1d451
About BlockSec
BlockSec is a full-stack blockchain security and crypto compliance provider. We build products and services that help customers to perform code audit (including smart contracts, blockchain and wallets), intercept attacks in real time, analyze incidents, trace illicit funds, and meet AML/CFT obligations, across the full lifecycle of protocols and platforms.
BlockSec has published multiple blockchain security papers in prestigious conferences, reported several zero-day attacks of DeFi applications, blocked multiple hacks to rescue more than 20 million dollars, and secured billions of cryptocurrencies.
-
Official website: https://blocksec.com/
-
Official Twitter account: https://twitter.com/BlockSecTeam



