• Uniswap 是一个去中心化加密货币交易平台,作为“链上市场”运行,支持用户在以太坊及超过 10 个其他区块链上买卖加密货币。

  • 交易者无需依赖中间机构或中心化实体,即可在 Uniswap 上自由兑换数千种不同的代币。

  • 用户还可以在 Uniswap 流动性资金池中提供流动性,从其他用户的兑换手续费中赚取收益。

中心化交易平台 (CEX)
去中心化交易平台 (DEX)

什么是Uniswap?

Uniswap 是一个 DEX,使用户无需依赖中心化机构或中间机构,即可进行加密货币交易。最初,Uniswap 仅支持以太坊网络,随后扩展至多个区块链平台。
Uniswap 的服务基于智能合约,这些合约是区块链上自动执行的程序,预设条件直接写入代码中。

Uniswap V2是一种基于以太坊的去中心化交易协议,旨在提供快速、安全、无信任的代币交换服务。它是Uniswap协议的第二个版本,是对第一个版本的改进和升级。

Uniswap V2的核心特点包括以下几个方面:

  1. 去中心化交易:Uniswap V2使用智能合约来执行交易,而不需要传统的中心化交易所。这意味着用户可以直接通过他们的以太坊钱包进行交易,无需信任或依赖第三方中介。

  2. 自动化做市商模型:Uniswap V2采用自动化做市商模型,其中流动性提供者可以将资金存入流动性池中,并通过提供资金来帮助形成交易对的市场价格。这种模型使得任何人都可以成为流动性提供者,并从交易手续费中获得奖励。

  3. 常量乘积函数:Uniswap V2使用常量乘积函数作为交易价格计算模型。根据这个函数,交易所需的两种代币的数量乘积在交易前后保持不变,从而决定了交易价格。这种机制可以在没有订单簿的情况下进行交易,并保持相对简单和高效。

  4. ERC-2代币0支持:Uniswap V2支持以太坊上的ERC-20代币进行交易。用户可以通过选择不同的代币对进行交易,并且任何人都可以创建新的代币对,只需提供相应的流动性即可。

  5. 流动性挖矿:Uniswap V2引入了流动性挖矿机制,通过奖励流动性提供者来吸引更多的资金注入流动性池。流动性提供者可以获得代币奖励作为对其提供流动性的补偿,如果市场价格剧烈波动,您存入的资金可能面临损失。

UniswapV2REC20.sol

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
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;
    // DOMAIN_SEPARATOR 包含了本条链的 chainId, 当前合约名称, 版本, 合约地址等信息
    bytes32 public DOMAIN_SEPARATOR;
    // PERMIT_TYPEHASH 的值是通过对 permit 函数的参数进行哈希计算而得到的固定值,用于验证 permit 函数的调用
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
    // DOMAIN_SEPARATOR和PERMIT_TYPEHASH被添加到签名信息中, 目的是让这个签名只能用于本条链, 本合约, 本功能(Permit)使用, 从而避免这个签名被拿到其他合约或者其他链的合约实施重放攻击
    // 某个地址的nonce值
    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)
            )
        );
    }

    /**
     * @dev: 铸造UNI-V2token函数
     * @param {address} to:接受token地址
     * @param {uint} value:接受token数量
     */
    function _mint(address to, uint value) internal {
        totalSupply = totalSupply.add(value);
        balanceOf[to] = balanceOf[to].add(value);
        emit Transfer(address(0), to, value);
    }

    /**
     * @dev: 销毁UNI-V2token函数
     * @param {address} from:销毁token地址
     * @param {uint} value:销毁token数量
     */

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

    /**
     * @dev: 授权某个地址能使用某个地址token函数
     * @param {address} owner:授权地址
     * @param {address} spender:被授权地址
     * @param {uint} value:授权token数量
     */
    function _approve(address owner, address spender, uint value) private {
        allowance[owner][spender] = value;
        emit Approval(owner, spender, value);
    }

    /**
     * @dev: 转账token函数
     * @param {address} from:进行转账的地址
     * @param {address} to:接受转账的地址
     * @param {uint} 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);
    }

    /**
     * @dev: 授权某个地址能使用msg.sender地址token函数
     * @param {address} spender:被授权地址
     * @param {uint} value:授权token数量

     */

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

    }

    /**

     * @dev: 转账token函数
     * @param {address} to:接受转账的地址
     * @param {uint} value:转账的数量
     * @return {bool}:是否转账成功

     */

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

    /**

     * @dev: 已被授权地址转账token函数

     * @param {address} from:进行转账的地址

     * @param {address} to:接受转账的地址

     * @param {uint} value:转账的数量

     * @return {bool}: 是否转账成功

     */

    function transferFrom(address from, address to, uint value) external returns (bool) {
        // 判断是否已经被授权 与-1判断是为了节省gas费,-1表示无限授权(uint256.max)
        if (allowance[from][msg.sender] != uint(-1)) {
            // 先对授权金额进行减少,其中若授权金额小于转账金额会require失败
          allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
        }
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev: 判断签名的有效性
     * @param {address} owner:授权地址
     * @param {address} spender:被授权地址
     * @param {uint} value:授权金额
     * @param {uint} deadline:签名有效时间内的时间戳
     * @param {uint8} v:原始签名的v
     * @param {bytes32} r:原始签名的r
     * @param {bytes32} s:原始签名的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))
            )
        );
        // ecrecover 函数可以返回与签名者对应的公钥地址
        address recoveredAddress = ecrecover(digest, v, r, s);
        // 判断签名者对应的公钥地址与授权地址是否一致
        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
        _approve(owner, spender, value);
    }
}

