抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言:

Uniswap V2代码结构

Uniswap智能合约代码由两个github项目组成。一个是core,一个是periphery。

https://github.com/Uniswap/uniswap-v2-core.git

https://github.com/Uniswap/uniswap-v2-periphery.git

core偏核心逻辑,单个swap的逻辑。periphery偏外围服务,一个个swap的基础上构建服务。单个swap,两种代币形成的交易对,俗称“池子”。每个交易对有一些基本属性:reserve0/reserve1以及total supply。reserve0/reserve1是交易对的两种代币的储存量。total supply是当前流动性代币的总量。每个交易对都对应一个流动性代币(LPT - liquidity provider token)。简单的说,LPT记录了所有流动性提供者的贡献。所有流动性代币的总和就是total supply。Uniswap协议的思想是reserve0*reserve1的乘积不变。

Periphery逻辑

核心逻辑实现在UniswapV2Router02.sol中。称为Router,因为Periphery实现了“路由”,支持各个swap之间的连接。基本上实现了三个功能:1/ add liquidity(增加流动性)2/remove liqudity (抽取流动性) 3/ swap(交换)。

uniswap v2非常重要,必须要非常熟悉该协议!!!

以下便是对uniswap v2 代码的解读。

v2-core

1. UniswapV2ERC20

uniswap v2的代币实际上是 ERC20代币。实现ERC20标准方法。

代码解读如下:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
pragma solidity =0.5.16;

import './interfaces/IUniswapV2ERC20.sol';
import './libraries/SafeMath.sol';

contract UniswapV2ERC20 is IUniswapV2ERC20 {
using SafeMath for uint; // 将 SafeMath 库合约用于 uint 类型

string public constant name = 'Uniswap V2'; // 代币的名字
string public constant symbol = 'UNI-V2'; // 代币符号
uint8 public constant decimals = 18;
uint public totalSupply; // 发行量

// 存储某地址的代币余额,address => uint的映射
mapping(address => uint) public balanceOf;

// 存储某一地址对另一地址的代币授权量,授权之后可以允许被授权人使用授权人的代币进行转账 `transferFrom`
mapping(address => mapping(address => uint)) public allowance;

//
bytes32 public DOMAIN_SEPARATOR;

// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

//
mapping(address => uint) public nonces;

// 两个在授权和转账时会被触发的事件
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);

constructor() public {
uint chainId;
assembly {
// chainid指令用于获取当前区块链的链ID, 它唯一地标识了当前区块链的网络
chainId := chainid
}
// 初始化 DOMAIN_SEPARATOR 变量
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}

/**
1. 铸币操作,因为有库函数的引用,可以直接调用 add,sub等运算
2. totalSupply 发行量时对于整个系统来说,而balance是对于某个账户来说
*/
function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value); // 发行量累加 value
balanceOf[to] = balanceOf[to].add(value); // to账户的 余额累加 value
emit Transfer(address(0), to, value); // 触发交易事件
}

/**
1. 销币操作
*/
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value); // from 账户的 余额累减 value
totalSupply = totalSupply.sub(value); // 发行量累减 value
emit Transfer(from, address(0), value); // 触发交易事件
}

/**
1. 授权操作
2. owner一般是调用者,spender则是授权者,value是授权代币量
*/
function _approve(address owner, address spender, uint value) private {
// 记录owner 对spender 的授权量为 value
allowance[owner][spender] = value;
emit Approval(owner, spender, value); // 触发授权事件
}

/**
1. 转账操作
2. 内置函数_transfer(), from账户向to账户转移 value的代币
3. 而在外部函数中,transfer中的from为合约调用者
*/
function _transfer(address from, address to, uint value) private {
// from 账户余额减少value
balanceOf[from] = balanceOf[from].sub(value);
// to 账户余额增加 value
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}

/**
1. 外部授权操作
2. 外部授权规定了授权者是调用者,予以:'我'给spender授权
*/
function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}

/**
1. 外部转账操作
2. 规定了转账者是调用者,予以:'我'给to转账value
*/
function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}

