Q3: create 和 create2 有什么区别?

欢迎来到《Solidity面试修炼之道》专栏💎。

专栏核心理念:

核心 Slogan💸💸:从面试题到实战精通,你的 Web3 开发进阶指南。

一句话介绍🔬🔬: 150+ 道面试题 × 103 篇深度解析 = 你的 Solidity 修炼秘籍。

  1. ✅ 名称有深度和系统性
  2. ✅ "修炼"体现进阶过程
  3. ✅ 适合中文技术社区
  4. ✅ 记忆度高,易于传播
  5. ✅ 全场景适用

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 的主要优势:

  1. 地址可预测性:可以在部署前知道合约地址,用户可以向尚未部署的合约发送资金
  2. 状态通道:可以在链下计算合约地址,只在需要时部署
  3. 工厂模式:可以确保相同的合约代码总是部署到相同的地址(在不同链上)
  4. 合约升级:可以通过 selfdestruct 和重新部署实现合约"升级"(虽然不推荐)

代码示例:

solidity 复制代码
// 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 的安全考虑:

  1. 重新部署风险:如果合约使用 selfdestruct 自毁,可以使用相同的 salt 重新部署不同的代码(在 Cancun 升级后,selfdestruct 行为已改变)
  2. salt 冲突:如果 salt 已被使用,部署会失败
  3. 字节码依赖:地址依赖于完整的字节码,包括构造函数参数

在实际应用中,create2 常用于:

  • Uniswap V2 的配对合约部署
  • 最小代理(Minimal Proxy/EIP-1167)工厂
  • 跨链合约部署(在多个链上部署到相同地址)
  • 状态通道和 Plasma 实现

相关问题:

  • Q2: 智能合约大约可以有多大?
  • Q49: 以太坊地址是如何派生的?
相关推荐
a努力。几秒前
京东Java面试被问:Fork/Join框架的使用场景
java·开发语言·面试
小明的小名叫小明26 分钟前
0.Uniswap协议概述
区块链
顾林海1 小时前
Android暗黑模式适配全攻略:从入门到精通,告别"阴间配色"
android·面试·性能优化
T___T1 小时前
从 0 搭建 React 待办应用:状态管理、副作用与双向绑定模拟
前端·react.js·面试
独自归家的兔1 小时前
面试实录:三大核心问题深度拆解(三级缓存 + 工程规范 + 逻辑思维)
java·后端·面试·职场和发展
CryxxxHunter1 小时前
Dec 24, 2025
金融·区块链
白露与泡影1 小时前
春招 Java 面试大纲:Java+ 并发 +spring+ 数据库 +Redis+JVM+Netty 等
java·数据库·面试
xiaoxue..2 小时前
单向数据流不迷路:用 Todos 项目吃透 React 通信机制
前端·react.js·面试·前端框架
阿拉伯柠檬2 小时前
MySQL基本查询
linux·数据库·mysql·面试
培培说证2 小时前
2026大专区块链技术应用专业考什么证?
区块链