合约部署(Migrations)

迁移脚本(JavaScript 文件)可帮助我们将合约部署到波场网络。 这些文件负责暂存我们的部署任务,并且假设我们的部署需求会随着时间的推移而发生变化。随着项目的发展,我们将创建新的迁移脚本,以进一步推动区块链的发展。先前运行的部署记录通过特殊的 Migrations 迁移合约记录在链上,详细信息如下。

部署命令

要运行部署,请运行以下命令:

tronbox migrate

这将部署在项目的 migrations 目录中的所有迁移文件。 最简单的迁移只是一组管理部署脚本。 如果我们的迁移先前已成功运行,则 tronbox migrate 将从上次运行的迁移开始执行,仅运行新创建的迁移。 如果不存在新的迁移,tronbox migrate 将不会执行任何操作。 我们可以使用 --reset 选项从头开始运行所有迁移。 对于本地测试,确保在执行 migrate 之前安装了 docker,并启动了 TRE 本地测试区块链

脚本文件

一个简单的迁移文件,如文件名:4_example_migration.js

var MyContract = artifacts.require('XXXContract');

module.exports = function (deployer) {
  deployer.deploy(MyContract);
};

请注意,文件名以数字为前缀,后缀为描述。 编号前缀是必需的,以便记录迁移是否成功运行。 后缀纯粹是为了人类的可读性和理解力。文件名数字前缀还有运行迁移文件顺序的作用。

artifacts.require()

在迁移开始时,我们通过 artifacts.require()方法告诉 TronBox 我们想要与哪些合约进行交互。 这个方法类似于 Node 的 require,但在我们的例子中,它特别返回了一个 合约抽象 contract abstraction,我们可以在其余的部署脚本中使用它。 指定的名称应与该源文件中的 合约定义的名称 相匹配。 不传递源文件的文件名,因为文件可以包含多个合约。

考虑这个示例,其中在同一源文件中指定了两个合约:
文件名: ./contracts/Contracts.sol

contract ContractOne {
  // ...
}

contract ContractTwo {
  // ...
}

通过 artifacts.require() 引入 ContractTwo 的语句像下面这样:

var ContractTwo = artifacts.require('ContractTwo');

也可以引入两个合约,语句如下:

var ContractOne = artifacts.require('ContractOne');
var ContractTwo = artifacts.require('ContractTwo');

module.exports

所有迁移都必须通过 module.exports 语法导出函数。 每次迁移导出的函数都应该接受 deployer 对象作为其第一个参数。 此对象通过为部署智能合约提供清晰的语法以及执行部署职责(例如保存已部署的 artifacts 供以后使用)。 deployer 对象是用于暂存部署任务最主要接口,其 API 在本页底部描述。

我们的迁移功能也可以接受其他参数。 请参阅以下示例。

初始化迁移功能

TronBox 要求我们有迁移合约(Migrations 合约)才能使用迁移功能。 此合约必须包含特定的接口,但我们可以随意编辑此合约。 对于大多数项目,此合约最初将作为第一个迁移文件进行部署,不会再次更新。 在使用tronbox init创建新项目时,我们也会默认收到此合约。

文件: contracts/Migrations.sol

pragma solidity >0.4.18 < 0.6.0;

contract Migrations {
  address public owner;
  uint public last_completed_migration;

  modifier restricted() {
    if (msg.sender == owner) _;
  }

  constructor() public {
    owner = msg.sender;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }

  function upgrade(address new_address) public restricted {
    Migrations upgraded = Migrations(new_address);
    upgraded.setCompleted(last_completed_migration);
  }
}

我们必须在第一次迁移中部署此合约才能利用迁移功能。 为此,需要创建以下迁移脚本文件:

文件名: migrations/1_initial_migration.js

var Migrations = artifacts.require('Migrations');

module.exports = function (deployer) {
  deployer.deploy(Migrations);
};

这里,我们可以使用增加的编号前缀创建新的迁移,以部署其他合约并执行更多的部署步骤。

部署程序 Deployer

我们的迁移文件将用于部署程序 deployer 来(分阶段)部署任务。 因此,我们可以同步编写部署任务,它们将以正确的顺序执行:

