创建、部署合约
我们可以通过合约的创建字节码 (creation bytecode) 创建、部署合约,并获取合约地址。creation bytecode 包含了部署合约所需的所有信息,包含构造函数和初始化代码。
实现合约的创建、部署功能需要在 Solidity 的内联汇编 assembly { ... }
里面调用 create 方法。
create 方法:create(v, p, n)
;v
表示要发送的以太数量、p
是 creation bytecode 的起始指针,用于告诉 Solidity 编译器 creation bytecode 的起始位置、n
表示 creation bytecode 的长度;create 方法会创建、部署合约并返回合约地址。
-
在 Solidity 中 发送的以太数量用
msg.value
表示,但在汇编中 用callvalue()
表示 -
在 Solidity 中,动态数组和字符串的第一个 32 字节存储的是其长度。因此,实际的 creation bytecode 数据从第 32 字节开始。所以:
- 传入
p
时要跳过 32 byte,在汇编中可以通过add(_creationCode, 0x20)
实现 - creation bytecode 的长度
n
在汇编中可以用mload(_creationCode)
获取
- 传入
contract Demo {
address public owner = msg.sender;
}
contract Helper {
function getByteCode() public pure returns (bytes memory) {
// 获取 Demo 合约的 creation bytecode 并返回
return type(Demo).creationCode;
}
}
contract Proxy {
function deployDemo(
bytes memory _creationCode // 合约的 creation bytecode
) public payable returns (address addr) {
assembly {
// 创建、部署合约, 获取合约地址
addr := create(callvalue(), add(_creationCode, 0x20), mload(_creationCode))
}
// 检查合约地址的有效性
require(addr != address(0), "Failed to deploy contract");
}
}
-
部署 Helper 合约、Proxy 合约
-
执行 Helper 合约的 getByteCode 方法,得到 Demo 合约的 creation bytecode
-
将 Demo 合约的 creation bytecode 作为参数传入 Proxy 合约的 deployDemo 方法并执行,创建、部署 Demo 合约并得到其地址
-
通过控制台获取到 Demo 合约地址;通过地址添加 Demo 合约到 Remix
-
检查 Demo 合约的 owner 是否为 Proxy 合约的地址
部署合约并传入参数
contract Demo {
address public owner = msg.sender;
uint public count;
constructor(uint _count) {
count = _count;
}
}
contract Helper {
function getByteCode(uint _count) public pure returns (bytes memory) {
return abi.encodePacked(type(Demo).creationCode, abi.encode(_count));
// 用 encode 编码传入的参数; 用 encodePacked 包装 Demo 合约的 creation bytecode 和编码后的参数
}
}
contract Proxy {
function deployDemo(
bytes memory _creationCode
) public returns (address addr) {
assembly {
addr := create(callvalue(), add(_creationCode, 0x20), mload(_creationCode))
}
require(addr != address(0), "Failed to deploy contract");
}
}
-
部署 Helper 合约、Proxy 合约
-
传入参数 _count 到 Helper 合约的 getByteCode 方法并执行,得到包装好的 Demo 合约的 creation bytecode 和编码后的参数
-
将步骤 2 得到的数据作为参数传入 Proxy 合约的 deployDemo 方法并执行,创建、部署 Demo 合约并得到其地址
-
通过控制台获取到 Demo 合约地址;通过地址添加 Demo 合约到 Remix
-
检查 Demo 合约的 owner 是否为 Proxy 合约的地址、count 是否为步骤 2 中传入的参数
部署合约并传输以太
contract Demo {
address public owner = msg.sender;
uint public value = msg.value;
constructor() payable {} // 声明 payable 构造方法, 以支持在部署合约时传输以太
}
contract Helper {
function getByteCode() public pure returns (bytes memory) {
return type(Demo).creationCode;
}
}
contract Proxy {
function deployDemo(
bytes memory _creationCode
) public payable returns (address addr) {
// 使用 payable 修饰方法, 以支持在 public / external 方法中使用 callvalue()
assembly {
addr := create(callvalue(), add(_creationCode, 0x20), mload(_creationCode))
}
require(addr != address(0), "Failed to deploy contract");
}
}
-
部署 Helper 合约、Proxy 合约
-
执行 Helper 合约的 getByteCode 方法,得到 Demo 合约的 creation bytecode
-
设置以太数量、将 Demo 合约的 creation bytecode 作为参数传入 Proxy 合约的 deployDemo 方法并执行,部署 Demo 合约并得到其地址
-
通过控制台获取到 Demo 合约地址;通过地址添加 Demo 合约到 Remix
-
检查 Demo 合约的 owner 是否为 Proxy 合约的地址、value 是否为步骤 3 中传输的以太数量
CREATE2
CREATE2 是以太坊虚拟机(EVM)中的一个操作码,用于创建智能合约。与传统的 CREATE 操作码不同,CREATE2 允许开发者在部署合约之前预测其地址。
CREATE2 通过以下公式计算合约地址:address = keccak256(0xff + sender + salt + keccak256(bytecode))
,其中:0xff
是一个常量,用于区分 CREATE2 和 CREATE;sender
是创建合约的合约地址;salt
是一个 32 字节的随机值,由开发者提供;bytecode
是要部署的合约的创建字节码。
contract DeployWithCreate2 {
address public owner;
constructor(address _owner) {
owner = _owner;
}
}
contract Create2Factory {
event Deploy(address addr);
// 使用 CREATE2 操作码部署 DeployWithCreate2 合约
function deploy(uint _salt) external {
DeployWithCreate2 _contract = new DeployWithCreate2{
salt: bytes32(_salt)
}(msg.sender);
emit Deploy(address(_contract));
}
function getBytecode(address _owner) public pure returns (bytes memory) {
// 获取 DeployWithCreate2 合约的创建字节码
bytes memory bytecode = type(DeployWithCreate2).creationCode;
// 用 encode 编码传入的参数; 用 encodePacked 包装 DeployWithCreate2 合约的 creation bytecode 和编码后的参数
return abi.encodePacked(bytecode, abi.encode(_owner));
}
// 计算合约地址
function getAddress(
bytes memory bytecode,
uint _salt
) public view returns (address) {
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
_salt,
keccak256(bytecode)
)
);
// 将 hash 转为 uint 再转为 uint160, 表示取最后 20 个字节, 因为以太坊地址的长度为 20 字节
return address(uint160(uint(hash)));
}
}
-
部署 Create2Factory 合约
-
将编辑器的地址作为参数传入 getBytecode 方法并调用,获取包装好的 DeployWithCreate2 合约的 creation bytecode 和编码后的参数
-
将步骤 2 获取到的数据和随机值作为参数传入 getAddress 方法并调用,计算合约地址;随机值这里以 123 为例
-
调用 deploy 方法,传入随机值 123,部署 DeployWithCreate2 合约
-
查看 DeployWithCreate2 合约地址,与计算的合约地址比对,两者应该相等