# CREATE2 操作码使用方法详解

By [xyyme.eth](https://paragraph.com/@xyyme) · 2022-03-27

---

CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么：

![](https://storage.googleapis.com/papyrus_images/0e9f098fc0b04e14c5736cc0f81132dc2b6ccaff915a1996f8ac5a22b1edf97c.png)

这段代码是 Uniswap v2-core 里面的工厂合约[代码](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol)，使用 create2 操作码创建了 pair 合约，返回值是 pair 的地址，这样就可以逻辑中直接使用其地址进行接下来的操作。

那么 create2 到底是怎么使用呢，根据官方 [EIP 文档](https://eips.ethereum.org/EIPS/eip-1014)，create2 一共接收四个参数，分别是：

1.  endowment（创建合约时往合约中打的 ETH 数量）
    
2.  memory\_start（代码在内存中的起始位置，一般固定为 **add(bytecode, 0x20)** ）
    
3.  memory\_length（代码长度，一般固定为 **mload(bytecode)** ）
    
4.  salt（随机数盐）
    

这里要注意的是第一个参数如果大于 0 的话，需要待部署合约的构造方法带有 **payable**。随机数盐是由用户自定，须为 **bytes32** 格式，例如在上面 Uniswap 的例子中，salt 为：

    bytes32 salt = keccak256(abi.encodePacked(token0, token1));
    

create2 还有一个优点，相较于以前的 new 创建合约方法，可以在合约未创建之前就能够计算出合约的地址。我们之前在使用 new 创建新合约的时候，必须在取到合约对象之后，再取其 address 才能获取地址。而使用 create2，就可以这样提前计算出地址（[参考](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Create2.sol)）：

    // salt 为部署合约时使用的随机数盐
    // bytecode 为合约的字节码哈希（keccak256)
    // deployer 为部署合约的地址（在A合约中部署B合约，则此处为A）
    function computeAddress(
        bytes32 salt,
        bytes32 bytecodeHash,
        address deployer
    ) internal pure returns (address) {
        bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash));
        return address(uint160(uint256(_data)));
    }
    

能够提前计算出合约地址这一点，就会给我们许多想象力。比如我们可以让合约地址变成靓号（例如前缀地址是 0x000，0x666，0x888），原因就是 salt 是我们自己定义的，那么就可以像 POW 挖矿那样，不断找寻随机数，以达到目的。这里我们就不再对这个话题过多深入，感兴趣的同学可以看看 [这里](https://hackernoon.com/using-ethereums-create2-nw2137q7) 或者 [这里](https://www.liaoxuefeng.com/article/1430588932227106)。

接下来我们就尝试一下使用 create2 部署合约，方便的是，[OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Create2.sol) 库中就有现成的模版可以供我们使用，先来看看它是怎么实现的：

![](https://storage.googleapis.com/papyrus_images/86479ef4422c73705e784b572bb34bbe2bfb9758c4eadb2ddf33093474769f34.png)

注意到注释中对于参数的要求：

1.  bytecode 必须非空
    
2.  对于相同的 bytecode，salt 不能重复（否则就计算出重复的地址）
    
3.  部署合约中必须有 amount 数量的 ETH（如果 amount 大于 0 的话）
    
4.  如果 amount 非 0，则待部署合约必须有 payable 修饰符
    

逻辑还是比较简单的，我们在使用的时候直接套用模版就行，来实操一下：

    pragma solidity 0.8.10;
    
    import "@openzeppelin/contracts/utils/Create2.sol";
    
    contract Factory {
        event Deployed(address addr);
    
        // 计算合约地址的方法
        function getAddress() public view returns (address) {
            return
                Create2.computeAddress(
                    keccak256("Here is salt"),
                    keccak256(
                        abi.encodePacked(type(Template).creationCode, abi.encode(3))
                    )
                );
        }
    
        // 部署合约
        function deploy() public {
            address addr = Create2.deploy(
                0,
                keccak256("Here is salt"),
                abi.encodePacked(type(Template).creationCode, abi.encode(3))
            );
            emit Deployed(addr);
        }
    }
    
    contract Template {
        uint256 public a;
    
        constructor(uint256 _a) {
            a = _a;
        }
    }
    

我们使用 **keccak256("Here is salt")** 为盐，当然这里可以使用任何 bytes32 类型的数据。

有一点需要注意的是，对于构造函数有参数的情况，需要将参数编码并拼接在合约字节码后面作为完整的字节码传入：

    abi.encodePacked(type(Template).creationCode, abi.encode(3))
    

我们部署一下，先调用 getAddress 计算合约地址：

![](https://storage.googleapis.com/papyrus_images/67c3e8ad4058aa88807032d35e2d42687ba705704860a1447815743215654bfe.png)

然后再调用 deploy 部署合约，在事件中查看部署的地址为：

![](https://storage.googleapis.com/papyrus_images/de0760d594b6b1abf62c6e8b28f21746b867f44c930e9416dad54258c2e8e905.png)

验证地址确实相同。

这里还有一点需要说一下的是，如果要在 EtherScan 中上传代码，是需要将上面的所有合约，也就是 Factory 和 Template，包括 import 的合约都需要上传，仅仅上传 Template 是无法成功的，这里当时卡了我很长时间，最后试了试全部粘贴才能成功。

由于对于相同的合约、参数、盐，create2 计算所得的合约地址都是相同的，因此我们就可以通过 create2 与 selfdestruct 相结合，在同一个地址上多次部署合约。我在[这篇文章](https://mirror.xyz/xyyme.eth/PmYCLYIF-pu2kcccHSNBkziehHlWdilmlPfEVzbIVFA)中有详细介绍。

### 总结

本文只介绍了 create2 的使用方法，其实它的应用场景还有很多，比如：

1.  [CREATE2 在广义状态通道中的使用](https://learnblockchain.cn/2019/10/23/create2-statechannel)
    
2.  [通过CREATE2获得合约地址：解决交易所充值账号问题](https://learnblockchain.cn/article/1297)
    

感兴趣的同学可以多看看学习学习。

### 参考

[

EIP-1014: Skinny CREATE2
------------------------

Adds a new opcode ( CREATE2) at 0xf5, which takes 4 stack arguments: endowment, memory\_start, memory\_length, salt. Behaves identically to CREATE ( 0xf0), except using keccak256( 0xff ++ address ++ salt ++ keccak256(init\_code))\[12:\] instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.

https://eips.ethereum.org



](https://eips.ethereum.org/EIPS/eip-1014)

[

openzeppelin-contracts/contracts/utils/Create2.sol at master · OpenZeppelin/openzeppelin-contracts
--------------------------------------------------------------------------------------------------

OpenZeppelin Contracts is a library for secure smart contract development. - openzeppelin-contracts/contracts/utils/Create2.sol at master · OpenZeppelin/openzeppelin-contracts

https://github.com

![](https://storage.googleapis.com/papyrus_images/3dd1af02b0158489530ad460402b5e9a67959c63eebb12bdbf8ce7675fe699fa.png)

](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Create2.sol)

[

Using Ethereum's CREATE2 | HackerNoon
-------------------------------------

To see the contract that uses CREATE2, jump to Step 2.

https://hackernoon.com

![](https://storage.googleapis.com/papyrus_images/8188e68c442781e2ce693ab50935169f681dda5f5597116970b7859c7eb4ae8a.avif)

](https://hackernoon.com/using-ethereums-create2-nw2137q7)

[

如何在不同链部署地址完全相同的合约
-----------------

廖雪峰的官方网站 (liaoxuefeng.com) 研究互联网产品和技术，提供原创中文精品教程

https://liaoxuefeng.com

![](https://storage.googleapis.com/papyrus_images/57d0d7234db9cb900e35fd8f394b011a3dd184fb209af79dbf560dc0b86b57b7.jpg)

](https://www.liaoxuefeng.com/article/1430588932227106)

---

*Originally published on [xyyme.eth](https://paragraph.com/@xyyme/create2)*
