Chapter9: Truffleとユニットテスト | Solidity Programming Essentialsを読む
イーサリアムの知識を整理するために2022年6月発売のSolidity Programming Essentials 2nd Editionを読み進める試みです。この記事ではChapter9「Basics of Truffle and Unit Testing」を読み進めます。
読書ログは以下のスクラップで逐次更新していきます。
この章で扱われるトピックは以下の通りです。
- 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