アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

Truffleのmigrateでasync/awaitを使う方法

今回はtruffleのmigrate taskの中でasync/awaitを使って非同期処理をする方法についてまとめました。
この記事は truffle v4.0.4時点でのものです。

migrate taskの中でasync/awaitを使いたいシチュエーションとしては、例えば、TokenAとUseTokenという2つのコントラクトがあり、UseTokenをdeployする時は初期値としてTokenAのアドレスを渡したいといった場合があります。
または、blockのtimestampを取得してそのtimestamp +2daysした値を初期値として与えたい場合などもあります。

前者の場合は、最悪migrationファイルを2つに分けることでdeploy可能ですが、後者の場合はどうしてもmigrationファイルの中でasync/awaitしないといけません。
この様な場合の記述方法についてまとめました。

例1.TokenAとそのTokenAを初期値として必要なUseTokenをdeployする

ダメな例

この様な場合に多分、真っ先に書くのが以下の様なmigration taskだと思います。が、これはうまく動きません。

2_deploy_use_token.js

const TokenA = artifacts.require("./TokenA.sol");
const UseToken = artifacts.require("./UseToken.sol");

module.exports = async (deployer) => {
  await deployer.deploy(TokenA)
  await deployer.deploy(UseToken, TokenA.address)
}

truffle console上ではdeployが成功したログは表示されます。うまく動きませんとは言いましたが、deploy自体は成功しています。が、migration taskの挙動としては正常に終了しておらず、deploy自体は成功したものの、build/contracts/*.jsonにはdeploy結果のcontract addressが保存されないため、例えばconsole上で UseToken.deployed()を呼び出してinstanceを取得しようとしてもエラーになります。

良い例

Migrationに渡すcallback自体ではなく、deployerに対してasyncな関数を渡してあげます。

const TokenA = artifacts.require("./TokenA.sol");
const UseToken = artifacts.require("./UseToken.sol");

module.exports = (deployer) => {
  deployer.then(async () => {
    await deployer.deploy(TokenA)
    return deployer.deploy(UseToken, TokenA.address)
  })
}

例2. block.timestampを使ってcontractを初期化したい場合

async/awaitが正常に動作する書き方はすでに説明済みなのでここでは良い例だけ紹介します。
例えばCrowdSaleコントラクトなどをdeployする時に参考になると思います。

const MyCrowdSale = artifacts.require("./MyCrowdSale.sol");

const promisefy = (fn, ...args) => new Promise((accept, reject) => fn(...args, (err, res) => err ? reject(err) : accept(res)))
const days = 60 * 60 * 24
module.exports = (deployer) => {
  deployer.then(async () => {
    const timestamp = (await promisefy(web3.eth.getBlock, 'latest')).timestamp
    const startTime = timestamp + 7 * days
    const endTime = startTime + 14 * days
    return deployer.deploy(MyCrowdSale, startTime, endTime)
  })
}

まとめ

なぜ、deployerに対してasync関数を渡す場合にうまく動くのか不思議だったので、truffle-migrateのソースを追ってみました。
今回の原因の該当箇所はここです。
https://github.com/trufflesuite/truffle-migrate/blob/f01b5ea15ca422d973f21e19ca54dfe70041ad8c/index.js#L82-L83

migrateに渡されたcallback関数をcallしているだけなので、非同期部分の処理の完了を待たずにfinish()が呼ばれてしまうため、deployは正常にできても、そのcontractのaddressが保存されないという動きになってしまっています。
いっぽう、migrate taskに渡されるdeployerはpromise対応がされているため、deployerに対してasync関数を渡した場合は正常に動作します。
https://github.com/trufflesuite/truffle-deployer/blob/52e63189453e078f64b84aa3a50a2c7991b022b4/index.js