去中心化金融笔记(二)—— Uniswap V2 详解

54 阅读4分钟

加粗为自己添加的内容

配套课程视频:【01 区块链金融课程简介】- B站

课程实验以及讲义:liangpeili/defi-practices - Github

完成的实验代码: github.com/DestinyWei/… github.com/DestinyWei/…

若有任何问题或错误,可在Notion评论或直接评论

完整笔记请查看 Notion 链接:dune-marten-78b.notion.site/85b1d29c863…

个人博客:Howe的个人博客

Uniswap V2

手续费的计算机制

  1. 手续费全给项目方

因为项目方在池子中并没有 share,所以需要进行增发即 SmS_m 部分

S1S_1:LP 提供流动性得到的 share 数量 SmS_m:系统按手续费比例增发的 share 数量

k1\sqrt{k_1}:添加流动性对应的 k 值 k2\sqrt{k_2}:添加流动性加上增发的 share 所对应的 k 值

图1

由上面可得,

SmS1+Sm=k2k1k2Smk2=S1k2+Smk2S1k1Smk1(化简)Sm=k2k1k1S1\begin{aligned} \frac{S_m}{S_1+S_m}&=\frac{\sqrt{k_2}-\sqrt{k_1}}{\sqrt{k_2}} \\ S_m*\sqrt{k_2}&=S_1*\sqrt{k_2}+S_m*\sqrt{k_2}-S_1*\sqrt{k_1}-S_m*\sqrt{k_1}(化简) \\ S_m&=\frac{\sqrt{k_2}-\sqrt{k_1}}{\sqrt{k_1}}*S_1 \end{aligned}
  1. 手续费全给 Liquidity Provider

LP 的手续费并不是给 LP 增发新的 share 即 Sm=0S_m=0,而是仍然是 S1S_1 的数量,但随着手续费的累积,k 值会变大(此时可以理解为,LP 的 share 数量没有变化,在没有手续费收入的时候只共享 S1S_1 价值,在有手续费收入的时候则共享 S1+手续费S_1+手续费 价值 )

图2 所以,LP 为 0,项目方为 0

例子:最开始有100 DAI:1 ETH (k 值为 100\sqrt{100}),经过一系列的交换,此时池子中有 96 DAI:1.5 ETH (k 值为 144\sqrt{144})

  1. 项目方拿取一定比例(用 ϕ\phi 表示,为 SmS_m 占整个增发部分的比例)的手续费

图3

由上面可得,

SmSm+S1=k2k1k2ϕ\frac{S_m}{S_m+S_1}=\frac{\sqrt{k_2}-\sqrt{k_1}}{\sqrt{k_2}}*\phi

SmSm+S1\frac{S_m}{S_m+S_1} 表示的是项目方增发的比例,k2k1k1\frac{\sqrt{k_2}-\sqrt{k_1}}{\sqrt{k_1}} 表示的是 LP 增加的比例

由此可以推导出

Smk2=Smk2ϕ+S1k2ϕSmk1ϕS1k1ϕSm=(k2k1)S1(1ϕ1)k2+k1\begin{aligned} S_m*\sqrt{k_2} &= S_m*\sqrt{k_2}*\phi + S_1*\sqrt{k_2}*\phi - S_m*\sqrt{k_1}*\phi - S_1*\sqrt{k_1}*\phi \\ S_m &= \frac{(\sqrt{k_2}-\sqrt{k_1})*S_1}{(\frac{1}{\phi}-1)\sqrt{k_2}+\sqrt{k_1}} \end{aligned}

ϕ=1\phi=1 时,则为手续费全给项目方时所得到的公式

Spot Price

在使用 x 交换成 y 的时候,显示的价格为 P0=yxP_0=\frac{y}{x},但实际成交价格为 P1=ΔyΔxP_1=\frac{\Delta y}{\Delta x}P0P_0P1P_1 之间的差值就是所谓的滑点

Spot Price

xy=(x+Δx)(yΔy)ΔyΔx=yx+Δx\begin{aligned} x*y &= (x+\Delta x)*(y-\Delta y) \\ \frac{\Delta y}{\Delta x} &= \frac{y}{x+\Delta x} \end{aligned}

