# CREATE2 操作码使用方法详解 **Published by:** [xyyme.eth](https://paragraph.com/@xyyme/) **Published on:** 2022-03-27 **URL:** https://paragraph.com/@xyyme/create2 ## Content CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相较于以前的 new 创建合约方法,可以在合约未创建之前就能够计算出合约的地址。我们之前在使用 new 创建新合约的时候,必须在取到合约对象之后,再取其 address 才能获取地址。而使用 create2,就可以这样提前计算出地址(参考):// 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 挖矿那样,不断找寻随机数,以达到目的。这里我们就不再对这个话题过多深入,感兴趣的同学可以看看 这里 或者 这里。 接下来我们就尝试一下使用 create2 部署合约,方便的是,OpenZeppelin 库中就有现成的模版可以供我们使用,先来看看它是怎么实现的:注意到注释中对于参数的要求:bytecode 必须非空对于相同的 bytecode,salt 不能重复(否则就计算出重复的地址)部署合约中必须有 amount 数量的 ETH(如果 amount 大于 0 的话)如果 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 计算合约地址:然后再调用 deploy 部署合约,在事件中查看部署的地址为:验证地址确实相同。 这里还有一点需要说一下的是,如果要在 EtherScan 中上传代码,是需要将上面的所有合约,也就是 Factory 和 Template,包括 import 的合约都需要上传,仅仅上传 Template 是无法成功的,这里当时卡了我很长时间,最后试了试全部粘贴才能成功。 由于对于相同的合约、参数、盐,create2 计算所得的合约地址都是相同的,因此我们就可以通过 create2 与 selfdestruct 相结合,在同一个地址上多次部署合约。我在这篇文章中有详细介绍。总结本文只介绍了 create2 的使用方法,其实它的应用场景还有很多,比如:CREATE2 在广义状态通道中的使用通过CREATE2获得合约地址:解决交易所充值账号问题感兴趣的同学可以多看看学习学习。参考EIP-1014: Skinny CREATE2Adds 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.orgopenzeppelin-contracts/contracts/utils/Create2.sol at master · OpenZeppelin/openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development. - openzeppelin-contracts/contracts/utils/Create2.sol at master · OpenZeppelin/openzeppelin-contractshttps://github.comUsing Ethereum's CREATE2 | HackerNoonTo see the contract that uses CREATE2, jump to Step 2.https://hackernoon.com如何在不同链部署地址完全相同的合约廖雪峰的官方网站 (liaoxuefeng.com) 研究互联网产品和技术,提供原创中文精品教程https://liaoxuefeng.com ## Publication Information - [xyyme.eth](https://paragraph.com/@xyyme/): Publication homepage - [All Posts](https://paragraph.com/@xyyme/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@xyyme): Subscribe to updates - [Twitter](https://twitter.com/xyymeeth): Follow on Twitter