这个合约它是一个 ERC20 代币合约 + permit签名授权功能
Uniswap V2 用它来表示“流动性凭证(LP Token)
基础的ERC20就是负责最核心的余额,转账,还有授权
对应:

1
2
3
totalSupply // 总供应量  
balanceOf // 每个人的钱
allowance // 授权

还有对应关键的函数
但是用了permit签名授权功能就可以节省一次交易,也就是节省一次手续费,少花一次gas

现在来看permit部分

传统的授权是需要gas的

    /**
     * @dev: 判断签名的有效性
     * @param {address} owner:授权地址
     * @param {address} spender:被授权地址
     * @param {uint} value:授权金额
     * @param {uint} deadline:签名有效时间内的时间戳
     * @param {uint8} v:原始签名的v
     * @param {bytes32} r:原始签名的r
     * @param {bytes32} s:原始签名的s
     */
注释是这样说的,转换成人话就是:

参数 含义
owner 谁的钱
spender 谁可以用
value 能用多少
deadline 过期时间
v r s 签名

就是:owner 签字:允许 spender 用 value 钱

require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');这个是防止签名一直有效,判断是否在有效时间内

nonces[owner]++这里是防止签名被重复使用,如果没有这个nonces的话,就可以通过一个签名一直调用,钱直接没了

ecrecover 函数就是从签名反推出签名者地址
DOMAIN_SEPARATOR 防止“跨链 / 跨合约重放攻击” ,有了它之后签名就只能在当前链当前合约生效

SafeMath.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pragma solidity =0.5.16;

// 一个用于执行溢出安全数学的库,由DappHub提供(https://github.com/dapphub/ds-math)

library SafeMath {
    function add(uint x, uint y) internal pure returns (uint z) {
        require((z = x + y) >= x, 'ds-math-add-overflow');
    }

    function sub(uint x, uint y) internal pure returns (uint z) {
        require((z = x - y) <= x, 'ds-math-sub-underflow');
    }

    function mul(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
    }
}
1
add(uint x, uint y)

讲一下为什么要防止溢出
它这里$z = x +y>= x$
如果$x$= max_uint,$y = 1$的话, 那么$z = x + y = 0 不>= x$
所以可以防止溢出,减法同理可知

1
2
3
    function mul(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
    }

核心就是 $$(x +y)/y==x$$
$$z = x * y$$
1)
当y = 0 的时候$x * 0 = 0$,直接就通过了
2)
当$x * y$ = overflowed value, 再除一下$y$ !=$x$

Math.sol

用于执行各种数学运算的库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pragma solidity =0.5.16;
// 用于执行各种数学运算的库

