Skip to content

Latest commit

 

History

History
125 lines (94 loc) · 3.77 KB

075.md

File metadata and controls

125 lines (94 loc) · 3.77 KB

Deep Sepia Gazelle

Medium

The use of slot0 to get the sqrtPriceX96 leads to potential price manipulation

Summary

In the Numa protocol slot0 is used to receive the sqrtPriceX96 price. But this is not recommended, because slot0 can be manipulated very easy.

Root Cause

The Numa protocol is using slot0 to receive the sqrtPriceX96 and sqrtPriceX96Spot variables in NumaOracle::getV3SpotPrice, NumaOracle::getV3SqrtLowestPrice and NumaOracle::getV3SqrtHighestPrice. The problem is that the slot0 is the most recent data point and is therefore extremely easy to manipulate.

Internal pre-conditions

Price manipulation, the returned price can be higher and at the end swaps can result in users receiving less tokens than they intended.

External pre-conditions

No response

Attack Path

No response

Impact

No response

PoC

Let's consider the functions: getV3SpotPrice, getV3SqrtLowestPrice and getV3SqrtHighestPrice:

 function getV3SpotPrice(
    address _numaPool,
    uint _numaAmount
) external view returns (uint256) {
@>  (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(_numaPool).slot0();
    uint256 numerator = (
        IUniswapV3Pool(_numaPool).token0() == token
            ? sqrtPriceX96
            : FixedPoint96.Q96
    );
    uint256 denominator = (
        numerator == sqrtPriceX96 ? FixedPoint96.Q96 : sqrtPriceX96
    );

    uint256 TokenPerNumaMulAmount = FullMath.mulDivRoundingUp(
        FullMath.mulDivRoundingUp(denominator, denominator, numerator),
        _numaAmount,
        numerator
    );

    return TokenPerNumaMulAmount;
}


function getV3SqrtLowestPrice(
    address _uniswapV3Pool,
    uint32 _intervalShort,
    uint32 _intervalLong
) public view returns (uint160) {
    require(
        _intervalLong > _intervalShort,
        "intervalLong must be longer than intervalShort"
    );

    uint160 sqrtPriceX96;

    //Spot price of the token
    (uint160 sqrtPriceX96Spot, , , , , , ) = IUniswapV3Pool(_uniswapV3Pool)
@>      .slot0();

    //TWAP prices for short and long intervals
    uint160 sqrtPriceX96Short = getV3SqrtPriceAvg(
        _uniswapV3Pool,
        _intervalShort
    );
    uint160 sqrtPriceX96Long = getV3SqrtPriceAvg(
        _uniswapV3Pool,
        _intervalLong
    );
    .
    .
    .
}

function getV3SqrtHighestPrice(
    address _uniswapV3Pool,
    uint32 _intervalShort,
    uint32 _intervalLong
) public view returns (uint160) {
    require(
        _intervalLong > _intervalShort,
        "intervalLong must be longer than intervalShort"
    );

    uint160 sqrtPriceX96;
    //Spot price of the token
    (uint160 sqrtPriceX96Spot, , , , , , ) = IUniswapV3Pool(_uniswapV3Pool)
@>      .slot0();
    //TWAP prices for short and long intervals
    uint160 sqrtPriceX96Short = getV3SqrtPriceAvg(
        _uniswapV3Pool,
        _intervalShort
    );
    uint160 sqrtPriceX96Long = getV3SqrtPriceAvg(
        _uniswapV3Pool,
        _intervalLong
    );
    .
    .
    .
}

These three functions get the sqrtPriceX96 and sqrtPriceX96Spot from IUniswapV3Pool.slot0. An attacker can simply manipulate the sqrtPriceX96 price and set a higher price than the intended one.

Mitigation

Use TWAP to get the value of sqrtPriceX96.