/**
1. 外部转账操作,msg.sender是代理人
2. 在 pragma=0.5.16的版本中,uint(-1)= 115792089237316195423570985008687907853269984665640564039457584007913129639935
3. 要调用该函数,事先要让 from执行approve函数,给msg.sender授权
4. 最后调用_transfer()函数,执行from 向 to 转账 value的操作
*/
function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}

/**
1. 许可操作
2. permit函数的作用是将代币授权给指定的目标地址,
使得目标地址可以代表代币持有人进行交易,而无需进行传统的授权交易。
这种新型的授权方法可以提高代币交易的效率和安全性,
同时也可以减少交易的成本和时间。
3. owner授权者,spender被授权者,value代币数目,deadline:授权的截止时间,必须在此时间之前完成授权
nonce:随机数,用于避免授权被重复使用,v、r、s:用于验证授权的签名参数。
*/
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
}

2. UniswapV2Factory

工厂合约,用于创建Pair合约(以及设置协议手续费接收地址)

代码解读如下:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
pragma solidity =0.5.16;

import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';

contract UniswapV2Factory is IUniswapV2Factory {

address public feeTo; // 手续费接收地址
address public feeToSetter; // 手续费接收地址的设置者

/**
如果将 getPair设置为public,则编译的时候会在该合约中默认生成set 和get函数
解读:通过两个地址获取到交易对地址
*/
mapping(address => mapping(address => address)) public getPair;

// 数组,存储所有交易对
address[] public allPairs;

// 交易对创建事件
event PairCreated(address indexed token0, address indexed token1, address pair, uint);

// 初始化,手续费接收地址的设置者
constructor(address _feeToSetter) public {
feeToSetter = _feeToSetter;
}

// 获取交易对的对数
function allPairsLength() external view returns (uint) {
return allPairs.length;
}

/**
首先将token0 token1按照顺序排序,确保token0字面地址小于token1。
接着使用assembly + create2创建合约。
assembly可以在Solidity中使用Yul语言直接操作EVM,是较底层的操作方法。
《Uniswap v2 白皮书》中讲到,create2主要用于创建确定性的交易对合约地址,
目的是根据两个代币地址直接计算pair地址,而无需调用链上合约查询。
*/
function createPair(address tokenA, address tokenB) external returns (address pair) {
// 两种代币地址不能相同
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
// 先将token0 token1按照顺序排序,确保token0字面地址小于token1
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
// 确保token0不等于 address(0),则两个地址都不为 address(0)
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
// 确保这两种代币地址的交易对为address(0),即这两个代币尚未创建交易对
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
// 获取 `UniswapV2Pair`的字节码
bytes memory bytecode = type(UniswapV2Pair).creationCode;
// 生成盐salt,salt由这两个地址紧打包再hash获得,是唯一的
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
// 通过creat2计算交易对地址
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
// 将新生成的交易对中的两种代币设置为 token0, token1
IUniswapV2Pair(pair).initialize(token0, token1);
// 记录token0 和 token1生成的交易对
getPair[token0][token1] = pair;
// 反向填充映射
getPair[token1][token0] = pair; // populate mapping in the reverse direction
// 保存该交易对pair
allPairs.push(pair);
// 创建成功,触发交易对生成事件
emit PairCreated(token0, token1, pair, allPairs.length);
}


// 设置手续费接收地址
function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeTo = _feeTo;
}

// 修改手续费接收地址的设置者
function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeToSetter = _feeToSetter;
}
}

3. UniswapV2Pair

Pair(交易对)合约,定义和交易有关的几个最基础方法,如swap/mint/burn,价格预言机等功能,其本身是一个ERC20合约,继承UniswapV2ERC20

Pair合约主要实现了三个方法:mint(添加流动性)、burn(移除流动性)、swap(兑换)。

代码解读如下:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
pragma solidity =0.5.16;

import './interfaces/IUniswapV2Pair.sol';
import './UniswapV2ERC20.sol';
import './libraries/Math.sol';
import './libraries/UQ112x112.sol';
import './interfaces/IERC20.sol';
import './interfaces/IUniswapV2Factory.sol';
import './interfaces/IUniswapV2Callee.sol';

contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
// 引用库函数
using SafeMath for uint;
using UQ112x112 for uint224;

// 最低额度的流动性
uint public constant MINIMUM_LIQUIDITY = 10**3;
// tansfer函数的选择器
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));

// 工厂?token0和token1交易对中的两种代币
address public factory;
address public token0;
address public token1;

// token0和token1交易对中的两种代币的存储量
uint112 private reserve0; // uses single storage slot, accessible via getReserves
uint112 private reserve1; // uses single storage slot, accessible via getReserves
// 上次更新的时间
uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves

// 累加交易价格
uint public price0CumulativeLast;
uint public price1CumulativeLast;
uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event

// 锁标志
uint private unlocked = 1;

// 修饰器为了防止异步
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}

// 读取交易对中两种代币的余额,以及上一次交易对更新的时间
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}

/**
1. 通过call的方式,调用token中的 transfer 函数---兼容性更强
2. 判断调用是否成功,success是否为true,data是否为空
*/
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}

event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);

// 初始化 工厂地址为调用者
constructor() public {
factory = msg.sender;
}

// called once by the factory at time of deployment
// 初始化交易对中的两种代币地址
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
token0 = _token0;
token1 = _token1;
}

// update reserves and, on the first call per block, price accumulators
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
// 记录当前更新时间
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
// 记录过去了多久
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
// 计算出当前的交易价格
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
// 将这两种代币的存储量设置为代币的余额
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
// 更新当前操作时间
blockTimestampLast = blockTimestamp;
// 触发同步事件
emit Sync(reserve0, reserve1);
}

// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
// 计算铸币手续费
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
address feeTo = IUniswapV2Factory(factory).feeTo(); // 获取当前factory铸币手续费的接收地址
feeOn = feeTo != address(0); // 检查该factory是否设置了手续费接收地址
uint _kLast = kLast; // gas savings
if (feeOn) { // 如果该factory有手续费接收地址
if (_kLast != 0) {
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
uint denominator = rootK.mul(5).add(rootKLast);
uint liquidity = numerator / denominator;
if (liquidity > 0) _mint(feeTo, liquidity); // 给feeTo地址铸币liquidity
}
}
} else if (_kLast != 0) {
kLast = 0;
}
}

// this low-level function should be called from a contract which performs important safety checks
// 铸币操作,添加流动性
function mint(address to) external lock returns (uint liquidity) {
// 读取代币的存储量
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// 获取这两种代币的余额
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
// 计算当前合约中两个代币的净增量,并赋值给amount0和amount1变量
// 净增量等于余额减去储备量
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
// 调用_mintFee函数,计算是否需要收取协议手续费,并返回一个布尔值,赋值给feeOn变量
bool feeOn = _mintFee(_reserve0, _reserve1); // 理解为支付完铸币费用
// 记录发行量,难道这里也讲究异步的现象?
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee

// 如果是首次提供该交易对的流动性,则根据根号xy生成流动性代币,并销毁其中的MINIMUM_LIQUIDITY(即1000wei
if (_totalSupply == 0) {
// 计算流动性代币的数量,等于两个代币净增量乘积的平方根减去最小流动性常量,并赋值给liquidity变量
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
// 调用_mint函数,向零地址铸造最小流动性常量数量的流动性代币(永久锁定)
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
// 计算流动性代币的数量,等于两个代币净增量与储备量比例乘积与总供应量乘积的较小值,并赋值给liquidity变量
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity); // 为to铸币,liquidity

_update(balance0, balance1, _reserve0, _reserve1); // 调用_update函数,更新当前合约中两个代币的储备量为最新的余额
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}

// this low-level function should be called from a contract which performs important safety checks
// 销币操作,移除流动性
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];

// 参考白皮书,为了节省交易手续费,Uniswap v2只在mint/burn流动性时收取累计的协议手续费。
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
// 采用mint中计算 liquidity的方法倒推amount
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
// 销毁本合约的 流动性 liquidity
_burn(address(this), liquidity);
// 调用_token0(_token1)中的 transfer函数

// 通过各自token中实现的transfer将token转移回to
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
//更新合约自身的token0、1余额
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
//更新池子中的储备量和价格积累器
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}

