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
deposittransaction through the public mempool. - An attacker is running a MEV searcher, which notices the
deposittransaction. The searcher frontruns with their own transaction that does two things:- Calls
depositon the same position as the innocent user, but only provides1 weiof liquidity. This mints1 weiof ERC20 to the attacker. - Calls
minton 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 = 1andaddedLiquidity < 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
withdrawcall. 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