21

I am trying to compute a Uniswap V3 pool token price in Solidity.

I can get the square root price of Q64.96 number as uint160 (e.g., 1234217676608908277512433764 - value of DAI/ETH pool (price at that time around 1 ETH for 4090 DAI)).

This can be retrieved via IUniswapV3PoolState.slot0()

As the function docstring says: "sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value"

How can I convert this number to uint256?

The calculated price should suggest the value of 1 ETH for 4090 DAI

Is there any other way of getting the Uniswap V3 pair price?

The answer would preferably be in solidity, but other answers are also acceptable.

Unsuccessful attempt

I tried squaring the number 1234217676608908277512433764 and then shifting it by 96, but the result was 1.922666416729829e+25 which doesn't seem too be correct.

(1234217676608908277512433764^2) >> 96 = 1.922666416729829e+25

1

6 Answers 6

21

The relationship between sqrtPriceX96 and price is metioned in the official doc:https://docs.uniswap.org/sdk/guides/fetching-prices

    sqrtPriceX96 = sqrt(price) * 2 ** 96
    # divide both sides by 2 ** 96
    sqrtPriceX96 / (2 ** 96) = sqrt(price)
    # square both sides
    (sqrtPriceX96 / (2 ** 96)) ** 2 = price
    # expand the squared fraction
    (sqrtPriceX96 ** 2) / ((2 ** 96) ** 2)  = price
    # multiply the exponents in the denominator to get the final expression
    sqrtRatioX96 ** 2 / 2 ** 192 = price
3
  • 1
    Where did sqrtRatioX96 at the bottom come from?
    – Barry G
    Nov 22, 2022 at 22:31
  • 1
    sqrtRatioX96 and sqrtPriceX96 are interchangeable terms according to the official docs linked in this answer
    – Voy
    Dec 6, 2022 at 15:27
  • thanks for this, good mathematical breakdown
    – gosuto
    Mar 22 at 19:30
16
    function getPrice(address tokenIn, address tokenOut)
        external
        view
        returns (uint256 price)
    {
        IUniswapV3Pool pool = IUniswapV3Pool(factory.getPool(tokenIn, tokenOut, FEE);
        (uint160 sqrtPriceX96,,,,,,) =  pool.slot0();
        return uint(sqrtPriceX96).mul(uint(sqrtPriceX96)).mul(1e18) >> (96 * 2);
    }

Returns spot price with 1e18 precision. Be careful to use spot price because it is a subject for a flash loan attacks. Either use TWAP price or check that price hasn't move much before using spot price. Also, I haven't tested this code for precision loss and upper/lower boundaries. Because Uniswap v3 use Q64.96 notion for decimals but it doesn't seem that practical. The output of the function is a number with e18 precision. It depends which decimal lib you use in your project.

1
  • This does not seem to generalize well. How would this work on a pair like usdt/wbtc which is of decimals 8 and 6 respectively?
    – Barry G
    Dec 25, 2022 at 13:49
5

TLDR: This is something that seems to work with every token (regardless of how many decimals it has) and it takes into consideration math overflow issues. Read explanation below:

import '@uniswap/v3-core/contracts/libraries/FullMath.sol';

  function sqrtPriceX96ToUint(uint160 sqrtPriceX96, uint8 decimalsToken0)
    internal
    pure
    returns (uint256)
  {
    uint256 numerator1 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
    uint256 numerator2 = 10**decimalsToken0;
    return FullMath.mulDiv(numerator1, numerator2, 1 << 192);
  }

Regarding decimals: If you are calculating the price on a token with 18 decimal places, multiplying sqrtPriceX96 by 1e18 before the division should work just fine, but it will break for tokens with other decimals (USDC anyone). In order to obtain the correct results you should pass ERC20(token0).decimals() to the above function.

Regarding math overflow: Depending on the price you convert the uint256 will overflow and you will end up with a wrong price. The safest way to do this is with the MathFull.sol library from Uniswap v3 which accounts for overflowing.

0
2

Another way to calculate the price is by taking advantage of the relationship between liquidity, sqrtPricex96 and token amounts.

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@uniswap/v3-core/contracts/libraries/FixedPoint96.sol';
import '@uniswap/v3-core/contracts/libraries/FullMath.sol';
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol';

  function calculatePriceFromLiquidity(
    address token0,
    address token1,
    uint24 fee,
    address factory
  ) public view returns (uint256) {
    IUniswapV3Pool pool = IUniswapV3Pool(IUniswapV3Factory(factory).getPool(token0, token1, fee));
    (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();

    uint256 amount0 = FullMath.mulDiv(pool.liquidity(), FixedPoint96.Q96, sqrtPriceX96);

    uint256 amount1 = FullMath.mulDiv(pool.liquidity(), sqrtPriceX96, FixedPoint96.Q96);

    return (amount1 * 10**ERC20(token0).decimals()) / amount0;
  }
1

We can adapt code from the actual Uniswap V3 library function OracleLibrary.getQuoteAtTick(): https://github.com/Uniswap/v3-periphery/blob/v1.3.0/contracts/libraries/OracleLibrary.sol

function getQuoteFromSqrtRatioX96(
    uint160 sqrtRatioX96, // same as sqrtPriceX96
    uint128 baseAmount,
    bool inverse
) internal pure returns (uint256 quoteAmount) {
    // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
    if (sqrtRatioX96 <= type(uint128).max) {
        uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
        quoteAmount = !inverse
            ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
            : FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
    } else {
        uint256 ratioX128 = FullMath.mulDiv(
            sqrtRatioX96,
            sqrtRatioX96,
            1 << 64
        );
        quoteAmount = !inverse
            ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
            : FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
    }
}
0

I created a rust lib that simply returns the price and abstracts the calculations away. Feel free to use it https://github.com/FredCoen/uniswap-utilities

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.