// Stage deploying A before B
deployer.deploy(A);
deployer.deploy(B);

或者,部署程序上的每个函数可以使用 Promise,等待上一个任务执行的部署任务完成之后执行(进入一个部署队列):

// Deploy A, then deploy B, passing in A's newly deployed address
deployer.deploy(A).then(function () {
  return deployer.deploy(B, A.address);
});

如果你发现更清晰语法也可以为部署编写为单个 promise 链, 部署 API 将在本页底部讨论。

考虑网络

可以根据网络配置,条件性地选择在某个网络运行部署。

需要在编写迁移时,加入第二个参数 network, 例如:

module.exports = function (deployer, network) {
  if (network == 'nile') {
    // Do something specific to the network named "nile".
  } else {
    // Perform a different step otherwise.
  }
};

可用账号

迁移也会通过我们的 TronWeb provider 提供给我们的帐户列表,供我们在部署期间使用。

module.exports = function (deployer, network, accounts) {
  // Use the accounts within your migrations.
};

部署程序接口 Deployer API

部署程序包含许多可用于简化迁移的功能。

deployer.deploy(contract, args..., options)

部署合约可以通过使用指定合约对象可选的合约构造函数的参数来进行合约部署。对于单个合约很有用,DApp 只存在此合约的一个实例。 它将在部署之后设置合约地址(即Contract.address 将等于新部署的地址),并且它将覆盖任何先前存储的地址。

我们也可以选择传递一组合约或一组数组,以加快多个合约的部署。另外,最后一个参数是一个可选对象,它可以包含名为overwrite的键以及其他交易参数如 feelimitfrom。如果overwrite设置为false,则部署程序如果发现之前已经部署了该合约,则不会再次部署该合约。这对于由外部依赖提供合约地址的某些情况下很有用。

注意,在调用deploy之前,我们需要首先部署和链接合约所依赖的库。有关详细信息,请参阅下面的link功能。

通过下面示例会更好理解 deploy 方法:

// Deploy a single contract without constructor arguments
deployer.deploy(A);

// Deploy a single contract with constructor arguments
deployer.deploy(A, arg1, arg2, ...);

// Don't deploy this contract if it has already been deployed
deployer.deploy(A, {overwrite: false});

// Set a maximum amount of fee_limit and `userFeePercentage`  for the deployment
deployer.deploy(A, {fee_limit: 1.1e8, userFeePercentage: 30});

// Deploying multiple contracts as an array is now deprecated.
// This used to be quicker than writing three `deployer.deploy()` statements as the deployer
// can perform the deployment as a single batched request.
// deployer.deploy([
//   [A, arg1, arg2, ...],
//   B,
//   [C, arg1]
// ]);

// External dependency example:
//
// For this example, our dependency provides an address when we're deploying to the
// live network, but not for any other networks like testing and development.
// When we're deploying to the live network we want it to use that address, but in
// testing and development we need to deploy a version of our own. Instead of writing
// a bunch of conditionals, we can simply use the `overwrite` key.
deployer.deploy(SomeDependency, {overwrite: false});

deployer.link(library, destinations)

将已部署的库链接到合约或多个合约。 参数 destinations 可以是单个合约,也可以是多个合约的数组。 如果目的(即参数指定的)合约中有不依赖于链接的库,则合约将被忽略。

示例:

// Deploy library LibA, then link LibA to contract B, then deploy B.
deployer.deploy(LibA);
deployer.link(LibA, B);
deployer.deploy(B);

// Link LibA to many contracts
deployer.link(LibA, [B, C, D]);

deployer.then(function() {...})

就像 promise 一样,可运行任意部署步骤。 使用此选项可在迁移期间调用特定的合约函数,以添加,编辑和重新组织合约数据。

示例:

var a, b;
deployer
  .then(function () {
    // Create a new version of A
    return A.new();
  })
  .then(function (instance) {
    a = instance;
    // Get the deployed instance of B
    return B.deployed();
  })
  .then(function (instance) {
    b = instance;
    // Set the new instance of A's address on B via B's setA() function.
    return b.setA(a.address);
  });