// this low-level function should be called from a contract which performs important safety checks

/**
实现两种代币的交换(交易)功能-----闪电贷功能
uint amount0Out:要转换的第一种代币的数量。
uint amount1Out:要转换的第二种代币的数量。
address to:接收转换后代币的目标地址。
bytes data:可选的额外数据,用于向目标地址提供更多信息。
*/
// 接收者 to 必须要实现 `uniswapV2Call`函数,通过此函数输入交换的代币
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {

require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// 检查输出量是否小于储备量
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

uint balance0;
uint balance1;

{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
// 检查接收地址是否合法
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');

if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
// 如果有额外数据,就调用接收地址的回调函数
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);

balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
// 计算输入量
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;

require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');

{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
// 计算调整后的余额,因为在solidity中没有浮点数 0.3%,这样是为了模拟出 0.3%
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}

_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}

// force balances to match reserves
/**
用于移除资金池中多余代币的函数。它的作用是将代币池中多余的代币转移到指定的目标地址,以便于在资金池中保持正确的代币比例
*/
function skim(address to) external lock {
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}

// force reserves to match balances
// 同步,迫使代币余额与代币储量相匹配,调用_update函数
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}

分析 mint函数,举例

假设有一个流动性池,其中有 10 个 ETH 和 5000 个 USDC,总供应量为 70.71 个流动性代币(这里面包括过去交易留下的Fees)。现在,有人想向这个池中添加 1 个 ETH 和 500 个 USDC,以获得更多的流动性代币。那么,他们将获得多少流动性代币呢?

首先,我们需要计算添加后的两种代币的储备量和余额。储备量等于原来的储备量加上添加的数量,余额等于储备量加上协议手续费(假设为 0.3%)。因此,我们有:

  • 储备量:reserve0 = 10 + 1 = 11 ETH,reserve1 = 5000 + 500 = 5500 USDC
  • 余额:balance0 = reserve0 * (1 + 0.003) = 11.033 ETH,balance1 = reserve1 * (1 + 0.003) = 5516.5 USDC

接下来,我们需要计算添加后的总供应量。如果是第一次添加流动性,则使用公式 sqrt(x * y) — MINIMUM_LIQUIDITY,否则使用公式 min(x * totalSupply / reserve0, y * totalSupply / reserve1)。因为这不是第一次添加流动性,所以我们使用后者。因此,我们有:

  • 总供应量:totalSupply = min(balance0 * 70.71 / reserve0, balance1 * 70.71 / reserve1) = min(71.41, 71.41) = 71.41

最后,我们需要计算添加者获得的流动性代币的数量。这个数量等于添加后的总供应量减去添加前的总供应量。因此,我们有:

  • 流动性代币:liquidity = totalSupply — 70.71 = 71.41–70.71 = 0.7

也就是说,添加者将获得大约 0.7 个流动性代币。

分析burn,举例

假设有一个池子,它允许交易 ETH 和 DAI,它的地址是 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11。这个池子有以下的状态:

  • 流动性代币的总供应量是 3,000,000
  • 池子里有 10,000 个 ETH 和 4,000,000 个 DAI。
  • 协议费用是 0.05%
  • 你拥有 30,000 个流动性代币,也就是池子的 1%

现在,你想要退出这个池子,把你的流动性代币销毁,并拿回你的 ETH 和 DAI。你可以调用这个函数,把 to 参数设为你自己的地址。这样,函数会做以下的事情:

  • 它会从池子里获取 ETH 和 DAI 的储备量,分别是 _reserve0 = 10,000_reserve1 = 4,000,000
  • 它会获取 ETH 和 DAI 的地址,分别是 _token0 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2_token1 = 0x6B175474E89094C44Da98b954EedeAC495271d0F
  • 它会获取合约自身的 ETH 和 DAI 的余额,分别是 balance0 = 10,000balance1 = 4,000,000。注意,这里假设没有其他人在同一区块内与池子交互,否则余额可能会有变化。
  • 它会获取合约自身的流动性代币余额,也就是 liquidity = 30,000
  • 它会调用 _mintFee 函数,来分发协议费用给流动性提供者。假设在你加入池子后,没有发生过任何交易,那么协议费用就是零,所以 _mintFee 函数不会改变任何东西,并返回 feeOn = false
  • 它会计算你能够拿回的 ETH 和 DAI 的数量,分别是 amount0 = liquidity * balance0 / totalSupply = 30,000 * 10,000 / 3,000,000 = 100amount1 = liquidity * balance1 / totalSupply = 30,000 * 4,000,000 / 3,000,000 = 40,000。这保证了按比例分配。

