Curve v2 CryptoSwap: white paper
By 0xmc & 0xstan
understand Curve v2 CryptoSwap white paper
Curve v2 CryptoSwap 详解系列:
- Curve v2 CryptoSwap: white paper
- Curve v2 CryptoSwap: add and remove liquidity
- Curve v2 CryptoSwap: exchange and fee
- Curve v2 CryptoSwap: repegging
- Curve v2 CryptoSwap: math lib
Curve 一直是 Defi 领域重要的组成部分,我们将在接下来的系列文章中,从白皮书原理到实际业务代码,详细解读其 V2 的运行机制和业务逻辑的细节,领略 Curve 设计之精妙,代码实现之精巧。
有关 v1 的原理和代码详解可以参见我们之前的文章 https://0xreviews.xyz/2022/02/12/Curve-v1-StableSwap.
本篇将针对白皮书原理的重点和难点做进一步解读。
Transformed pegged invariants
v2 的 CryptoSwap 希望比 v1 StableSwap 更近一步,不仅仅只做锚定资产(例如稳定币或 ETH-sETH)之间的兑换。在 v1 中 D 的定义是在价格处于平衡点状态下:
其中 x_i
代表 token 实际的数量(balance),可以替换为 b 来表示。但由于 v2 中并不是相互锚定的资产,波动比较大,需要将定义中的数量 b 替换为价值 b'。
我们引入一个内部缩放价格 p , 在代码中是 price_scale
,令其作为锚定第一个资产的价格,例如 USDT-WBTC-WETH 池子,将会以 USDT 作为锚定标的,假设 WBTC 和 WETH 市价为 40000 和 3000 美元, 那么价格数组为 [1, 40000, 3000]
。第一个价格始终是 1,因为该资产和自身锚定,比值永远是 1。
价值 b' 的定义是数量乘以价格 。
我们将所有资产的数量和余额分别整合到一起,组成 2 个向量 b 和 b',他们之间的转换关系如下
白皮书中上述两个定义式写反了,会造成 p 是价格的倒数的误会,经过反复比对代码和白皮书描述,可以确认表达式应为这里定义的形式。
v2 中对于 D 的定义如下
当池子价格处于平衡点时(例如首次注入流动性时),每个资产的价值相等,其值为 x_eq
虽然定义了基于 x_eq
给出了 D 的定义,但价格总在变化,大部分情况不在平衡点,我们很难去求出当时的 x_eq
。所以代码中的 D 是根据平衡方程利用牛顿法求解。
CurveCrypto invariant
curve v2 的平衡方程
其中 K 的定义与 v1 中的 A 不同
A * K_0 实际就是 v1 的系数
v2 的系数 K 定义中多了关于 gamma 的调整系数
newton's method
为了求解 D 的值,和 v1 一样,将使用牛顿法迭代求解,在改变流动性时求解 D,当进行交易时,求解输出资产的数量 x_i 。将平衡方程写成 F(x, D)=0
的形式
导函数 F'(x,D):
D 和 x_i 的牛顿法迭代初值分别设为如下将能更快的找到解
白皮书中
x_i,0
初值的定义有误,分子中的 D 的幂写成 N-1,分母中 N 的幂写成 N-1,应该都为 N,代码中已经修正正确
白皮书中错误的初值定义:
牛顿法函数在 EVM 中该过程将消耗约 35K gas。
Quantification of a repegging loss
为了衡量池子的总价值,量化利润或亏损,定义了 X_cp
需要注意的一点是,p_i
是 price_scale
, 而该价格只受 repegging 过程影响,所以这里在交易过程中,如果没有触发 repegging, 分母中的 p 是不变的,那么 X_cp
将只受到 D 的变化的影响。
TotalSupply&VirtualPrice
LP token 的发行总量 total_supply
,在第一次注入流动性时将被赋值为 X_cp
,之后增减流动性,将根据 D 的变化同比放缩。
定义 virtual_price 为 Xcp/total_supply
白皮书中没有定义 virtual_price, 但在代码中比较重要
Algorithm for repegging
我们在流动性变化或交易时追踪 virtual_price
的变化,用 xcp_profit
来量化 LP 的损益情况。
每当发生交易时,包括不平衡的添加或移除流动性,对价格产生了影响,都会进入 repegging 逻辑(调整价格)。
undo repegging: 当利润不足时,会撤销 Repegging 操作,即只有当利润足够时才会执行 repegging 逻辑。
when virtual_price - 1 > (xcp_profit - 1) / 2 do repegging
virtual_price
代表了 xcp_profit_real
, 这里的含义实际上是只有当 LP 每单位收益超过之前收益的一半时,进行 repegging,反言之,跌到之前收益一半以下,将不进行价格调整。
xcp_profit 在白皮书中定义有误。白皮书中定义为 xcp_after/xcp_before, 但是当流动性增加且不影响价格时, xcp 会同比增大,但这个时候总利润显然没有增长的,这并不能很好的表现收益情况。正确的定义应该根据 virtual_price 进行同比缩放,这一点在代码中已经修正。
xcp_profit
会跟随 virtual_price
同比变化,且两者的初值都是 1,理论上来说两者值应该保持同步,但实际情况却不同,前者往往比后者要大,因为 xcp_profit
经常包含了未提取的 admin_fee
,在此基础上同比放缩,两者的值不会完全同步。
每次交易产生的价格 last_price
经过时间加权组成一个预言机价格 price_oracle
。
注意: last_price
和 price_scale
不同
price_scale
每种资产与第一个资产的缩放(锚定)价格last_price
是交易时产生的价格,是每一笔交易输入和输出资产 xp 差量的比值;xp = balance * price_scale
定义时间权重系数 alpha,其中 t
是距离上次更新的时间间隔,T_1/2
是人为设定的 half_time, 对应代码中的 ma_half_time
。
oracle.1:
price_oracle
预言机价格以时间加权的方式更新,即 EMA (Exponential Moving Average)
oracle.2:
根据 price_oracle
来调整 price_scale
oracle.3:
先将 oracle 公式稍作变形便于接下来的理解
- 调整的思路是在每次交易后,让
price_scale
向price_oracle
做一定程度的偏移 - 构建一个 N 维的向量空位(N 是资产数量),我们可以将变化前后的价格的比值
p_i/p_i,before
组成的数组作为其中的向量,那么 1 即为坐标系的原点 - 将更新前后的价格的比值作为比较对象
-
price_scale
的前后比值 -
price_oracle
和price_scale
的比值 比值 - 1
实际上就是计算到向量和原点 1 的差值
-
-
s
是 step 的缩写,即调整步长;- 分母可以理解为向量到原点的距离(各个维度与原点的平方差)
- 最后根据这个调整系数来让
price_scale
向price_oracle
靠拢
Dynamic fees
手续费从 v1 的固定费率,改为动态调整,手续费会根据调整系数 g,在 f_mid 和 f_out 之间波动,距离平衡点越远,手续费将越高。
下一篇我们将深入源码探究 Curve v2 关于流动性的逻辑。
Reference
- Curve StableSwap white paper: https://curve.fi/files/stableswap-paper.pdf
- Curve Crypto Pools white paper: https://curve.fi/files/crypto-pools-paper.pdf