If you have not read my article on the functioning of the v2 contracts for Uniswap, I recommend you take a look here since you may need a good understanding of v2’s working for some of the comparisons mentioned in this article.
The defining idea of v3 is the idea of concentrated liquidity. Before v3, whenever investors provided liquidity to Uniswap pools, the constant product formula (x*y = k) made sure that there would always be liquidity in the pool no matter what the price is between token x and y (from 0 to infinity). Though functioning as expected, the current formula is not the most efficient way of storing liquidity in a pool. The idea of concentrated liquidity allows LPs to concentrate their liquidity to smaller price ranges between the two assets x and y.
Liquidity distribution in v2
Every portion of liquidity that is provided in a concentrated manner for a certain price range and for a certain fee — yes, the LPs can set the fees they want to take from the swaps using their liquidity — is called a position. Liquidity providers may initially create pools at three fee levels: 0.05%, 0.30%, and 1%. For stablecoin pairs, lower fees is the recommended one and the higher fees are recommended for more volatile asset pairs.
Understanding virtual reserves
Suppose that the price for a certain pair fluctuates only between two price points a and b. Let the current price is at c. Hence for a price movement from c to a the real supply of y required would be Yreal. Similarly for a price movement from c to b, the supply of x tokens required would be Xreal. Hence the rest of the liquidity of both X and Y (x-Xreal ; y-Yreal), is idle liquidity which is not used. If we shift the curve of x, y such that the x- and y-intercepts are at a and b, and set a parameter L =sqrt(x*y), the formula comes out to be:
Real liquidity distribution for v3
Liquidity providers are free to create as many positions as they see fit, each on its own price range. Moreover, this serves as a mechanism to let the market decide where liquidity should be allocated. Rational LPs can reduce their capital costs by concentrating their liquidity in a narrow band around the current price, and adding or removing tokens as the price moves to keep their liquidity active. When the price exits a position’s range, the position’s liquidity is no longer active, and no longer earns fees
“Positions on very small ranges act similarly to limit orders — if the range is crossed, the position flips from being composed entirely of one asset, to being composed entirely of the other asset”. Suppose that an LP provides 100 tokens of x and 300 tokens of y in the price range of a to b. If a. trader buys 150 tokens of x, and the liquidity provided by our LP is the only liquiidty at current price c (a≤ c ≤ b), the trader gets his 150 tokens of x, and sells y to the pool instead. As a result, the LP remains with 0 tokens of x and approximately 300+c*y tokens of y. Hence the only assets in the position is now token y.
Uniswap v1 and v2, the pool contract is also an ERC-20 token contract, whose tokens represent liquidity held in the pool. The changes made in Uniswap v3 force this issue by making completely fungible liquidity tokens impossible. Due to the custom liquidity provision feature, fees are now collected and held by the pool as individual tokens, rather than automatically reinvested as liquidity in the pool.
If you have read my previous piece explaining v2 contracts, you may remember that in order for an external caller to use Uniswap contracts as a price (TWAP — time-weighted average price) oracle between a pair of assets, the caller must read the contract data form the contract twice — once before the time period for which TWAP is to be calculated and once after. This approach provided accurate data for the price but was not very comfortable to use. V3 contracts eliminate the need to read the contract data in the beginning this time period for which the price is to be calculated. It does this by storing a list of previous values for the price accumulator in an array. The price accumulator, as you may remember from v2, is a running accumulator of the price at the beginning of each block, multiplied by the number of seconds since the last block. Every time a transaction touches a pool (for the first time in a block), the price accumulator is added to an array of 65,536 checkpoints. Once the array is full, it overrides the data at position 0 and starts again (as in a cyclical array).
Uniswap v2 maintains two price accumulators — one for the price of token0 in terms of token1, and one for the price of token1 in terms of token0. Users can compute the time-weighted arithmetic mean of the prices over any period, by subtracting the accumulator value at the beginning of the period from the accumulator at the end of the period, then dividing the difference by the number of seconds in the period. Note that accumulators for token0 and token1 are tracked separately, since the time-weighted arithmetic mean price
3The maximum of 65,536 checkpoints allows fetching checkpoints for at least 9 days after they are written, assuming 13 seconds pass between each block and a checkpoint is written every block of token0 is not equivalent to the reciprocal of the time-weighted arithmetic mean price of token1. Suppose if the price for a token is 100 on day 1 and 300 on day 2, the average price would be 200. However, the average of the reciprocals (1/100 and 1/300 respectively) would yiels 1/150 (and not 1/200 as expected). However, instead of recording the time-weighted arithmetic mean of the prices over any period, v3 uses the time-weighted geometric mean price. It tracks log 𝑃 rather than 𝑃, and log 𝑃 can represent a wide range of prices with consistent precision. For easier understanding, you may remember that log P1 + log P2 = log (P1*P2), so by storing the logs of prices, if we find the average of logs, we will technically finding out the geometric mean. For two time periods, t1 and t2, the formula to find the TWAP in v3 would be :
Since log to the base of 1.0001 is considered for v3, the formula can be re-written as :
“In addition to the seconds-weighted accumulator of log1.0001 𝑝𝑟𝑖𝑐𝑒, Uniswap v3 also tracks a seconds-weighted accumulator of 𝐿1 (the reciprocal of the virtual liquidity currently in range) at the beginning of each block.This can be used by external liquidity mining contracts to fairly allocate rewards”
To sum up the improvements in v3 over v2 for the uniswap contract, we can say that the main upgrade was the new concept of concentrated liquidity which not only allows the market to decide where they want to concentrate their liquidity, but also results in a more efficient liquidity accumulation over the same price ranges as before. Other improvements include a more convenient and theoretically more accurate calculation of average prices for the price oracle, addition of a liquidity oracle as a data source for external applications. I will be taking an even deeper dive into the code behind the contract and summarising all the interesting things in future.