知识点:

abi.decode(data, (bool))是Solidity中的一种函数调用,用于将字节数组(byte array)解码为布尔值(bool)类型。

具体来说,abi.decode函数接受两个参数:字节数组和数据类型。在这里,字节数组是要解码的数据,数据类型是要解码成的目标类型,即布尔值。

函数调用abi.decode(data, (bool))将字节数组解码为一个布尔值。这个布尔值的值取决于字节数组中的数据。如果字节数组中的数据为0,则解码后的布尔值为false,否则为true。

知识点:

UQ112x112.encode(_reserve1).uqdiv(_reserve0)

UQ112x112.encode(_reserve1).uqdiv(_reserve0)是Uniswap V2中的一个计算交易价格的操作,使用了UQ112x112固定点数算法。

具体来说,_reserve0_reserve1是Uniswap V2交易对中两种资产的余额(reserve),UQ112x112.encode()函数将余额编码为UQ112x112固定点数格式,然后使用了.uqdiv()函数对两种资产的余额进行了除法操作,计算出当前的交易价格。

UQ112x112固定点数算法是一种用于在以太坊合约中进行精确数学计算的算法。它将浮点数转换为整数,并使用固定的小数位数进行计算。在Uniswap V2中,UQ112x112固定点数算法被广泛应用于计算交易价格和资金池分配等。

在这个操作中,UQ112x112.encode(_reserve1)_reserve1编码为UQ112x112格式,然后.uqdiv(_reserve0)将编码后的_reserve1除以_reserve0。最终的结果是一个UQ112x112格式的数,表示当前的交易价格。

v2-periphery

1. UniswapV2Router02

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
pragma solidity =0.6.6;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './interfaces/IUniswapV2Router02.sol';
import './libraries/UniswapV2Library.sol';
import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IWETH.sol';

contract UniswapV2Router02 is IUniswapV2Router02 {
using SafeMath for uint;

address public immutable override factory;
address public immutable override WETH;

// 修饰器确保 操作在截止日期之前
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}

// 初始化factory 和 WETH地址
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}

// 接收ETH
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}

// **** ADD LIQUIDITY ****
/** _addLiquidity可以帮助计算最佳汇率。如果是首次添加流动性,则会先创建交易对合约;
否则根据当前池子余额计算应该注入的最佳代币数量。
*/
function _addLiquidity(
address tokenA, // 代币A
address tokenB, // 代币B
uint amountADesired, // 希望存入的代币A数量
uint amountBDesired, // 希望存入的代币B数量
uint amountAMin, // 最少存入的代币A数量
uint amountBMin // 最少存入的代币B数量
) internal virtual returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
// 获取交易对中代币A B的存储量
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
// 如果储备量都为 0,那两个预期支付额就是成交量
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
// 采用控制变量法算出最佳的汇率

/**
如果计算得出的结果值 amountBOptimal 不比 amountBDesired 大,且不会小于 amountBMin,
就可将 amountADesired 和该 amountBOptimal 作为结果值返回。
如果 amountBOptimal 大于 amountBDesired,则根据 amountBDesired 计算得出需要支付多少 tokenA,
得到 amountAOptimal,只要 amountAOptimal 不大于 amountADesired 且不会小于 amountAMin,
就可将 amountAOptimal 和 amountBDesired 作为结果值返回。
*/

// 调用quote函数,换算amountADesired 对应的B代币为多少
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);

