本文将解释什么是 Uniswap V3 中的 tick。tick 实现了对集中流动性进行高效利用 Gas 的记账,因此让我们先快速回顾一下集中流动性。
集中流动性意味着流动性不再像 Uniswap V2 那样在整条价格曲线上必定是恒定的。流动性提供者可以选择价格曲线上的特定区段来放置他们的流动性。下面的动画演示了 Uniswap V2 和 Uniswap V3 价格曲线之间的区别。
例如,在一个 ETH:USDC 池中,如果 ETH 的价格是 2,000 USDC,流动性提供者可能会选择将他们的流动性放置在 1,800 USDC 到 2,200 USDC 之间,这样他们就可以在预期资产交易的价格区间内捕获更多的费用。在下图的示例中,与其他区段相比,1800 USDC 到 2200 USDC 之间的区段具有更大的流动性。

在特定区间内拥有更高的流动性意味着该区间内兑换的价格影响会更低。反之,较低的流动性会使价格影响变大。
介绍 tick 及其在 Uniswap V3 中为何必不可少
为了支持集中流动性,Uniswap V3 协议需要对流动性如何沿着价格曲线变化进行建模。
流动性可以沿着曲线变化,但不能在任意点上变化。如果 Uniswap V3 允许在曲线上的任意点修改流动性,那么复杂度和 Gas 成本将会急剧增加。因为每当价格发生哪怕是一丁点的变动,协议都将不得不去检查流动性是否发生了变化。
因此,为了显著减少我们必须检查流动性是否变化的次数,在 Uniswap V3 中,流动性只能在预先定义好的价格点上进行调整。
这些预定义的价格点被称为 tick。下图说明了这个概念,并展示了其中的部分 tick。请注意,Uniswap V3 中的 tick 数量远多于下面所描绘的数量。价格曲线被 tick(从原点出发的红色射线)“切片”。

回顾一下,Uniswap V2 和 V3 中的“价格”可以被解释为从原点到价格曲线的射线的角度。与 x 轴的“角度”越大,资产 X 相对于资产 Y 的价格就越高。因此,角度更大的 tick 对应着资产 X 更高的价格。
在下面的动画中,青色射线的角度代表资产 X 的价格,而红色射线代表 tick。虽然资产 X 的价格可以变化并取任何值,但 tick 是固定不变的。
重要的是要理解,tick 代表曲线上用作标记的点。让我们将其与带有里程碑的公路作类比。虽然汽车可以在公路上的任何地方,但里程碑被放置在特定的点上,通常按照某种可预测的间隔。同样地,在 Uniswap V3 中,代币价格可以是任何值,但 tick 静态分布在由协议预先定义的特定位置。
tick 在价格曲线上作为参考点,用于指示流动性可以发生变化的位置。
我们在下方演示了这个概念。tick 被显示为从原点出发的红色射线。在左侧,流动性在 tick 之间进行调整,这是 Uniswap V3 所允许的。在右侧,场景描绘了试图在曲线上非 tick 的点之间调整流动性,这是协议所不允许的。

我们很快会展示 Uniswap V3 是如何确定把 tick 放置在哪里的,但首先让我们快速回顾一下“价格”是如何表示“代币 X 以代币 Y 计价的价格”的。
在 Uniswap V3 中,我们使用代币 X 的价格
在 Uniswap V3 中,价格始终指的是代币 X 以代币 Y 计价的价格。 因此,无论何时我们写出 ,它指的都是代币 X 的价格,公式为 。
代币 Y 以代币 X 计价的价格,公式为 ,可以通过 计算得出,即 。因此,协议只需要跟踪以代币 X 计价的价格()。以代币 Y 计价的价格()可以据此计算得出。
例如,如果代币 X 的价格是 ,那么代币 Y 的价格可以计算为 或 。选择使用代币 X 的价格而不是代币 Y 的价格仅仅是一种约定俗成的习惯。
tick 到价格的转换公式
我们说过 tick 是预定义的价格,但这些价格是如何定义的呢?
它们由以下公式定义:
其中 是一个被称为 tick 索引 (tick index) 的整数,而 是该 tick 索引代表的价格。我们将其称为“tick 价格”。请记住,每一个 tick 仅仅是一个固定价格的标签。
一些 tick 的示例包括:
- tick 索引 0 定义了 1:1 的价格,因为 。
- tick 索引 1 定义了 1:1.0001 的价格,因为 。
- tick 索引 2 定义了 1:1.00020001 的价格,因为 。
- tick 索引 -1 定义了大约 1:0.99990001 的价格,因为 。
允许的 tick 索引范围从 -887,272 到 887,272,关于这个范围的原因将在下一章解释。
通常也将索引 称为 tick,并且在代码库中大量使用了这种称呼。 指代 tick 索引还是 tick 价格应可根据上下文明确。无论如何,每个 tick 索引 都唯一地关联到一个 tick 价格 ,反之亦然。
资产价格与 tick 的关系示例
假设我们有一个 USDC:USDT 池,其中代币 X 是 USDC,代币 Y 是 USDT。如果这两种资产具有完全相同的价值,那么价格将正好落在 tick 索引 0 上,因为 USDC 以 USDT 计价的价格是 1:1。如下所示(tick 并非按比例绘制)。tick 由红色射线表示,USDC 与 USDT 交易的价格由末端带黄点的黄色虚线射线表示,价格曲线为青色曲线:

