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: 以太坊地址是如何派生的?
相关推荐
好好沉淀2 小时前
1.13草花互动面试
面试·职场和发展
阿蒙Amon5 小时前
C#每日面试题-常量和只读变量的区别
java·面试·c#
程序员小白条5 小时前
面试 Java 基础八股文十问十答第八期
java·开发语言·数据库·spring·面试·职场和发展·毕设
DICOM医学影像7 小时前
7. go语言从零实现以太坊请求端 - 查询区块链账户余额 - 手写JSONRPC
golang·区块链·以太坊·web3.0·jsonrpc·从零实现以太坊
xlp666hub7 小时前
Linux 设备模型学习笔记(1)
面试·嵌入式
南囝coding8 小时前
CSS终于能做瀑布流了!三行代码搞定,告别JavaScript布局
前端·后端·面试
gunner68 小时前
LazyMinting是如何实现的?
solidity
踏浪无痕8 小时前
Go 的协程是线程吗?别被"轻量级线程"骗了
后端·面试·go
CryptoPP9 小时前
对接API获取马来西亚历史数据
linux·运维·服务器·金融·区块链
一只叫煤球的猫9 小时前
为什么Java里面,Service 层不直接返回 Result 对象?
java·spring boot·面试