library Math {
    // 返回两个uint的较小值
    function min(uint x, uint y) internal pure returns (uint z) {
        z = x < y ? x : y;
    }

    // 计算给定参数 y 的平方根
    function sqrt(uint y) internal pure returns (uint z) {
        if (y > 3) {
            z = y;
            uint x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
}

理解起来很简单

UQ112x112.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pragma solidity =0.5.16;

// 一个处理二进制固定点数的库(https://en.wikipedia.org/wiki/Q_(number_format))

// range: [0, 2**112 - 1]
// resolution: 1 / 2**112

library UQ112x112 {
    uint224 constant Q112 = 2**112;
    // 将uint112编码为UQ112x112
    function encode(uint112 y) internal pure returns (uint224 z) {
        z = uint224(y) * Q112; // never overflows
    }

    // UQ112x112除以uint112,返回UQ112x112
    function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
        z = x / uint224(y);
    }
}

在 Solidity 0.5中 $1/3 =0$ 没有浮点数
那么这个就是把小数放大 2^112 倍

即:
存储值 = 真实值 × 放大倍数($2^{112}$)
真实值 = 存储值 ÷ 放大倍数($2^{112}$)

uint224 = 112整数 + 112小数

假如我现在要存入1.5
那么存储值 = $1.5 * 2^{112}$ = $2^{112}+2^{111}$
所以后112位都是小数,前112位存的就是整数

再比如存入1.6

1.6 = 16 / 10 = 8 / 5
存储值 = $1.6 * 2^{112} = (8 / 5) * 2^{112}= (8 × 2^{112}) / 5$
先乘,再除(避免精度丢失)结果如下:
$$z = (8 × 2^{112}) / 5$$
然后还原就是:
$$[(8 × 2^{112}) / 5] ÷ 2^{112}=0.6$$
UQ112x112 不是在“算小数”,而是在“假装有小数”

UniswapV2Factory.sol

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;
    // 设置feeTo的权限者地址
    address public feeToSetter;
    // 两种token对应的交易对地址
    mapping(address => mapping(address => address)) public getPair;
    // 所有的交易对地址
    address[] public allPairs;
    // 定义交易对创建事件,返回参数tokenA地址,tokenB地址,pair地址,allPairs长度(第几个交易对)
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);
    // 设置feeTo的权限者地址
    constructor(address _feeToSetter) public {
        feeToSetter = _feeToSetter;
    }
    // 所有交易对的数量
    function allPairsLength() external view returns (uint) {
        return allPairs.length;
    }
    /**
     * @dev: 创建tokenA和tokenB的交易对并获得pair地址
     * @param {address} tokenA:tokenA地址
     * @param {address} tokenB:tokenB地址
     * @return {address} 返回对应的pair地址
     */
    function createPair(address tokenA, address tokenB) external returns (address pair) {
        // 判断两个token是否一样
        require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
        // tokenA和tokenB中的谁的地址小,谁是token0,大的是token1
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        // 判断两个token是否一样
        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
        // 判断是否已经有这两种token的交易对
        require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS');
        // type(x).creationCode 获得包含x的合约的bytecode,是bytes类型(不能在合同本身或继承的合约中使用,因为会引起循环引用)
        bytes memory bytecode = type(UniswapV2Pair).creationCode;
        // 两个地址是确定值,salt可以通过链下计算
        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        assembly {
            /**
             * @dev: create2方法 - 在已知交易对及salt的情况下创建一个新的交易对,返回新的交易对地址(针对此算法可以提前知道交易对的地址)
             * @notice create2(V, P, N, S) - V: 发送V数量wei以太,P: 起始内存地址,N: bytecode长度,S: salt
             * @param {uint}:指创建合约后向合约发送x数量wei的以太币
             * @param {bytes} add(bytecode, 32):opcode的add方法,将bytecode偏移后32位字节处,因为前32位字节存的是bytecode长度
             * @param {bytes} mload(bytecode):opcode的方法,获得bytecode长度
             * @param {bytes} salt: 盐值
             * @return {address}:返回新的交易对地址
             */
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        // 设置pair地址交易对的两种token
        IUniswapV2Pair(pair).initialize(token0, token1);
        // 将token0和token1的交易对地址设置到mapping中(0和1的双向交易对)
        getPair[token0][token1] = pair;
        getPair[token1][token0] = pair;
        allPairs.push(pair);
        emit PairCreated(token0, token1, pair, allPairs.length);
    }

    // 设置收取手续费的地址
    function setFeeTo(address _feeTo) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeTo = _feeTo;
    }
    // 更改设置feeTo的权限者地址
    function setFeeToSetter(address _feeToSetter) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeToSetter = _feeToSetter;
    }
}

