Back to Blog

#8 Bunni Incident: Repeated Small Withdrawals Compound a Rounding Error into an $8.4M Drain

Code Auditing
February 12, 2026

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:

  1. Querying Liquidity Density:

    1. Invoke the liquidity density function ldf.query() which obtains the liquidity density outside the current price tick range.

    2. Invoke LiquidityAmounts.getAmountsForLiquidity() to get the density within the current tick range.

    3. Calculate the total liquidity density of token0 and token1 in both directions, denoted as totalDensity0 and totalDensity1.

    Notably, the function LiquidityAmounts.getAmountsForLiquidity() uses rounding up to ensure that the calculated token quantities are conservatively no less than the theoretical values.

  2. Calculate Available Balance

    The available balances used for liquidity calculations are denoted as balance0 and balance1. 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:

    • balance0=rawBalance0+reserve0idleBalancebalance0 = rawBalance0 + reserve0 - idleBalance

    • balance1=rawBalance1+reserve1balance1 = rawBalance1 + reserve1

  3. Estimate Effective Liquidity

    1. Estimate the liquidity each token can support based on its actual available balance (balance0 or balance1) and calculated total density (totalDensity0or totalDensity1).

    2. Select the smaller of the two estimates as the final effective total liquidity.

    The formula is as follows:

    L=min(balance0totalDensity0,balance1totalDensity1)L= min(\frac{balance0}{totalDensity0},\frac{balance1}{totalDensity1})

  4. 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:

(rawBalance,reserves,idleBalance)=(rawBalance,reserves,idleBalance)×(1sharestotalSupply)(rawBalance, reserves, idleBalance) \\= (rawBalance, reserves, idleBalance) \times (1-\frac{shares}{totalSupply})

Where:

  • shares is the number of liquidity shares the user removes;
  • totalSupply is 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**,**balance=rawBalance+reserveidlebalancebalance = rawBalance + reserve - idle balance. 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

  1. https://x.com/bunni_xyz/status/1962833866277744953

  2. https://etherscan.io/tx/0x1c27c4d625429acfc0f97e466eda725fd09ebdc77550e529ba4cbdbc33beb97b

  3. https://uniscan.xyz/tx/0x4776f31156501dd456664cd3c91662ac8acc78358b9d4fd79337211eb6a1d451

  4. https://x.com/bunni_xyz/status/1981160279871558114

  5. https://docs.bunni.xyz/docs/v2/overview

  6. https://github.com/Bunniapp/bunni-v2/blob/2b303b8c1b9f8afbb169d62ba52da93d6d2171fe/src/lib/QueryLDF.sol#L40

  7. https://blog.bunni.xyz/posts/exploit-post-mortem/


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.

Sign up for the latest updates
Zcash Orchard Soundness Bug Analysis | BlockSec Weekly
Security Insights

Zcash Orchard Soundness Bug Analysis | BlockSec Weekly

During the week of June 1, 2026, a critical soundness vulnerability was publicly disclosed in Zcash's Orchard shielded pool circuit, caused by a missing equality constraint in the halo2 ECC scalar multiplication gadget that could have enabled undetectable counterfeiting of ZEC within the Orchard pool through double-spending. The vulnerability, which existed for over four years since Orchard's activation in May 2022, was discovered by an AI-assisted security audit and patched through an emergency network upgrade (NU6.2). This single-event report covers the technical root cause (under-constrained ZK circuit relation), the AI-assisted discovery by researcher Taylor Hornby using Anthropic's Opus 4.8 model, the emergency response timeline, and the broader implications for the ZKP ecosystem.

Newsletter - May 2026
Security Insights

Newsletter - May 2026

In May 2026, the DeFi ecosystem experienced three major security incidents. Echo Protocol lost ~$76.7M due to an administrator key compromise that enabled unauthorized minting of unbacked eBTC on Monad, StablR suffered ~$12.8M from a multisig governance breach leading to unauthorized stablecoin issuance, and the Verus-Ethereum Bridge incurred ~$11.7M following a type-validation failure that allowed a crafted supplemental export to be misclassified as a valid primary export.

~$16M Lost: DxSale, SquidRouterModule & More | BlockSec Weekly
Security Insights

~$16M Lost: DxSale, SquidRouterModule & More | BlockSec Weekly

This weekly security report covers 5 notable attack incidents between May 25 and May 31, 2026, with combined losses of approximately $16M across BNB Chain, Ethereum, Base, Arbitrum, and Cosmos. Key incidents include the DxSale token locker exploit ($7.3M) involving three missing state updates compounded by a deployer key compromise, the SquidRouterModule exploit ($3.2M) caused by improper input validation in an Axelar Bridge integration that allowed forged cross-chain messages to drain 86 Safe wallets, and the Gravity Bridge signing key compromise ($5.4M). Other incidents involve a compromised deployer key (Stake DAO, $91K) and a vulnerable off-chain bridge backend (Alephium, $300K).

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit