Uniswap v4 pool initialization can be frontrunned, setting an arbitrary price, and stealing tokens
Description:
SpiritFactory::_setupUniswapPool() mints a single sided position, from the current tick to the max tick. Thus, users can buy Child tokens for at least a price of the tick, which goes up as liquidity decreases.
However, an attacker can predict the address of the Child token (deterministic), or frontrun the transaction, and initialize the pool to the lowest price possible. This will make it add liquidity in the full range of the Uniswap pool, and an attacker can get all Child very cheaply.
function _setupUniswapPool(address childToken, uint256 childTokenAmount, uint160 initialSqrtPriceX96)
internal
returns (uint256 tokenId)
{
// Ensure tokens are in the correct order (lower address first)
Currency currency0 =
childToken < address(SPIRIT) ? Currency.wrap(address(childToken)) : Currency.wrap(address(SPIRIT));
Currency currency1 =
childToken > address(SPIRIT) ? Currency.wrap(address(childToken)) : Currency.wrap(address(SPIRIT));
// Create the pool key
PoolKey memory poolKey = PoolKey({
currency0: currency0,
currency1: currency1,
fee: DEFAULT_POOL_FEE,
tickSpacing: DEFAULT_TICK_SPACING,
hooks: IHooks(address(0))
});
// Initialize the pool
POSITION_MANAGER.initializePool(poolKey, initialSqrtPriceX96); //@audit anyone can frontrun this
tokenId = _mintSingleSidedLiquidityPosition(childToken, childTokenAmount, poolKey);
}The key thing is that POSITION_MANAGER.initializePool() doesn't revert, it fails silently, so there is nothing the admin can do:
abstract contract PoolInitializer_v4 is ImmutableState, IPoolInitializer_v4 {
/// @inheritdoc IPoolInitializer_v4
function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24) {
try poolManager.initialize(key, sqrtPriceX96) returns (int24 tick) {
return tick;
} catch {
return type(int24).max;
}
}
}Impact:
Stolen Child
Recommended Mitigation:
Check the return value of the pool initialization, it must not be the maximum tick, revert if it is. Additionally, add a salt to the child token address generation, so any name/symbol token can be created. This may cause conflicts, so make sure to track the deployed name/symbol pair (can just add a mapping in the factory).
0xSimao:
Fixed in PR #8.