Factory = Uniswap 的“工厂”,专门负责创建 Pair(交易对)

UniswapV2Pair.sol

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
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;
    bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
    //对应的UniswapV2Factory合约地址
    address public factory;
    // 交易对中的两个token地址
    address public token0;
    address public token1;
    // 两个token在交易对中的储备量
    uint112 private reserve0;
    uint112 private reserve1;
    // 相对于上一次更新储备量的时间间隔
    uint32 private blockTimestampLast;
    // 价格累计值
    uint public price0CumulativeLast;
    uint public price1CumulativeLast;
    // 常量乘积模型的k值
    uint public kLast;
    uint private unlocked = 1;
    // 防重入锁
    modifier lock() {
        require(unlocked == 1, 'UniswapV2: LOCKED');
        unlocked = 0;
        _;
        unlocked = 1;
    }

    // 获取token在交易对中的储备量和相对于上一次更新储备量的时间间隔
    function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
        _reserve0 = reserve0;
        _reserve1 = reserve1;
        _blockTimestampLast = blockTimestampLast;
    }

    /**
     * @dev: 转账token函数
     * @param {address} token:进行转账的token地址
     * @param {address} to:接受转账的地址
     * @param {uint} uint:转账的数量
     */
    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;
    }

    // 在部署时由工厂调用一次
    function initialize(address _token0, address _token1) external {
        require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
        token0 = _token0;
        token1 = _token1;
    }

    /**
     * @dev: 更新储备,并在每个区块的第一次调用时更新价格累加器
     * @param {uint} balance0:更新后tokenA的储备量
     * @param {uint} balance1:更新后tokenA的储备量
     * @param {uint112} _reserve0:当前tokenA的储备量
     * @param {uint112} _reserve1:当前tokenB的储备量
     */

    function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
        require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
        // 取时间戳的低 32 位
        uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
        // 时间间隔
        uint32 timeElapsed = blockTimestamp - blockTimestampLast;
        if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
            // 永远不会溢出,+ overflow是理想的
            // priceCumulativeLast += ((_reserve1 * 2 ** 112 ) / _reserve0 ) * timeElapsed
            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);
    }
    /**
     * @dev: 如果打开收费功能,就约等于1/6的增长的根号(k)
     * @param {uint112} _reserve0:tokenA的储备量
     * @param {uint112} _reserve1:tokenB的储备量
     * @return {bool} feeOn: 返回是否接受手续费
     */
    function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
        // 获取收取手续费的地址
        address feeTo = IUniswapV2Factory(factory).feeTo();
        feeOn = feeTo != address(0);
        // 节省gas
        uint _kLast = kLast;
        if (feeOn) {

            if (_kLast != 0) {
                // rootk=sqrt(_reserve0 * _reserve1)
                uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
                // 上一次交易后的sqrt(k)值
                uint rootKLast = Math.sqrt(_kLast);
                if (rootK > rootKLast) {
                    // 分子(lptoken总量*(rootK-rootKLast))
                    uint numerator = totalSupply.mul(rootK.sub(rootKLast));
                    // 分母(rooL*5+rooKLast)
                    uint denominator = rootK.mul(5).add(rootKLast);
                    // liquidity = ( totalSupply * ( sqrt(_reserve0 * _reserve1) -  sqrt(_kLast) ) ) / sqrt(_reserve0 * _reserve1) * 5 + sqrt(_kLast)
                    uint liquidity = numerator / denominator;
                    if (liquidity > 0) _mint(feeTo, liquidity);
                }
            }
        } else if (_kLast != 0) {
            kLast = 0;
        }
    }

    /**
     * @dev: 铸造lptoken
     * @param {address} to:接受lptoken的地址
     * @return {uint} liquidity: lptoken的数量
     */
    function mint(address to) external lock returns (uint liquidity) {
        //  节省gas
        (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
        // 获取进行添加流动性的两个token的数量
        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);
        // 节省gas,必须在这里定义,因为totalSupply可以在_mintFee中更新
        uint _totalSupply = totalSupply;
        // 创建一个新的流动性池
        if (_totalSupply == 0) {
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
            // 永久锁定MINIMUM_LIQUIDITY
            _mint(address(0), MINIMUM_LIQUIDITY);
        } else {
            // 添加流动性所获得的lptoken数量(进行添加流动性的两种token的数量*目前lptoken的数量/当前token的储备量-->取较小值)
            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
        }
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        // 铸造lptoken函数
        _mint(to, liquidity);
        // 更新储备函数
        _update(balance0, balance1, _reserve0, _reserve1);
        // 如果收取手续费,更新交易后的k值
        if (feeOn) kLast = uint(reserve0).mul(reserve1);
        emit Mint(msg.sender, amount0, amount1);
    }

    /**
     * @dev: 销毁lptoken退出流动性
     * @param {address} to:接受交易对返回token的地址
     * @return {uint} amount0: 返回获得的tokenA的数量
     * @return {uint} amount1: 返回获得的tokenB的数量
     */
    function burn(address to) external lock returns (uint amount0, uint amount1) {
        // 节省gas
        (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
        address _token0 = token0;
        address _token1 = token1;
        uint balance0 = IERC20(_token0).balanceOf(address(this));
        uint balance1 = IERC20(_token1).balanceOf(address(this));
        // 为什么用addres(this)?-->因为获取退出lptoken数量时,是在route合约中先将lptoken转到当前合约,然后直接获得当前合约lptoken的数量
        uint liquidity = balanceOf[address(this)];
        // 收取手续费
        bool feeOn = _mintFee(_reserve0, _reserve1);
        // 节省gas,必须在这里定义,因为totalSupply可以在_mintFee中更新
        uint _totalSupply = totalSupply;
        // 使用余额确保按比例分配-->(持有lptoken/总lptoken)*合约中持有token的数量
        amount0 = liquidity.mul(balance0) / _totalSupply;
        amount1 = liquidity.mul(balance1) / _totalSupply;
        require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
        _burn(address(this), liquidity);
        // 转账两种token
        _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);
         // 如果收取手续费,更新交易后的k值
        if (feeOn) kLast = uint(reserve0).mul(reserve1);
        emit Burn(msg.sender, amount0, amount1, to);
    }
    /**

     * @dev: 根据tokenA的数量在交易池中进行交换tokenB
     * @param {uint} amount0Out:to地址接受tokenA的数量
     * @param {uint} amount1Out:to地址接受tokenB的数量
     * @param {address} to:接受token交换的地址
     * @param {bytes} data:是否进行回调其他方法
     */

    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
        require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
        // 获取token在交易对中的储备量,节省gas
        (uint112 _reserve0, uint112 _reserve1, ) = getReserves();
        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
        uint balance0;
        uint balance1;
        {

            // _token{0,1}的作用域,避免堆栈过深的错误
            address _token0 = token0;
            address _token1 = token1;
            require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
            // 转移代币
            if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out);
            if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out);
            // 用于回调合约来实现一些特定的业务逻辑或其他自定义功能(闪电贷....)
            if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
            // 合约拥有两种token的数量
            balance0 = IERC20(_token0).balanceOf(address(this));
            balance1 = IERC20(_token1).balanceOf(address(this));
        }
        // 进行兑换的token量
        // 获得合约两种token的数量,前提是(balance > _reserve - amountOut),就是当前合约拥有的token数量应该是大于(储备值-输出到to地址的值),返回之间的差值
        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');
        {
            // Adjusted{0,1}的作用域,避免堆栈过深的错误
            // balanceAdjusted = balance * 1000 - amountIn * 3(确保在计算余额调整后的值时不会因为小数精度问题而导致错误)
            uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
            uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
            // 确保在交易完成后,资金池的储备量满足 Uniswap V2 中的 K 恒定公式,即 K = _reserve0 * _reserve1
            require(
                // balance0Adjusted * balance1Adjusted >= _reserve0 * _reserve0 * 1000 ** 2
                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);
    }

    /**
     * @dev: 使两个token的余额与储备相等
     * @param {address} to:接受两个token的余额与储备之间差值的地址
     */
    function skim(address to) external lock {
        // 节省汽油
        address _token0 = token0;
        address _token1 = token1;
        _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
        _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
    }
    /**
     * @dev: 使两个token的储备与余额相匹配
     */
    function sync() external lock {
        _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
    }
}

