uniswap源码阅读

Seven 2022-02-17 15:45:01
Categories: > > Tags:

功能

V2

原理解析

v2

价格区间

价格:A/B

lp:A*B

资金利用率

源码

v2

UniswapV2Factory

代码
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
pragma solidity =0.5.16;

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

contract UniswapV2Factory is IUniswapV2Factory {
address public feeTo;
address public feeToSetter;

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;
}

function createPair(address tokenA, address tokenB) external returns (address pair) {
//验证两个地址ERC代币地址不能相同
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
//第二行用于将两个代币的合约地址从小到大排序,因为底层地址类型其实是uint160,所以有大小可以排序
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
//验证地址不能为0地址,为啥只验证一个,因为token1比token0大,验证一个即可
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
//创建的lp合约不能是0地址
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
//模板合约UniswapV2Pair的创建字节码creationCode。
//请注意,它返回的结果是一个字节数组,其中包含创建的字节码,类型为bytes。
//creationCode主要用于自定义内联汇编中的合约创建过程
//注意这个值不能在合约本身或者继承的合约中获取,因为这会导致自循环引用。
bytes memory bytecode = type(UniswapV2Pair).creationCode;
//它使用两个代币地址作为计算源,这意味着对于任何交易对,它都应该salt是一个固定值,并且可以离线计算。
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
//assembly表示这是一段嵌入的汇编代码,Solidity中嵌入的汇编语言是Yul语言。
//在 Yul 中,使用同名的内置函数,而不是直接使用操作码,这样更容易阅读。下面的左括号表示内联汇编范围的开始。
assembly {
//create2函数(函数名表示使用了 create2 操作码)来创建一个新合约
//add(bytecode, 32)。为什么会在bytecode上加32呢?
//因为刚才提到从bytecode开始的32字节存储的是creationCode的长度,从第二个32字节开始才是存的实际creationCode内容。
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
//设置getPair
getPair[token1][token0] = pair; // populate mapping in the reverse direction
//把合约添加到数组里面
allPairs.push(pair);
//调用事件
emit PairCreated(token0, token1, pair, allPairs.length);
}

//这个函数也很简单,用来设置新的feeTo以切换开发团队手续费开关(可以为开发团队接收手续费的地址,也可以为零地址)。
//注意,该函数首先使用require函数验证了调用者必须为feeTo的设置者feeToSetter,如果不是则会重置整个交易。
function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeTo = _feeTo;
}

//该函数用来转让feeToSetter。它首先判定调用者必须是原feeToSetter,否则重置整个交易。
//但这里有可能存在这么一种情况:当原feeToSetter不小心输错了新的设置者地址_feeToSetter时,设置会立即生效,
//此时feeToSetter为一个错误的或者陌生的无控制权的地址,无法再通过该函数设置回来。
//虽然UniswapV2团队不会存在这种疏忽,但是我们自己在使用时,还是有可能发生的。
//有一种方法可以解决这个问题,就是使用一个中间地址值过渡一下,而新的feeToSetter必须再调用一个接受方法才能真正成为设置者。
//如果在接受之前发现设置错误,原设置者可以重新设置。具体代码实现可以参考下面的Owned合约的owner转让实现:
function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeToSetter = _feeToSetter;
}
}

UniswapV2Pair

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
pragma solidity =0.5.16;

import './interfaces/IUniswapV2Pair.sol';
import './UniswapV2ERC20.sol';
//自定义的Math库,只有两个功能,一个是求两个uint的最小值,另一个是对一个uint进行开方运算。
import './libraries/Math.sol';
//导入自定义的数据格式库。在UniswapV2中,价格为两种代币的数量比值,而在Solidity中,对非整数类型支持不好,通常两个无符号整数相除为地板除,会截断。为了提高价格精度,UniswapV2使用uint112来保存交易对中资产的数量,而比值(价格)使用UQ112x112表示,一个代表整数部分,一个代表小数部分。
import './libraries/UQ112x112.sol';
//导入标准ERC20接口,在获取交易对合约资产池的代币数量(余额)时使用。
import './interfaces/IERC20.sol';
//导入factory合约相关接口,主要是用来获取开发团队手续费地址。
import './interfaces/IUniswapV2Factory.sol';
//有些第三方合约希望接收到代币后进行其它操作,好比异步执行中的回调函数。这里IUniswapV2Callee约定了第三方合约如果需要执行回调函数必须实现的接口格式。当然了,定义了此接口后还可以进行FlashSwap。
import './interfaces/IUniswapV2Callee.sol';

