第五篇:Uniswap V3 Swap机制源码深度剖析
全面解析swap函数的状态机设计与跨Tick交易实现
核心要点速览
swap函数的精妙设计
function swap(
address recipient,
bool zeroForOne, // token0 -> token1 (true) or token1 -> token0 (false)
int256 amountSpecified, // 正数=精确输入,负数=精确输出
uint160 sqrtPriceLimitX96,
bytes calldata data
) external override noDelegateCall returns (int256 amount0, int256 amount1);
核心循环:跨Tick交易
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
// 1. 找到下一个初始化的Tick
(step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord(...);
// 2. 计算在当前Tick内的交换
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) =
SwapMath.computeSwapStep(...);
// 3. 更新状态
state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount);
state.amountCalculated -= step.amountOut;
// 4. 如果到达边界,跨越Tick
if (state.sqrtPriceX96 == TickMath.getSqrtRatioAtTick(step.tickNext)) {
if (step.initialized) {
int128 liquidityNet = ticks.cross(...);
state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet);
}
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
}
}
SwapMath.computeSwapStep详解
这是swap的核心计算函数:
function computeSwapStep(
uint160 sqrtRatioCurrentX96, // 当前价格
uint160 sqrtRatioTargetX96, // 目标价格(下一个Tick)
uint128 liquidity, // 当前流动性
int256 amountRemaining, // 剩余交换数量
uint24 feePips // 手续费率
) internal pure returns (
uint160 sqrtRatioNextX96, // 交换后的价格
uint256 amountIn, // 实际输入
uint256 amountOut, // 实际输出
uint256 feeAmount // 手续费
) {
bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96;
bool exactIn = amountRemaining >= 0;
if (exactIn) {
// 精确输入模式
uint256 amountRemainingLessFee = FullMath.mulDiv(
uint256(amountRemaining),
1e6 - feePips,
1e6
);
amountIn = zeroForOne
? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true)
: SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true);
if (amountRemainingLessFee >= amountIn) {
sqrtRatioNextX96 = sqrtRatioTargetX96; // 到达目标
} else {
sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput(...);
}
} else {
// 精确输出模式
amountOut = zeroForOne
? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false)
: SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false);
if (uint256(-amountRemaining) >= amountOut) {
sqrtRatioNextX96 = sqrtRatioTargetX96;
} else {
sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput(...);
}
}
// 计算另一侧的数量和手续费
if (sqrtRatioTargetX96 == sqrtRatioNextX96) {
amountIn = ...;
amountOut = ...;
} else {
amountIn = ...;
amountOut = ...;
}
if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) {
feeAmount = uint256(amountRemaining) - amountIn;
} else {
feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips);
}
}
回调机制的完整流程
// 1. Pool先转出代币
if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0));
if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1));
// 2. 记录转出前的余额
uint256 balance0Before = balance0();
uint256 balance1Before = balance1();
// 3. 回调要求转入代币
IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
// 4. 验证余额增加
if (amount0 > 0) require(balance0Before.add(uint256(amount0)) <= balance0());
if (amount1 > 0) require(balance1Before.add(uint256(amount1)) <= balance1());
关键特性
- 跨Tick交易:自动处理跨越多个价格区间
- 双向交换:支持token0→token1和token1→token0
- 双模式:精确输入或精确输出
- 价格限制:sqrtPriceLimitX96防止滑点
- 闪电交易:先转出后转入的设计
实战案例:跨3个Tick的交易
初始状态:
currentTick = 1000, liquidity = 1000000
amountIn = 10000 token0
Tick布局:
tick 1000: liquidityNet = 0 (current)
tick 1200: liquidityNet = +300000
tick 1500: liquidityNet = -200000
tick 1800: liquidityNet = -100000
交易流程:
Step 1: [1000, 1200), liquidity=1000000
→ 使用 3000 token0,获得output1
→ 到达tick 1200,跨越
Step 2: cross tick 1200
→ liquidity = 1000000 + 300000 = 1300000
→ 翻转feeGrowthOutside
Step 3: [1200, 1500), liquidity=1300000
→ 使用 4000 token0,获得output2
→ 到达tick 1500,跨越
Step 4: cross tick 1500
→ liquidity = 1300000 - 200000 = 1100000
Step 5: [1500, 1800), liquidity=1100000
→ 使用剩余 3000 token0,获得output3
→ 价格停在tick 1750(未到1800)
最终结果:
totalOutput = output1 + output2 + output3
finalTick = 1750
finalLiquidity = 1100000
关键优化
- TickBitmap快速查找:O(1)找到下一个Tick
- 循环内最小化SLOAD:缓存状态到内存
- 手续费即时更新:避免后续重新计算
- 预言机同步更新:利用已有的SSTORE
详细内容请参考系列文章和源码
本文是"Uniswap V3源码赏析系列"的第五篇