创建合约 create
本章讲解在 Solidity
中,如何使用 create
创建合约。
在以太坊区块链上,共有两种途径创建智能合约:
1) 外部账户使用 JSON-RPC 创建智能合约
外部账户 EOA
使用 JSON-RPC
接口,比如 eth_sendTransaction
,来发送一个包含合约字节码的交易,然后提交到以太坊上,从而创建一个智能合约。
这也是最常见的创建方式,象使用 Remix
、Hardhat
、Foundry
,以及 js
库来部署合约,都是使用的这种方式。
2) 在智能合约中创建其它合约
在 Solidity
编写的智能合约中,可以通过特定的语句,创建其它合约的实例。
比如:去中心化交易所 UniSwap
的 Factory
合约中,就是使用这种方式来创建 Pair
合约。
在以太坊虚拟机 EVM
中,有两条指令都可以创建合约,分别是:CREATE
和 CREATE2
。
其中,指令 CREATE
是最基本的创建方法。它与外部账户使用 JSON-RPC
创建智能合约,底层机制是完全相同的。
指令 CREATE2
是在 2019 年 “君士坦丁堡” 升级时引入的,目的是为了更好地控制新创建合约的地址。
那么,我们在 Solidity
代码中,如何调用 CREATE
指令来创建合约呢?
共有两种方式:使用 new
操作符,或者调用内联汇编的 create
操作码。
1. 使用 new 创建合约
new 操作符语法
ContractType instance = new ContractType{value: _value}(param1, param2, ...)
其中,ContractType
是新建合约的类型;
_value
是创建合约时,存入合约以太币的数量;
param1
, param2
是新建合约的构造函数的参数;
返回值 instance
是新建合约的实例。
new 操作符示例
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // 被创建合约 contract Callee { string value1; string value2; // 构造函数有两个参数 constructor(string memory _value1,string memory _value2) { value1 = _value1; value2 = _value2; } } // 合约创建者 contract ContractCreator { // 新合约地址 address public contractAddress; // 创建合约 function newContract(string memory value1,string memory value2) external { // 创建 Callee 合约实例,参数为 value1, value2 Callee callee = new Callee(value1, value2); // 设置新建合约的地址 contractAddress = address(callee); } }
将这个合约 ContractCreator
部署到 Remix
上,首先点击 contractAddr
,还未创建 Callee
合约,所以值为 0。
输入字符串 "a","b",点击 newContract
,创建 Callee
合约成功。再点击 contractAddr
,它的值已经变成新合约的地址。
2. 使用内联汇编 create 创建合约
内联汇编 create 语法
assembly { instance := create(value, codeOffset, codeLength) }
其中,参数 value
是创建合约时,存入合约以太币的数量;
codeOffset
是合约字节码的位置偏移;
codeLength
是合约字节码的长度;
返回值 instance
是新建合约的地址。
内联汇编 create 示例
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // 被创建合约 contract Callee { string value1; string value2; // 构造函数有两个参数 constructor(string memory _value1,string memory _value2) { value1 = _value1; value2 = _value2; } } // 合约创建者 contract ContractCreator { // 新合约地址 address public contractAddress; // 创建合约 function createContract(string memory value1,string memory value2) external { // 将合约字节码和参数 value1, value2 打包编码 bytes memory bytecode = abi.encodePacked( type(Callee).creationCode, abi.encode(value1, value2) ); address addr; assembly { // 创建 Callee 合约实例 addr := create(0, add(bytecode, 0x20), mload(bytecode)) } // 设置新建合约的地址 contractAddress = addr; } }
其中,type(Callee).creationCode
是获取 Callee
合约的字节码。
mload(bytecode)
获取智能合约字节码中执行代码的长度。在智能合约字节码中,前 32 字节为执行代码的长度。
add(bytecode, 0x20)
获取智能合约字节码中执行代码的偏移位置。在智能合约字节码中,32 字节后为执行代码。
将这个合约 ContractCreator
部署到 Remix
上,首先点击 contractAddr
,还未创建 Callee
合约,所以值为 0。
输入字符串 "a","b",点击 createContract
,创建 Callee
合约成功。再点击 contractAddr
,它的值已经变成新合约的地址。
使用 create
创建的合约,它的生成地址由部署者地址和 nonce 计算获得。由于每次部署时的 nonce 不同,所以新创建合约的地址也是不同的。