22

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

Kof
  • 2,954
  • 6
  • 23
pipip
  • 321
  • 1
  • 2
  • 5

7 Answers7

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
NoobMusketeer
  • 319
  • 2
  • 2
17
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.

MShakeG
  • 1,603
  • 6
  • 37
Igor Yalovoy
  • 344
  • 2
  • 9
  • 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 '22 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.

MShakeG
  • 1,603
  • 6
  • 37
Navigator
  • 71
  • 1
  • 1
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; }

MShakeG
  • 1,603
  • 6
  • 37
Navigator
  • 71
  • 1
  • 1
  • This is wrong/misleading, if the active liquidity in a pool is 0 then your function would attempt to divide by 0. Liquidity is irrelevant to calculating price, the only reason your function returns the correct price(provided liquidity != 0) is because your function can be reduced to the same formula as many of the other answers listed here:

    (sqrtPriceX96 * sqrtPriceX96 * 1e18)/(2^96 * 2 ^ 96) = (sqrtPriceX96sqrtPriceX96 1e18)/(2^192)

    – MShakeG Dec 02 '23 at 14:07
1

We can adapt code from the actual Uniswap v3-periphery library function OracleLibrary.getQuoteAtTick. Instead of having the function accept the tick and then convert the tick to sqrtRatioX96 using TickMath.getSqrtRatioAtTick(tick), we modify it to directly accept the sqrtRatioX96 as shown below:

/**
 * @dev Calculates the amount of quote token received for a given amount of base token
 * based on the square root of the price ratio (sqrtRatioX96).
 *
 * @param sqrtRatioX96 The square root of the price ratio(in terms of token1/token0) between two tokens, encoded as a Q64.96 value.
 * @param baseAmount The amount of the base token for which the quote is to be calculated. Specify 1e18 for a price(quoteAmount) with 18 decimals of precision.
 * @param inverse Specifies the direction of the price quote. If true then baseAmount must be the amount of token1, if false then baseAmount must be the amount for token0
 *
 * @return quoteAmount The calculated amount of the quote token for the specified baseAmount
 */
function getQuoteFromSqrtRatioX96(
    uint160 sqrtRatioX96,
    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);
    }
}

To further simplify getting the price we can define the following function.

/**
 * @dev Returns the fixed-point price for a specified sqrtRatioX96, denoted in terms of token1/token0.
 * This function simplifies the process of obtaining the price from sqrtRatioX96 by using a standard
 * base amount (1e18) and setting the price as non-inverted (token1/token0).
 *
 * @param sqrtRatioX96 The square root of the price ratio between two tokens, encoded as a Q64.96 value.
 * 
 * @return priceU18 The calculated price in terms of token1/token0, with 18 decimal places of precision.
 */
function getPriceFromSqrtRatioX96(uint160 sqrtRatioX96) internal pure returns (uint256 priceU18) {
    // if we specified inverted = true, then we'd get the price in terms of token0/token1
    // whereas the UniswapV3Pool sqrtRatioX96 is in terms of token1/token0
    return getQuoteFromSqrtRatioX96(sqrtRatioX96, 1e18, false);
}
MShakeG
  • 1,603
  • 6
  • 37
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

FreddyC
  • 301
  • 1
  • 7
0

Had to write code for this exact purpose but with both decimal numbers variable, this did the trick for me:

 function getPrice(uint160 sqrtRatioX96, uint dec0, uint dec1)
    external
    pure
    returns (uint256 price)
{

uint256 dec = dec1<=dec0 ? (18-dec1)+dec0 :dec0; uint256 numerator1 =uint256(sqrtRatioX96) uint256(sqrtRatioX96);
uint256 numerator2 =10
*dec; price = FullMath.mulDiv(numerator1, numerator2, 1 << 192); }

Fićo
  • 11