Introduction to Uniswap V3

This chapter retells the whitepaper of Uniswap V3. Again, it’s totally ok if you don’t understand all the concepts. They will be clearer when converted to code.

To better understand the innovations Uniswap V3 brings, let’s first look at the imperfections of Uniswap V2.

Uniswap V2 is a general exchange that implements one AMM algorithm. However, not all trading pairs are equal. Pairs can be grouped by price volatility:

  1. Tokens with medium and high price volatility. This group includes most tokens since most tokens don’t have their prices pegged to something and are subject to market fluctuations.
  2. Tokens with low volatility. This group includes pegged tokens, mainly stablecoins: USDC/USDT, USDC/DAI, USDT/DAI, etc. Also: ETH/stETH, ETH/rETH (variants of wrapped ETH).

These groups require different, let’s call them, pool configurations. The main difference is that pegged tokens require high liquidity to reduce the demand effect (we learned about it in the previous chapter) on big trades. The prices of USDC and USDT must stay close to 1, no matter how big the number of tokens we want to buy and sell. Since Uniswap V2’s general AMM algorithm is not very well suited for stablecoin trading, alternative AMMs (mainly Curve) were more popular for stablecoin trading.

What caused this problem is that liquidity in Uniswap V2 pools is distributed infinitely–pool liquidity allows trades at any price, from 0 to infinity:

The curve is infinite

This might not seem like a bad thing, but this makes capital inefficient. Historical prices of an asset stay within some defined range, whether it’s narrow or wide. For example, the historical price range of ETH is from 4,800 (according to CoinMarketCap). Today (June 2022, 1 ETH costs $1,800), no one would buy 1 ether at $5000, so it makes no sense to provide liquidity at this price. Thus, it doesn’t make sense to provide liquidity in a price range that’s far away from the current price or that will never be reached.

However, we all believe in ETH reaching $10,000 one day.

Concentrated Liquidity

Uniswap V3 introduces concentrated liquidity: liquidity providers can now choose the price range they want to provide liquidity into. This improves capital efficiency by allowing to put more liquidity into a narrow price range, which makes Uniswap more diverse: it can now have pools configured for pairs with different volatility. This is how V3 improves V2.

In a nutshell, a Uniswap V3 pair is many small Uniswap V2 pairs. The main difference between V2 and V3 is that, in V3, there are many price ranges in one pair. And each of these shorter price ranges has finite reserves. The entire price range from 0 to infinite is split into shorter price ranges, with each of them having its own amount of liquidity. But, what’s crucial is that within that shorter price range, it works exactly as Uniswap V2. This is why I say that a V3 pair is many small V2 pairs.

Now, let’s try to visualize it. What we’re saying is that we don’t want the curve to be infinite. We cut it at the points and and say that these are the boundaries of the curve. Moreover, we shift the curve so the boundaries lay on the axes. This is what we get:

Uniswap V3 price range

It looks lonely, doesn’t it? This is why there are many price ranges in Uniswap V3–so they don’t feel lonely 🙂

As we saw in the previous chapter, buying or selling tokens moves the price along the curve. A price range limits the movement of the price. When the price moves to either of the points, the pool becomes depleted: one of the token reserves will be 0, and buying this token won’t be possible.

On the chart above, let’s assume that the start price is at the middle of the curve. To get to the point , we need to buy all available and maximize in the range; to get to the point , we need to buy all available and maximize in the range. At these points, there’s only one token in the range!

Fun fact: this allows using Uniswap V3 price ranges as limit orders!

What happens when the current price range gets depleted during a trade? The price slips into the next price range. If the next price range doesn’t exist, the trade ends up partially fulfilled-we’ll see how this works later in the book.

This is how liquidity is spread in the USDC/ETH pool in production:

Liquidity in the real USDC/ETH pool

You can see that there’s a lot of liquidity around the current price but the further away from it the less liquidity there is–this is because liquidity providers strive to have higher efficiency of their capital. Also, the whole range is not infinite, its upper boundary is shown in the image.

The Mathematics of Uniswap V3

Mathematically, Uniswap V3 is based on V2: it uses the same formulas, but they’re… let’s call it augmented.

To handle transitioning between price ranges, simplify liquidity management, and avoid rounding errors, Uniswap V3 uses these new concepts:

is the amount of liquidity. Liquidity in a pool is the combination of token reserves (that is, two numbers). We know that their product is , and we can use this to derive the measure of liquidity, which is –a number that, when multiplied by itself, equals . is the geometric mean of and .

is the price of token 0 in terms of 1. Since token prices in a pool are reciprocals of each other, we can use only one of them in calculations (and by convention Uniswap V3 uses ). The price of token 1 in terms of token 0 is simply . Similarly, .

Why using instead of ? There are two reasons:

  1. Square root calculation is not precise and causes rounding errors. Thus, it’s easier to store the square root without calculating it in the contracts (we will not store and in the contracts).

  2. has an interesting connection to : is also the relation between the change in output amount and the change in .

Proof:

Pricing

Again, we don’t need to calculate actual prices–we can calculate the output amount right away. Also, since we’re not going to track and store and , our calculation will be based only on and .

From the above formula, we can find :

See the third step in the proof above.

As we discussed above, prices in a pool are reciprocals of each other. Thus, is:

and allow us to not store and update pool reserves. Also, we don’t need to calculate each time because we can always find and its reciprocal.

Ticks

As we learned in this chapter, the infinite price range of V2 is split into shorter price ranges in V3. Each of these shorter price ranges is limited by boundaries–upper and lower points. To track the coordinates of these boundaries, Uniswap V3 uses ticks.

Price ranges and ticks

In V3, the entire price range is demarcated by evenly distributed discrete ticks. Each tick has an index and corresponds to a certain price:

Where is the price at tick . Taking powers of 1.0001 has a desirable property: the difference between two adjacent ticks is 0.01% or 1 basis point.

Basis point (1/100th of 1%, or 0.01%, or 0.0001) is a unit of measure of percentages in finance. You could’ve heard about the basis point when central banks announced changes in interest rates.

As we discussed above, Uniswap V3 stores , not . Thus, the formula is in fact:

So, we get values like: , , .

Ticks are integers that can be positive and negative and, of course, they’re not infinite. Uniswap V3 stores as a fixed point Q64.96 number, which is a rational number that uses 64 bits for the integer part and 96 bits for the fractional part. Thus, prices (equal to the square of ) are within the range: . And ticks are within the range:

For deeper dive into the math of Uniswap V3, I cannot but recommend this technical note by Atis Elsts.