Bunni Bug Report

Bunni is a new protocol that aggregates Uniswap V3 LP positions into fungible ERC20 tokens, making LP positions more easily integrated with other DeFi applications. After the protocol's announcement, I looked through Bunni's code and discovered an interesting exploit that the protocol was vulnerable to. The exploit would have allowed a MEV searcher to steal all early deposits sent through the public mempool.

To understand the exploit, we must first examine how Bunni mints its ERC20 tokens. Similar to how many other protocols operate, Bunni ERC20 tokens represent "shares" of the TVL of a position. To calculate the number of "shares" to mint, the contract considers two cases:
function mintShares(/* ... */) internal {
    /* ... */
    uint256 existingShareSupply = shareToken.totalSupply();
    if (existingShareSupply == 0) {
        shares = addedLiquidity;
    } else {
        shares = FullMath.mulDiv(
            existingShareSupply,
            addedLiquidity,
            existingLiquidity
        );
    }
    shareToken.mint(recipient, shares);
}
  • If there are currently zero shares, the amount minted is equal to the amount of liquidity provided.
  • If shares already exist, the amount minted is proportional to the percentage of liquidity provided. In other words, supplying x% of the protocol's liquidity will give you x% of the corresponding ERC20 token supply.
Unfortunately, this code is missing a subtle but crucial check. The function mulDiv(a,b,c) can be thought of as floor(a*b/c), so if a user were to deposit such thatexistingShareSupply*addedLiquidity was less than existingLiquidity, zero shares would be minted. Can this realistically happen? Well, consider the following scenario:
  1. An innocent user wants to provide $10,000 worth of liquidity to a Bunni position that has never been deposited in before (since the protocol just launched, this is expected to happen frequently). To provide liquidity, they send a deposit transaction through the public mempool.
  2. An attacker is running a MEV searcher, which notices the deposit transaction. The searcher frontruns with their own transaction that does two things:

    • Calls deposit on the same position as the innocent user, but only provides 1 wei of liquidity. This mints 1 wei of ERC20 to the attacker.

    • Calls mint on the underlying Uniswap V3 pool to mint $10,001 worth of liquidity to the Bunni protocol. In Uniswap V3, anyone can provide liquidity on your behalf by setting the recipient address.
  3. Next, the innocent user's transaction executes. Since existingShareSupply = 1 and addedLiquidity < existingLiquidity, the user will be minted zero shares (despite providing $10,000 worth of liquidity).
  4. Finally, the MEV searcher backruns the user's transaction with a withdraw call. Since they own 100% of the ERC20 supply, they will be given 100% of the protocol's liquidity, including the $10,000 from the innocent user. Since the ERC20 supply would become zero after this, the attacker can repeat this exploit.
This is a variant of a well-known attack, the key difference being the attacker provides liquidity directly to the protocol instead of directly transferring some tokens. The common fix for this vulnerability is to mint some of the initial shares to an unusable address (like the zero address). Since the attacker would then operate with a larger totalSupply that can't be manipulated, the cost of the attack becomes larger. Choosing large enough values will make the attack infeasible.


October 14, 2022