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: 以太坊地址是如何派生的?
相关推荐
han_2 小时前
前端高频面试题之Vuex篇
前端·vue.js·面试
被瞧不起的神3 小时前
校招面经(一)入门篇
面试
hygge9995 小时前
JVM GC 垃圾回收体系完整讲解
java·开发语言·jvm·经验分享·面试
hygge9996 小时前
MySQL 全体系深度解析(存储引擎、事务、日志、MVCC、锁、索引、执行计划、复制、调优)
数据库·经验分享·mysql·adb·面试
友莘居士6 小时前
EVM(以太坊虚拟机)及其运行机制详解
区块链·智能合约·solidity·以太坊·evm·以太坊虚拟机
TechubNews6 小时前
美国政府停摆结束,Balancer 新流动性将全部汇集于 V3
区块链
拉不动的猪6 小时前
CSS 像素≠物理像素:0.5px 效果的核心密码是什么?
前端·css·面试
沐怡旸7 小时前
【穿越Effective C++】条款19:设计class犹如设计type——用户定义类型的艺术与科学
c++·面试
Q741_1478 小时前
C++ 面试高频考点 链表 迭代 递归 力扣 25. K 个一组翻转链表 每日一题 题解
c++·算法·链表·面试·递归·迭代