合约是干什么的?

就是存放两种代币,即(token1 and token2 ),用户可以用其中一种代币兑换另一种,用户可以提供流动性和撤回流动性,而且还可以自己计算价格,保证reserve0 * reserve1 = k

具体分析

1
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {

它继承了 UniswapV2ERC20,也就是 LP Token 的 ERC20 实现,这个Pair合约本身就是一个代币合约吧,用户添加流动性然后得到的就是这个合约的代币也就是(LP Token)

1
uint public constant MINIMUM_LIQUIDITY = 10 ** 3;

最小流动性:1000。第一次添加流动性时会永久锁定 1000 个 LP Token,防止有人通过极小份额操纵价格。

怎么理解呢

假设 没有 最小流动性限制,那么第一个流动性提供者可以只添加 极少的两种代币,比如:
添加 0.000001 个 tokenA 和 0.000001 个 tokenB
那么池子的 reserve0 = 0.000001reserve1 = 0.000001k = 10⁻¹²
Solidity 里整数运算,很多代币的精度是 18 位小数。如果储备量太小,后续任何计算(尤其是 sqrt(amount0 * amount1))都可能出现 四舍五入为 0的情况,导致无法继续添加流动性或兑换。

或者攻击者只需要很少的资金就能大幅度的改变价格,比如用 0.000001 个 tokenB 换走池子里几乎全部的 tokenA,导致价格剧烈波动,然后用这个错误价格去攻击依赖该池子的其它合约

也是防止第一个人 withdraw 把池子抽干

1
2
3
4
5
6
    modifier lock() {
        require(unlocked == 1, 'UniswapV2: LOCKED');
        unlocked = 0;
        _;
        unlocked = 1;
    }

所有改变状态的关键函数(mint, burn, swap, skim, sync)都加了 lock,防止在函数执行过程中被再次调用(比如通过恶意回调攻击)。

1
2
3
4
5
6
7
    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');

    }