现在假设 USDC 相对于 USDT 微微升值。也就是说,必须花费 1.00005 USDT 才能获得 1 USDC。这将使得 USDC 的当前价格略高于 tick 0,但还没达到 tick 1:

这是完全可以的——协议并不要求资产具有正好与某个 tick 相匹配的价值。
如果 USDC 继续升值,直到需要 1.0001 USDT 才能兑换 1 USDC,那么价格曲线上的点将正好落在 tick 1 上。该价格精确落在 tick 1 上,因为 。

正数 tick 和负数 tick
- 如果 tick 索引为负数,这对应于小于 1 的价格,因为如果 为负数, 将小于 1。
- 如果 ,那么价格为 1(意味着资产具有相同的价值),因为任何数的 0 次方都是 1。
- 如果 大于或等于 1,那么价格将大于 1。
要查看负数 tick、tick 0 和正数 tick 之间的关系,请参见下方带有标记的 tick。具体来说,下图使用了以下示例价格:

代币价格与 tick 之间的关系
人们通常很容易将代币价格与 tick 混淆。如上所述,tick 是价格曲线上的参考点,类似于公路上的里程碑。它们同样也是价格,因为它们是价格曲线上的点,并且由公式 定义。
代币的价格是指它的当前价格:它是价格曲线上在兑换期间发生变化的点。例如,一个代币现在的价格可能是 10,而在未来的某个时刻变成了 33.2。随着价格的变化,它沿着曲线移动并穿过 tick,这很像一辆行驶在公路上的汽车驶过一个个里程碑。
有时候,代币的价格会精确匹配给定的 tick 的价格,但通常情况下,代币的价格不会匹配任何 tick。这类似于公路上的汽车暂时停留在设有里程碑的位置。
关于代币价格是如何通过兑换确定的,将在本书后续几章中进行讨论。必须清楚的是,公式 仅仅是定义作为价格曲线上标记的 tick 的一种方式。它们仅作为能够调整流动性的指定位置。
为什么使用数值 1.0001 来定义 tick?
根据 Uniswap V3 白皮书,选择 1.0001 这个数值是因为
“它具有一个理想的属性,即每个 tick 距离其相邻的 tick 都有 0.01%(1 个基点)的价格变动。”
在 tick 之间使用基点表达的是价格的相对差异,而非绝对差异。
绝对差异(比如 0.1 美分),对于不同价格的代币而言,其含义可能截然不同。对于价值 100,000 美元的代币来说,0.1 美分微不足道,而对于价值 1 美分的代币来说,这却代表了一个显著的变动。
相比之下,基点差异确保了相同的相对比例。例如,对于价格为 100,000 美元的代币,1 个基点的差值等于 10 美元;而对于价值 1 美分的代币,1 个基点等于 0.0001 美分。尽管 10 美元和 0.000001 美元是不同的绝对值,但它们代表着相对于代币价格相同的相对变动。
因此,我们希望相邻 tick 之间的差异对于价值 100,000 美元的代币是 10 美元,而对于价值 1 美分的代币是 0.000001 美元。
让我们看一些相邻 tick 之间出现 1 个基点变动的示例:
示例 1:tick 100000 和 tick 100001
对于 tick 100000 和 100001,我们得到以下价格:
(为了清晰起见,前四位数字高亮显示)。我们可以看到差值是 2.201545604853891(大约是 22015 的 1 个基点或 0.01%),这是针对价值约为 22015 个代币 Y 的代币而言的。
示例 2:tick 10 和 tick 11
现在,差值是 0.000100100045012(0.0001 或大约 1 个基点),这是针对价值约为 1 个代币 Y 的代币而言的。
tick 区间
当我们写出 时,假设 和 是 tick,我们指的是 和 之间的价格区间(不包含边界)。在这种上下文中, 被称为 下限 tick (lower tick), 被称为 上限 tick (upper tick)。
当我们把一个 tick 区间写为 时,我们实际上引用的是 ,其中 -10 和 10 是 tick 索引,而 和 是对应的 tick 价格。
在下图中,我们看到了位于 和 之间的一个 tick 区间示例。

将价格曲线表示为一条直线
价格曲线的另一种常见表示方式是一条直线。价格曲线绘制在 x 轴和 y 轴上,而在数轴直线上,每个点代表一个价格。
在曲线上,(代币 X 的)价格上涨会向上并向左移动;而在直线上,它则是从左向右增加的:
因此,我们有时会使用下面的直线图来表示曲线的价格和流动性,其中蓝色区域代表包含在 tick 区间内的流动性。

