在以太坊链上,除了用户可以创建智能合约,智能合约同样也可以创建新的智能合约。两种常见的创建合约的方式:
一、create
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
contract MyToken {
address public factory;//工厂合约地址
address public token1;//代币合约地址1
address public token2;//代币合约地址2
constructor() payable{
factory = msg.sender;
}
function initialize(address _token0, address _token1) external{
require(msg.sender == factory, 'FORBIDDEN');
token1 = _token0;
token2 = _token1;
}
}
// 工厂合约示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./MyToken.sol";
contract TokenFactory {
mapping(address => mapping(address => address)) public getPair; // 通过两个代币地址查Pair地址
address[] public allPairs; // 保存所有Pair地址
function create(address token1,address token2) external returns(address pairAddr){
// 创建新合约
MyToken token = new MyToken();
// 调用新合约的initialize方法
token.initialize(token1,token2);
// 更新地址map
pairAddr = address(token);
allPairs.push(pairAddr);
getPair[token1][token2] = pairAddr;
getPair[token2][token1] = pairAddr;
}
function getToken(address token1,address token2) external view returns(address) {
return getPair[token1][token2];
}
function getFactory (address token) external view returns(address) {
return MyToken(token).factory();
}
}
二、create2
计算合约地址的预测值:
使用 keccak256 哈希函数计算合约的初始化代码(包括合约的字节码和构造函数的参数)的哈希值。
从创建者地址(通常是工厂合约的地址)和一个称为 salt 的值中构造创建合约时的合约地址。
使用 CREATE2 指令创建合约:
使用 CREATE2 指令,通过调用一个现有合约的方法(通常是一个工厂合约)来创建新合约。
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.20;
contract Token1{
uint256 public value;
constructor(uint256 _value) {
value = _value;
}
}
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.20;
import "./Token1.sol";
contract TokenFactory1 {
event ContractCreated(address indexed newContract);
/// 使用 create2 创建合约
function createContract(bytes32 _salt,uint _x) external{
Token1 _contract = new Token1{salt: _salt}(_x);
emit ContractCreated(address(_contract));
}
///计算被部署合约地址
function getContractAddr(bytes32 salt, bytes memory bytecode) external view returns(address){
address newContract = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
bytes1(0xff),// 固定字符串
address(this),// 当前工厂合约地址
salt,// salt值
keccak256(bytecode)// 被部署合约机器码的hash
)
)
)
)
);
return newContract;
}
// 获取被部署合约bytecode,参数_x为被部署合约构造函数的参数
function getBytecode(uint _x) external pure returns(bytes memory) {
bytes memory bytecode = type(Token1).creationCode;
return abi.encodePacked(bytecode, abi.encode(_x));
}
}
三、区别
在以太坊中,create 和 create2 都是用于创建新合约实例的指令,但它们有一些关键的区别:
地址生成方式:
- create: 创建的合约地址是基于创建者地址和创建者账户中的 nonce。创建者地址和 nonce 的组合决定了新合约的地址。
- create2: 创建的合约地址是基于三个输入参数:创建者地址、salt 值和初始化代码的 keccak256 哈希。这样,您可以通过选择不同的 salt 值来创建不同的地址。
合约地址可预测性:
- create: 合约地址是可预测的,但需要等待上一个创建者账户中的 nonce 增加。
- create2: 合约地址是在创建时就能够预测的,不受 nonce 的影响。
用途:
- create: 适用于在合约之间直接通信,无需事先知道合约地址。
- create2: 适用于在创建合约时预测合约地址,并通过地址存储信息,以便其他合约能够可靠地找到它。
重复部署:
- create: 如果两个不同的创建者同时尝试使用相同的 nonce 创建合约,它们可能会发生 nonce 竞争,导致一个创建失败。
- create2: 使用不同的 salt,两个创建者可以同时创建具有相同初始化代码的合约,而不会发生地址冲突。
总体而言,create2
提供了更多的地址生成灵活性和可预测性,特别是对于一些需要在合约中存储地址信息的应用场景。在某些情况下,选择使用 create
或 create2
取决于您的具体需求和设计。