factory contract
工厂合约是所有已部署pair合约的注册表。我们不希望出现相同的pair,这样流动性就不会被分割成多个。factory合约还简化了pair合约的部署:无需手动部署pair合约,只需调用factory合约中的方法即可。
https://github.com/XuHugo/solidityproject/tree/master/uniswapv2xq
Uniswap 只部署了一个factory合约,该合约是 Uniswap 的官方注册机构。这对发现pair也很有用:人们可以通过token地址查询合约,找到pair。此外,还可以通过扫描合约的历史事件来查找所有已部署的pair。当然,我们也可以手动部署pair,而不在factory合约中注册。
contract ZuniswapV2Factory {error IdenticalAddresses();error PairExists();error ZeroAddress();event PairCreated(address indexed token0,address indexed token1,address pair,uint256);mapping(address => mapping(address => address)) public pairs;address[] public allPairs;...
工厂合约非常简洁明了:它只在创建pair时发出 PairCreated 事件,并存储所有已创建pair的列表和映射。
不过,创建pair很麻烦:
function createPair(address tokenA, address tokenB)publicreturns (address pair){if (tokenA == tokenB) revert IdenticalAddresses();(address token0, address token1) = tokenA < tokenB? (tokenA, tokenB): (tokenB, tokenA);if (token0 == address(0)) revert ZeroAddress();if (pairs[token0][token1] != address(0)) revert PairExists();bytes memory bytecode = type(ZuniswapV2Pair).creationCode;bytes32 salt = keccak256(abi.encodePacked(token0, token1));assembly {pair := create2(0, add(bytecode, 32), mload(bytecode), salt)}IZuniswapV2Pair(pair).initialize(token0, token1);pairs[token0][token1] = pair;pairs[token1][token0] = pair;allPairs.push(pair);emit PairCreated(token0, token1, pair, allPairs.length);}
首先,我们不允许pair使用相同的Token。请注意,我们并不检查Token合约是否真的存在,我们并不关心,因为这取决于用户是否提供有效的 ERC20Token地址。
接下来,我们对Token地址进行排序,这对于避免重复很重要(pair合约允许双向交换)。此外,token地址还用于生成pair地址,我们将在下一节中讨论。
使用create2 创建合约
在以太坊中,合约可以部署合约。人们可以调用已部署合约的函数,该函数将部署另一个合约,这使得部署合约变得更加容易。你不需要从电脑上编译和部署一个合同,你可以通过现有的合同来完成。
在 EVM 中,有两个操作码可以部署合约:
1、CREATE,从一开始就存在于 EVM 中。此操作码创建一个新账户,并在此地址部署合约代码。新地址是根据部署者合约的 nonce 计算出来的,这与手动部署合约时确定合约地址的方式相同。Nonce 是地址成功交易的计数器:发送交易时,nonce 会增加。生成新账户地址时对 nonce 的依赖使得 CREATE 具有非确定性:地址取决于部署者合约的 nonce,而这是无法控制的。你可以知道它,但当你部署你的合约时,nonce 可能已经不同了。
2、CREATE2 在 EIP-1014 中添加。该操作码的作用与 CREATE 完全相同,但它允许确定性地生成新合约地址。CREATE2 不使用外部状态(如其他合约的 nonce)来生成合约地址,并允许我们完全控制地址的生成方式。你不需要知道 nonce,只需要知道已部署的合约字节码(它是静态的)和盐(它是由你选择的字节序列)。
...bytes memory bytecode = type(ZuniswapV2Pair).creationCode;bytes32 salt = keccak256(abi.encodePacked(token0, token1));assembly {pair := create2(0, add(bytecode, 32), mload(bytecode), salt)}...
第一行是 ZuniswapV2Pair 合约的创建字节码。创建字节码是实际的智能合约字节码。它包括
1、构造逻辑。这部分负责智能合约的初始化和部署。它不存储在区块链上。
2、运行时字节码,即合约的实际业务逻辑。正是这些字节码存储在以太坊区块链上。
我们希望在此使用完整的字节码。
下一行创建salt,这是一个字节序列,用于确定性地生成新合约的地址。我们通过散列一对token的地址来创建salt,这意味着每一对独一无二的token都会产生一个独一无二的salt,每一对令牌都会有独一无二的salt和地址。
最后一行是我们调用 create2 的地方:
1、使用字节码 + 盐,确定性地创建一个新地址。
2、部署一个新的 ZuniswapV2Pair 合约。
3、获取该pair的地址。
// ZuniswapV2Pair.solfunction initialize(address token0_, address token1_) public {if (token0 != address(0) || token1 != address(0))revert AlreadyInitialized();token0 = token0_;token1 = token1_;}
createPair 的其余部分应该很清楚:
1、部署配对后,我们需要对其进行初始化,简单地说就是设置其标记:
2、然后,新的pair将存储在pairs和 allPairs 数组中。
3、最后,我们就可以发出 PairCreated 事件了。