Δx\Delta x 较小时,我们可以理解为是在计算 limΔx0yx+Δx\lim_{\Delta x \to 0}\frac{y}{x+\Delta x}

Price Oracle

TWAP (Time Weighted Average Price) 时间权重的平均价格

PnP_n 是在 tnt_n 时间点的价格

Price Oracle1

由上面可得,

i=0n1Pi(Ti+1Ti)\sum_{i=0}^{n-1}P_i*(T_{i+1}-T_i)

假如我们想要从 tkt_k 计算,而不是从 0 计算

Price Oracle2

由上面可得,

P=i=kn1Pi(Ti+1Ti)TnTk=i=0n1Pi(Ti+1Ti)i=0k1Pi(Ti+1Ti)TnTk\begin{aligned} P &= \frac{\sum_{i=k}^{n-1}P_i*(T_{i+1}-T_i)}{T_n-T_k} \\ &= \frac{\sum_{i=0}^{n-1}P_i*(T_{i+1}-T_i) - \sum_{i=0}^{k-1}P_i*(T_{i+1}-T_i)}{T_n-T_k} \end{aligned}

通过这个公式,我们可以计算比如一个代币在一小时里的平均价格

如何计算 Uniswap V2 的无常损失

无常损失是出现在添加/移除流动性的情况下,而滑点是出现在两个代币交换的情况下

假设我们有一个初始 LP 为:100DAI:1ETH,此时 K = 100,PE=1001=100P_E=\frac{100}{1}=100,两个代币总价值为 100 + 100 = $200

  • 当 ETH 涨价时,LP 为:120DAI:0.83ETH,此时 K 不变,PE=1200.83=144.58P_E=\frac{120}{0.83}=144.58,两个代币总价值为 120 + 120 = 240,但如果我们并没有添加流动性而是拿住最开始的100DAI1ETH,两个代币总价值为100+1144.58=240,但如果我们并没有添加流动性而是拿住最开始的 100DAI 和 1ETH,两个代币总价值为 100 + 1 * 144.58 = 244.58,那么 244.58 与 240 的差值就是无常损失的值
  • 当 ETH 降价时,LP 为:80DAI:1.25ETH,此时 K 不变,PE=801.25=64P_E=\frac{80}{1.25}=64,两个代币总价值为 80 + 80 =160,但如果我们并没有添加流动性而是拿住最开始的100DAI1ETH,两个代币总价值为100+164=160,但如果我们并没有添加流动性而是拿住最开始的 100DAI 和 1ETH,两个代币总价值为 100 + 1 * 64 = 164,那么 164 与 160 的差值就是无常损失的值

即在添加流动性所产生的无常损失会导致 ETH 涨价时相比拿住赚得更少,ETH 降价时相比拿住亏得更多

通过上面的例子我们可以抽象出更通用的模型,我们可以列出下面这三个公式,PiP_i 表示在 i 时刻某个代币的价格,d 表示价格变化的因素 (当 0<d<10 \lt d \lt 1 时表示降价,d=1d=1 时表示价格不变,d>1d \gt 1 时表示涨价)

P1=P0dxy=kP=yx\begin{align} P_1 &= P_0*d \\ x*y &= k \\ P &= \frac{y}{x} \end{align}

由 (2) (3) 可以计算得到 x 和 y 用价格P 和流动性k 的表达式

x=kyky=yPy2=kPy=kPx=yP=kPP=kP\begin{align} x=\frac{k}{y} \Rightarrow \frac{k}{y}=\frac{y}{P} \Rightarrow y^2 &= k*P \Rightarrow y = \sqrt{k}*\sqrt{P} \\ x &= \frac{y}{P}=\frac{\sqrt{k}*\sqrt{P}}{P}=\frac{\sqrt{k}}{\sqrt{P}} \end{align}

假设一开始为 t0t_0x0x_0y0y_0P0=y0x0P_0=\frac{y_0}{x_0}

添加流动性(无手续费)之后为 t1t_1x1x_1y1y_1P1=y1x1P_1=\frac{y_1}{x_1}

