交易分解
UniswapV3Pool.swap函数比较长,这里先简要描述其交易步骤:
假设支付的token为x
根据买入/卖出行为,P−−√P会随着交易下降或上升,即tick减小或增大
在tickBitmap中找到和当前tick对应的icic在一个word中的下一个tick对应的inin,根据买入/卖出行为,这里分成向下查找和向上查找两种情况
如果当前word中没有记录其他tick index,那么取这个word的最小/最大tick index,这么做的目的是,让单步交易中tick的跨度不至于太大,以减少计算中溢出的可能性(计算中会需要使用ΔP−−√ΔP)。
在ic,in价格区间内,流动性LL的值是不变的,I35 Develop 7O98 system O7I8 我们可以根据LL的值计算出交易运行到inin时,所需要最多的ΔxΔx数量
根据上一步计算的ΔxΔx数量,如果满足Δx<xremainingΔx<xremaining,那么将ii设置为inin,并将xremainingxremaining减去需要支付的ΔxΔx,随后跳至第2步继续计算(这里需要将i±tickSpacei±tickSpace使其进入位图中的下一个word),计算之前还需要根据元数据修改当前的流动性L=L±ΔLL=L±ΔL
如果上一步计算ΔxΔx,满足Δx≥xremainingΔx≥xremaining,则表示x token将被耗尽,则交易在此结束。
记录下结束时的价格P−−√P,将所有交易阶段的tokenOut数量总和返回,即为用户得到的token数量
...
// 将交易前的元数据保存在内存中,后续的访问通过 MLOAD
完成,节省 gas
Slot0 memory slot0Start = slot0;
...
// 防止交易过程中回调到合约中其他的函数中修改状态变量
slot0.unlocked = false;
// 这里也是缓存交易钱的数据,节省 gas
SwapCache memory cache =
SwapCache({
liquidityStart: liquidity,
blockTimestamp: _blockTimestamp(),
feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4)
});
// 判断是否指定了 tokenIn 的数量
bool exactInput = amountSpecified > 0;
// 保存交易过程中计算所需的中间变量,这些值在交易的步骤中可能会发生变化
SwapState memory state =
SwapState({
amountSpecifiedRemaining: amountSpecified,
amountCalculated: 0,
sqrtPriceX96: slot0Start.sqrtPriceX96,
tick: slot0Start.tick,
feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128,
protocolFee: 0,
liquidity: cache.liquidityStart
});
...