Timeless Finance released a product called Bunni. The purpose of this is to make Uniswap v3 liquidity pools composable. This allows loans to be taken out via Uniswap liquidity and aims to have lower gas fees.
When a user passes in funds and it mints tokens, the function _mintShares() is called. If the ERC20 token has zero supply, then the minted shares are simply the amount of liquidity added. Otherwise, it mints the amount proportional to the percentage of the total liquidity.
In the second case, this is done with mulDiv(totalSupply,liquidityAdded,existingLiquidity). It should be noted that the division will round down. If totalSupply * addedLiquidity is less than existingLiquidity then this value could be 0.
An attacker can take advantage of this scenario with the following steps:
- A user deposits funds into the new contract. For example, let's use 10K in liquidity.
- A MEV bot sees this and frontruns the transaction with two steps:
- Provide 1 wei of liquidity to the contract to make the supply 1.
- Provide 10,001 of liquidity to the Uniswap pool on behalf of Bunni. This is possible with the standard Uniswap functions.
- The users transaction executes. The math turns out as follows:
1 * $10K / 10,001. This will round down to 0.
- Withdraw all of the liquidity, including the profiting 10K.
The attacker owns all of the original shares from the 1 wei deposit. Since the attackers owns all of the shares and zero shares were minted for the user depositing 10K, withdrawing the funds from the contract will get all of the funds!
To fix this problem, there are two main ways. First, requiring a minimum deposit to start with could make this infeasible. The other solution would be having a check to ensure that it's not possible to create zero shares.
The twitter thread links to
code4rena and by
Yannis Smaragdakis, which have similar findings. Overall, interesting find that exploits weird math.