Uniswap v3
Last updated
Last updated
Concentrated Liquidity
LPs can concentrate their liquidity by “bounding" it within an arbitrary price range that is smaller than (0, ∞). This improves the pool’s capital efficiency and allows LPs to approximate their preferred reserves curve.
Previously liquidity was distributed uniformly along the 𝑥𝑦 = 𝑘 curve.
Liquidity concentrated to a finite range is called 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 within that range.
A position only needs to hold enough of asset X to cover price movement to its upper bound, because upwards price movement corresponds to depletion of the X reserves. Similarly, it only needs to hold enough of asset Y to cover price movement to its lower bound.
When the price exits a position’s range, the position’s liquidity is no longer active, and no longer earns fees. At that point, its liquidity is composed entirely of a single asset, because the reserves of the other asset must have been entirely depleted. If the price ever reenters the range, the liquidity becomes active again.
Multiple Pools Per Pair
In Uniswap v1 and v2, every pair of tokens corresponds to a single liquidity pool, which applies a uniform fee of 0.30% to all swaps. While this default fee tier historically worked well enough for many tokens, it is likely too high for some pools (such as pools between two stablecoins), and too low for others (such as pools that include highly volatile or rarely traded tokens).
Uniswap v3 introduces multiple pools for each pair of tokens, each with a different swap fee. All pools are created by the same factory contract. The factory contract initially allows pools to be created at three fee tiers: 0.05%, 0.30%, and 1%. Additional fee tiers can be enabled by UNI governance.
Non-Fungible Utility
Non-Compounding Fees - Fees earned in earlier versions were continuously deposited in the pool as liquidity. This meant that liquidity in the pool would grow over time, even without explicit deposits, and that fee earnings compounded. In Uniswap v3, due to the non-fungible nature of positions, this is no longer possible. Instead, fee earnings are stored separately and held as the tokens in which the fees are paid.
Removal of Native Liquidity Tokens - In Uniswap v1 and v2, the pool contract is also an ERC-20 token contract, whose tokens represent liquidity held in the pool. While this is convenient, it actually sits uneasily with the Uniswap v2 philosophy that anything that does not need to be in the core contracts should be in the periphery, and blessing one “canonical" ERC-20 implementation discourages the creation of improved ERC-20 token wrappers. Arguably, the ERC-20 token implementation should have been in the periphery, as a wrapper on a single liquidity position in the core contract.
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. As a result, in v3, the pool contract does not implement the ERC-20 standard.
Anyone can create an ERC-20 token contract in the periphery that makes a liquidity position more fungible, but it will have to have additional logic to handle distribution of, or reinvestment of, collected fees. Alternatively, anyone could create a periphery contract that wraps an individual liquidity position (including collected fees) in an ERC-721 non-fungible token.
Oracle Upgrades
Uniswap v3 includes three significant changes to the time-weighted average price (TWAP) oracle that was introduced by Uniswap v2.
Uniswap v3 removes the need for users of the oracle to track previous values of the accumulator externally. Uniswap v3 brings the accumulator checkpoints into core, allowing external contracts to compute onchain TWAPs over recent periods without storing checkpoints of the accumulator value.
Another change is that instead of accumulating the sum of prices, allowing users to compute the arithmetic mean TWAP, Uniswap v3 tracks the sum of log prices, allowing users to compute the geometric mean TWAP.
Finally, Uniswap v3 adds a liquidity accumulator that is tracked alongside the price accumulator, which accumulates 1/𝐿 for each second. This liquidity accumulator is useful for external contracts that want to implement liquidity mining on top of Uniswap v3. It can also be used by other contracts to inform a decision on which of the pools corresponding to a pair will have the most reliable TWAP.
UniswapV3Factory
Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees
createPool()
Creates a pool for the given two tokens and fee
The call will revert if the pool already exists, the fee is invalid, or the token arguments are invalid.
get tick spacing according to the fee amount desired
Calls deploy
on PoolDeployer contract:
UniswapV3Pool
swap()
Swaps token0 for token1, or token1 for token0
The caller of this method receives a callback - ISwapRouter.uniswapV3SwapCallback()
recipient
address
The address to receive the output of the swap
zeroForOne
bool
The direction of the swap, true for token0 to token1, false for token1 to token0
amountSpecified
int256
The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
sqrtPriceLimitX96
uint160
The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap
data
bytes
Any data to be passed through to the callback
checks that amountSpecified is non-zero (why not positive?)
all the ticks bs
transfer output tokens to recipient
call the SwapCallback
in the router contract
Router for stateless execution of swaps against Uniswap V3
constructor sets singleton factory and WETH addresses as immutable state variables
exactInputSingle()
Swaps amountIn
of one token for as much as possible of another token
Calls exactInputInternal()
using input params
Checks that amountOut is at least the minimum amount requested in input params, else function reverts
exactInput()
Swaps amountIn
of one token for as much as possible of another along the specified path
Calls exactInputInternal()
using input params
exactInputInternal()
internal function called by above exactInput
external functions
if the recipient is the zero address, set recipient as router address
calls UniswapV3Pool.swap()
exactOutputSingle()
exactOutput()
uniswapV3SwapCallback()
Called to msg.sender
after executing a swap via IUniswapV3Pool.swap()
.
In the implementation you must pay the pool tokens owed for the swap.
The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
checks that both amounts are positive
call pay()
in helper contracts to transfer input tokens from user to