6

I'm looking at the uniswap docs which states this example:

An example of finding the price of WETH in a WETH / USDC pool, where WETH is token0 and USDC is token1:

You have an oracle reading that shows a return of tickCumulative as [70_000, 1_070_000], with an elapsed time between the observations of 10 seconds.

We can derive the average tick over this interval by taking the difference in accumulator values (1_070_000 - 70_000 = 1_000_000), and dividing by the time elapsed (1_000_000 / 10 = 100_000).

With a tick reading of 100_000, we can find the value of token1 (USDC) in terms of token0 (WETH) by using the current tick as i in the formula p(i) = 1.0001**i (see 6.1 in the whitepaper).

1.0001**100_000 ≅ 22015.5 USDC / WETH

The price of WETH is not $22015.50. I though maybe they just use an example with easy numbers. So I decided to try the example from the whitepaper on the USDC/WETH pool

enter image description here

Calling slot0 on the contract returns:

enter image description here

Making the price

1.0001 ** 205930 = 876958666.4726943

Clearly the price for ETH is not 876958666 USDC. The current tick is 205930, but the price for ETH is just 1200.49 USDC. How do I get the correct USDC price of ETH from the tick?

TylerH
  • 20,799
  • 66
  • 75
  • 101
Ritzy Dev
  • 387
  • 3
  • 10

4 Answers4

22

On the mainnet, the USDC ERC-20 token has 6 decimals, ETH has 18 decimals. However, the price tracked by Uniswap internally is not aware of these decimals: it's actually the price of one micro-USDC (i.e. 0.000001 USDC) per one wei.

This price is calculate by computing the exponent of the Uniswap v3 tick basis constant 1.0001. If the price tick is 205930, you get your result:

1.0001 ** 205930 = 876958666.4726943

To adjust this internal price to a human readable price, you must multiply it by 10**6 (the decimals of USDC) and divide by 10**18 (the decimals of [W]ETH), which is equal to multiplying the price by 10**-12.

876958666.4726943 * (10 ** -12) = 0.0008769586664726943

Finally, you probably want the inverse this number. The small number that you just got is the price of USDC in terms of ETH, but we usually want to track the price of ETH in terms of USD[C]. This is because price in Uniswap is defined to be equal to token1/token0, and this is a USDC/WETH pool, meaning that token0 is USDC and token1 is WETH.

This computes the inverse:

1 / 0.0008769586664726943 = 1140.3045984164828

The answer is approximately 1140.30 USDC per one ETH.

If you do this Solidity, take into account that it does not have floating-point numbers, all calculations are done with fixed-point numbers. Specifically Uniswap uses binary fixed-point numbers (Q numbers), and keeps track of square root of the price multiplied by 2**96.

kfx
  • 8,136
  • 3
  • 28
  • 52
  • Thanks, Just for clarification the `it's actually the price of one micro-USDC (i.e. 0.000001 USDC) per one wei.` Its priced per Micro USDC, because USDC is token 0 right? – Ritzy Dev Nov 30 '22 at 00:21
  • @RitzyDev yes USDC is token0, and the tick 205930 describes the price of token0 in terms of token1. – kfx Nov 30 '22 at 09:31
1

Concrete working code for getting price (WETH).

//Call decimal number from token that is not WETH
const tokenDetails = await getTokenDetails(isToken);

const tokenDecimals = tokenDetails.decimals;
const tokenDecimalsBN = new BN(tokenDecimals);
const tokenDecimalsNumber = tokenDecimalsBN.toNumber();
console.log(`Number of Decimal:`, tokenDecimalsNumber)


const poolContract = new web3.eth.Contract(IUniswapV3PoolStateABI, pooladdress);
const poolState = await poolContract.methods.slot0().call();

const tick = poolState.tick;
const tickNumber = Number(tick);

// Calculate the price based on the tick
const p = Math.pow(1.0001, tickNumber);
console.log('P:', p);

// Adjust the internal price to a human-readable price
const p_readable = p / Math.pow(10, tokenDecimalsNumber - 18); // Assuming WETH has 18 decimals
console.log('P_Readable:', p_readable);

// Price of token0 (ETH) in token1 (Your Token) - i.e., for 1 ETH how many of your token
const token0_price = p_readable;
console.log('Token0', token0_price)

// Price of token1 (Your Token) in token0 (ETH) - i.e., for 1 of your token how much ETH
const token1_price = 1 / token0_price;
console.log('Token1', token1_price)

//Log formatted version of price of token1 (ex. 0.000000012458547891)
const formattedPrice = token1_price.toFixed(18);
TylerH
  • 20,799
  • 66
  • 75
  • 101
RasmonT
  • 391
  • 2
  • 13
0

If you read whitepaper-v3, "5.2 Geometric Mean Price Oracle"

Using the time-weighted-geometric-mean price, asUniswap v3does, avoids the need to track separate accumulators for these ratios.

It says TIME WEIGHTED AVERAGE PRICE that means you need to define a time interval, in this interval, calculate the geometric mean of those ticks and use this average tick.

enter image description here

the reason why Uniswap implements TWAP is to prevent the price manipulation of the pool. You can read this: How does someone manipulate the TWAP?

The tick value "205930" is the tick of the time you requested. But to calculate the price, you need the average tick of a given time. This library, OracleLibrary.sol calculates the average tick. If you check the consult function

function consult(address pool, uint32 secondsAgo)
        internal
        view
        returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity){}

if you look at the signature of the function, it needs a time interval to calculate the arithmeticMeanTick. this function returns arithmeticMeanTick and this tick value is used to get the quote from getQuoteAtTick function in the same contract:

function getQuoteAtTick(
        int24 tick,
        uint128 baseAmount,
        address baseToken,
        address quoteToken
    ) internal pure returns (uint256 quoteAmount) {} 
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
  • I'm not operating on the test net, I'm operating directly on polygon mainnet. The price doesn't seem to represent the Price of Either tokens, It doesn't appear to be the sqrt price either. – Ritzy Dev Nov 25 '22 at 20:23
  • My question is how to get the price from the tick? – Ritzy Dev Nov 25 '22 at 21:11
  • @RitzyDev you dont get the price from tick, you get it from average tick – Yilmaz Nov 27 '22 at 06:28
  • I get that the TWAP price is better for calculation as it's resistant to manipulation. However, I don't understand how the sqrtX96 Price correlates to the amount of Token 1. I'm looking for a more verbal explanation – Ritzy Dev Nov 27 '22 at 07:15
  • I appreciate your time spent on this answer, however, I believe the mention of oracle in the quote is a red herring. What they want is probably a more elementary answer. – kfx Nov 29 '22 at 18:52
  • @kfx correct! I think u explained better. I saw the shared docs and thought about oracles immediately – Yilmaz Nov 30 '22 at 01:01
0

I see that the uniswap SDK has a way to convert the tick to the sqrtPriceX96 which can be done like so.

return TickMath.getSqrtRatioAtTick(tick);

While this works the Math inside is relatively convoluted and hard to read.

Note: the sqrtRatio and SqrtPrice are used interchangable throughout uniswap

Ritzy Dev
  • 387
  • 3
  • 10