CrossTickSwap

CrossTickSwap 指集中流动性自动做市商中的跨刻度交换。交易推动当前价格越过一个或多个已初始化刻度时,协议必须分段计算成交量,并在每个边界更新有效流动性。理解该过程的关键是区分当前有效流动性、刻度上的净流动性变化和价格方向。

核心对象

  • sqrtPriceX96:当前价格平方根乘以 2^96 后的定点数。
  • tick:离散价格索引,价格近似满足 price = 1.0001^tick
  • liquidity:当前价格区间内可参与交易的有效流动性。
  • liquidityNet:穿越某个已初始化刻度时应加入或移除的净流动性。
  • feePips:以百万分之一为精度表示的手续费率。

跨刻度执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
flowchart TD
A[接收输入数量与价格限制] --> B[定位当前刻度和有效流动性]
B --> C[查找交易方向上的下一个初始化刻度]
C --> D[计算到目标价格所需的输入和输出]
D --> E{本轮输入是否足够到达目标价格}
E -- 否 --> F[在当前区间内计算最终价格]
E -- 是 --> G[价格到达刻度边界]
G --> H[更新手续费增长量]
H --> I[按方向应用 liquidityNet]
I --> J{剩余输入是否为零}
J -- 否 --> C
J -- 是 --> K[返回成交结果]
F --> K

零换一方向表示价格下降。穿越刻度时,需要对该刻度记录的 liquidityNet 取反后再作用于当前流动性。一换零方向表示价格上升,可直接应用 liquidityNet。方向处理错误会造成流动性突然增大、下溢或报价失真。

单步计算框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while (amountRemaining > 0 && sqrtPriceX96 != sqrtPriceLimitX96) {
int24 nextTick = nextInitializedTick(currentTick, zeroForOne);
uint160 nextPrice = getSqrtRatioAtTick(nextTick);
uint160 targetPrice = selectTarget(nextPrice, sqrtPriceLimitX96, zeroForOne);
SwapStep memory step = computeSwapStep(
sqrtPriceX96, targetPrice, liquidity, amountRemaining, feePips
);
amountRemaining -= step.amountIn + step.feeAmount;
amountOut += step.amountOut;
sqrtPriceX96 = step.sqrtPriceNextX96;
if (sqrtPriceX96 == nextPrice) {
int128 delta = ticks[nextTick].liquidityNet;
liquidity = zeroForOne ? addDelta(liquidity, -delta) : addDelta(liquidity, delta);
currentTick = zeroForOne ? nextTick - 1 : nextTick;
} else {
currentTick = getTickAtSqrtRatio(sqrtPriceX96);
}
}

验证重点

  1. 检查交易方向与刻度搜索方向是否一致。
  2. 检查到达边界和未到达边界时的舍入方向。
  3. 检查跨刻度前后有效流动性是否符合仓位范围。
  4. 检查价格限制能否阻止循环越界。
  5. 对连续空刻度、极端价格和流动性为零的区间执行测试。

CrossTickSwap 原始示意图