Q3: create 和 create2 有什么区别?
欢迎来到《Solidity面试修炼之道》专栏💎。
专栏核心理念:
核心 Slogan💸💸:从面试题到实战精通,你的 Web3 开发进阶指南。
一句话介绍🔬🔬: 150+ 道面试题 × 103 篇深度解析 = 你的 Solidity 修炼秘籍。
- ✅ 名称有深度和系统性
- ✅ "修炼"体现进阶过程
- ✅ 适合中文技术社区
- ✅ 记忆度高,易于传播
- ✅ 全场景适用
Q3: create 和 create2 有什么区别?
简答:
create 使用部署者地址和 nonce 计算新合约地址,地址不可预测;create2 使用部署者地址、salt 和合约字节码哈希计算地址,地址可预测且与 nonce 无关。
详细分析:
create 是传统的合约部署方式,新合约的地址由部署者地址和该地址的 nonce(交易计数)决定。公式为:address = keccak256(rlp([sender, nonce]))[12:]。这意味着每次部署时,即使是相同的合约代码,地址也会不同,因为 nonce 会递增。
create2 在 EIP-1014 中引入,允许在部署前预测合约地址。地址计算公式为:address = keccak256(0xff ++ sender ++ salt ++ keccak256(bytecode))[12:]。这里的 salt 是一个 32 字节的值,由部署者选择。只要 sender、salt 和 bytecode 相同,生成的地址就相同,与 nonce 无关。
create2 的主要优势:
- 地址可预测性:可以在部署前知道合约地址,用户可以向尚未部署的合约发送资金
- 状态通道:可以在链下计算合约地址,只在需要时部署
- 工厂模式:可以确保相同的合约代码总是部署到相同的地址(在不同链上)
- 合约升级:可以通过 selfdestruct 和重新部署实现合约"升级"(虽然不推荐)
代码示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;/*** @title CreateExample* @notice 演示 create 和 create2 的区别*/// 要部署的简单合约
contract SimpleContract {uint256 public value;constructor(uint256 _value) {value = _value;}
}contract CreateExample {event ContractCreated(address contractAddress, string method);/*** @notice 使用 create 部署合约* @dev 地址由 sender 和 nonce 决定,不可预测*/function deployWithCreate(uint256 _value) public returns (address) {SimpleContract newContract = new SimpleContract(_value);address contractAddress = address(newContract);emit ContractCreated(contractAddress, "create");return contractAddress;}/*** @notice 使用 create2 部署合约* @dev 地址由 sender、salt 和 bytecode 决定,可预测*/function deployWithCreate2(uint256 _value, bytes32 _salt) public returns (address) {SimpleContract newContract = new SimpleContract{salt: _salt}(_value);address contractAddress = address(newContract);emit ContractCreated(contractAddress, "create2");return contractAddress;}/*** @notice 预测 create2 部署的合约地址* @dev 在实际部署前计算地址*/function predictCreate2Address(bytes32 _salt,uint256 _value) public view returns (address) {// 获取合约创建字节码bytes memory bytecode = abi.encodePacked(type(SimpleContract).creationCode,abi.encode(_value));// 计算字节码哈希bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff),address(this),_salt,keccak256(bytecode)));// 返回地址(取哈希的后 20 字节)return address(uint160(uint256(hash)));}/*** @notice 演示 create 地址的不可预测性*/function demonstrateCreateUnpredictability(uint256 _value) public returns (address, address) {// 连续部署两次相同的合约address addr1 = address(new SimpleContract(_value));address addr2 = address(new SimpleContract(_value));// 地址不同,因为 nonce 递增了assert(addr1 != addr2);return (addr1, addr2);}/*** @notice 演示 create2 地址的可预测性*/function demonstrateCreate2Predictability(uint256 _value,bytes32 _salt) public returns (address, address) {// 预测地址address predictedAddr = predictCreate2Address(_salt, _value);// 实际部署address actualAddr = address(new SimpleContract{salt: _salt}(_value));// 地址相同assert(predictedAddr == actualAddr);return (predictedAddr, actualAddr);}
}/*** @title Create2Factory* @notice 使用 create2 的工厂合约示例*/
contract Create2Factory {mapping(bytes32 => address) public deployedContracts;/*** @notice 部署合约,如果已存在则返回现有地址*/function deploy(uint256 _value, bytes32 _salt) public returns (address) {// 检查是否已部署address predicted = predictAddress(_salt, _value);if (deployedContracts[_salt] != address(0)) {require(deployedContracts[_salt] == predicted, "Salt collision");return deployedContracts[_salt];}// 部署新合约SimpleContract newContract = new SimpleContract{salt: _salt}(_value);address contractAddress = address(newContract);deployedContracts[_salt] = contractAddress;return contractAddress;}function predictAddress(bytes32 _salt, uint256 _value) public view returns (address) {bytes memory bytecode = abi.encodePacked(type(SimpleContract).creationCode,abi.encode(_value));bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode)));return address(uint160(uint256(hash)));}
}
理论补充:
create2 的引入为以太坊带来了新的可能性,特别是在状态通道和 Layer 2 解决方案中。例如,两个用户可以在链下协商一个合约的参数和 salt,然后在链下进行交互,只在发生争议时才将合约部署到链上。由于地址是可预测的,双方可以安全地向该地址发送资金,即使合约尚未部署。
create2 的安全考虑:
- 重新部署风险:如果合约使用 selfdestruct 自毁,可以使用相同的 salt 重新部署不同的代码(在 Cancun 升级后,selfdestruct 行为已改变)
- salt 冲突:如果 salt 已被使用,部署会失败
- 字节码依赖:地址依赖于完整的字节码,包括构造函数参数
在实际应用中,create2 常用于:
- Uniswap V2 的配对合约部署
- 最小代理(Minimal Proxy/EIP-1167)工厂
- 跨链合约部署(在多个链上部署到相同地址)
- 状态通道和 Plasma 实现
相关问题:
- Q2: 智能合约大约可以有多大?
- Q49: 以太坊地址是如何派生的?

