code-423n4 / 2024-08-superposition-validation

0 stars 0 forks source link

Newly created pool.rs contracts can have their initializers frontrunned #211

Closed c4-bot-5 closed 1 month ago

c4-bot-5 commented 1 month ago

Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/pool.rs#L49-L61

Vulnerability details

Impact

Initializers can be frontrunned, requiring more pools to be deployed.

Proof of Concept

Pools can be created through create_pool_D650_E2_D0() in lib.rs by the seawater_admin. This function calls self.pools.setter(pool).init(price, fee, tick_spacing, max_liquidity_per_tick).

  pub fn create_pool_D650_E2_D0(
        &mut self,
        pool: Address,
        price: U256,
        fee: u32,
        tick_spacing: u8,
        max_liquidity_per_tick: u128,
    ) -> Result<(), Revert> {
        assert_eq_or!(
            msg::sender(),
            self.seawater_admin.get(),
            Error::SeawaterAdminOnly
        );

>       self.pools
            .setter(pool)
            .init(price, fee, tick_spacing, max_liquidity_per_tick)?;

The init() function in pool.rs checks that the sqrt_price is not set.

   pub fn init(
        &mut self,
        price: U256,
        fee: u32,
        tick_spacing: u8,
        max_liquidity_per_tick: u128,
    ) -> Result<(), Revert> {
        assert_eq_or!(
>           self.sqrt_price.get(),
            U256::ZERO,
            Error::PoolAlreadyInitialised
        );

This init() function can be frontrunned before create_pool_D650_E2_D0() is called to set the values. After that, init() cannot be called already.

Tools Used

Manual Review

Recommended Mitigation Steps

In Uniswap, the pool contract is created and initialized at the same time:


    /// @inheritdoc IUniswapV3Factory
    function createPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external override noDelegateCall returns (address pool) {
        require(tokenA != tokenB);
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0));
        int24 tickSpacing = feeAmountTickSpacing[fee];
        require(tickSpacing != 0);
        require(getPool[token0][token1][fee] == address(0));
>       pool = deploy(address(this), token0, token1, fee, tickSpacing);
        getPool[token0][token1][fee] = pool;
        // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses
        getPool[token1][token0][fee] = pool;
        emit PoolCreated(token0, token1, fee, tickSpacing, pool);
    }

Or make sure init() can be only called by lib.rs.

Assessed type

Invalid Validation