Uniswap v3 中最小的 tick 是 -887,272,最大的 tick 是 887,272。本章将解释该范围背后的基本原理,其基础是找到与协议所能存储的最高价格相对应的 tick。
价格限制
在上一章中,我们看到协议将代币价格的平方根存储为 Q64.96 类型的定点数。这种类型的变量的最大整数值为 。因此,它所能存储的最高价格为 。
这意味着协议无法处理大于 的价格。换言之,在 Uniswap v3 中,代币的真实价格永远不可能超过 。如果不遵守这个限制,代币可能会达到协议无法存储的价格值。
因此,为了与最高价格保持一致,最高的 tick 必须是对应于价格 的 tick。
最高 tick 索引
为了计算对应于价格 的 tick,我们需要回顾价格和 tick 之间的关系由下式给出:
对等式两边取以 1.0001 为底的对数,可以对该关系进行反推。
因为对于任意底数 ,都有 。
通过上述公式,我们可以在已知价格 的情况下计算出 tick 索引 。
现在,我们需要确定与可能达到的最高代币价格相关的 tick 索引,该价格为 。
将 代入上述公式,我们得到:
这个计算可以在 Python 中实现如下:
from math import log
log(2**128,1.0001) # log_1.0001(2**128) = 887272
正因如此,协议使用的最高 tick 索引为 887,272,因为大于 887,272 的 tick 所对应的价格会超过 sqrtPriceX96 变量所能存储的最大值。
最低 tick 索引
最低 tick 索引被设定为 -887,272,即最高可用 tick 的相反数。
这种对称性是合理的,因为代币 X 相对于代币 Y 的价格是代币 Y 相对于代币 X 的价格的倒数。因此,将最低代币价格限制在 是合理的,这正好对应于 tick -887272。
代码库中的最小值和最大值
在 Uniswap v3 的 TickMath 库中,最小和最大 tick 索引被硬编码为 MIN_TICK 和 MAX_TICK。
sqrtPriceX96 变量可取的最小值和最大值也分别被硬编码为 MIN_SQRT_RATIO 和 MAX_SQRT_RATIO。这可以从下方的截图中看到,这些数值将在后面的小节中进行计算。

Tick 与价格的平方根
在 Introducing ticks in Uniswap v3 这一章中,我们看到 tick 是由以下公式定义的:
其中 是 tick 索引。
我们可以使用价格的平方根而不是价格本身来进行计算,并且可以针对给定的 tick 索引计算出价格的平方根。
为此,只需对上面的公式取平方根:
例如,要计算 tick 为 100 时的 ,我们有 。根据此信息,如果我们想要获得 (tick 100 对应的价格),只需将其平方即可:。
请注意,我们忽略了负平方根,仅保留了正平方根,因为价格不能为负数。因此,我们始终可以通过将价格的平方根取平方来毫无歧义地恢复原始价格。
协议中允许的最高和最低 Q64.96 价格平方根
MIN_SQRT_RATIO 和 MAX_SQRT_RATIO 的值可以通过以下方式计算:
最小的 tick 索引为 -887,272,因此允许的最小价格平方根由 给出。其计算如下:
要将该值转换为 Q64.96 定点数,需要将其乘以 。因此,
随之而来的一个问题是:我们应该向上取整还是向下取整这个值?假设我们向下取整,这意味着变量 sqrtPriceX96 可能的最小值为 4295128738。换言之,sqrtPriceX96 可能会达到 4295128738 这个值。
这个值 4295128738 略低于 tick -887272(记住,与 tick -887272 对应的值是 4295128738.152353)。因此,如果价格达到 4295128738,当前的 tick 将向下取整为最接近的 tick。也就是说,它将变为 -887273。但是,tick -887273 是不允许的,因为我们设定了 tick -887272 为最小值。
因此,我们得出结论,必须向上取整。也就是说,sqrtPriceX96 变量所能接受的最小值是 4295128739,这正如代码库中所硬编码的那样。

这个计算可以在 Python 中实现如下:
math.ceil(1.0001**(-887272/2)*(2**96)) # 4295128739
MAX_SQRT_RATIO 的计算也类似,但这次我们使用 tick 索引可能达到的最大值,即 887,272:
这种计算可以在 Python 中进行,但会遭遇精度丢失的问题。在后面的章节中,我们将看到 Solidity 是如何精确地执行这类计算且没有精度损失的。
为什么使用 int24 作为 tick 索引
存储 887,272 所需的位数是 。由于我们还有负数 tick,我们需要存储的 tick 数量是原来的两倍。为了同时容纳正数及其负数值,我们的 tick 变量需要支持 21 位。
由于 Solidity 仅支持是 8 的倍数的 int 大小,能够容纳我们所需的所有 tick 的最小 int 大小就是 int24。因此,Uniswap V3 使用 int24 来保存 tick 索引(代码链接),如下方所示。

总结
- Tick 索引 的变化范围是 -887,272 到 887,272。这些 tick 代表代币在协议中能够具有的最低和最高价格,分别为 和 。
MIN_SQRT_RATIO和MAX_SQRT_RATIO的值代表协议定义的 Q64.96 格式中允许的最小和最大价格平方根。这些值被硬编码在代码库中。