May 18, 2021 | Dan Robinson
Uniswap v3 replaces fungible ERC-20 liquidity positions with non-fungible ERC-721 liquidity positions.
Does that mean it no longer supports flexible Uniswap-v2-style liquidity mining? Or that liquidity mining programs will have to be actively managed, selecting specific ranges to incentivize? Or that liquidity mining programs could be gamed by providing huge amounts of inactive liquidity?
No.
Uniswap v3 can support the same kind of liquidity mining as Uniswap v2—incentivizing all active liquidity pro rata, at a constant rate per second—with only relatively modest compromises. It indirectly incentivizes concentration of liquidity, since liquidity providers are rewarded for their share of virtual liquidity (while it is active), rather than the total value of the tokens they supplied. It can even do all this in a single staking contract, allowing people to stake the same liquidity with multiple incentives at the same time and eliminating the need to deploy new contracts for different incentives.
Omar Bohsali (currently an Entrepreneur in Residence at Paradigm) recently received a grant from the Uniswap Grants Program to implement this algorithm. You can see track his progress on GitHub.
This is the first in a series of posts on How I Learned To Stop Worrying and Love Non-Fungible Liquidity. The next post will talk about how Uniswap v3 positions can be used as collateral.
To understand how the standard liquidity mining algorithm can be adapted for Uniswap v3, we first need to understand how it works for earlier versions of Uniswap.
This algorithm—for efficient incremental calculation of proportional reward distribution—was first described in Scalable Reward Distribution on the Ethereum Blockchain, by Bogdan Batog, Lucian Boca, and Nick Johnson. (Hat tip @FrankieIsLost for discovering this paper.)
The first liquidity mining program where rewards were computed on-chain was SNX's incentive for the sETH Uniswap pool. This was implemented in the Unipool sETH contract, written in 2019 by Anton Bukov. I consider this one of the most influential smart contracts ever written.
Suppose you want to incentivize liquidity in a Uniswap v1 or v2 pool over a particular period. You want to distribute tokens fairly across liquidity providers and linearly over time—say, at a rate of
To do this, imagine cutting the pool into single-second time slices. For each of those slices, a given liquidity provider should receive
This would be easy enough to compute off-chain. But how can we efficiently compute it on-chain, with only incremental updates every time someone enters or leaves the pool?
If the user's balance
We can then decompose that sum into a difference of two sums. (A very similar trick was used for the oracle accumulator introduced in Uniswap v2.)
This means that all we need to track in the staking contract is a single accumulator tracking "seconds per liquidity" since the beginning of the pool:
In the Unipool contract, this accumulator is called rewardPerTokenStored
. When anyone stakes or unstakes liquidity—thus changing
When someone stakes liquidity, the contract checkpoints their starting value of the accumulator,
Uniswap v1 and v2 didn't need any built-in support for these incentives, because these numbers (both the accumulator and the checkpointed values) only change when the staking contract is touched.
In addition to laying the groundwork for the yield farming trend—during which it was forked repeatedly—this clever algorithm turned out to be quite versatile. For example, Uniswap v3 uses a similar algorithm to track fees earned by individual positions (with some additional tricks to support concentrated liquidity). While it is most useful on-chain (where efficiency is paramount), it is also useful for simplifying off-chain computations. The UNI retroactive distribution was based on a query that reimplemented this algorithm in SQL.
Uniswap v3 complicates the situation, but not unsolveably.
As described in the whitepaper, Uniswap v3 supports concentrated liquidity—liquidity that is active only while the current price tick (
This means that a price movement can change the liquidity balance of staked positions without the staking contract being touched. For a given position with
This can be decomposed into:
We can interpret this as secondsPerLiquidityInside = secondsPerLiquidity
- secondsPerLiquidityBelow(lowerTick) - secondsPerLiquidityAbove(upperTick)
.
The first term can be computed using the same global secondsPerLiquidityX128
.
To allow us to compute the other two terms, Uniswap v3 checkpoints secondsPerLiquidityOutside
for each tick every time that tick is crossed. "Outside" means on the opposite side of the tick from the current price. This allows us to compute the total secondsPerLiquidity
accrued on either side of the tick at any time. For example, if tickCurrent < i
, then secondsPerLiquidityBelow(i) = secondsPerLiquidity - secondsPerLiquidityOutside(i)
; if tickCurrent >= i
, then secondsPerLiquidityBelow(i) = secondsPerLiquidityOutside(i)
. (This is quite similar to how Uniswap v3 tracks and computes the fees earned above and below ticks.)
For any range, we can therefore use the above formula to compute secondsPerLiquidityInside
for that range. Uniswap v3 does this calculation for you, and exposes the result through the convenient snapshotCumulativesInside
function.
The staking contract can simply snapshot this secondsPerLiquidityInside
when a user stakes. When they later unstake, the staking contract looks at the new secondsPerLiquidityInside
, takes the difference, and multiplies it by
We do have to make some compromises based on technical limitations of the new system:
Far from "breaking" liquidity mining, Uniswap v3 opens up a vast design space for it. While this post describes an algorithm for implementing "classic" liquidity mining on top of Uniswap v3, I think this barely scratches the surface. As described in section 6.3 of the whitepaper, the Uniswap v3 core contracts expose several other indexes (such as secondsOutside
and tickCumulativeOutside
). I'm looking forward to seeing how protocols make use of these new features.
In a future post, I'll address another common concern about building on top of Uniswap v3 positions: how lending protocols can use them as collateral.
Disclaimer: This post is for general information purposes only. It does not constitute investment advice or a recommendation or solicitation to buy or sell any investment and should not be used in the evaluation of the merits of making any investment decision. It should not be relied upon for accounting, legal or tax advice or investment recommendations. This post reflects the current opinions of the authors and is not made on behalf of Paradigm or its affiliates and does not necessarily reflect the opinions of Paradigm, its affiliates or individuals associated with Paradigm. The opinions reflected herein are subject to change without being updated.