Computing the Current Tick Given sqrtPriceX96

In the previous chapters, we saw that the protocol stores the square root of the price instead of the price itself. Therefore, it is necessary to relate ticks to the square root of the price in the fixed-point Q64.96 format.

In this chapter, we will explore how to convert between sqrtPriceX96 and ticks.

The protocol stores tick indexes

A tick is a discrete price, given by the formula $p(i)=1.0001^i$, where $i$ is called the tick index or simply tick. Having the tick index, it is possible to uniquely determine the tick price, and vice versa. Since storing tick indexes $i$ occupies fewer bits than storing $p(i)$, the protocol stores tick indexes instead of tick prices.

In this book, we will use the terms tick (price) and tick index interchangeably. In the codebase, variables named tick always refer to tick index $i$.

Compute tick index, Given sqrtPriceX96

We begin by recalling the relationship between the square root of the price and the variable sqrtPriceX96:

$$ \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right) = \sqrt{p} $$

Thus, the relationship between the price and sqrtPriceX96 is given by taking the square of both sides.

$$ \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)^2 = p $$

Since the relation between price and tick index is $p = 1.0001^i$, we have that

$$ \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)^2 = 1.0001^i $$

To determine the tick index $i$ in terms of sqrtPriceX96, we take the logarithm of both sides of the equation above.

$$ \begin{align*} \log \left( \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)^2 \right) &= \log (1.0001^i) \\ 2 \, \log \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right) &= i \, \log(1.0001) && \text{used that} \;\log(a^b) = b \; \log(a) \\ i &= \frac{2 \, \log \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)}{\log(1.0001)} && \text{divided both sides by } \log (1.0001) \end{align*} $$

A logarithm is always taken with respect to a base. For example, if $b^a=c$, then $\log_b{c}=a$ (log is taken in base b). However, the logarithm in the equation above can be taken in any base. The reason for this is explained in the last section of this chapter.

The tick index is a discrete value

Actually, the above formula is not entirely correct for one reason: sqrtPriceX96 is a continuous value, while the tick index i is discrete (an integer). Therefore, the value needs to be rounded down.

This can be seen in the illustration below, where we use a tool that can be found in our article on ticks. Note that while the price changes continuously, the corresponding tick for that price is always the one immediately below it.

Thus, the accurate formula for tick is:

$$ i = \lfloor \frac{2 \, \log \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)}{\log(1.0001)} \rfloor $$

where the symbol $\lfloor …\rfloor$ means to round down, for instance, $\lfloor 3.14 \rfloor = 3$.

Compute sqrtPriceX96 given the tick index

To calculate the square root of the price from the tick index, we start with the formula

$$ \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)^2 = 1.0001^i $$

Then, take the square root of both sides and multiply by $2^{96}$,

$$ \begin{align*} \frac{\text{sqrtPriceX96}}{2^{96}} &= \sqrt{1.0001^{i}} \\ \text{sqrtPriceX96} &= \sqrt{1.0001^{i}} \cdot 2^{96} \end{align*} $$

From ticks to price and vice-versa in the codebase

The conversion between ticks and sqrtPriceX96 in Solidity is handled by the TickMath library, which will be studied in a separate chapter due to the complexity of implementing log and square root in Solidity.

It contains two functions: getSqrtRatioAtTick and getTickAtSqrtRatio, which perform the conversion between sqrtPriceX96 and its corresponding tick index, as well as the opposite. This can be seen below.

Functions for converting between tick and price

We saw the mathematics of these formulas in the previous section. In this section, we will perform this calculation using Python.

The getSqrtRatioAtTick function

The getSqrtRatioAtTick function in Python can be written as:

def getSqrtRatioAtTick(i):
    return math.sqrt(1.0001 ** i) * 2**96    

As an example, the value of sqrtPriceX96 for the upper bound price can be calculated using getSqrtRatioAtTick(887272), resulting in 1.4614467034780703e+48, which (approximately) corresponds to the MAX_SQRT_RATIO constant from the TickMath library.

Max ratio constant in TickMath

Using the Decimal library

To get a more accurate result, we can use the Decimal library in Python. The same formula, using this library, is coded below, together with the calculated value for MAX_SQRT_RATIO.

import math
from decimal import Decimal, getcontext

getcontext().prec = 50 

def getSqrtRatioAtTick(i):
    base = Decimal("1.0001")
    exponent = Decimal(i)
    sqrt_value = base ** (exponent / 2) 
    multiplier = Decimal(2) ** 96
    return sqrt_value * multiplier

print(int(getSqrtRatioAtTick(887272)))  # 1461446703485210103244672773810124308346321380902

The getTickAtSqrtRatio function

The getTickAtSqrtRatio function in Python can be written as:

def getTickAtSqrtRatio(sqrtPriceX96):
    return math.floor(2*math.log(sqrtPriceX96/2**96)/math.log(1.0001))

As an example, the tick corresponding to the lower bound of sqrtPriceX96, 4295128739, can be calculated using getTickAtSqrtRatio(4295128739), resulting in the value -887272, which is expected for the lower bound for ticks.

