Uniswap-v2

介绍

uniswap-v1在以太坊链上基于”constant product formula”(恒定乘积公式)实现了AMM。v1能为ETH和ERC20提供流动性,并从中收取一定的交易费用于支付给流动性提供者(LP,Liquidity Provider)。而Uniswap v2是基于v1的原理上重构代码,并新增许多实用的功能。接下将会介绍v2中新增的功能和它们的原理。

新增功能。

uniswap-v2是基于v1新增了许多功能,下面介绍v2主要新增的功能:

  • 允许任意erc20/erc20 pair
  • flash swap
  • 使用时间加权平均估算价格
  • 协议费用开关

支持任意的ERC20 pair

与v1不同,v2支持了任意 ERC20/ERC20 pair之间交易。为了理解v2是怎样优化v1这部分功能,我们用uniswap白皮书中的例子进行讲解。

对于一个 ABC 和 XYZ的交易池。

  • v1:如果使用v1协议创建这两个ERC20 pair,需要创建ABC/ETH,XYZ/ETH两个交易池。其中ABC/XYZ需要经过ETH作为桥接货币。

    因为v1的代码决定了它只能创建ERC20和ETH的交易池。

  • v2:支持了创建任意两个ERC20 pair,能够直接创建 ABC/XYZ 的交易池。不需要经过ETH桥接。

v2的这项功能优点:

  1. 解决了v1的ETH/ABC pair和 ETH/XYZ pair这种交易对带来的碎片化。
  2. v1的LP需要提供两倍于v2的LP的资产才能创建相同资产数量的ABC/XYZ pair。
  3. 对于LP来说,使用v1添加 ABC/XYZ pair还需要提供ETH作为这两者桥接货币,那么LP也会面临ETH与ABC、XYZ的无偿损失。而v2的LP直接需要面对ABC与XYZ的无偿损失。

假设 ABC、XYZ是稳定币,v1的无偿损失会比v2的无偿损失更大。下图无偿损失例子计算:

v2_1

闪电贷(Flash Swap)

闪电贷(Flash Swap):闪电贷是区块链一个特殊的机制。用户被允许在一个区块内自由借出资金,并在这个区块归还资金和其费用,整个借贷过程无需任何抵押品。

为什么会有闪电贷?

区块链的原子性:我们知道比特币、以太坊等区块链只有在区块上链时区块链状态才会发生改变。这就意味着想要通过一系列交易改变某个状态需要这些交易都能成功并被打包上链,这就是区块链的原子性。

没有归还贷款可以revert:在区块链里我们可以检查一个区块里借出的贷款是否在这个区块内被归还,这是由合约逻辑决定的。如果这个区块内没有归还贷款,合约就会revert交易。

区块链的原子性导致了闪电贷的出现:闪电贷是无需任何抵押品的,这是因为用户没有在区块内归还贷款,那么发出借贷的交易永远不可能上链,该用户的资产状态也不可能有任何变化。

闪电贷有什么应用

  • 套利:比如现在有两个类似uniswap的交易所,其中一个以太坊价格为3500$,另一个为3700$。那么套利者可以使用闪电贷在3500$的交易所买入一个ETH,然后在另一个交易所以3700$的价格出售。整个过程套利者只需支付闪电贷一点点手续费就能获得200$的利润。
  • 再融资贷款:一些常规的借贷DeFi会需要抵押品,比如1eth能抵押出2000DAI。当用户借出的DAI用于其他项目时,用户可以使用闪电贷赎回自己eth,并用部分eth偿还闪电贷。
  • 偿还即将清算的抵押品:像Maker这类协议,用户抵押品清算时会受到一笔清算惩罚,抵押品会用于归还债务和扣除10%的惩罚,剩下返回给用户。为了避免清算惩罚,可以使用闪电贷赎回抵押品,然后用抵押品归还闪电贷,这样就能规避清算惩罚。

ERC3156-闪电贷实现标准

flash Swap使用ERC3156标准实现。ERC3156有两个合约接口需要实现,分别是FlashBorrower和FlashLender。

借出方需要实现接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.9.0;
import "./IERC3156FlashBorrower.sol";


