创建合约 create

本章讲解在 Solidity 中,如何使用 create 创建合约。

官网binschool.app
推特@BinSchool    DiscordBinDAO   微信:bkra50 

在以太坊区块链上,共有两种途径创建智能合约:

1) 外部账户使用 JSON-RPC 创建智能合约

外部账户 EOA 使用 JSON-RPC 接口,比如 eth_sendTransaction,来发送一个包含合约字节码的交易,然后提交到以太坊上,从而创建一个智能合约。

这也是最常见的创建方式,象使用 RemixHardhatFoundry,以及 js 库来部署合约,都是使用的这种方式。

2) 在智能合约中创建其它合约

Solidity 编写的智能合约中,可以通过特定的语句,创建其它合约的实例。

比如:去中心化交易所 UniSwapFactory 合约中,就是使用这种方式来创建 Pair 合约。

在以太坊虚拟机 EVM 中,有两条指令都可以创建合约,分别是:CREATECREATE2

其中,指令 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 不同,所以新创建合约的地址也是不同的。