getTickAtSqrtRatio(4295128739) // -887272

Changing the base of the logarithm

When we derived the formula

$$ i = \lfloor \frac{2 \, \log \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)}{\log(1.0001)} \rfloor $$

we mentioned that the logarithm can be in any base. In the implementation of the formula in Python, we used the natural logarithm, but we could also use the logarithm in base 10 or any other, which would yield the same result.

The reason is a basic property of logarithms that relates logarithms in different bases,

$$ \log_{\boxed{b}}(k) = \frac{\log_a(k)}{\log_a(\boxed{b})} $$

where $a$ and $b$ are two different bases, and $k$ is the argument of the logarithm we want to calculate. For instance, converting a log from natural base $e$ to base 10, we have

$$ \log_{10}(k) = \frac{\log_e(k)}{\log_e(10)} $$

The catch is that the divisor, $\log_e{(10)}$, depends only on the bases and not on the argument. Suppose we have a fraction:

$$ \frac{\log_{10}(x)}{\log_{10}(y)} $$

If we want to convert the logarithms to some arbitrary base b, we divide both the numerator and denominator by $\log_b(10)$. However, dividing both the numerator and denominator of a fraction by the same value has no effect on the final answer because it cancels out.

Let’s make this explicit by applying this relationship to our formula for tick index $i$ and converting from the natural base to base 10:

$$ \begin{align*} i &= \lfloor \frac{2 \, \log_e \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)}{\log_e(1.0001)} \rfloor \\i &= \lfloor \frac{2 \, \log_{10} \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right) / \log_{10}(e)}{\log_{10}(1.0001) / \log_{10}(e)} \rfloor && \text{from base e to base 10} \\ i &= \lfloor \frac{2 \, \log_{10} \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right) / \cancel{\log_{10}(e)}}{\log_{10}(1.0001) / \cancel{\log_{10}(e)}} \rfloor \\ i &= \lfloor \frac{2 \, \log_{10} \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)}{\log_{10}(1.0001)} \rfloor \end{align*} $$

As we can see, the transformation factor cancels out and the calculation is the same regardless of the base.

Summary

  • The protocol needs to convert between sqrtPriceX96 and the tick index. This is done using the getSqrtRatioAtTick and getTickAtSqrtRatio functions, which are located in the TickMath library.
  • To convert from sqrtPriceX96 to the tick index, the following formula should be used:

$$ i = \lfloor \frac{2 \, \log \left( \frac{\text{sqrtPriceX96}}{2^{96}}\right)}{\log(1.0001)} \rfloor $$

  • To convert from tick index to sqrtPriceX96, the following formula should be used:

$$ \sqrt{1.0001^{i}} \cdot 2^{96} $$

Practice Exercises

Look through the Uniswap V3 Pools to find the pool address, then open the address in a block explorer. Look for the public variable slot0 and convert the tick to price and vice versa.

Etherscan output of slot0

Check that your calculation is close to what Uniswap provides and then convert the sqrtPriceX96 to dollars.

Do it for the following pools:

We will learn later why it is not safe for a contract to directly consume sqrtPriceX96 (which can give us the price) and tick data from slot0, this is only for practice at this point.

Uniswap V3 Factory and the Relationship Between Tick Spacing and Fees

Uniswap V3 Factory and the Relationship Between Tick Spacing and Fees In early chapters, we introduced the concept of ticks, which discretize the price curve. A tick is a price defined by the formula $p(i)=1.0001^i$, where $i$ is named tick index. Tick indexes are integers within the range $[-887272,887272]$, resulting in 1,774,545 ticks along the […]

ZK Proof of Selection Sort

ZK Proof of Selection Sort Most computations of interest are generally “stateful” — that is, they need to go through a series of steps to produce the final result. Sometimes, we do not need to show we executed the computation but only show the result. For example, if A is a list, we can prove […]

How a ZKVM Works

How a ZKVM Works A Zero-Knowledge Virtual Machine (ZKVM) is a virtual machine that can create a ZK-proof that verifies it executed a set of machine instructions correctly. This allows us to take a program (a set of opcodes), a virtual machine specification (how the virtual machine behaves, what opcodes it uses, etc), and prove […]

The Permutation Argument

The Permutation Argument A permutation argument is a proof that two lists hold the same elements, but possibly in a different order. For example, [2,3,1] is a permutation of [1,2,3] and vice-versa. The permutation argument is useful for proving one list is a sorted version of another. That is, if list B has the same […]

Opportunities

Looking for an audit?

Leverage our extensive network of top security specialists.

Get A Quote
Rust/Solana Auditor

We’re looking for someone to design and implement security measures and defense-in-depth controls to prevent and limit vulnerabilities.

Apply Now
Full Stack Developer

We’re looking for a Senior Full-Stack Engineer to play a foundational role in working across the entire offchain stack of products.

Apply Now
Rust Developer

We are seeking a talented Rust Developer to build a robust, scalable blockchain indexers and analytic backend.

Apply Now