0%

defi | uniswap v2 uniswap-v2-core 工厂合约

这里讲一下 uniswap v2 core 核心库的代码。


参考资料



代码解析


整体分析

core 核心主要有三个合约文件:

  • UniswapV2ERC20.sol
    • LP Token 合约
  • UniswapV2Pair.sol
    • 配对合约,部署一个 LP token
  • UniswapV2Factory.sol
    • 工厂合约

UniswapV2Factory.sol

创建 Pair

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createPair(address tokenA, address tokenB) external returns (address pair) {
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // populate mapping in the reverse direction
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}

催眠大师的中文注释版本。

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

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

//uniswap工厂
contract UniswapV2Factory is IUniswapV2Factory {
...
function createPair(address tokenA, address tokenB) external returns (address pair) {
//确认tokenA不等于tokenB
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
//将tokenA和tokenB进行大小排序,确保tokenA小于tokenB
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
//确认token0不等于0地址
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
//确认配对映射中不存在token0=>token1
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
//给bytecode变量赋值"UniswapV2Pair"合约的创建字节码
bytes memory bytecode = type(UniswapV2Pair).creationCode;
//将token0和token1打包后创建哈希
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
//内联汇编
//solium-disable-next-line
assembly {
//通过create2方法布署合约,并且加盐,返回地址到pair变量
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
//调用pair地址的合约中的"initialize"方法,传入变量token0,token1
IUniswapV2Pair(pair).initialize(token0, token1);
//配对映射中设置token0=>token1=pair
getPair[token0][token1] = pair;
//配对映射中设置token1=>token0=pair
getPair[token1][token0] = pair; // populate mapping in the reverse direction
//配对数组中推入pair地址
allPairs.push(pair);
//触发配对成功事件
emit PairCreated(token0, token1, pair, allPairs.length);
}
}

上面代码的重点在于

1
2
3
4
5
6
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);

上述代码可以参考

根据

里面创建合约采用了 create2,这是一个汇编 opcode,这是我要重点讲解的部分。

很多小伙伴应该都知道,一般创建新合约可以使用 new 关键字,比如,创建一个新配对合约,也可以这么写:

UniswapV2Pair newPair = new UniswapV2Pair();

那为什么不使用 new 的方式,而是调用 create2 操作码来新建合约呢?使用 create2 最大的好处其实在于:可以在部署智能合约前预先计算出合约的部署地址。最关键的就是以下这几行代码:

1
2
3
4
5
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}

第一行获取 UniswapV2Pair 合约代码的创建字节码 creationCode,结果值一般是这样:

0x0cf061edb29fff92bda250b607ac9973edf2282cff7477decd42a678e4f9b868

类似的,其实还有运行时的字节码 runtimeCode,但这里没有用到。

这个创建字节码其实会在 periphery 项目中的 UniswapV2Library 库中用到,是被硬编码设置的值。所以为了方便,可以在工厂合约中添加一行代码保存这个创建字节码:

bytes32 public constant INIT_CODE_PAIR_HASH = keccak256(abi.encodePacked(type(UniswapV2Pair).creationCode));

回到上面代码,第二行根据两个代币地址计算出一个盐值,对于任意币对,计算出的盐值也是固定的,所以也可以线下计算出该币对的盐值。

接着就用 assembly 关键字包起一段内嵌汇编代码,里面调用 create2 操作码来创建新合约。因为 UniswapV2Pair 合约的创建字节码是固定的,两个币对的盐值也是固定的,所以最终计算出来的 pair 地址其实也是固定的。

接下来,我们将在 bsc testnet 网络中实验一下。

首先根据,下面的博客部署下面的合约。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity >=0.4.21 <0.8.0;


contract Hello {
string public hello = "Hello World";
}

contract ERC20PreContract {

address public contract_address;

function createPair() external returns (address pair) {
bytes memory bytecode = type(Hello).creationCode;
bytes32 salt = keccak256(abi.encodePacked(0x74d50FC654742e1B08c23B6B4De8BF471D426008, 0xD1B260aba3bA6f3D3099725b1b1C7cdD12581974));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
contract_address = pair;
}
}

我们使用 web3.py 进行链上互动。

部署上面的合约,我们得到合约地址 0xFc33320759b0B96EFe841D0ea090772715915AfF

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
from web3 import Web3

abi = """
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "contract_address",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function",
"constant": true
},
{
"inputs": [],
"name": "createPair",
"outputs": [
{
"internalType": "address",
"name": "pair",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
"""

abi2 = """
[
{
"inputs": [],
"name": "hello",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]
"""

wallet_address = ""
wallet_private = ""

w3 = Web3(Web3.HTTPProvider("https://bsc.getblock.io/testnet/?api_key=****"))


def get_pair():
contract_coin = w3.eth.contract(
address=Web3.toChecksumAddress("0xFc33320759b0B96EFe841D0ea090772715915AfF"), abi=abi)
print(contract_coin.functions.contract_address().call())


def create():
contract_coin = w3.eth.contract(
address=Web3.toChecksumAddress("0xFc33320759b0B96EFe841D0ea090772715915AfF"), abi=abi)
tx_dic = contract_coin.functions.createPair().buildTransaction(
{
'gas': 800000,
'gasPrice': w3.eth.gasPrice,
'nonce': w3.eth.getTransactionCount(wallet_address),
})
sign_tx = w3.eth.account.signTransaction(tx_dic, wallet_private)
txn_hash = w3.eth.sendRawTransaction(sign_tx.rawTransaction)
print(Web3.toHex(txn_hash))


def get_hello():
contract_coin = w3.eth.contract(
address=Web3.toChecksumAddress("0xB5DCf739515fd938b23d514E50F3C9317260d954"), abi=abi2)
print(contract_coin.functions.hello().call())

先调用 get_pair() 输出 0x00;

再调用 create()

然后再调用 get_pair() 输出 0xB5DCf739515fd938b23d514E50F3C9317260d954

然后,我们调用 get_hello() 发现输出了 hello world

请我喝杯咖啡吧~