interface IERC3156FlashLender {

/**
* @dev The amount of currency available to be lended.
* @param token The loan currency.
* @return The amount of `token` that can be borrowed.
*/
function maxFlashLoan(
address token
) external view returns (uint256);

/**
* @dev The fee to be charged for a given loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function flashFee(
address token,
uint256 amount
) external view returns (uint256);

/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}

贷款方需要实现接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.9.0;


interface IERC3156FlashBorrower {

/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}

实现代码可以参考:https://github.com/alcueca/ERC3156/tree/main

时间加权平均价格预言机

价格预言机是向区块链提供各种加密货币价格的信息输入机。Uniswap作为一个去中心化交易所,它也具备有提供价格信息的能力。

Uniswap v1的价格是交易池中两种货币的比率(ratio),这种瞬时的价格是及其容易被操控的。比如:攻击者可以通过使用闪电贷使价格发生剧烈变化,然后对使用uniswap预言机价格进行决策的合约进行攻击。

Uniswap v2使用TWAP作为价格预言机的机制,它的大致思路是:

  • 价格取决于过去一段时间价格的平均值。
  • 当前的价格不会被计算到oracle中。

TWAP

时间加权平均价格(Time Weighted Average Price):Uniswap v2使用一个时间加权的价格累加器作为快照,然后用这个累加器除以累加器经过的时间得到时间加权平均价格。

注明:Ti是价格Pi所处的一段时间,ti是价格Pi所处的时刻

v2_2

当我们对现在,假设是t4到之后t7的价格感兴趣,我们可以记录当下的累加器快照和t7时刻累加器的快照。计算方式如下:

(P4*T4+P5*T5+P6*T6+P7*T7)/(T4+T5+T6+T7)

(a7-a4)/(t4-t1)

v2_6

TWAP需要解决的问题

跟踪两个资产TWAP

如果你尝试计算 ETH/DAI 和 DAI/ETH 的TWAP,你会发现它们价格并不是呈倒数关系。比如:ETH/DAI 的价格从100涨到300,时间为1,计算出TWAP(ETH/DAI) = 200。而如果使用 DAI/ETH 进行计价,那么价格是从1/100 跌到 1/300,计算出TWAP(DAI/ETH) = 1/150。可以看出 TWAP(ETH/DAI)TWAP(DAI/ETH)不是倒数关系。所以Uniswap需要跟踪两个资产的TWAP。

及时更新价格

在uniswap v2代码的时间累加器的更新由_updata()函数触发。这会导致以下两种情况出现:

  1. 攻击者绕过更新累加器去更改池子的余额和价格
  2. 一段时间没有触发swap这类函数去更新累加器

这两种情况都会使得TWAP不能及时更新,为了避免这种情况,合约提供了 sync() 函数能够主动触发_updata()更新TWAP。

协议费用(Protocol fee)

uniswap v2设置了一个协议费用(Protocol fee),如果开启这个协议费用,每次LP添加流动性时就会为feeToSetter的地址mint一笔LPT作为协议收取的费用。增发LPT分走LP部分利润的公式如下:

v2_7

接下将通过三步逐渐分析,Uniswap v2是怎样通过上面的公式收取协议费用

LPT增值

通过swap的手续费,池中储备x*y会逐渐变大,LP通过添加流动性mint的LPT也会增值。LP可以通过burn LPT获得的代币也会更多。

v2_5

增发LPT拿走LPT增值的利润

项目方可以通过增发LPT的方式拿走池子中部分收益。下面假设协议通过增发LPT拿走LPT增值所有利润。此时LP手中持有的LPT增值为0,因为增值部分价值通过增发LPT分走了。

v2_4

实际上协议费只会分走一定比例的增值利润,协议费用的公式推导如下:

v2_3

Uniswap v2设置的φ = 1/6,即协议费用等于LP赚取1/6 的利润。交易中为交易支付费用0.30%,LP可以获得池子交易费用的0.25%,协议获得0.05%。

v2_8

引用

  1. Uniswap V2 Book | RareSkills

  2. whitepaper.pdf (uniswap.org)

  3. Jeiwan/uniswapv2-contracts: Uniswap V2 contracts, ready to be deployed to local or test network (github.com)

  4. 梁培利的个人空间-梁培利个人主页-哔哩哔哩视频 (bilibili.com)