Uniswap Part Ⅱ | 提供/移除流动性
提供流动性
在合约内,v3 会保存所有用户的流动性,代码内称作 Position
1 |
|
NonfungiblePositionManager
的mint函数实现初始的流动性的添加。increaseLiquidity
函数实现了流动性的增加。这两个函数的逻辑基本一致,都是通过调用addLiquidity
函数实现。mint需要额外创建ERC721的token。
1 |
|
addLiquidity
1 |
|
getLiquidityForAmounts
用来计算流动性
1 |
|
情况1:当前池中的价格小于等于价格范围的最小值
此时添加的流动性全部为x token
以上过程表示在getLiquidityForAmount0中
1 |
|
情况二:当前池中的价格在价格范围中
此时添加的流动性包含两个币种,可以通过任意一个 token 数量计算出流动性
1 |
|
比较两种算法的结果,取更小的一个
情况三:当前池中的价格大于等于价格范围的最大值
此时添加的流动性全部为y token
以上过程表示在getLiquidityForAmount0中
1 |
|
MintCallbackData
这里是为了将Position的owner和实际流动性token的支付者解耦,让合约来管理用户的流动性,并将流动性token化(ERC721)
用户调用NonfungiblePositionManager来提供流动性,所以Position的owner是NonfungiblePositionManager,NonfungiblePositionManager是通过NFT token将Position和用户关联起来的
这个函数可以将指定数量的token0与token1发送到合约中去
1 |
|
mint
位于UniswapV3Pool.sol
1 |
|
_modifyPosition
ModifyPositionParams用于存储Position(流动性)相关的数据信息,包括流动性所有者地址、流动性的上下限、流动性的改动:
1 |
|
_modifyPosition用于更新当前Position
添加流动性的规则
我们知道V3的核心公式
y = p*x
x*y = L^2
可以得到
x = L / √p
y = L * √p
当价格p在区间内时
橙色区域是我们实际需要添加的流动性,虚拟流动性是绿色区域扣除橙色的部分的宽度和高度。
delta x
就是 p
(红点) 和 p_upper
在 x 轴上的距离, delta y
就是 p
和 p_lower
在 y 轴上的距离。
1 |
|
再变换一下,改写成求 L(流动性数量)的等式
1 |
|
当价格p大于区间时
1 |
|
当价格p小于区间时
1 |
|
转化为代码
注意:这里的amount0与amount1为int256类型,也就是说这里的amount0与amount1这两个返回值可正可负,如果为正则表示流动性提供至需要给池子给予的数量,为负数则表示池子需要给流动性提供者给予的数量
1 |
|
getAmountDelta
在 SqrtPriceMath
库中
在具体的计算过程中,分成了 RoundUp 和 RoundDown 两种情况,简单来说:
- 当提供/增加流动性时,会使用 RoundUp,这样可以保证增加数量为 L 的流动性时,用户提供足够的 token 到 pool 中
- 当移除/减少流动性时,会使用 RoundDown,这样可以保证减少数量为 L 的流动性时,不会从 pool 中给用户多余的 token
1 |
|
_updatePosition
1 |
|
tick
V3使用的等幂数列
1 |
|
这里的 i
也就是价格的序号,我们称之为 tick
,而由所有序号组成的集合称之为 Ticks
。在合约代码中,主要是以 tick 来记录流动性的区间。
在 UniswapV3Pool
合约中有两个状态变量记录了 tick 相关的信息:
1 |
|
tick 位图用于记录所有被引用的 lower/upper tick index,我们可以用过 tick 位图,从当前价格找到下一个(从左至右或者从右至左)被引用的 tick index。
tick 位图有以下几个特性:
- 对于不存在的 tick,不需要初始值,因为访问 map 中不存在的 key 默认值就是 0
- 通过对位图的每个 word(uint256) 建立索引来管理位图,即访问路径为 word index -> word -> tick bit
liquidityGross
: 很好理解,每当有流动性将该 tick 设为价格区间时,不论是价格上限还是价格下限, liquidityGross
都会增加。换言之,当 liquidityGross > 0
说明该 tick 已经初始化,正在被流动性使用,而 liquidityGross == 0
则该 tick 未初始化,没有流动性使用,计算时可以忽略。
liquidityNet
表示当价格从左至右经过此 tick 时整体流动性需要变化的净值。在单个流动性中,对于 lower tick 来说,它的值为正,对于 upper tick 来说它的值为 负。
在注入或移除数量为 l
的流动性时,具体规则如下:
- 注入流动性,tick 是价格下限,
liquidityNet
增加l
- 注入流动性,tick 是价格上限,
liquidityNet
减少l
- 移除流动性,tick 是价格下限,
liquidityNet
减少l
- 移除流动性,tick 是价格上限,
liquidityNet
增加l
在Tick.sol中,update用于更新 tick 元数据,此函数返回的 flipped 表示此 tick 的引用状态是否发生变化,之前的 _updatePosition
中的代码会根据这个返回值去更新 tick 位图
1 |
|
tickspacing
V3 引入了费率三档可选等级和相应的 tick
疏密程度,也就是 tickspacing
。对于每一种交易对而言,都有三档可选费率等级,0.05%, 0.3%, 1%,并且以后通过社区治理,还有可能永久增加可选的挡位。每种交易费率等级都由给定的 tickspacing,比如稳定币交易对,就是 tick 之间需要间隔 10 个才是有效的可使用的 tick 。位于间隔内的 tick 虽然存在,但程序不会去初始化和使用,也就不会产生 gas 费用。因此,我们在等幂数列的基础上,进一步节省了计算消耗。
费率 | tickspacing | 建议的使用范围 |
---|---|---|
0.05% | 10 | 稳定币交易对 |
0.3% | 60 | 适用大多数交易对 |
1% | 200 | 波动极大的交易对 |
在UniswapV3Factory.sol中设定
1 |
|
移除流动性
在合约UniswapV3Pool中,burn用来实现流动性的移除
1 |
|