code-423n4 / 2024-04-panoptic-findings

0 stars 0 forks source link

Missing slippage control system. Users may lose a lot of funds due to front-running MEV bots #46

Closed c4-bot-1 closed 2 months ago

c4-bot-1 commented 3 months ago

Lines of code

https://github.com/code-423n4/2024-04-panoptic/blob/main/contracts/PanopticFactory.sol#L335-L380

Vulnerability details

Impact

The absence of slippage control in liquidity-related functions could result in users receiving significantly less output (tokens) in their transactions than expected. Malicious actors, such as MEV bots, can monitor pending transactions and manipulate the market (e.g., by front-running) to capitalize on this vulnerability.

Proof of Concept

Scenario:

A user intends to swap 1 ETH for Token X on a decentralized exchange using a function that lacks slippage control. A MEV bot observes the pending transaction in the mempool. The MEV bot strategically executes a large trade that depletes the immediate liquidity for Token X, causing its price to spike within the pool. The user's original transaction executes when the price of Token X is significantly higher. The user receives far less Token X than initially anticipated due to the price manipulation.

Technical Explanation:

The vulnerable function relies on the spot price from the liquidity pool at the time of execution. This spot price can be highly volatile. Without limits, the transaction proceeds even if the price drastically changes between submission and execution. This creates an opportunity for malicious actors to front-run and profit at the expense of the user.

function _mintFullRange:

    function _mintFullRange(
        IUniswapV3Pool v3Pool,
        address token0,
        address token1,
        uint24 fee
    ) internal returns (uint256, uint256) {
        (uint160 currentSqrtPriceX96, , , , , , ) = v3Pool.slot0();

        // For full range: L = Δx * sqrt(P) = Δy / sqrt(P)
        // We start with fixed token amounts and apply this equation to calculate the liquidity
        // Note that for pools with a tickSpacing that is not a power of 2 or greater than 8 (887272 % ts != 0),
        // a position at the maximum and minimum allowable ticks will be wide, but not necessarily full-range.
        // In this case, the `fullRangeLiquidity` will always be an underestimate in respect to the token amounts required to mint.
        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
                    )
                );

                // Pick the greater of the liquidities - i.e the more "expensive" option
                // This ensures that the liquidity added is sufficiently large
                fullRangeLiquidity = liquidity0 > liquidity1 ? liquidity0 : liquidity1;
            }
        }

Tools Used

in-house tool

Recommended Mitigation Steps

Assessed type

MEV

c4-judge commented 2 months ago

Picodes marked the issue as unsatisfactory: Invalid