本文通过代数运算,逐步展示了 get_D() 和 get_y() 的代码是如何从 StableSwap 不变量中推导出来的。
已知 StableSwap 不变量:
A n n ∑ x i + D = A n n D + D n + 1 n n ∏ x i An^n\sum x_i +D=An^nD+\frac{D^{n+1}}{n^n\prod x_i} A n n ∑ x i + D = A n n D + n n ∏ x i D n + 1
我们希望用它来进行两个常用的数学操作:
在给定固定的 A A A 值以及储备量 x 1 , … , x n x_1,\dots,x_n x 1 , … , x n 的情况下计算 D D D 。请注意,n n n (即资金池支持的代币种类数量)在资金池部署时是固定的。这就是 get_D() 函数的作用。
在给定 D D D 的情况下,我们希望将其中一个储备量 x i x_i x i 增加到一个新值 x i ′ x_i' x i ′ ,并计算出另一个储备量 x j x_j x j 需要减少多少以保持等式平衡。这就是 get_y() 函数的作用。这里的 “y” 指的是 x i ′ x_i' x i ′ 。
在 Curve StableSwap 中,这些操作分别被称为 get_D() 和 get_y()。
get_D() 的目标
在 Curve V1(StableSwap)中,D 的作用类似于 Uniswap V2 中的 k —— D 越大,储备量就越多,价格曲线也就越向外推移。在添加或移除流动性,或者手续费改变了资金池余额之后,D 会发生变化——并且需要重新计算。这就是 get_D() 函数的用途。在给定资金池当前储备量的情况下,它会计算出 D。
如果一个 Curve 资金池包含两种代币 x 和 y,则 StableSwap 不变量为
4 A ( x + y ) + D = 4 A D + D 3 4 x y 4A(x + y) + D = 4AD+\frac{D^3}{4xy} 4 A ( x + y ) + D = 4 A D + 4 x y D 3
就我们的目的而言,“放大系数”(amplification factor)A 可以被视为一个常数。
get_y() 的目标
get_y() 函数在代币兑换(swap)期间使用。与 Uniswap V2 中的 k 类似,D 在兑换期间必须保持不变(忽略手续费)。具体来说,在给定 x 的新值时,它会计算出能使等式保持平衡的 y 值。因此,这是一个重要的子程序,用于计算“如果我向资金池中投入这么多代币 x,可以取出多少代币 y? ”
Curve 资金池可以包含 2 种以上的代币(例如 3pool 包含 USDT、USDC 和 DAI)。Curve 通过数组中的索引来识别代币。因此,在这种情况下,x 和 y 指的是该数组中的特定代币。在此背景下,get_y() 的意思是改变特定代币 x 的余额,保持其他余额不变,但允许另一个代币 y 的值发生变化。然后,在给定 x 的特定变化量时,计算 y 应如何变化以保持不变量平衡。
n 种代币的不变量为:
A n n ∑ x i + D = A n n D + D n + 1 n n ∏ x i An^n\sum x_i +D=An^nD+\frac{D^{n+1}}{n^n\prod x_i} A n n ∑ x i + D = A n n D + n n ∏ x i D n + 1
为简便起见,在本文其余部分,我们将使用 S S S 代替求和符号,使用 P P P 代替求积符号,因此不变量变为:
A n n S + D = A D n n + D n + 1 n n P An^nS + D = ADn^n+\frac{D^{n+1}}{n^nP} A n n S + D = A D n n + n n P D n + 1
其中 S S S 是代币余额之和(x 0 + x 1 + … + x n x_0 + x_1 + … + x_n x 0 + x 1 + … + x n ),P P P 是余额的乘积(x 0 x 1 . . . x n x_0x_1...x_n x 0 x 1 ... x n ),x i x_i x i 是代币 i 的余额。
在白皮书 中,S S S 被写为 ∑ x i \sum x_i ∑ x i ,P P P 被写为 ∏ x i \prod x_i ∏ x i 。白皮书中的公式复现如下:
A n n ∑ x i + D = A D n n + D n + 1 n n ∏ x i An^n\sum x_i + D=ADn^n+\frac{D^{n+1}}{n^n\prod x_i} A n n ∑ x i + D = A D n n + n n ∏ x i D n + 1
我们将使用 S S S 和 P P P 来代替求和与求积符号。
我们假设资金池可以包含任意数量为 n n n 的代币,因此公式将反映这一点。然而在实践中,n n n 必须是一个较小的数,否则 D n + 1 D^{n+1} D n + 1 项容易发生溢出。
使用 get_D() 计算 D D D
在 get_D() 中,已知一组余额 x_0, x_1, ..., x_n,我们需要计算 D。
我们无法通过代数方法直接求解
A n n S + D = A D n n + D n + 1 n n P An^nS + D = ADn^n+\frac{D^{n+1}}{n^nP} A n n S + D = A D n n + n n P D n + 1
得到 D D D 。相反,我们需要应用牛顿法来进行数值求解。为此,我们构造一个函数 f ( D ) f(D) f ( D ) ,当等式平衡时,该函数为 0。
0 = A D n n + D n + 1 n n P − D − A n n S 0 =ADn^n+\frac{D^{n+1}}{n^nP}-D-An^nS 0 = A D n n + n n P D n + 1 − D − A n n S
0 = D n + 1 n n P + A D n n − D − A n n S 0 =\frac{D^{n+1}}{n^nP}+ADn^n-D-An^nS 0 = n n P D n + 1 + A D n n − D − A n n S
f ( D ) = D n + 1 n n P + A n n D − D − A n n S \color{green}{f(D)=\frac{D^{n+1}}{n^nP}+An^nD-D-An^nS} f ( D ) = n n P D n + 1 + A n n D − D − A n n S
并且我们计算其关于 D D D 的导数 f ′ ( D ) f'(D) f ′ ( D ) 如下:
f ′ ( D ) = ( n + 1 ) D n n n P + A n n − 1 f'(D) = \frac{(n+1)D^n}{n^nP}+An^n-1 f ′ ( D ) = n n P ( n + 1 ) D n + A n n − 1
牛顿法公式
我们可以使用以下公式迭代求解 D D D :
D next = D − f ( D ) f ′ ( D ) D_\text{next}=D-\frac{f(D)}{f'(D)} D next = D − f ′ ( D ) f ( D )
将 f ′ ( D ) f'(D) f ′ ( D ) 表示为带有分母 D D D 的形式会很有帮助。首先,我们将定义 f ′ ( D ) f'(D) f ′ ( D ) 的左侧分数分子和分母同时乘以 D D D 。
f ′ ( D ) = ( n + 1 ) D n + 1 n n P D + A n n − 1 f'(D) = \frac{\frac{(n+1)D^{n+1}}{n^nP}}{D}+An^n-1 f ′ ( D ) = D n n P ( n + 1 ) D n + 1 + A n n − 1
然后将 f ′ ( D ) f'(D) f ′ ( D ) 合并为一个分数:
f ′ ( D ) = ( n + 1 ) D n + 1 n n P D + ( A n n − 1 ) D D f ′ ( D ) = ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D \begin{align*}
f'(D) &= \frac{\frac{(n+1)D^{n+1}}{n^nP}}{D}+\frac{(An^n-1)D}{D}\\f'(D) &=\color{red}{\frac{\frac{(n+1)D^{n+1}}{n^nP}+(An^n-1)D}{D}}
\end{align*} f ′ ( D ) f ′ ( D ) = D n n P ( n + 1 ) D n + 1 + D ( A n n − 1 ) D = D n n P ( n + 1 ) D n + 1 + ( A n n − 1 ) D
我们可以将牛顿法公式重写为具有公分母的形式:
D next = D − f ( D ) f ′ ( D ) // 牛顿法 = D f ′ ( D ) f ′ ( D ) − f ( D ) f ′ ( D ) // 将 D 乘以 f ′ ( D ) f ′ ( D ) = D f ′ ( D ) − f ( D ) f ′ ( D ) // 提取公分母合并 \begin{align*}
D_\text{next}&=D-\frac{f(D)}{f'(D)}&&\text{// 牛顿法}\\
&=D\frac{f'(D)}{f'(D)}-\frac{f(D)}
{f'(D)} &&\text{// 将 } D \text{ 乘以 }\frac{f'(D)}{f'(D)}\\
&=\frac{\color{violet}{D}\color{red}{f'(D)}-\color{green}{f(D)}}{\color{red}{f'(D)}}&&\text{// 提取公分母合并}
\end{align*} D next = D − f ′ ( D ) f ( D ) = D f ′ ( D ) f ′ ( D ) − f ′ ( D ) f ( D ) = f ′ ( D ) D f ′ ( D ) − f ( D ) // 牛顿法 // 将 D 乘以 f ′ ( D ) f ′ ( D ) // 提取公分母合并
将之前的 f ( D ) f(D) f ( D ) 和 f ′ ( D ) f'(D) f ′ ( D ) 代入重写后的牛顿法公式中,我们得到:
= D ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D − ( D n + 1 n n P + A n n D − D − A n n S ) ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D =\frac{\color{violet}{D}\color{red}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{D}}-\color{green}{(\frac{D^{n+1}}{n^nP}+An^nD-D-An^nS)}}{\color{red}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{D}}} = D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D D D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D − ( n n P D n + 1 + A n n D − D − A n n S )
由于我们重排了 f ′ ( D ) f'(D) f ′ ( D ) 使其分母为 D D D ,D f ′ ( D ) \color{violet}D\color{red}f'(D) D f ′ ( D ) 项将很好地被约掉:
= D ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D − ( D n + 1 n n P + A n n D − D − A n n S ) ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D =\frac{\color{violet}{\cancel{D}}\color{red}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{\cancel{D}}}-\color{green}{(\frac{D^{n+1}}{n^nP}+An^nD-D-An^nS)}}{\color{red}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{D}}} = D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D D D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D − ( n n P D n + 1 + A n n D − D − A n n S )
= ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D − ( D n + 1 n n P + A n n D − D − A n n S ) ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D =\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D-\color{green}{(\frac{D^{n+1}}{n^nP}+An^nD-D-An^nS)}}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{D}} = D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D − ( n n P D n + 1 + A n n D − D − A n n S )
展开所有项以消除分子中的括号:
= D n + 1 n n P + n D n + 1 n n P + A n n D − D − D n + 1 n n P − A n n D + D + A n n S ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D =\frac{\frac{D^{n+1}}{n^nP}+\frac{nD^{n+1}}{n^nP}+An^nD-D-\frac{D^{n+1}}{n^nP}-An^nD+D+An^nS}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{D}} = D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D n n P D n + 1 + n n P n D n + 1 + A n n D − D − n n P D n + 1 − A n n D + D + A n n S
这会产生大量的相消:
= D n + 1 n n P + n D n + 1 n n P + A n n D − D − D n + 1 n n P − A n n D + D + A n n S ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D =\frac{\cancel{\frac{D^{n+1}}{n^nP}}+\frac{nD^{n+1}}{n^nP}+\cancel{An^nD}-\cancel{D}-\cancel{\frac{D^{n+1}}{n^nP}}-\cancel{An^nD}+\cancel{D}+An^nS}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{D}}\\ = D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D n n P D n + 1 + n n P n D n + 1 + A n n D − D − n n P D n + 1 − A n n D + D + A n n S
= n D n + 1 n n P + A n n S ( n + 1 ) D n + 1 n n P + ( A n n − 1 ) D D =\frac{\frac{nD^{n+1}}{n^nP}+An^nS}{\frac{(n+1)\frac{D^{n+1}}{n^nP}+(An^n-1)D}{D}} = D ( n + 1 ) n n P D n + 1 + ( A n n − 1 ) D n n P n D n + 1 + A n n S
我们将分子和分母同乘 D D D :
= ( A n n S + n D n + 1 n n P ) D ( A n n − 1 ) D + ( n + 1 ) D n + 1 n n P =\frac{(An^nS+n{\frac{D^{n+1}}{n^nP}})D}{(An^n-1)D+(n+1){\frac{D^{n+1}}{n^nP}}} = ( A n n − 1 ) D + ( n + 1 ) n n P D n + 1 ( A n n S + n n n P D n + 1 ) D
如果我们将 D p D_p D p 定义为:
D p = D n + 1 n n P D_p=\frac{D^{n+1}}{n^nP} D p = n n P D n + 1
并代入 D p D_p D p ,我们得到:
= ( A n n S + n D n + 1 n n P ) D ( A n n − 1 ) D + ( n + 1 ) D n + 1 n n P =\frac{(An^nS+n\boxed{\frac{D^{n+1}}{n^nP}})D}{(An^n-1)D+(n+1)\boxed{\frac{D^{n+1}}{n^nP}}} = ( A n n − 1 ) D + ( n + 1 ) n n P D n + 1 ( A n n S + n n n P D n + 1 ) D
D next = ( A n n S + D p n ) D ( A n n − 1 ) D + ( n + 1 ) D p D_\text{next}=\frac{(An^nS+D_pn)D}{(An^n-1)D+(n+1)D_p} D next = ( A n n − 1 ) D + ( n + 1 ) D p ( A n n S + D p n ) D
与原始源代码的比较
这与 Vyper 代码 中的内容完全一致:
变量 D p D_p D p 的定义如下:
Copy D_P : uint256 = D # D_P = S
for _x in xp:
D_P = D_P * D / (_x * N_COINS )
xp 是代币的数量,因此循环将执行 n 次。因此,在分母中我们有 n 个 D D D 相乘:
D p = D n + 1 n n ∏ i = 1 n x i D_p=\frac{D^{n+1}}{n^n\prod_{i=1}^nx_i} D p = n n ∏ i = 1 n x i D n + 1
使用 get_y() 计算 y
其思路是,我们强制其中一个 x i x_i x i 取一个新值(代码中称之为 x),并计算出另一个 x j x_j x j (其中 i ≠ j i \neq j i = j )的正确值,从而使等式保持平衡。其他代币的余额保持不变。x j x_j x j 在此处被称为 y y y 。
尽管 StableSwap 资金池可能包含多种代币,但使用 get_y() 每次只能交换其中的两种代币。
同样,我们有相同的不变量:
A n n S + D = A D n n + D n + 1 n n P An^nS + D = ADn^n+\frac{D^{n+1}}{n^nP} A n n S + D = A D n n + n n P D n + 1
D D D 、A A A 和 n n n 是固定的,但我们将改变 S S S 和 P P P 中的两个值:
S = x 0 + x 1 + . . . + x n P = x 0 x 1 . . . x n \begin{align*}
S &= x_0+x_1+...+x_n\\
P &= x_0x_1...x_n
\end{align*} S P = x 0 + x 1 + ... + x n = x 0 x 1 ... x n
因此,我们需要对公式稍作调整,因为 S S S 和 P P P 包含了我们正在计算的值。
S ′ S' S ′ 将是所有余额的总和,不包括 我们试图求解的代币 x i x_i x i 的新余额。
P P P 将是所有代币余额的乘积,不包括 我们试图求解的那一个代币。
换句话说:
S = S ′ + y P = P ′ y \begin{align*}
S &= S'+y \\
P &= P'y
\end{align*} S P = S ′ + y = P ′ y
为了与代码保持一致,我们将试图计算其新余额的那个代币称为 y y y 。
公式随之变为:
A n n ( S ′ + y ) + D = A D n n + D n + 1 n n P ′ y An^n(S'+y) + D = ADn^n+\frac{D^{n+1}}{n^nP'y} A n n ( S ′ + y ) + D = A D n n + n n P ′ y D n + 1
同样,我们推导出一个当等式平衡时为 0 的 f ( y ) f(y) f ( y ) ,以及它关于 y 的导数:
f ( y ) = A D n n + D n + 1 n n P ′ y − A n n ( S ′ + y ) − D f ′ ( y ) = − D n + 1 n n P ′ y 2 − A n n \begin{align*}f(y) &= \color{green}{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^n(S'+y) - D}\\f'(y)&=\color{red}{\frac{-D^{n+1}}{n^nP'y^2}-An^n}\end{align*} f ( y ) f ′ ( y ) = A D n n + n n P ′ y D n + 1 − A n n ( S ′ + y ) − D = n n P ′ y 2 − D n + 1 − A n n
下面再次给出牛顿法公式:
y next = y − f ( y ) f ′ ( y ) y_\text{next}=y-\frac{f(y)}{f'(y)} y next = y − f ′ ( y ) f ( y )
将 f ( y ) f(y) f ( y ) 和 f ′ ( y ) f'(y) f ′ ( y ) 代入牛顿法后,我们得到:
y next = y − A D n n + D n + 1 n n P ′ y − A n n ( S ′ + y ) − D − D n + 1 n n P ′ y 2 − A n n y_\text{next}=y-\frac{\color{green}{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^n(S'+y) - D}}{\color{red}{\frac{-D^{n+1}}{n^nP'y^2}-An^n}} y next = y − n n P ′ y 2 − D n + 1 − A n n A D n n + n n P ′ y D n + 1 − A n n ( S ′ + y ) − D
从分母中提取出 − 1 -1 − 1 :
y next = y − − 1 ⋅ A D n n + D n + 1 n n P ′ y − A n n ( S ′ + y ) − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=y--1\cdot\frac{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^n(S'+y) - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = y − − 1 ⋅ n n P ′ y 2 D n + 1 + A n n A D n n + n n P ′ y D n + 1 − A n n ( S ′ + y ) − D
y next = y + A D n n + D n + 1 n n P ′ y − A n n ( S ′ + y ) − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=y+\frac{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^n(S'+y) - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = y + n n P ′ y 2 D n + 1 + A n n A D n n + n n P ′ y D n + 1 − A n n ( S ′ + y ) − D
乘以 y y y (如下方方框所示)以获得公分母:
y next = y + A D n n + D n + 1 n n P ′ y − A n n ( S ′ + y ) − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=\boxed{y}+\frac{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^n(S'+y) - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = y + n n P ′ y 2 D n + 1 + A n n A D n n + n n P ′ y D n + 1 − A n n ( S ′ + y ) − D
y next = y D n + 1 n n P ′ y 2 + A n n D n + 1 n n P ′ y 2 + A n n + A D n n + D n + 1 n n P ′ y − A n n ( S ′ + y ) − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=y\frac{\frac{D^{n+1}}{n^nP'y^2}+An^n}{\frac{D^{n+1}}{n^nP'y^2}+An^n}+\frac{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^n(S'+y) - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = y n n P ′ y 2 D n + 1 + A n n n n P ′ y 2 D n + 1 + A n n + n n P ′ y 2 D n + 1 + A n n A D n n + n n P ′ y D n + 1 − A n n ( S ′ + y ) − D
在左侧项中展开 y y y :
y next = y D n + 1 n n P ′ y 2 + A n n D n + 1 n n P ′ y 2 + A n n + A D n n + D n + 1 n n P ′ y − A n n ( S ′ + y ) − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=y\boxed{\frac{\frac{D^{n+1}}{n^nP'y^2}+An^n}{\frac{D^{n+1}}{n^nP'y^2}+An^n}}+\frac{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^n(S'+y) - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = y n n P ′ y 2 D n + 1 + A n n n n P ′ y 2 D n + 1 + A n n + n n P ′ y 2 D n + 1 + A n n A D n n + n n P ′ y D n + 1 − A n n ( S ′ + y ) − D
y next = D n + 1 n n P ′ y + A n n y D n + 1 n n P ′ y 2 + A n n + A D n n + D n + 1 n n P ′ y − A n n S ′ − A n n y − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=\frac{\frac{D^{n+1}}{n^nP'y}+An^ny}{\frac{D^{n+1}}{n^nP'y^2}+An^n}+\frac{ADn^n+\frac{D^{n+1}}{n^nP'y}-An^nS'-An^ny - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = n n P ′ y 2 D n + 1 + A n n n n P ′ y D n + 1 + A n n y + n n P ′ y 2 D n + 1 + A n n A D n n + n n P ′ y D n + 1 − A n n S ′ − A n n y − D
将具有公分母的和式进行合并:
y next = A D n n + 2 D n + 1 n n P ′ y − A n n S ′ − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=\frac{ADn^n+2\frac{D^{n+1}}{n^nP'y}-An^nS' - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = n n P ′ y 2 D n + 1 + A n n A D n n + 2 n n P ′ y D n + 1 − A n n S ′ − D
从原始不变量中构造代换
这个等式似乎无法进一步化简,但如果我们回看原始的不变量:
A n n ( S ′ + y ) + D = A D n n + D n + 1 n n P ′ y An^n(S'+y) + D = ADn^n+\frac{D^{n+1}}{n^nP'y} A n n ( S ′ + y ) + D = A D n n + n n P ′ y D n + 1
我们可以求解出 A D n n ADn^n A D n n ,得到:
A n n ( S ′ + y ) + D = A D n n + D n + 1 n n P ′ y An^n(S'+y) + D = \boxed{ADn^n}+\frac{D^{n+1}}{n^nP'y} A n n ( S ′ + y ) + D = A D n n + n n P ′ y D n + 1
A n n ( S ′ + y ) + D − D n + 1 n n P ′ y = A D n n An^n(S'+y) + D -\frac{D^{n+1}}{n^nP'y}= \boxed{ADn^n} A n n ( S ′ + y ) + D − n n P ′ y D n + 1 = A D n n
A D n n = − D n + 1 n n P ′ y + A n n ( S ′ + y ) + D ADn^n=\boxed{-\frac{D^{n+1}}{n^nP'y}+An^n(S'+y) + D} A D n n = − n n P ′ y D n + 1 + A n n ( S ′ + y ) + D
然后,如果我们将 A D n n ADn^n A D n n 代入到我们最新的 y next y_\text{next} y next 公式分子中,我们得到:
y next = A D n n + 2 D n + 1 n n P ′ y − A n n S ′ − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=\frac{\boxed{ADn^n}+2\frac{D^{n+1}}{n^nP'y}-An^nS' - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = n n P ′ y 2 D n + 1 + A n n A D n n + 2 n n P ′ y D n + 1 − A n n S ′ − D
y next = ( − D n + 1 n n P ′ y + A n n ( S ′ + y ) + D ) + 2 D n + 1 n n P ′ y − A n n S ′ − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=\frac{\boxed{(-\frac{D^{n+1}}{n^nP'y}+An^n(S'+y) + D)}+2\frac{D^{n+1}}{n^nP'y}-An^nS' - D}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = n n P ′ y 2 D n + 1 + A n n ( − n n P ′ y D n + 1 + A n n ( S ′ + y ) + D ) + 2 n n P ′ y D n + 1 − A n n S ′ − D
会发生大量的相消:
y next = − D n + 1 n n P ′ y + A n n S ′ + A n n y + D + 2 D n + 1 n n P ′ y − A n n S ′ − D D n + 1 n n P ′ y 2 + A n n y_\text{next}=\frac{-\frac{D^{n+1}}{n^nP'y}+\cancel{An^nS'}+An^ny + \cancel{D}+\cancel{2}\frac{D^{n+1}}{n^nP'y}-\cancel{An^nS'} - \cancel{D}}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = n n P ′ y 2 D n + 1 + A n n − n n P ′ y D n + 1 + A n n S ′ + A n n y + D + 2 n n P ′ y D n + 1 − A n n S ′ − D
最后我们留下了一个小得多的等式:
y next = A n n y + D n + 1 n n P ′ y D n + 1 n n P ′ y 2 + A n n y_\text{next}=\frac{An^ny +\frac{D^{n+1}}{n^nP'y}}{\frac{D^{n+1}}{n^nP'y^2}+An^n} y next = n n P ′ y 2 D n + 1 + A n n A n n y + n n P ′ y D n + 1
我们将分子分母同乘 y A n n \frac{y}{An^n} A n n y :
y next = ( A n n y + D n + 1 n n P ′ y ) y A n n ( D n + 1 n n P ′ y 2 + A n n ) y A n n y_\text{next}=\frac{(An^ny +\frac{D^{n+1}}{n^nP'y})\frac{y}{An^n}}{(\frac{D^{n+1}}{n^nP'y^2}+An^n)\frac{y}{An^n}} y next = ( n n P ′ y 2 D n + 1 + A n n ) A n n y ( A n n y + n n P ′ y D n + 1 ) A n n y
y next = y 2 + D n + 1 n n P ′ A n n D n + 1 n n P ′ y 1 A n n + y y_\text{next}=\frac{y^2 +\frac{D^{n+1}}{n^nP'An^n}}{\frac{D^{n+1}}{n^nP'y}\frac{1}{An^n}+y} y next = n n P ′ y D n + 1 A n n 1 + y y 2 + n n P ′ A n n D n + 1
回到我们的不变量,我们可以求解出分母中的分数项:
A n n ( S ′ + y ) + D = A D n n + D n + 1 n n P ′ y An^n(S'+y) + D = ADn^n+\boxed{\frac{D^{n+1}}{n^nP'y}} A n n ( S ′ + y ) + D = A D n n + n n P ′ y D n + 1
D n + 1 n n P ′ y = A n n ( S ′ + y ) + D − A D n n \frac{D^{n+1}}{n^nP'y}=\boxed{An^n(S'+y) + D -ADn^n} n n P ′ y D n + 1 = A n n ( S ′ + y ) + D − A D n n
然后我们可以将其代入 ynext 的等式中:
y next = y 2 + D n + 1 n n P ′ A n n D n + 1 n n P ′ y 1 A n n + y y_\text{next}=\frac{y^2 +\frac{D^{n+1}}{n^nP'An^n}}{\boxed{\frac{D^{n+1}}{n^nP'y}}\frac{1}{An^n}+y} y next = n n P ′ y D n + 1 A n n 1 + y y 2 + n n P ′ A n n D n + 1
y next = y 2 + D n + 1 n n P ′ A n n ( A n n ( S ′ + y ) + D − A D n n ) 1 A n n + y y_\text{next}=\frac{y^2 +\frac{D^{n+1}}{n^nP'An^n}}{\boxed{(An^n(S'+y) + D -ADn^n)}\frac{1}{An^n}+y} y next = ( A n n ( S ′ + y ) + D − A D n n ) A n n 1 + y y 2 + n n P ′ A n n D n + 1
然后我们可以展开 1 A n n \frac{1}{An^n} A n n 1 并化简分母:
y next = y 2 + D n + 1 n n P ′ A n n ( ( S ′ + y ) + D A n n − D ) + y y_\text{next}=\frac{y^2 +\frac{D^{n+1}}{n^nP'An^n}}{((S'+y) + \frac{D}{An^n} -D)+y} y next = (( S ′ + y ) + A n n D − D ) + y y 2 + n n P ′ A n n D n + 1
y next = y 2 + D n + 1 n n P ′ A n n ( S ′ + y + D A n n − D ) + y y_\text{next}=\frac{y^2 +\frac{D^{n+1}}{n^nP'An^n}}{(S'+y + \frac{D}{An^n} -D)+y} y next = ( S ′ + y + A n n D − D ) + y y 2 + n n P ′ A n n D n + 1
通过去掉括号并将两个 y y y 加在一起,进一步化简分母:
y next = y 2 + D n + 1 n n P ′ A n n 2 y + S ′ + D A n n − D y_\text{next}=\frac{y^2 +\frac{D^{n+1}}{n^nP'An^n}}{2y+S' + \frac{D}{An^n} -D} y next = 2 y + S ′ + A n n D − D y 2 + n n P ′ A n n D n + 1
在原始代码中,Curve 定义了额外的变量:
c = D n + 1 n n P ′ A n n c = \frac{D^{n+1}}{n^nP'An^n} c = n n P ′ A n n D n + 1
b = S ′ + D A n n b = S' + \frac{D}{An^n} b = S ′ + A n n D
将其代入 y next y_\text{next} y next 的公式后,我们得到:
y next = y 2 + D n + 1 n n P ′ A n n 2 y + S ′ + D A n n − D y_\text{next}=\frac{y^2 +\boxed{\frac{D^{n+1}}{n^nP'An^n}}}{2y+\boxed{S' + \frac{D}{An^n}} -D} y next = 2 y + S ′ + A n n D − D y 2 + n n P ′ A n n D n + 1
y next = y 2 + c 2 y + b − D y_\text{next}=\frac{y^2 +c}{2y+b -D} y next = 2 y + b − D y 2 + c
与原始源代码的比较
这与 Curve 代码完全一致,请见下方的紫色方框:
Ann 与 Anⁿ 之间的不匹配
令人困惑的是,Curve 白皮书使用的不变量是 A n n An^n A n n ,但代码库中使用的是 A n n Ann A nn 。也就是说,代码库似乎在计算 A * n * n 而不是 A * n ** n。造成这种差异的原因是,代码库将 A A A 存储为了 A n n − 1 An^{n-1} A n n − 1 。由于 n n n 在部署时是固定的,预先计算 n n − 1 n^{n-1} n n − 1 可以让代码避免在链上进行指数运算,因为这种运算的成本更高。
总结
Curve 的核心不变量不允许对变量 D D D 或 x i x_i x i 进行符号求解。相反,这些项必须通过数值方法进行求解。
从这次推导中我们可以得出一个结论,那就是优秀的代数变换是一种非常有效的Gas优化 技巧。Curve 的开发者们能够计算出比简单粗暴地代入 f f f 及其导数要小得多的牛顿法公式。
引用与致谢
在撰写本文时参考了以下资源:
StableSwap - efficient mechanism for Stablecoin liquidity, Michael Egorov, https://resources.curve.fi/pdf/curve-stableswap.pdf
Understanding the Curve AMM, Part -1: StableSwap Invariant, Atul Agarwal https://atulagarwal.dev/posts/curveamm/stableswap/
Curve Finance Discord, “chanho”
https://discord.com/channels/729808684359876718/729812922649542758/1126630568004698132
Curve - Code Explaind - get_y() | DeFi, Smart Contract Programmer https://www.youtube.com/watch?v=jAhKbxoeskQ