使用 CREATE2 部署合约及相关测试

1. 什么是 CREATE2?

CREATE2 是以太坊虚拟机(EVM)中的一个操作码(opcode),用于确定性地创建合约,使合约地址可预测。这与 CREATE 操作码不同,CREATE2 允许在合约部署前 计算出合约的地址,这对于智能合约钱包、工厂合约和 Layer 2 解决方案至关重要。

CREATE2 计算合约地址方式

使用 CREATE2 部署合约时,合约地址的计算方式如下:

address = keccak256(0xff + sender + salt + keccak256(bytecode)) [后 20 字节]

其中:

  • 0xff → 固定前缀(标识 CREATE2 操作码)。

  • sender → 部署合约的地址(即工厂合约的地址)。

  • salt → 一个随机数,可用于生成不同的地址。

  • keccak256(bytecode) → 被部署合约的代码哈希值。

2. 为什么在部署前就能知道合约地址?

CREATE2 机制下,合约地址是可以在部署前计算出来的,这是 CREATE2 的一个核心特性。

在工厂合约中,通常会有如下计算合约地址的方法:

function computeAddress(uint256 salt, bytes32 bytecodeHash) external view returns (address) {
    return address(uint160(uint256(keccak256(abi.encodePacked(
        bytes1(0xff),
        address(this),
        salt,
        bytecodeHash
    )))));
}

在 JavaScript 部署脚本或测试代码中,可以提前计算合约地址:

const precomputedAddress = await factory.computeAddress(salt, ethers.utils.keccak256(bytecode));
await factory.deploy(bytecode, salt);

因为 CREATE2 依赖于 saltbytecodefactory 地址,因此只要这些值保持不变,合约的地址也是可预测的。

3. 使用 CREATE2 部署合约

(1) 目标

我们将创建一个工厂合约(Factory Contract),该合约使用 CREATE2 部署新的合约,并且可以提前计算合约地址

(2) 代码实现

Step 1: 目标合约(SimpleContract.sol)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleContract {
    string public message;

    constructor(string memory _message) {
        message = _message;
    }
}

该合约非常简单,部署时存储一个 message 变量。

Step 2: 工厂合约(Create2Factory.sol)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Create2Factory {
    event Deployed(address addr, uint256 salt);

    function deploy(bytes memory bytecode, uint256 salt) public returns (address) {
        address addr;
        assembly {
            addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
        }
        require(addr != address(0), "Deployment failed");
        emit Deployed(addr, salt);
        return addr;
    }
    
    function computeAddress(uint256 salt, bytes32 bytecodeHash) external view returns (address) {
        return address(uint160(uint256(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            bytecodeHash
        )))));
    }
}

(3) 部署合约并测试

Step 3: 编写 Hardhat 测试

test/create2-test.js 文件中:

const { ethers } = require("hardhat");
const { expect } = require("chai");

describe("CREATE2 Factory", function () {
  let factory, deployer;

  before(async function () {
    [deployer] = await ethers.getSigners();
    const Factory = await ethers.getContractFactory("Create2Factory");
    factory = await Factory.deploy();
  });

  it("Should deploy contract using CREATE2", async function () {
    const Contract = await ethers.getContractFactory("SimpleContract");
    const bytecode = Contract.bytecode;
    const salt = ethers.utils.id("custom_salt");

    const precomputedAddress = await factory.computeAddress(salt, ethers.utils.keccak256(bytecode));

    await factory.deploy(bytecode, salt);
    expect(await ethers.provider.getCode(precomputedAddress)).to.not.equal("0x");
  });
});

4. 为什么 CREATE2 重要?

  • 提前计算地址:合约未部署前,即可知道地址,适用于钱包合约

  • 降低部署成本:避免重复部署多个合约实例,提高 Gas 效率。

  • 去中心化应用(DApps):适用于模块化合约架构,如 Uniswap V4 的 Hook 机制。

  • 账户抽象(ERC-4337):通过 CREATE2 创建智能账户,在 Layer 2 解决方案中尤为重要。

5. 结论

  • CREATE2 使合约地址可预测,提高了安全性和可扩展性。

  • Safe、Uniswap 等项目广泛使用 CREATE2 进行合约钱包和去中心化交易所的地址管理。

  • 通过 Hardhat 进行测试,验证 CREATE2 部署的正确性。

这种技术在 智能合约钱包、L2 预部署方案、账户抽象(ERC-4337) 等领域有很大应用价值!

posted @ 2025-02-20 10:07  若-飞  阅读(164)  评论(0)    收藏  举报