Waffleライブラリを使用したシンプルなスマートコントラクトのテスト
このチュートリアルでは、以下について学びます
- ウォレットの残高が変わることのテスト
- 特定の引数でイベントが発行されることのテスト
- トランザクションが取り消されたことのアサーション
前提知識
- 新規のJavaScriptまたはTypeScriptのプロジェクトを作成できる
- JavaScriptのテストの基本的な経験がある
- yarnやnpmなどのパッケージマネージャーを使用したことがある
- スマートコントラクトおよびSolidityのごく基本的な知識を持っている
はじめに
このチュートリアルでは、yarnを使ってテストのセットアップおよび実行をしていますが、npmの方が良ければnpmでも問題ありません。公式のWaffleのドキュメントは、こちら(opens in a new tab)になります。
依存関係のインストール
プロジェクトに対してethereum-waffleとtypescriptの依存関係を開発環境の依存関係に追加(opens in a new tab)します。
yarn add --dev ethereum-waffle ts-node typescript @types/jest
スマートコントラクトのコード例
このチュートリアルでは、EtherSplitterというシンプルなスマートコントラクトの例に取り組みます。 このコードでは、誰もがweiを送信でき、それを2つの事前定義された受信者間で均等に分割するだけです。 split関数ではweiの数が偶数でなければなりません。さもないと処理が取り消されます。 両方の受信者に対して、weiの送金を実行し、続いてTransferイベントを発行します。
src/EtherSplitter.sol
に、EtherSplitterコードのスニペットを配置します。
1pragma solidity ^0.6.0;23contract EtherSplitter {4 address payable receiver1;5 address payable receiver2;67 event Transfer(address from, address to, uint256 amount);89 constructor(address payable _address1, address payable _address2) public {10 receiver1 = _address1;11 receiver2 = _address2;12 }1314 function split() public payable {15 require(msg.value % 2 == 0, 'Uneven wei amount not allowed');16 receiver1.transfer(msg.value / 2);17 emit Transfer(msg.sender, receiver1, msg.value / 2);18 receiver2.transfer(msg.value / 2);19 emit Transfer(msg.sender, receiver2, msg.value / 2);20 }21}すべて表示コピー
コントラクトのコンパイル
コントラクトをコンパイル(opens in a new tab)するのに、package.jsonファイルに次のエントリを追加します。
1"scripts": {2 "build": "waffle"3 }コピー
次に、プロジェクトのルートディレクトリにWaffleの設定ファイル (waffle.json
) を作成し、次の設定を貼り付けます。
1{2 "compilerType": "solcjs",3 "compilerVersion": "0.6.2",4 "sourceDirectory": "./src",5 "outputDirectory": "./build"6}コピー
yarn build
を実行してください。 終了すると、build
ディレクトリに、JSON形式でコンパイルされたEtherSplitterコントラクトが現れます。
テストの設定
Waffleでテストするには ChaiマッチャーとMochaが必要になるため、プロジェクトに追加(opens in a new tab)します。 次のようにscriptの場所にtest
エントリを追加してpackage.jsonファイルを更新してください。
1"scripts": {2 "build": "waffle",3 "test": "export NODE_ENV=test && mocha -r ts-node/register 'test/**/*.test.ts'"4 }コピー
テストを実行(opens in a new tab)する場合は、 yarn test
を実行します。
テストを実行する
それでは、test
ディレクトリを作成し、新しいファイル test\EtherSplitter.test.ts
を作成してください。 以下のスニペットをコピーして、テストファイルに貼り付けてください。
1import { expect, use } from "chai"2import { Contract } from "ethers"3import { deployContract, MockProvider, solidity } from "ethereum-waffle"4import EtherSplitter from "../build/EtherSplitter.json"56use(solidity)78describe("Ether Splitter", () => {9 const [sender, receiver1, receiver2] = new MockProvider().getWallets()10 let splitter: Contract1112 beforeEach(async () => {13 splitter = await deployContract(sender, EtherSplitter, [14 receiver1.address,15 receiver2.address,16 ])17 })1819 // add the tests here20})すべて表示
始める前に、少し解説をします。 MockProvider
は、ブロックチェーンのモックバージョンを作成します。 また、EtherSplitterコントラクトのテストで役立つモックウォレットも提供します。 このプロバイダーで、getWallets()
メソッドを呼び出すと最大10個までウォレットを取得することができます。 この例では、3つのウォレットを取得します。1つは、送信者用で、2つは、受信者用です。
次に、「splitter」という変数を宣言します。これは、 EtherSplitterコントラクトのモックです。 このモックは、単一のテストを実行する前にdeployContract
メソッドによって作成されます。 当該のメソッドは、最初のパラメータとして渡されたウォレット (この場合は送信者のウォレット) からコントラクトのデプロイメントをシミュレートします。 2番目のパラメータは、テストされるコントラクトのABIとバイトコードです。コンパイルされたEtherSplitterコントラクトのjsonファイルをbuild
ディレクトから渡します。 3番目のパラメータは、コントラクトのコンストラクタ引数を持つ配列です。この場合、受信者の2つのアドレスです。
changeBalances
まず、splitメソッドによって実際に受取人のウォレットの残高が変わるかどうかを確認します。 送信者のアカウントから50weiを分割すると、両方の受信者の残高が25wei増えることが期待されます。 ここで、WaffleのchangeBalances
マッチャーを使います。
1it("Changes accounts balances", async () => {2 await expect(() => splitter.split({ value: 50 })).to.changeBalances(3 [receiver1, receiver2],4 [25, 25]5 )6})
マッチャーの最初のパラメータとして、受信者のウォレットの配列を渡し、2番目のパラメータとして、対応するアカウントで予想される増加分を配列で渡します。 特定のウォレットの残高を確認したい場合は、以下の例のように、配列を渡さなくてもchangeBalance
マッチャーを使えます。
1it("Changes account balance", async () => {2 await expect(() => splitter.split({ value: 50 })).to.changeBalance(3 receiver1,4 255 )6})
changeBalance
と changeBalances
のどちらの場合も、split関数をコールバックとして渡します。マッチャーは呼び出しの前後に残高の状態にアクセスする必要があるためです。
次では、weiの各転送後にTransferイベントが発行されたかどうかをテストします。 それでは、Waffleの別のマッチャーに移ります。
Emit
1it("Emits event on the transfer to the first receiver", async () => {2 await expect(splitter.split({ value: 50 }))3 .to.emit(splitter, "Transfer")4 .withArgs(sender.address, receiver1.address, 25)5})67it("Emits event on the transfer to the second receiver", async () => {8 await expect(splitter.split({ value: 50 }))9 .to.emit(splitter, "Transfer")10 .withArgs(sender.address, receiver2.address, 25)11})すべて表示
emit
マッチャーを使うと、メソッドの呼び出し時にコントラクトがイベントを発行したかどうかを確認できます。 emit
マッチャーへのパラメーターとして、イベントを発行することが予期されるモックコントラクトとそのイベントの名前を渡します。 この場合、モックコントラクトはsplitter
で、イベント名はTransfer
です。 また、イベントの発行で引数の正確な値を検証することもできます。その場合、イベントの宣言で期待される数の引数を withArgs
マッチャーに渡します。 EtherSplitterコントラクトの場合では、送金されるwei単位の金額とともに送信者と受信者のアドレスを渡します。
revertedWith
最後の例として、weiの数が奇数の場合にトランザクションが取り消されるかどうかを確認します。 ここでは、revertedWith
マッチャーを使います。
1it("Reverts when Vei amount uneven", async () => {2 await expect(splitter.split({ value: 51 })).to.be.revertedWith(3 "Uneven wei amount not allowed"4 )5})
このテストをパスすれば、トランザクションが実際に取り消されたことが保証されます。 ただし、require
ステートメントで渡したメッセージと、revertedWith
で期待しているメッセージとが完全に一致している必要があります。 EtherSplitterコントラクトのコードに戻った場合、weiの金額のrequire
ステートメントで、「偶数でないwei単位の金額は許可されていません」というメッセージが表示されます。 これは、テストで予期されるメッセージと一致します。 それらが等しくなければ、テストは失敗します。
おめでとうございます!
Waffleでスマートコントラクトをテストするための最初の大きな一歩を踏み出すことができました。 他のWaffleのチュートリアルについては、以下をご参照ください。
- Waffleを使って、ERC-20をテストする
- Waffleを使った動的モックアップの活用およびコントラクト呼び出しのテスト
- WaffleでHardhatとethersを使って「Hello world!」と出力するチュートリアル
最終編集者: @nhsz(opens in a new tab), 2024年2月27日