本文首发于公众号:Keegan小钢
UniswapV3Pool 合约则复杂很多了,其引用的库合约就达到了 13 个,通过 using 方式使用的也达到了 9 个,如下所示:
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;
using SafeCast for uint256;
using SafeCast for int256;
using Tick for mapping(int24 => Tick.Info);
using TickBitmap for mapping(int16 => uint256);
using Position for mapping(bytes32 => Position.Info);
using Position for Position.Info;
using Oracle for Oracle.Observation[65535];
LowGasSafeMath
是用于加减乘除算法计算的,SafeCast
用于类型转换,Tick
和 TickBitmap
用于管理 tick 处理相关的操作和计算,Position
则主要用于更新流动性的头寸,Oracle
则是用于预言机计算的。
接着,来看看定义了哪些状态变量:
address public immutable override factory;
address public immutable override token0;
address public immutable override token1;
uint24 public immutable override fee;
int24 public immutable override tickSpacing;
uint128 public immutable override maxLiquidityPerTick;
struct Slot0 {
// the current price
uint160 sqrtPriceX96;
// the current tick
int24 tick;
// the most-recently updated index of the observations array
uint16 observationIndex;
// the current maximum number of observations that are being stored
uint16 observationCardinality;
// the next maximum number of observations to store, triggered in observations.write
uint16 observationCardinalityNext;
// the current protocol fee as a percentage of the swap fee taken on withdrawal
// represented as an integer denominator (1/x)%
uint8 feeProtocol;
// whether the pool is locked
bool unlocked;
}
Slot0 public override slot0;
uint256 public override feeGrowthGlobal0X128;
uint256 public override feeGrowthGlobal1X128;
// accumulated protocol fees in token0/token1 units
struct ProtocolFees {
uint128 token0;
uint128 token1;
}
ProtocolFees public override protocolFees;
uint128 public override liquidity;
mapping(int24 => Tick.Info) public override ticks;
mapping(int16 => uint256) public override tickBitmap;
mapping(bytes32 => Position.Info) public override positions;
Oracle.Observation[65535] public override observations;
前 5 个变量我们都已经了解过了,第 6 个变量 maxLiquidityPerTick
表示每个 tick 能接受的最大流动性,是在构造函数中根据 tickSpacing 计算出来的。
slot0
记录了当前的一些状态值,都封装在了结构体 Slot0
中,其共有 7 个字段。sqrtPriceX96
是当前价格,记录的是根号价格,且做了扩展,准确来说:sqrtPriceX96 = (token1数量 / token0数量) ^ 0.5 * 2^96
。换句话说,这个值代表的是 token0 和 token1 数量比例的平方根,经过放大以获得更高的精度。这样设计的目的是为了方便和优化合约中的一些计算。如果想从 sqrtPriceX96
得出具体的价格,还需要做一些额外的计算。tick
记录了当前价格对应的价格点。observationIndex
、observationCardinality
和 observationCardinalityNext
是跟 observations
数组有关的,也是计算预言机价格时需要的,这在之前的文章《价格预言机的使用总结(三):UniswapV3篇》讲解 UniswapV3 预言机时已经介绍过,这里不再赘述。feeProtocol
则用来存储协议费率,初始化时为 0,可通过 setFeeProtocol
函数来重置该值。unlocked
记录池子的锁定状态,初始化时为 true,主要作为一个防止重入锁来使用。
feeGrowthGlobal0X128
和 feeGrowthGlobal1X128
记录两个 token 的每单位流动性所获取的手续费。
protocolFees
则记录了两个 token 的累计未被领取的协议手续费。
liquidity
记录了池子当前可用的流动性。注意,这里不是指注入池子里的所有流动性总量,而是包含了当前价格的那些有效头寸的流动性总量。
ticks
记录池子里每个 tick 的详细信息,key 为 tick 的序号,value 就是详细信息。tickBitmap
记录已初始化的 tick 的位图。如果一个 tick 没有被用作流动性区间的边界点,即该 tick 没有被初始化,那在交易过程中可以跳过这个 tick。而为了更高效地寻找下一个已初始化的 tick,就使用了 tickBitmap 来记录已初始化的 tick。如果 tick 已被初始化,位图中对应于该 tick 序号的位置设置为 1,否则为 0。
positions
记录每个流动性头寸的详细信息,具体信息如下:
library Position {
// 用于存储每个用户的头寸信息
struct Info {
// 当前头寸的总流动性
uint128 liquidity;
// 截止最后一次更新流动性或所欠费用时,每单位流动性的费用增长
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
// 欠头寸所有者的费用
uint128 tokensOwed0;
uint128 tokensOwed1;
}
...
}
observations
则是存储了计算预言机价格相关的累加值,包括 tick 累加值和流动性累加值。具体用法在《价格预言机的使用总结(三):UniswapV3篇》一文中已经介绍过,这里也不再赘述。
接下来就到合约函数了,UniswapV3Pool 核心的函数在 IUniswapV3PoolActions 接口里有定义,该接口共定义了 7 个函数:
initialize
:初始化 slot0 状态mint
:添加