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: 以太坊地址是如何派生的?
相关推荐
努力学算法的蒟蒻37 分钟前
day24(12.4)——leetcode面试经典150
面试·职场和发展
TL滕43 分钟前
从0开始学算法——第七天(快速排序算法)【面试高频】
笔记·学习·算法·面试·职场和发展·排序算法
EB_Coder44 分钟前
前端面试题-JavaScript高级篇
前端·javascript·面试
Web3VentureView1 小时前
特朗普回归到全球金融震荡:链上制度正成为新的稳压器
大数据·金融·web3·去中心化·区块链
玩具猴_wjh1 小时前
快手(安全方向)面试准备
安全·面试·职场和发展
狂炫冰美式7 小时前
TRAE SOLO 驱动:重构AI模拟面试产品的复盘
前端·后端·面试
区块链小八歌11 小时前
从电商收入到链上资产:Liquid Royalty在 Berachain 重塑 RWA 想象力
大数据·人工智能·区块链
Query*14 小时前
杭州2024.08 Java开发岗面试题分类整理【附面试技巧】
java·开发语言·面试
YSGZJJ14 小时前
股指期货的基本概念是什么?
区块链