📖

Chapter9: Truffleとユニットテスト | Solidity Programming Essentialsを読む

2022/08/05に公開

イーサリアムの知識を整理するために2022年6月発売のSolidity Programming Essentials 2nd Editionを読み進める試みです。この記事ではChapter9「Basics of Truffle and Unit Testing」を読み進めます。

Solidity Programming Essentials: A guide to building smart contracts and tokens using the widely used Solidity language, 2nd Edition (English Edition)

読書ログは以下のスクラップで逐次更新していきます。
https://zenn.dev/mah/scraps/ea8c79961ae8c8


この章で扱われるトピックは以下の通りです。

  • Application development life cycle management
  • Understanding and installing Truffle
  • Contract development with Truffle
  • Testing contracts with Truffle
  • Interactively working with Truffle

スマートコントラクトの開発環境であるTruffleをざっとウォークスルーしてみる章です。

Truffleのインストール

Node.jsがセットアップされている環境であれば、以下のコマンドで一通りインストールされます。

$ npm install -g truffle

正常にインストールされたかどうかはtruffle versionを実行して確かめましょう。

$ truffle version
Truffle v5.5.24 (core: 5.5.24)
Ganache v7.4.0
Solidity v0.5.16 (solc-js)
Node v16.15.1
Web3.js v1.7.4

Truffleでの開発

プロジェクトの初期化

次に適当なディレクトリを作成してtruffle initでプロジェクトを作成します。

$ mkdir spe-reading
$ cd spe-reading
$ truffle init

初期状態のプロジェクトはこんな感じ。

$ tree
.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
└── truffle-config.js
  • contracts: コントラクトを保存するディレクトリ。最初に配置されているMigrations.solはコントラクトのデプロイ処理に必要。Migrationsコントラクトでは最後に実行したマイグレーションIDを保存している。
  • migrations: コントラクトのデプロイ手順を記述したJavaScriptファイルを保存するディレクトリ。ファイル先頭の番号付けは1から連番になるように設定する。
  • test: テストスクリプトを保存するディレクトリ。

接続先ネットワークの設定

truffle-config.js内のnetworksキー内のdevelopmentキーのコメントアウトを解除する。

デフォルトではポート番号が8545になっていますが、Ganacheのポート番号が7545なので、筆者はポート番号を7545に変えています。

テスト用のコントラクトを作成

contractsディレクトリ内にfirst.solというファイル名でコントラクトを作成します。

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

contract First {
  int256 public mydata;

  function GetDouble(int256 _data) public returns (int256 _output) {
    mydata = _data * 2;

    return mydata;
  }
}

更にsecond.solという名前で以下のコントラクトも作成します。

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

import "./first.sol";

contract Second {
  address firstAddress;

  int256 public data;

  constructor(address _first) {
    firstAddress = _first;
  }

  function SetData() public {
    First h = First(firstAddress);

    data = h.GetDouble(21);
  }
}

この時点でcontractsディレクトリ内はこんな感じのファイル配置になっているはずです。

.
├── Migrations.sol
├── first.sol
└── second.sol

マイグレーションファイルを作成

次にマイグレーションファイルを作成します。migrationsディレクトリ内に2_custom.jsという名前で以下のファイルを作成します。

var firstContract = artifacts.require("First");
var secondContract = artifacts.require("Second");

module.exports = function (deployer) {
  deployer.deploy(firstContract).then(function () {
    return deployer.deploy(secondContract, firstContract.address);
  });
};

デプロイ時にコンストラクタへパラメータを渡したい場合はここで渡す感じですね。

コントラクトをコンパイル

truffle compileコマンドでコントラクトをコンパイルします。buildディレクトリ以下にJSONファイルでデプロイ用のデータ(ABI、バイトコード)が作成されます。

$ truffle compile

Compiling your contracts...
===========================
✓ Fetching solc version list from solc-bin. Attempt #1
✓ Downloading compiler. Attempt #1.
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/first.sol
> Compiling ./contracts/second.sol
> Artifacts written to /Users/mah_lab/dev/ethereum/contracts/spe-reading/build/contracts
> Compiled successfully using:
   - solc: 0.8.15+commit.e14f2714.Emscripten.clang

コントラクトをデプロイ

truffle migrateコマンドでコントラクトをデプロイします。

$ truffle migrate

Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.


Starting migrations...
======================
> Network name:    'development'
> Network id:      5777
> Block gas limit: 6721975 (0x6691b7)


