Uniswap v3 Core: March 2021 Hayden Adams Noah Zinsmeister Moody Salem
Uniswap v3 Core: March 2021 Hayden Adams Noah Zinsmeister Moody Salem
Uniswap v3 Core: March 2021 Hayden Adams Noah Zinsmeister Moody Salem
March 2021
Hayden Adams Noah Zinsmeister Moody Salem
[email protected] [email protected] [email protected]
ABSTRACT In this paper, we present Uniswap v3, a novel AMM that gives
Uniswap v3 is a noncustodial automated market maker imple- liquidity providers more control over the price ranges in which
mented for the Ethereum Virtual Machine. In comparison to earlier their capital is used, with limited effect on liquidity fragmentation
versions of the protocol, Uniswap v3 provides increased capital and gas inefficiency. This design does not depend on any shared
efficiency and fine-tuned control to liquidity providers, improves assumption about the price behavior of the tokens. Uniswap v3
the accuracy and convenience of the price oracle, and has a more is based on the same constant product reserves curve as earlier
flexible fee structure. versions [1], but offers several significant new features:
• Liquidity Oracle: The contracts expose a time-weighted av- liquidity is composed entirely of a single asset, because the reserves
erage liquidity oracle (Section 5.3). of the other asset must have been entirely depleted. If the price ever
The Uniswap v2 core contracts are non-upgradeable by de- reenters the range, the liquidity becomes active again.
sign, so Uniswap v3 is implemented as an entirely new set of The amount of liquidity
√ provided can be measured by the value
contracts, available here. The Uniswap v3 core contracts are also 𝐿, which is equal to 𝑘. The real reserves of a position are described
non-upgradeable, with some parameters controlled by governance by the curve:
as described in Section 4.
𝐿 √
(𝑥 + √ )(𝑦 + 𝐿 𝑝𝑎 ) = 𝐿 2 (2.2)
2 CONCENTRATED LIQUIDITY 𝑝𝑏
The defining idea of Uniswap v3 is that of concentrated liquidity: This curve is a translation of formula 2.1 such that the position is
liquidity bounded within some price range. solvent exactly within its range (Fig. 2).
In earlier versions, liquidity was distributed uniformly along the
𝑥 ·𝑦 = 𝑘 (2.1)
reserves curve, where 𝑥 and 𝑦 are the respective reserves of two virtual reserves (2.1)
assets X and Y, and 𝑘 is a constant [1]. In other words, earlier ver- real reserves (2.2)
sions were designed to provide liquidity across the entire price
range (0, ∞). This is simple to implement and allows liquidity to
be efficiently aggregated, but means that much of the assets held in 𝑏
a pool are never touched.
Y Reserves
Having considered this, it seems reasonable to allow LPs to
concentrate their liquidity to smaller price ranges than (0, ∞). We
call liquidity concentrated to a finite range a position. A position
only needs to maintain enough reserves to support trading within
its range, and therefore can act like a constant product pool with
larger reserves (we call these the virtual reserves) within that range. 𝑎
virtual reserves
X Reserves
𝑥 real
Figure 2: Real Reserves
𝑏
Y Reserves
Liquidity
Liquidity
Liquidity
0 ∞ 𝑝𝑎 𝑝𝑏
Price Price Price
(I) Uniswap v2 (II) A single position on [𝑝𝑎 , 𝑝𝑏 ] (III) A collection of custom positions
wanted to compute a TWAP. Uniswap v3 brings the accumulator of token0 is not equivalent to the reciprocal of the time-weighted
checkpoints into core, allowing external contracts to compute on- arithmetic mean price of token1.
chain TWAPs over recent periods without storing checkpoints of Using the time-weighted geometric mean price, as Uniswap v3
the accumulator value. does, avoids the need to track separate accumulators for these
Another change is that instead of accumulating the sum of prices, ratios. The geometric mean of a set of ratios is the reciprocal of the
allowing users to compute the arithmetic mean TWAP, Uniswap geometric mean of their reciprocals. It is also easy to implement
v3 tracks the sum of log prices, allowing users to compute the in Uniswap v3 because of its implementation of custom liquidity
geometric mean TWAP. provision, as described in section 6. In addition, the accumulator can
Finally, Uniswap v3 adds a liquidity accumulator that is tracked be stored in a smaller number of bits, since it tracks log 𝑃 rather than
alongside the price accumulator, which accumulates 𝐿1 for each 𝑃, and log 𝑃 can represent a wide range of prices with consistent
second. This liquidity accumulator is useful for external contracts precision.4 Finally, there is a theoretical argument that the time-
that want to implement liquidity mining on top of Uniswap v3. It weighted geometric mean price should be a truer representation of
can also be used by other contracts to inform a decision on which the average price.5
of the pools corresponding to a pair (see section 3.1) will have the Instead of tracking the cumulative sum of the price 𝑃, Uniswap
most reliable TWAP. v3 accumulates the cumulative sum of the current tick index (𝑙𝑜𝑔1.0001 𝑃,
the logarithm of price for base 1.0001, which is precise up to 1 basis
5.1 Oracle Observations point). The accumulator at any given time is equal to the sum of
As in Uniswap v2, Uniswap v3 tracks a running accumulator of 𝑙𝑜𝑔1.0001 (𝑃) for every second in the history of the contract:
the price at the beginning of each block, multiplied by the number 𝑡
of seconds since the last block.
Õ
𝑎𝑡 = log1.0001 (𝑃𝑖 ) (5.1)
A pool in Uniswap v2 stores only the most recent value of this 𝑖=1
price accumulator—that is, the value as of the last block in which a We want to estimate the geometric mean time-weighted average
swap occurred. When computing average prices in Uniswap v2, it price (𝑝𝑡1 ,𝑡2 ) over any period 𝑡 1 to 𝑡 2 .
is the responsibility of the external caller to provide the previous
value of the price accumulator. With many users, each will have to 1
𝑡2 𝑡 2 −𝑡 1
provide their own methodology for checkpointing previous values ©Ö ª
𝑃𝑡1 ,𝑡2 = 𝑃𝑖 ® (5.2)
of the accumulator, or coordinate on a shared method to reduce
costs. And there is no way to guarantee that every block in which «𝑖=𝑡1 ¬
the pool is touched will be reflected in the accumulator. To compute this, you can look at the accumulator’s value at 𝑡 1
In Uniswap v3, the pool stores a list of previous values for the and at 𝑡 2 , subtract the first value from the second, divide by the
price accumulator (as well as the liquidity accumulator described number of seconds elapsed, and compute 1.0001𝑥 to compute the
in section 5.3). It does this by automatically checkpointing the time weighted geometric mean price.
accumulator value every time the pool is touched for the first time Í𝑡2
in a block, cycling through an array where the oldest checkpoint is 𝑖=𝑡 1 log1.0001 (𝑃𝑖 )
log1.0001 𝑃𝑡1 ,𝑡2 = (5.3)
eventually overwritten by a new one, similar to a circular buffer. 𝑡2 − 𝑡1
While this array initially only has room for a single checkpoint, 𝑎𝑡 − 𝑎𝑡 1
anyone can initialize additional storage slots to lengthen the array, log1.0001 𝑃𝑡1 ,𝑡2 = 2 (5.4)
𝑡2 − 𝑡1
extending to as many as 65,536 checkpoints.3 This imposes the
one-time gas cost of initializing additional storage slots for this 𝑎𝑡 2 −𝑎𝑡 1
𝑃𝑡1 ,𝑡2 = 1.0001 𝑡 2 −𝑡 1 (5.5)
array on whoever wants this pair to checkpoint more slots.
The pool exposes the array of past observations to users, as well
as a convenience function for finding the (interpolated) accumulator 5.3 Liquidity Oracle
value at any historical timestamp within the checkpointed period. In addition to the seconds-weighted accumulator of log1.0001 𝑝𝑟𝑖𝑐𝑒,
Uniswap v3 also tracks a seconds-weighted accumulator of 𝐿1 (the
5.2 Geometric Mean Price Oracle reciprocal of the virtual liquidity currently in range) at the begin-
Uniswap v2 maintains two price accumulators—one for the price of ning of each block: secondsPerLiquidityCumulative (𝑠𝑝𝑙 ).
token0 in terms of token1, and one for the price of token1 in terms This can be used by external liquidity mining contracts to fairly
of token0. Users can compute the time-weighted arithmetic mean allocate rewards. If an external contract wants to distribute rewards
of the prices over any period, by subtracting the accumulator value at an even rate of 𝑅 tokens per second to all active liquidity in the
at the beginning of the period from the accumulator at the end of 4 Inorder to support tolerable precision across all possible prices, Uniswap v2 repre-
the period, then dividing the difference by the number of seconds sents each price as a 224-bit fixed-point number. Uniswap v3 only needs to represent
in the period. Note that accumulators for token0 and token1 are 𝑙𝑜𝑔1.0001 𝑃 as a signed 24-bit number, and still can detect price movements of one tick,
tracked separately, since the time-weighted arithmetic mean price or 1 basis point.
5 While arithmetic mean TWAPs are much more widely used, they should theoretically
be less accurate in measuring a geometric Brownian motion process (which is how price
3 The maximum of 65,536 checkpoints allows fetching checkpoints for at least 9 days movements are usually modeled). The arithmetic mean of a geometric Brownian motion
after they are written, assuming 13 seconds pass between each block and a checkpoint process will tend to overweight higher prices (where small percentage movements
is written every block. correspond to large absolute movements) relative to lower ones.
4
Uniswap v3 Core
contract, and a position with 𝐿 liquidity was active from 𝑡 0 to 𝑡 1 , Whenever the price crosses an initialized tick, virtual liquidity
then its rewards for that period would be 𝑅·L·(𝑠𝑝𝑙 (𝑡 1 ) − 𝑠𝑝𝑙 (𝑡 0 )). is kicked in or out. The gas cost of an initialized tick crossing is
In order to extend this so that concentrated liquidity is rewarded constant, and is not dependent on the number of positions being
only when it is in range, Uniswap v3 stores a computed checkpoint kicked in or out at that tick.
based on this value every time a tick is crossed, as described in Ensuring that the right amount of liquidity is kicked in and out
section 6.3. of the pool when ticks are crossed, and ensuring that each position
This accumulator can also be used by on-chain contracts to make earns its proportional share of the fees that were accrued while
their oracles stronger (such as by evaluating which fee-tier pool to it was within range, requires some accounting within the pool.
use the oracle from). The pool contract uses storage variables to track state at a global
(per-pool) level, at a per-tick level, and at a per-position level.
6 IMPLEMENTING CONCENTRATED
LIQUIDITY 6.2 Global State
The rest of this paper describes how concentrated liquidity provi- The global state of the contract includes seven storage variables
sion works, and gives a high-level description of how it is imple- relevant to swaps and liquidity provision. (It has other storage
mented in the contracts. variables that are used for the oracle, as described in section 5.)
any fees have been earned. In some ways, liquidity can be thought
of as virtual liquidity tokens. Δ𝑦 = 𝑦𝑖𝑛 · (1 − 𝛾) (6.11)
Alternatively, liquidity can be thought of as the amount that
If you used the computed virtual reserves (𝑥 and 𝑦) for the token0
√ reserves (either actual or virtual) changes for a given change
token1
and token1 balances, then this formula could be used to find the
in 𝑃: amount of token0 sent out:
Δ𝑌 𝑥 ·𝑦
𝐿=
√ (6.7) 𝑥𝑒𝑛𝑑 = (6.12)
Δ 𝑃 𝑦 + Δ𝑦
√
We track 𝑃 instead of 𝑃 to take advantage of this relationship, But remember that in v3,
√ the contract actually tracks liquidity (𝐿)
and to avoid having to take any square roots when computing and square root of price ( 𝑃) instead of 𝑥 and 𝑦. We could compute
swaps, as described in section 6.2.3. 𝑥 and 𝑦 from those values, and then use those to calculate the
The global state also tracks the current tick index as tick (𝑖𝑐 ), a execution price of the trade. But it turns out that √
there are simple
signed integer representing the current tick (more specifically, the formulas that describe the relationship between Δ 𝑃 and Δ𝑦, for a
nearest tick below the current price). This is an optimization (and given 𝐿 (which can be derived from formula 6.7):
a way of avoiding precision issues with logarithms), since at any
time, you should be able to compute the current tick based on the √ Δ𝑦
Δ 𝑃= (6.13)
current sqrtPrice. Specifically, at any given time, the following 𝐿
equation should be true: √
j √ k Δ𝑦 = Δ 𝑃 · 𝐿 (6.14)
𝑖𝑐 = log√1.0001 𝑃 (6.8) There are also simple formulas that describe the relationship
between Δ √1 and Δ𝑥:
6.2.2 Fees. Each pool is initialized with an immutable value, fee 𝑃
(𝛾), representing the fee paid by swappers in units of hundredths 1 Δ𝑥
of a basis point (0.0001%). Δ√ = (6.15)
𝑃 𝐿
It also tracks the current protocol fee, 𝜙 (which is initialized to
zero, but can changed by UNI governance).6 This number gives you 1
Δ𝑥 = Δ √ · 𝐿 (6.16)
the fraction of the fees paid by swappers that currently goes to the 𝑃
protocol rather than to liquidity providers. 𝜙 only has a limited set When swapping one √ token for the other, the pool contract can
of permitted values: 0, 1/4, 1/5, 1/6, 1/7, 1/8, 1/9, or 1/10. first compute the new 𝑃 using formula 6.13 or 6.15, and then
The global state also tracks two numbers: feeGrowthGlobal0 can compute the amount of token0 or token1 to send out using
(𝑓𝑔,0 ) and feeGrowthGlobal1 (𝑓𝑔,1 ). These represent the total amount formula 6.14 or 6.16. √
of fees that have been earned per unit of virtual liquidity (𝐿), over These formulas will work for any swap that does not push √𝑃
the entire history of the contract. You can think of them as the total
amount of fees that would have been earned by 1 unit of unbounded √ of the next initialized tick. If the computed Δ 𝑃
past the price
would cause 𝑃 to move past that next initialized tick, the contract
liquidity that was deposited when the contract was first initialized. must only cross up to that tick—using up only part of the swap—and
They are stored as fixed-point unsigned 128x128 numbers. Note then cross the tick, as described in section 6.3.1, before continuing
that in Uniswap v3, fees are collected in the tokens themselves with the rest of the swap.
rather than in liquidity, for reasons explained in section 3.2.1.
Finally, the global state tracks the total accumulated uncollected 6.2.4 Initialized Tick Bitmap. If a tick is not used as the endpoint
protocol fee in each token, protocolFees0 (𝑓𝑝,0 ) and protocolFees1 of a range with any liquidity in it—that is, if the tick is uninitial-
(𝑓𝑝,1 ). This is an unsigned uint128. The accumulated protocol fees ized—then that tick can be skipped during swaps.
can be collected by UNI governance, by calling the collectProtocol As an optimization to make finding the next initialized tick more
function. efficient, the pool tracks a bitmap tickBitmap of initialized ticks.
The position in the bitmap that corresponds to the tick index is set
6.2.3 Swapping Within a Single Tick. For small enough swaps, that to 1 if the tick is initialized, and 0 if it is not initialized.
do not move the price past a tick, the contracts act like an 𝑥 · 𝑦 = 𝑘 When a tick is used as an endpoint for a new position, and that
pool. tick is not currently used by any other liquidity, the tick is initialized,
Suppose 𝛾 is the fee, i.e., 0.003, and 𝑦𝑖𝑛 as the amount of token1 and the corresponding bit in the bitmap is set to 1. An initialized
sent in. tick can become uninitialized again if all of the liquidity for which
First, feeGrowthGlobal1 and protocolFees1 are incremented: it is an endpoint is removed, in which case that tick’s position on
the bitmap is zeroed out.
Δ𝑓𝑔,1 = 𝑦𝑖𝑛 · 𝛾 · (1 − 𝜙) (6.9)
6.3 Tick-Indexed State
Δ𝑓𝑝,1 = 𝑦𝑖𝑛 · 𝛾 · 𝜙 (6.10) The contract needs to store information about each tick in order to
Δ𝑦 is the increase in 𝑦 (after the fee is taken out). track the amount of net liquidity that should be added or removed
6 Technically, the storage variable called “protocolFee" is the denominator of this when the tick is crossed, as well as to track the fees earned above
fraction (or is zero, if 𝜙 is zero). and below that tick.
6
Uniswap v3 Core
Start
Fail
S0. Check input Stop
Pass
No
S2. Is there remaining input or output? S5. Execute computed swap
Yes
REFERENCES
𝑓𝑢 = 𝑙 · (𝑓𝑟 (𝑡 1 ) − 𝑓𝑟 (𝑡 0 )) (6.28) [1] Hayden Adams, Noah Zinsmeister, and Dan Robinson. 2020. Uniswap v2 Core.
Retrieved Feb 24, 2021 from https://uniswap.org/whitepaper.pdf
Then, the contract updates the position’s liquidity by adding [2] Guillermo Angeris and Tarun Chitra. 2020. Improved Price Oracles: Constant
liquidityDelta. It also adds liquidityDelta to the liquidityNet Function Market Makers. In Proceedings of the 2nd ACM Conference on Advances
value for the tick at the bottom end of the range, and subtracts it in Financial Technologies (AFT ’20). Association for Computing Machinery, New
York, NY, United States, 80–91. https://doi.org/10.1145/3419614.3423251
from the liquidityNet at the upper tick (to reflect that this new [3] Michael Egorov. 2019. StableSwap - Efficient Mechanism for Stablecoin Liquidity.
liquidity would be added when the price crosses the lower tick Retrieved Feb 24, 2021 from https://www.curve.fi/stableswap-paper.pdf
[4] Allan Niemerg, Dan Robinson, and Lev Livnev. 2020. YieldSpace: An Automated
going up, and subtracted when the price crosses the upper tick Liquidity Provider for Fixed Yield Tokens. Retrieved Feb 24, 2021 from https:
going up). If the pool’s current price is within the range of this //yield.is/YieldSpace.pdf
position, the contract also adds liquidityDelta to the contract’s [5] Abraham Othman. 2012. Automated Market Making: Theory and Practice. Ph.D.
Dissertation. Carnegie Mellon University.
global liquidity value.
Finally, the pool transfers tokens from (or, if liquidityDelta
is negative, to) the user, corresponding to the amount of liquidity
DISCLAIMER
burned or minted. This paper is for general information purposes only. It does not
The amount of token0 (Δ𝑋 ) or token1 (Δ𝑌 ) that needs to be constitute investment advice or a recommendation or solicitation to
deposited can be thought of as the amount that would be sold from buy or sell any investment and should not be used in the evaluation
the position if the price were to move from the current price (𝑃) to of the merits of making any investment decision. It should not be
the upper tick or lower tick (for token0 or token1, respectively). relied upon for accounting, legal or tax advice or investment rec-
These formulas can be derived from formulas 6.14 and 6.16, and ommendations. This paper reflects current opinions of the authors
depend on whether the current price is below, within, or above the and is not made on behalf of Uniswap Labs, Paradigm, or their
range of the position: affiliates and does not necessarily reflect the opinions of Uniswap
Labs, Paradigm, their affiliates or individuals associated with them.
0 𝑖𝑐 < 𝑖𝑙 The opinions reflected herein are subject to change without being
√ updated.
Δ𝑌 = Δ𝐿 · ( 𝑃 − 𝑝 (𝑖𝑙 ))
p
𝑖𝑙 ≤ 𝑖𝑐 < 𝑖𝑢 (6.29)
Δ𝐿 · ( 𝑝 (𝑖𝑢 ) − 𝑝 (𝑖𝑙 )) 𝑖𝑐 ≥ 𝑖𝑢
p p
Δ𝐿 · ( √ 1 − √ 1 ) 𝑖𝑐 < 𝑖𝑙
𝑝 (𝑖𝑙 ) 𝑝 (𝑖𝑢 )
Δ𝑋 = Δ𝐿 · ( √1 − √ 1 )
𝑖𝑙 ≤ 𝑖𝑐 < 𝑖𝑢 (6.30)
𝑃 𝑝 (𝑖𝑢 )
0
𝑖𝑐 ≥ 𝑖𝑢