欢迎来到《Solidity面试修炼之道》专栏💎。
专栏核心理念:
核心 Slogan💸💸:从面试题到实战精通,你的 Web3 开发进阶指南。
一句话介绍🔬🔬: 150+ 道面试题 × 103 篇深度解析 = 你的 Solidity 修炼秘籍。
- ✅ 名称有深度和系统性
- ✅ "修炼"体现进阶过程
- ✅ 适合中文技术社区
- ✅ 记忆度高,易于传播
- ✅ 全场景适用
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 的主要优势:
- 地址可预测性:可以在部署前知道合约地址,用户可以向尚未部署的合约发送资金
- 状态通道:可以在链下计算合约地址,只在需要时部署
- 工厂模式:可以确保相同的合约代码总是部署到相同的地址(在不同链上)
- 合约升级:可以通过 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 的安全考虑:
- 重新部署风险:如果合约使用 selfdestruct 自毁,可以使用相同的 salt 重新部署不同的代码(在 Cancun 升级后,selfdestruct 行为已改变)
- salt 冲突:如果 salt 已被使用,部署会失败
- 字节码依赖:地址依赖于完整的字节码,包括构造函数参数
在实际应用中,create2 常用于:
- Uniswap V2 的配对合约部署
- 最小代理(Minimal Proxy/EIP-1167)工厂
- 跨链合约部署(在多个链上部署到相同地址)
- 状态通道和 Plasma 实现
相关问题:
- Q2: 智能合约大约可以有多大?
- Q49: 以太坊地址是如何派生的?