1_initial_migration.js
======================

   Deploying 'Migrations'
   ----------------------
   > transaction hash:    0x14b48e5e7695729d7ff7d9a4404f224875ae117319044eb9d065d0e371d6fb1d
   > Blocks: 0            Seconds: 0
   > contract address:    0xF50949B3c3A51e9c41c43616FceA2f1d53579FE6
   > block number:        1
   > block timestamp:     1659656588
   > account:             0xF2A089e8364C031deF364b4E9596493E1d2d2907
   > balance:             99.99502292
   > gas used:            248854 (0x3cc16)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00497708 ETH

   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00497708 ETH


2_custom.js
===========

   Deploying 'First'
   -----------------
   > transaction hash:    0x66e200985bfd2fb6a8ea18f964a29d5d3d1156803b3d53c43c58010f5b4a26ec
   > Blocks: 0            Seconds: 0
   > contract address:    0x8bfc23719CA8f9ECC605d5609C012A52b378d819
   > block number:        3
   > block timestamp:     1659656589
   > account:             0xF2A089e8364C031deF364b4E9596493E1d2d2907
   > balance:             99.99011076
   > gas used:            203095 (0x31957)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.0040619 ETH


   Deploying 'Second'
   ------------------
   > transaction hash:    0x8d6b44cf503743814fe9c9592f6fb887b482e9ac98c5b3c60a7d6f899407a00d
   > Blocks: 0            Seconds: 0
   > contract address:    0xbCc8bE6449D63DB412d997f0EBB402664b2B9775
   > block number:        4
   > block timestamp:     1659656589
   > account:             0xF2A089e8364C031deF364b4E9596493E1d2d2907
   > balance:             99.986066
   > gas used:            202238 (0x315fe)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00404476 ETH

   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.00810666 ETH

Summary
=======
> Total deployments:   3
> Final cost:          0.01308374 ETH

補足

Ganacheで確認するとコントラクトが正常にデプロイされていることが分かります。

Migrationsコントラクトの中身を見ると、last_completed_migrationの値に2が入っていることが分かります。この番号とmigrationディレクトリ内のファイルの先頭にある番号が対応している、という仕組みです。

コントラクトのユニットテストを書く

Truffleにはユニットテストの仕組みも用意されています。testディレクトリにTestFirst.solという名前で以下のコードを保存してみましょう。

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/first.sol";

contract TestFirst {
  function testInitialBalanceUsingDeployedContract() public {
    First meta = First(DeployedAddresses.First());
    Assert.equal(meta.GetDouble(10), 20, "done");
  }
}

truffle testコマンドを実行するとtestディレクトリ内のテストが実行されます。

$ truffle test
Using network 'development'.

Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/first.sol
> Compiling ./contracts/second.sol
> Compiling ./test/TestFirst.sol
> Artifacts written to /var/folders/5t/8drw4vd54qq14_ct_36xw1440000gn/T/test--41817-j5eEEq20irL7
> Compiled successfully using:
   - solc: 0.8.15+commit.e14f2714.Emscripten.clang

  TestFirst
    ✔ testInitialBalanceUsingDeployedContract (209ms)

  1 passing (4s)

対話型インターフェースでコントラクトを実行する

Truffleには対話型インターフェースも用意されています。truffle console --network developmentと、ネットワークを指定してコンソールを実行してみましょう。

以下はFirstコントラクトのGetDouble関数をコンソール上で呼び出す例です。JavaScriptで記述します。

$ truffle console --network development
truffle(development)> let firstContract = await First.deployed()
undefined
truffle(development)> firstContract.address
'0x8bfc23719CA8f9ECC605d5609C012A52b378d819'
truffle(development)> await firstContract.GetDouble(10)
{
  tx: '0x35194ce0c170de7342eaa0fe44134f131c438fe475bffd8c0059a6bc9a340961',
  receipt: {
    transactionHash: '0x35194ce0c170de7342eaa0fe44134f131c438fe475bffd8c0059a6bc9a340961',
    transactionIndex: 0,
    blockHash: '0x741d1a811e99218068bf22e6b4b6c8a8c8ffc25af9ce4e773271615278b2f090',
    blockNumber: 27,
    from: '0xf2a089e8364c031def364b4e9596493e1d2d2907',
    to: '0x8bfc23719ca8f9ecc605d5609c012a52b378d819',
    gasUsed: 43016,
    cumulativeGasUsed: 43016,
    contractAddress: null,
    logs: [],
    status: true,
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
    rawLogs: []
  },
  logs: []
}

ざっくりTruffleの使い方はこんな感じです。

Discussion