//继承一个合约表明它继承了父合约的所有非私有的接口与状态变量。
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
//指定库函数的应用类型
using SafeMath for uint;
using UQ112x112 for uint224;
//定义了最小流动性。它是最小数值1的1000倍,用来在提供初始流动性时燃烧掉。
uint public constant MINIMUM_LIQUIDITY = 10**3;
//来计算标准ERC20合约中转移代币函数transfer的函数选择器。虽然标准的ERC20合约在转移代币后返回一个成功值,但有些不标准的并没有返回值。在这个合约里统一做了处理,并使用了较低级的call函数代替正常的合约调用。函数选择器用于call函数调用中。
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));

//用来记录factory合约地址和交易对中两种代币的合约地址。注意它们是public的状态变量,意味着合约外可以直接使用同名函数获取对应的值。
address public factory;
address public token0;
address public 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

//这段代码是用来防重入攻击的,在modifier(函数修饰器)中,_;代表执行被修饰的函数体。所以这里的逻辑很好理解,当函数(外部接口)被外部调用时,unlocked设置为0,函数执行完之后才会重新设置为1。在未执行完之前,这时如果重入该函数,lock修饰器仍然会起作用。这时unlocked仍然为0,无法通过修饰器中的require检查,整个交易会被重置。当然这里也可以不用0和1,也可以使用布尔类型true和false。
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;
}

//使用call函数进行代币合约transfer的调用(使用了函数选择器)。注意,它检查了返回值(首先必须调用成功,然后无返回值或者返回值为true)。
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定义是方便客户端进行各种追踪的。
//疑问:我部署的合约,别人可以追踪event吗?
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);

//构造器,很简单,记录factory合约的地址。其实按照Solidity代码规范(建议),这里的构造器和它前面的四个event定义应该放在getReserves函数之前。
constructor() public {
factory = msg.sender;
}

//进行合约的初始化。在第一篇核心合约源码学习中提到,因为factory合约使用create2函数创建交易对合约,无法向构造器传递参数,所以这里写了一个初始化函数用来记录合约中两种代币的地址。
// 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);
}

//手续费计算,Uniswap开发团队为了避免这种情况的出现,将开发团队手续费累积起来,在改变流动性时才发送。
// 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();
feeOn = feeTo != address(0);
//第三行uint _kLast = kLast;
//使用一个局部变量记录过去某时刻的恒定乘积中的积的值。注释表明使用局部变量可以减少gas(估计是因为减少了状态变量操作)。
uint _kLast = kLast; // gas savings
//if(feeOn)语句,如果手续费开关打开,计算手续费的值(手续费以增发该交易对合约流动性代币的方式体现
if (feeOn) {
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);
}
}
} 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));
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);

bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity);

_update(balance0, balance1, _reserve0, _reserve1);
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)];

bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
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');
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
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
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
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));
}
//和skim函数刚好相反,强制保存的恒定乘积的资产数量为交易对合约中两种代币的实际余额,用于处理一些特殊情况。通常情况下,交易对中代币余额和保存的恒定乘积中的资产数量是相等的。
// force reserves to match balances
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}
_mintFee函数

在我的那篇《UniswapV2介绍》中提到,如果开发团队手续费打开后,用户每次交易手续费的1/6会分给开发团队,剩下的5/6才会发给流动性提供者。如果每次用户交易都计算并发送手续费,无疑会增加用户的gas。Uniswap开发团队为了避免这种情况的出现,将开发团队手续费累积起来,在改变流动性时才发送。_mintFee函数就是计算并发送开发团队手续费的。函数的参数为交易对中保存的恒定乘积中的两种代币的数值。

UniswapV2ERC20

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
pragma solidity =0.5.16;

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

contract UniswapV2ERC20 is IUniswapV2ERC20 {
using SafeMath for uint;

string public constant name = 'Uniswap V2';
string public constant symbol = 'UNI-V2';
uint8 public constant decimals = 18;
uint public totalSupply;
mapping(address => uint) public balanceOf;
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 := chainid
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}

function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}

function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
}

function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}

function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}

function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}

function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}

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;
}

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);
}
}

附言

参考资料