这里讲一下 uniswap v2 core
核心库的代码。
参考资料
代码解析
整体分析
core
核心主要有三个合约文件:
UniswapV2ERC20.sol
UniswapV2Pair.sol
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') 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 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';
contract UniswapV2Factory is IUniswapV2Factory { ... 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'); 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; 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
。