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: 以太坊地址是如何派生的?
相关推荐
一条溺水的鱼34 分钟前
一次讲透 JS 闭包 —— 概念、原理、应用和内存泄漏
javascript·面试
程序员清风1 小时前
聊聊我的AI学习方法与思考!
java·后端·面试
流浪0011 小时前
Linux系统篇36——线程(一) 线程的概念、本质和Linux的实现方式
linux·面试·操作系统·线程
狂师2 小时前
2026互联网大厂秋招AI Coding笔试全攻略!
人工智能·面试·求职
彧azz17 小时前
Linux 网络编程学习总结
linux·网络·笔记·学习·面试
baopixiaoz18 小时前
BeeQuant × BeeAgent:用AI加速策略验证
大数据·人工智能·python·区块链
默_笙19 小时前
💫 闭包是个背包:拆解小米前端面试题里的三道"闭包陷阱"
前端·javascript·面试
木西20 小时前
把 NEAR 的 “链抽象与 AI 智能体” 搬进以太坊:用 Solidity 0.8.28 打造元交易意图结算核心底座
智能合约·solidity·以太坊
DeepAgent20 小时前
AI Agent 入门指南(05):Agent 循环——观察、思考、行动
面试·agent
顶点多余21 小时前
9.18 面试题总结 --- 绿盟
linux·面试