October 15, 2022
This series will be an attempt to provide the perfect mix between understanding the math behind V3 and also not getting lost in the details so you can still get a high-level grasp of how the protocol works. I am by no means an expert on Uniswap V3, but hopefully by the end of it you’ll know a little bit more about the juicy bits that make Uniswap V3 so different from V2.
After the release of Uniswap V2, researchers realized the lack of capital efficiency in some of the liquidity pools, especially in pools that didn’t fluctuate much in price, such as a DAI/USDC pair.
To illustrate the capital inefficiency, here is an example of a V2 pool between two stablecoins where the price consistently fluctuates between and .
Using the standard curve from V2, we can say:
In this example, the current price of the pool is . This is the price of one token denominated in tokens.
To see the (or, change in ) that is needed to be added to the pool to push the price down to , we start by assuming the price is already at and that . Then, new and values can be derived from here.
Isolate and to one side:
Plug in values to solve for :
Solve for :
The result is that an incoming of tokens to the pool and an outgoing of tokens from the pool are needed to drop the pool’s price from to . Meaning, only of the pool’s tokens were needed by the pool to cover a price drop to , which is only ~ of the tokens.
of the tokens were unnecessary…
If tokens are in the pool, it would be better that all tokens be swapped out before the price drops to . Similarly, it would be better that all tokens be swapped out before the price rises to .
To enable this behavior, two new features are needed: the ability for LPs (liquidity providers) to deposit liquidity between any two prices they see fit, and that the AMM (automated market maker) offers a pricing curve between any two prices where an LP has deposited their liquidity.
With the addition of these features, a central limit order book is introduced into the AMM. Rather than having traditional market makers create single-price limit orders, LPs will create ranged-price limit orders. And rather than the price skipping up and down due to bid/ask spreads, prices instead smooth out using a curve between the lower and upper price of a ranged-price limit order. It does, however, require active management of LP positions, which is something that was not necessary in uniswap V2.
So, how does V2’s curve evolve to support the addition of these new features in V3?
Specifically, the goal for V3 is to have a curve where two prices, and , are specified as lower and upper bounds for the curve.
With this new curve, when tokens are completely exhausted from the pool, the price will be . When tokens are completely exhausted from the pool, the price will be .
In a V2 stablecoin pair, prices tend to hover between and , but it allowed for the possibility of any price between and to be quoted because the curve is unbounded.
In V3, the curve forces trades to take place between prices and .
V3 uses the original function as a base to create a new bounded-curve function:
The following sections will describe how this new function is derived, which is introduced in the whitepaper.
In V2, all reserves would have to be depleted from the pool to reach the highest price on the curve, which would approach infinity. But in V3, we say the upper price bound is just .
So if is the current price, then the pool only has to reduce by amount of tokens to reach the price . And at that price , is the amount of tokens remaining in the pool.
But because we decided would be the absolute max price for this pool, those remaining tokens are not needed. Those tokens would only be used to trade between prices and , so we have no use for them in this new pool design.
In V3, those reserves are considered “virtual” and do not need to be provided by LPs. The number only exists in the math so that the curve can hold, where .
The same can be said in the opposite direction for , , and .
To recap:
: number of tokens in the pool that would make the price
: number of tokens in the pool that would make the price
: amount of tokens in the pool that, when removed, would move the price from to
: amount of tokens in the pool that, when removed, would move the price from to
Looking at the chart, we can see that the and line segments add up to . This means that LPs only contribute , and when combined with the virtual number, it adds up to the full value that is used in .
As another abstraction, V3 swaps out the value for (this will be explained later). Instead of the function , we insert our modifications and get:
To complete the V3 curve derivation, the only remaining task is to calculate and . To do that, we must look at our new curve as where we already know and .
V3 does not store global and values like it did in V2. Instead, V3 elects to store a global and value for each pool. The reasoning that Uniswap offers for this is that at any one time, only one of the values of or can change, which helps prevent rounding errors. Price () only changes with a swap between ticks, and changes when crossing ticks.
Using and , values for and can be calculated purely in terms of and .
Solving for :
we know that , so substitute it in:
Put on one side:
Take square root:
Solving for :
we know that , so substitute it in:
Put on one side:
Take square root:
By storing and globally, it makes it trivially easy to calculate and . If and were stored instead, each calculation for and would have a square root calculation added to it, which hampers efficiency with a smart contract implementation.
We now know how to calculate and but we still don’t know the exact ratio of to or to yet.
Looking at the price curve diagram, we can see that at price , there are zero reserves and only the virtual reserves remaining. Similarly, at price , there are zero reserves and only the virtual reserves remaining. Using this insight, we can finally calculate a value for and .
We know three things about and :
1. and
2. for some price , for some price
3a. At price , reserves are zero, so
3b. At price , reserves are zero, so
Putting this together, we can see that for the two specific prices, and :
Knowing how and are calculated, we plug them into our function:
And now we finally reach equation 2.2 from the Uniswap V3 whitepaper.