拿住为 thodlt_{hodl}x0x_0y0y_0P1=y1x1P_1=\frac{y_1}{x_1}

将无常损失与价格变化之间的关系函数设为 f(d)f(d),V 表示为代币的 value,则

f(d)=LP的损失拿住之后的价值=V1VhodlVhodl=V1Vhodl1f(d)=\frac{做LP的损失}{拿住之后的价值}=\frac{V_1-V_{hodl}}{V_{hodl}}=\frac{V_1}{V_{hodl}}-1

由 (4) (5) 可以计算出 V1V_1VhodlV_{hodl}

V1=y1+x1P1=k1P1+k1P1P1=2k1P1Vhodl=y0+x0P1=k0P0+k0P0P0d=(1+d)k0P0\begin{aligned} V_1=y_1+x_1*P_1 = \sqrt{k_1}*\sqrt{P_1}+\frac{\sqrt{k_1}}{\sqrt{P_1}}*P_1 &= 2*\sqrt{k_1}*\sqrt{P_1} \\ V_{hodl} = y_0+x_0*P_1=\sqrt{k_0}*\sqrt{P_0}+\frac{\sqrt{k_0}}{\sqrt{P_0}}*P_0*d &= (1+d)*\sqrt{k_0}*\sqrt{P_0} \end{aligned}

所以

f(d)=2k1P1(1+d)k0P01=2k1P0d(1+d)k0P01(因为无手续费,所以k1k0相同)=2d1+d1\begin{aligned} f(d) &= \frac{2*\sqrt{k_1}*{\sqrt{P_1}}}{(1+d)*\sqrt{k_0}*{\sqrt{P_0}}}-1 \\ &= \frac{2*\sqrt{k_1}*{\sqrt{P_0*d}}}{(1+d)*\sqrt{k_0}*{\sqrt{P_0}}}-1(因为无手续费,所以k_1和k_0相同) \\ &=\frac{2*\sqrt{d}}{1+d}-1 \end{aligned}

Uniswap 官方文档中给出的无常损失与价格变化的关系曲线

无常损失

Flash Swap

Flash Swap

传统的借贷需要用户先超额抵押才能借出代币

闪电交换是指用户无需质押一分钱即可借出一定数量的代币,例如当你发现有一个套利机会但没有足够的资金去进行超额抵押借贷时,我们可以通过闪电借贷借出 100 个 DAI,然后将这些 DAI 进行一系列的投资等去获取收益如变为 110 个 DAI,此时我们在把借出的 100 个 DAI 及其利息归还,剩下的收益就是自己的也就是 7 个 DAI

使用 Flash Swap 加杠杆

用户持有 3 个 ETH,每个 ETH 的价格为 200 DAI,抵押率为 150%。用户想要 2 倍杠杆

传统借贷

  1. 添加 3 ETH 到 Maker Vault
  2. 借出 400 DAI 出来
  3. 在 Uniswap 把 DAI 换成 ETH
  4. 重复 1-3 步

闪电借贷

  1. 跟 Uniswap 借 3 个 ETH
  2. 把用户的 3 个 ETH 和借的 3 个 ETH 抵押到 Maker Vault
  3. 借出 800 个 DAI 出来
  4. 还给 Uniswap 600 DAI

Uniswap V2 代码结构

代码结构

Uniswap V2 的核心合约就只有三个,分别是 Router、Factory 和 Pair 合约

图中的三个操作分别为 ① add liquidity ② swap ③ remove liquidity

用户通过前端页面即页面右边的长方块进行上述的三种操作时,会通过 Router 合约来判断执行的操作并调用相关合约的函数,在路由时合约会判断当前用户执行操作所对应的 Pair 是否已创建,若没有则会先创建一个 Factory 合约来创建相关的 Pair 合约,若有则直接调用对应 Pair 合约的函数

Uniswap V2 总结

一个核心:CPAMM

三个操作:添加流动性 / 加密资产交易 / 移除流动性

几个概念:手续费 / Price Oracle / TWAP / Falsh Swap / 无常损失 / 滑点等


后续笔记会在之后发布,让我们尽情期待,您也可以关注我的推特账号(@weihaoming)以获取更多笔记资源……