我们可以在对应的直线图上绘制流动性水平,如下所示:
下方是一个交互式工具,以进一步演示这两种表示方法是如何展示相同信息的。通过移动 k 滑块来更改某个价格区段的流动性,然后点击“Sweep Price”。点击 Sweep Price 后,两个图表的价格指示器(笛卡尔图的一条红色射线和直线图的一个红点)就会出现。请注意笛卡尔图上的红色射线是如何追踪直线图上的红点的。
我们期望读者能够理解这两种对价格曲线的表示:一种位于笛卡尔平面(x-y 轴),另一种则作为一条直线。这两种图表将用于解释本书各章节的概念。
V3 中 x-y 轴的含义
在 Uniswap V2 中,x 的值就是池子中持有的代币 X 的字面数量(储备量)(y 也是如此)。然而在 Uniswap V3 中,“储备量”是一个更加复杂的概念,因为每个曲线区段都持有不同数量的代币 X 和/或 Y。
将 x 和 y 轴视为衡量代币“数量”依然是有帮助的,但关于这个“数量”到底是什么,还存在相当多的微妙之处,因此我们将推迟到后面的章节再进行讨论。我们提及这一点,是为了避免将坐标轴误解为代币的价格,因为如果在没有澄清的情况下直接将轴标记为“x”,可能会引起歧义。
当前 tick
Uniswap V3 会跟踪“活跃 tick”或“当前 tick”,有时仅仅称之为“tick”。“当前 tick”是当前价格向下取整到最近的 tick。如果价格上涨并穿过一个 tick,那么刚刚被穿过的 tick 就变成了当前 tick。“穿过”并不要求价格一定要“越过”该 tick。如果价格停留在该 tick 上,那么这个 tick 也被认为是被穿过了的。
如果价格下跌并穿过了一个 tick,那么它必然已经穿过了之前的当前 tick,因此那个 tick 下方的 tick 就成了新的当前 tick。
下方的交互式工具演示了协议是如何选择一个 tick 作为当前 tick 的。移动工具顶部的滑块,观察在价格穿过 tick 时,tick(灰色)是如何变成当前 tick(绿色)的:
slot0 变量保存了当前 tick
协议将当前 tick 存储在一个名为 slot0 的结构体中(代码链接)。该变量是公开的,因此任何人都可以通过查询 slot0 直接在 Etherscan 上读取池子的当前 tick。

下方展示了 Base 上的 ETH:DAI 池查询结果:

当前 tick 是 81143。由于池子是 ETH:DAI,价格被表示为以太币(Ether)以 DAI 计价的价格。这两个代币都有 18 位小数。计算 tick 81143 处的价格,得出 。当前 tick 是当前价格向下取整到最接近的 tick 得到的结果。因此,我们可以认为当前 tick 粗略地对应了当前价格。所以对于该池子来说,1 ETH 价值约为 3340 DAI。
小数位数如何影响价格
我们再来看另一个例子,以说明小数位是如何影响价格和 tick 的。下方展示了 Base 上的 ETH:USDC 池的查询结果:

这个 tick 现在是负数,值为 -195186,价格可计算得出 。因为 ETH 有 18 位小数,而 USDC 只有 6 位小数,由于小数位数的差异,这里的 tick 是负数。
假设 ETH 的价值是 1000 美元,ETH 的最小单位价值 (即 ),而 USDC 的最小单位价值 。因此,尽管我们认为 1 个 ETH 比 1 个 USDC “更值钱”,但如果从它们各自的最小单位来考虑,1 个最小单位的 USDC 要比 1 个最小单位的 ETH 更值钱。
在这个例子中,为了考虑小数位数上的差异(ETH 为 18 位而 USDC 为 6 位),我们需要将价格乘以 。因此,该池的价格变为 ,这与 ETH:DAI 池中的数值大致相同。
总结
- 价格曲线由被称为 tick 的点进行划分,这些点由公式 定义,其中 被命名为 tick 索引。tick 代表一个固定的价格。
- tick 作为边界,流动性提供者可以在这些边界之间提供流动性。无法使用任意点作为边界来提供流动性。
- tick 可以是正数或负数。tick 0 对应于代币价值相同的场景。正数 tick 对应的价格下,一单位 X 可以兑换超过一单位的 Y。负数 tick 对应的价格下,兑换入一单位 Y 可以换回超过一单位的 X。
- 当前 tick 是最接近当前价格并向下取整的 tick。
- tick 的最小值和最大值分别是 -887,272 和 887,272。我们将在下一章讨论其原因。
练习题
在成本较低的 L2 上选择一个池子并提供流动性。池子列表在这里。注意观察哪些位置可以提供流动性,哪些位置不可以。