// 最佳的B代币数量少于希望存入的代币B数量
if (amountBOptimal <= amountBDesired) {
// 这是定 amountADesired,求B的最佳数量
// 而且最佳B代币数量要求 >= amountBMin
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
// 返回希望存入的A,和最佳的B数量
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
// 定amountBDesired,求A的最佳数量
// 调用quote函数,换算amountBDesired 对应的A代币为多少
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
// 返回希望存入的B,和最佳的A数量
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA, // 代币A
address tokenB, // 代币B
uint amountADesired, // 希望存入的代币A数量
uint amountBDesired, // 希望存入的代币B数量
uint amountAMin, // 用户可接受的最小成交代币A数量
uint amountBMin, // 用户可接受的最小成交代币B数量
address to, // 流动性代币接收地址
uint deadline // 该笔交易的有效时间,如果超过该时间还没得到交易处理就直接失效不进行交易了
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
// 调用_addLiquidity函数,返回 代币A和代币B 的最佳汇率
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
// 获取交易对地址
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
// 调用tokenA的`transferFrom`函数,实现msg.sender向交易对pair转入amountA代币
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
// 获取存入代币后uniswapv2中的流动性
liquidity = IUniswapV2Pair(pair).mint(to);
}

/** 添加流动性 */
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
// 调用_addLiquidity函数,返回 TOken和ETH的最佳汇率
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
// 获取交易对地址
address pair = UniswapV2Library.pairFor(factory, token, WETH);
// 调用token的`transferFrom`函数,实现msg.sender向交易对pair转入amountToken代币
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
// 先把amountETH数量的ETH存入本合约
IWETH(WETH).deposit{value: amountETH}();
// 再将amount数量的WETH转入交易对中
assert(IWETH(WETH).transfer(pair, amountETH));
// 获取存入代币后uniswapv2中的流动性
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
// 如果还有剩余的ETH,将退还
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}

// **** REMOVE LIQUIDITY ****
// 移除流动性
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
// 获取交易对地址
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
// 将流动性代币从用户划转到 pair 合约
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
// 收到的流动性代币占全部代币比例
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
// 如果低于用户设定的最低预期(amountAMin/amountBMin),则回滚交易
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}

function removeLiquidityETH(
address token, //待移除流动性的代币地址
uint liquidity, // 要移除的流动性数量
uint amountTokenMin, // 用户愿意接受的最小代币数量,如果实际返回的代币数量小于该值,则函数会抛出异常
uint amountETHMin, // 用户愿意接受的最小ETH数量,如果实际返回的ETH数量小于该值,则函数会抛出异常
address to, // 代币和ETH将被发送到的目标地址
uint deadline // 操作的截止时间,必须在该时间之前完成操作,否则操作将被视为无效
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
// 调用 removeLiquidity,得到退还的 Token 和 ETH
// removeLiquidity中执行了 `burn`但是是将token和ETH转移到了addrss(this),说明还可以执行 safeTransfer 和 withdraw
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
// 向to转账(token)
TransferHelper.safeTransfer(token, to, amountToken);
// 将weth取出来,暂时存在addrss(this)
IWETH(WETH).withdraw(amountETH);
// 将address(this)中的WETH转到指定地址to
TransferHelper.safeTransferETH(to, amountETH);
}

// 其实就是在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}

// 在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}

// **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
/**
返回值没有 amountToken;
调用 removeLiquidity 后也没有 amountToken 值返回
进行 safeTransfer 时传值直接读取当前地址的 token 余额。
*/
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}

function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}

// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
/** 历整个兑换路径,并对路径中每两个配对的 token 调用 pair 合约的兑换函数,实现底层的兑换处理 */
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
// 如下语句是在判断要兑换的是哪种 货币,比如 A 或者 B
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}

/** 用 ERC20 兑换 ERC20,但支付的数量是指定的,而兑换回的数量则是未确定的 */
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
// 将支付的代币转到 pair 合约
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
/** 用 ERC20 兑换 ERC20,与上一个函数不同,指定的是兑换回的数量 */
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}

/** 指定 ETH 数量兑换 ERC20 */
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
/** 用 ERC20 兑换成指定数量的 ETH */
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
/** 用指定数量的 ERC20 兑换 ETH */
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
/** 用 ETH 兑换指定数量的 ERC20 */
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
// refund dust eth, if any
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}

