code-423n4 / 2024-06-panoptic-validation

0 stars 0 forks source link

Usage of `slot0` is extremely easy to manipulate #42

Open c4-bot-7 opened 5 months ago

c4-bot-7 commented 5 months ago

Lines of code

https://github.com/code-423n4/2024-06-panoptic/blob/153f0d82440b7e63075d55b0659706531431145f/contracts/PanopticFactory.sol#L321 https://github.com/code-423n4/2024-06-panoptic/blob/153f0d82440b7e63075d55b0659706531431145f/contracts/PanopticFactory.sol#L315-L391

Vulnerability details

Description

Usage of slot0 is extremely easy to manipulate

Impact

Pool lp value can be manipulated and cause other users to receive less lp tokens.

Vulnerability Detail

Panoptic is using slot0 to calculate several variables in their codebase: slot0 is the most recent data point and is therefore extremely easy to manipulate.

  function _mintFullRange(
    IUniswapV3Pool v3Pool,
    address token0,
    address token1,
    uint24 fee
   ) internal returns (uint256, uint256) {
      @audit-issue : use of slot0 can lead to price manipulation .
    (uint160 currentSqrtPriceX96, , , , , , ) = v3Pool.slot0();
  uint128 fullRangeLiquidity;
    unchecked {
        // Since we know one of the tokens is WETH, we simply add 0.1 ETH + 
     worth in tokens
        if (token0 == WETH) {
            fullRangeLiquidity = uint128(
                Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_WETH, 
   currentSqrtPriceX96)
            );
        } else if (token1 == WETH) {
            fullRangeLiquidity = uint128(
                Math.mulDivRoundingUp(
                    FULL_RANGE_LIQUIDITY_AMOUNT_WETH,
                    Constants.FP96,
                    currentSqrtPriceX96
                )
            );
        } else {
            // Find the resulting liquidity for providing 1e6 of both tokens
            uint128 liquidity0 = uint128(
                Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN, 
        currentSqrtPriceX96)
            );
            uint128 liquidity1 = uint128(
                Math.mulDivRoundingUp(
                    FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN,
                    Constants.FP96,
                    currentSqrtPriceX96
                )
            );
     fullRangeLiquidity = liquidity0 > liquidity1 ? liquidity0 : liquidity1;
        }
    }

  // The maximum range we can mint is determined by the tickSpacing of the 
     pool
    // The upper and lower ticks must be divisible by `tickSpacing`, so
    // tickSpacing = 1: tU/L = +/-887272
    // tickSpacing = 10: tU/L = +/-887270
    // tickSpacing = 60: tU/L = +/-887220
    // tickSpacing = 200: tU/L = +/-887200
    int24 tickLower;
    int24 tickUpper;
    unchecked {
        int24 tickSpacing = v3Pool.tickSpacing();
        tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing;
        tickUpper = -tickLower;
    }

    bytes memory mintCallback = abi.encode(
        CallbackLib.CallbackData({
            poolFeatures: CallbackLib.PoolFeatures({token0: token0, token1: token1, fee: fee}),
            payer: msg.sender
        })
    );

    return
        IUniswapV3Pool(v3Pool).mint(
            address(this),
            tickLower,
            tickUpper,
            fullRangeLiquidity,
            mintCallback
        );
}

The main problem can come there

     if (token0 == WETH) {
            fullRangeLiquidity = uint128(
                Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_WETH, 
   currentSqrtPriceX96)
            );
        } 

If the currentSqrtPriceX96 is increased by the attacker more then FULL_RANGE_LIQUIDITY_AMOUNT_WETH it will convert the fullRangeLiquidity to zero which can lead to the huge loss of the protocol .

Tools Used

Manual review

Recommended Mitigation Steps

To make any calculation use a TWAP instead of slot0.

Assessed type

Uniswap