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:
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.
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:- 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. - 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 provides1 wei
of liquidity. This mints1 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.
- Calls
- Next, the innocent user's transaction executes. Since
existingShareSupply = 1
andaddedLiquidity < existingLiquidity
, the user will be minted zero shares (despite providing $10,000 worth of liquidity). - 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.
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