使用底层的 call 来调用代币的 transfer 函数。

为什么不用 IERC20.transfer?

因为有些token不返回bool或者返回奇怪的数据,需要用底层的兼容处理

1
2
3
4
5
6
7
8
9
    function initialize(address _token0, address _token1) external {

        require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check

        token0 = _token0;

        token1 = _token1;

    }

可能会想为什么不这样:

1
constructor(address _token0, address _token1)

因为Pair 是 factory 用 create2 创建的,所以不是 constructor 设置 token
只有 Factory 可以调用一次,之后就不能改了。

再来看_update这一部分

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
    function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {

        require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');

        // 取时间戳的低 32 位

        uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);

        // 时间间隔

        uint32 timeElapsed = blockTimestamp - blockTimestampLast;

        if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {

            // 永远不会溢出,+ overflow是理想的

            // priceCumulativeLast += ((_reserve1 * 2 ** 112 ) / _reserve0 ) * timeElapsed

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

    }

先检查是否溢出,reserve 是 uint112
如果余额太大,强制转换为 uint112 时会截断

1
uint32 blockTimestamp = uint32(block.timestamp % 2**32);

截取低32位,因为这里uint32最大可表示约 136 年,完全够了,这样可以节约存储空间,
然后计算时间间隔
由于时间戳单调递增,并且 uint32 每 136 年会自然溢出一次,但这个减法在 Solidity 中依然是安全的,因为即使发生溢出,timeElapsed 也会因为模 2^32 下的差值得到正确的时间差
但是基本上不会出现问题

mint (添加流动性) burn (撤回流动性) swap(兑换)

上述代码的逻辑性还是很清楚的,就是铸造LP Token ,销毁,兑换代币(或闪电贷)

1
2
3
4
5
6
7
8
9
10
11
12
13
    function skim(address to) external lock {

        // 节省汽油

        address _token0 = token0;

        address _token1 = token1;

        _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));

        _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));

    }

如果有人不小心(或恶意)直接向 Pair 合约转代币而不通过 swap/mint,会导致 balanceOf(this) 大于 reserve, 提取多余代币(balance > reserve)

1
2
3
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}

用 sync 强制让储备与当前余额同步。