// **** SWAP (supporting fee-on-transfer tokens) ****
// requires the initial amount to have already been sent to the first pair

function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
uint amountInput;
uint amountOutput;
{ // scope to avoid stack too deep errors
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
}
}

/** 指定数量的 ERC20 兑换 ERC20,支持转账时扣费 */
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
/** 指定数量的 ETH 兑换 ERC20,支持转账时扣费 */
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
payable
ensure(deadline)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
uint amountIn = msg.value;
IWETH(WETH).deposit{value: amountIn}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
/** 指定数量的 ETH 兑换 ERC20,支持转账时扣费 */
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}

// **** LIBRARY FUNCTIONS ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}

function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}

function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}

function getAmountsOut(uint amountIn, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}

function getAmountsIn(uint amountOut, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}

**解读 addLiquidity中的amountAMin 和 amountBMin **

该值一般是由前端根据预期值和滑点值计算得出的。比如,预期值 amountADesired 为 1000,设置的滑点为 0.5%,那就可以计算得出可接受的最小值 amountAMin 为 1000 * (1 - 0.5%) = 995。

addLiquidityETH

addLiquidityETH 则支付的其中一个 token 则是 ETH,而不是 ERC20 代币。来看看其代码实现:

image20210921212944219.png

可看到,入参不再是两个 token 地址,而只有一个 token 地址,因为另一个是以太坊主币 ETH。预期支付的 ETH 金额也是直接从 msg.value 读取的,所以入参里也不需要 ETH 的 Desired 参数。但是会定义 amountETHMin 表示愿意接受成交的 ETH 最小额。

实现逻辑上,请注意,调用 _addLiquidity 时传入的第二个参数是 WETH。其实,addLiquidityETH 实际上也是将 ETH 转为 WETH 进行处理的。可以看到代码中还有这么一行:

1
IWETH(WETH).deposit{value: amountETH}();

这就是将用户转入的 ETH 转成了 WETH。

而最后一行代码则会判断,如果一开始支付的 msg.value 大于实际需要支付的金额,多余的部分将返还给用户

Library

1. UniswapV2Library

image-20230718164550956

代码解读如下:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
pragma solidity >=0.5.0;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';

import "./SafeMath.sol";

library UniswapV2Library {
using SafeMath for uint;

// returns sorted token addresses, used to handle return values from pairs sorted in this order
// 给 tokenA和tokenB排序,按字面值排序
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}

// calculates the CREATE2 address for a pair without making any external calls
// 输入工厂地址和两个代币地址,计算这两个代币的交易对地址
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
// 先对这两个代币地址进行排序
(address token0, address token1) = sortTokens(tokenA, tokenB);
// 采用create2的方式计算地址
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
// UniswapV2Pair 合约的 creationCode 的哈希值
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}

// fetches and sorts the reserves for a pair
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
// 先让 tokenA 和 tokenB 从小到大排列
(address token0,) = sortTokens(tokenA, tokenB);

// 根据 `pairFor(factory, tokenA, tokenB)`算出一个新的地址
// .getReserves() 获取 该新地址的 reserve0,reserve1
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();

// 如果交换过顺序就交换输出,简单来说及时为了对应输入的形参
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}

// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
/**
数量为amountA的代币A,按照合约中两种代币余额比例,换算成另一个代币B。此时不考虑手续费,因为仅是计价单位的换算
【根据给定的两个 token 的储备量和其中一个 token 数量,计算得到另一个 token 等值的数值】
*/
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}


// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
/**
该方法计算:输入一定数量(amountIn)代币A,根据池子中代币余额,能得到多少数量(amountOut)代币B。
amountIn指输入的代币A,reserveIn 指代币A的存储量,reserveOut指代币B的存储量

【根据给定的两个 token 的储备量和输入的 token 数量,计算得到输出的 token 数量,该计算会扣减掉 0.3% 的手续费】
*/
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}

// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
/**
该方法计算当希望获得一定数量(amountOut)的代币B时,应该输入多少数量(amoutnIn)的代币A。
amountOut指要得到的代币B,reserveIn 指代币A的存储量,reserveOut指代币B的存储量
【根据给定的两个 token 的储备量和输出的 token 数量,计算得到输入的 token 数量,该计算会扣减掉 0.3% 的手续费】
*/
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}

// performs chained getAmountOut calculations on any number of pairs
/**
该方法用于计算在使用多个交易对时,输入一定数量(amountIn)的第一种代币,
最终能收到多少数量的最后一种代币(amounts)。amounts数组中的第一个元素表示amountIn,
最后一个元素表示该目标代币对应的数量。该方法实际上是循环调用getAmountIn方法。
【根据兑换路径和输入数量,计算得到兑换路径中每个交易对的输出数量】
*/
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
// 创建一个和path等长的uint数组
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}

// performs chained getAmountIn calculations on any number of pairs
/**
与getAmountsOut相对,getAmountsIn用于计算当希望收到一定数量(amountOut)的目标代币,
应该分别输入多少数量的中间代币。计算方法也是循环调用getAmountIn。
*/
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}


getAmountOut 的实现:

image20210921145914470.png

根据 AMM 的原理,恒定乘积公式「x * y = K」,兑换前后 K 值不变。因此,在不考虑交易手续费的情况下,以下公式会成立:

1
reserveIn * reserveOut = (reserveIn + amountIn) * (reserveOut - amountOut)

将公式右边的表达式展开,并推导下,就变成了:

1
2
3
4
5
reserveIn * reserveOut = reserveIn * reserveOut + amountIn * reserveOut - (reserveIn + amountIn) * amountOut
->
amountIn * reserveOut = (reserveIn + amountIn) * amountOut
->
amountOut = amountIn * reserveOut / (reserveIn + amountIn)

而实际上交易时,还需要扣减千分之三的交易手续费,所以实际上:

1
amountIn = amountIn * 997 / 1000

代入上面的公式后,最终结果就变成了:

1
2
3
4
5
amountOut = (amountIn * 997 / 1000) * reserverOut / (reserveIn + amountIn * 997 / 1000)
->
amountOut = amountIn * 997 * reserveOut / 1000 * (reserveIn + amountIn * 997 / 1000)
->
amountOut = amountIn * 997 * reserveOut / (reserveIn * 1000 + amountIn * 997)

这即是最后代码实现中的计算公式了。

解读getAmountsOut

根据兑换路径和输入数量,计算得到兑换路径中每个交易对的输出数量。

举例:

假如一个交易地址数组 path[A,B,C,D] ,其中

pair(A,B)中的tokenA=6,tokenB=9; pair(B,C)中的tokenA=10,tokenB=8; pair(C,D)中的tokenA=19,tokenB=84

则 amounts[0] = IN_tokenA,

pair(A,B) : amount[1] = getAmountOut(IN_tokenA, reserveA, reserveB),

pair(B,C): amount[2] = getAmountOut(IN_tokenA, reserveA, reserveB),

pair(C,D): amount[3] = getAmountOut(IN_tokenA, reserveA, reserveB).

该函数会计算 path 中每一个中间资产和最终资产的数量,比如 path 为 [A,B,C],则会先将 A 兑换成 B,再将 B 兑换成 C。返回值则是一个数组,第一个元素是 A 的数量,即 amountIn,而第二个元素则是兑换到的代币 B 的数量,最后一个元素则是最终要兑换得到的代币 C 的数量。

从代码中还可看到,每一次兑换其实都调用了 getAmountOut 函数,这也意味着每一次中间兑换都会扣减千分之三的交易手续费。那如果兑换两次,实际支付假设为 1000,那最终实际兑换得到的价值只剩下:

1
1000 * (1 - 0.003) * (1 - 0.003) = 994.009

即实际支付的交易手续费将近千分之六了。兑换路径越长,实际扣减的交易手续费会更多,所以兑换路径一般不宜过长。

getAmountsIn同理

参考链接

uniswap v2白皮书 链接1

uniswap v2代码 链接2

uiniswap v2代码core代码解读 链接3

登链社区

评论



政策 · 统计 | 本站使用 Volantis 主题设计