使用 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 依赖于 salt、bytecode 和 factory 地址,因此只要这些值保持不变,合约的地址也是可预测的。
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) 等领域有很大应用价值!

浙公网安备 33010602011771号