Waffleを使った動的モックアップの活用およびコントラクト呼び出しのテスト
チュートリアルの内容
このチュートリアルでは、以下について学びます:
- 動的モックアップの使用方法
- スマートコントラクト間のやりとりをテストする方法
前提知識:
Solidity
でシンプルなスマートコントラクトを書けるJavaScript
とTypeScript
が扱える- 他の
Waffle
のチュートリアルを受講したか、ある程度知識がある
動的モックアップ
動的モックアップにはどのような利点があるでしょうか? まず、統合テストではなく、単体テストを書くことができるという点が挙げられます。 どういう意味かと言うと、 スマートコントラクトの依存関係について心配する必要がないので、個々のスマートコントラクトを完全に隔離した状態でテストできるのです。 それでは、その方法を具体的に見ていきましょう。
1. プロジェクト
まずはじめに、シンプルなnode.jsのプロジェクトを作成します。
mkdir dynamic-mockingcd dynamic-mockingmkdir contracts srcyarn init# or if you're using npmnpm init
次に、mochaとchaiの依存関係をテストするtypescriptを追加します。
yarn add --dev @types/chai @types/mocha chai mocha ts-node typescript# or if you're using npmnpm install @types/chai @types/mocha chai mocha ts-node typescript --save-dev
さらに、Waffle
とethers
も追加します。
yarn add --dev ethereum-waffle ethers# or if you're using npmnpm install ethereum-waffle ethers --save-dev
これにより、プロジェクトの構造は次のようになっているはずです:
1。2├── contracts3├── package.json4└── test
2. スマートコントラクト
動的モックアップを使用するには、依存関係を含むスマートコントラクトが必要です。 こちらで用意してありますので、ご心配なく!
今回はSolidity
で書かれたシンプルなスマートコントラクトを使用しますが、このコントラクトの唯一の目的は、私たちがお金持ちであるかを確認することです。 つまり、十分なERC-20トークンを保有しているかどうかを確認するだけのスマートコントラクトです。 このコードを、./contracts/AmIRichAlready.sol
に追加します。
1pragma solidity ^0.6.2;23interface IERC20 {4 function balanceOf(address account) external view returns (uint256);5}67contract AmIRichAlready {8 IERC20 private tokenContract;9 uint public richness = 1000000 * 10 ** 18;1011 constructor (IERC20 _tokenContract) public {12 tokenContract = _tokenContract;13 }1415 function check() public view returns (bool) {16 uint balance = tokenContract.balanceOf(msg.sender);17 return balance > richness;18 }19}すべて表示コピー
動的モックアップで使用するだけなので、ERC-20全体は必要なく、関数を1つだけ持つIERC-20インターフェイスを使います。
さっそく、コントラクトをビルドしましょう! ビルドには、Waffle
を使用します。 まず、コンパイルのオプションを指定するシンプルなwaffle.json
設定ファイルを作成します。
1{2 "compilerType": "solcjs",3 "compilerVersion": "0.6.2",4 "sourceDirectory": "./contracts",5 "outputDirectory": "./build"6}コピー
さて、Waffleでコントラクトをビルドする準備が整いました。
npx waffle
簡単ですね。 build/
フォルダ内に、コントラクトとインターフェイスに対応する2つのファイルが現れました。 これらのファイルを使ってテストを行います。
3. テストを実行する
実際にテストするために、AmIRichAlready.test.ts
という名前のファイルを作成します。 まずはじめに、インポートを可能にしなければなりません。 これは、後ほど必要になります:
1import { expect, use } from "chai"2import { Contract, utils, Wallet } from "ethers"3import {4 deployContract,5 deployMockContract,6 MockProvider,7 solidity,8} from "ethereum-waffle"
JSの依存関係の他に、ビルドしたコントラクトとインターフェイスをインポートする必要があります:
1import IERC20 from "../build/IERC20.json"2import AmIRichAlready from "../build/AmIRichAlready.json"
Waffleでは、chai
を使ってテストを実行します。 ただしその前に、Waffleのマッチャーをchaiに追加する必要があります:
1use(solidity)
各テストの実行前にコントラクトの状態をリセットするため、beforeEach()
関数を実装する必要があります。 まず、この関数には何が必要かを考えてみましょう。 コントラクトをデプロイするには、ウォレットと、AmIRichAlready
コントラクトに引数として渡すためのデプロイされたERC-20コントラクトが必要です。
まず、ウォレットを作成します:
1const [wallet] = new MockProvider().getWallets()
次に、ERC-20コントラクトをデプロイする必要があります。 今のところ、私たちはインターフェイスしか持っていないので、工夫が必要になります。 ここで、Waffleが助けてくれます。 Waffleには、インターフェイスのABIだけを使用してコントラクトを作成できる、魔法のようなdeployMockContract()
関数が含まれているのです。
1const mockERC20 = await deployMockContract(wallet, IERC20.abi)
ウォレットとデプロイされたERC-20コントラクトの両方が準備できたので、さっそくAmIRichAlready
コントラクトをデプロイしましょう:
1const contract = await deployContract(wallet, AmIRichAlready, [2 mockERC20.address,3])
これで、beforeEach()
関数が作成できました。 現在のところ、 AmIRichAlready.test.ts
のファイルは以下のようになっているはずです:
1import { expect, use } from "chai"2import { Contract, utils, Wallet } from "ethers"3import {4 deployContract,5 deployMockContract,6 MockProvider,7 solidity,8} from "ethereum-waffle"910import IERC20 from "../build/IERC20.json"11import AmIRichAlready from "../build/AmIRichAlready.json"1213use(solidity)1415describe("Am I Rich Already", () => {16 let mockERC20: Contract17 let contract: Contract18 let wallet: Wallet1920 beforeEach(async () => {21 ;[wallet] = new MockProvider().getWallets()22 mockERC20 = await deployMockContract(wallet, IERC20.abi)23 contract = await deployContract(wallet, AmIRichAlready, [mockERC20.address])24 })25})すべて表示
このAmIRichAlready
コントラクトの最初のテストを書きましょう。 テストの対象は何にすべきだと思いますか? 言うまでもなく、 私たちがお金持ちであるかをチェックするのですね :)
でもちょっと待ってください。 今作成したコントラクトでは、どのような値を返すべきかを指定していませんでした。 balanceOf()
関数のロジックが実装されていないからです。 ここでもWaffleが助けてくれます。 モックアップを作成したコントラクトには、新たに以下のような素敵なコードが付け加えられています:
1await mockERC20.mock.<nameOfMethod>.returns(<value>)2await mockERC20.mock.<nameOfMethod>.withArgs(<arguments>).returns(<value>)
これを知っていれば、最初のテストを書くことができます。
1it("returns false if the wallet has less than 1000000 tokens", async () => {2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))3 expect(await contract.check()).to.be.equal(false)4})
このテストを、構成要素に分解してみましょう:
- モックアップのERC-20コントラクトは、常に、999999トークンの残高を返すように設定します。
contract.check()
メソッドが、false
を返すか確認します。
ようやく、テストを実行する準備ができました。
テストは実行されましたが・・・改善の余地がありますね。 balanceOf()
関数は、常に99999を返します。 実際のコントラクトのように、関数が値を返すウォレットを指定するともっとテストらしくなるでしょう。
1it("returns false if the wallet has less than 1000001 tokens", async () => {2 await mockERC20.mock.balanceOf3 .withArgs(wallet.address)4 .returns(utils.parseEther("999999"))5 expect(await contract.check()).to.be.equal(false)6})
このテストでは、今のところ、私たちが十分にお金持ちではない場合のみをチェックしています。 次に、その反対もチェックできるようにしてみましょう:
1it("returns true if the wallet has at least 1000001 tokens", async () => {2 await mockERC20.mock.balanceOf3 .withArgs(wallet.address)4 .returns(utils.parseEther("1000001"))5 expect(await contract.check()).to.be.equal(true)6})
テストを実行します・・・
そして・・・うまく行きました! 私たちのコントラクトは、意図したとおりに動作しているようです :)
コントラクトの呼び出しをテストする
これまでの進展をまとめておきましょう。 AmIRichAlready
コントラクトの機能をテストし、正常に動作していることが確認できたようです。 これで終わりだろうって? いいえ、まだ少し残っています。 Waffeを使えば、さらに多くの事項をテストすることができます。 具体的に説明すると、 WaffleにはcalledOnContract()
マッチャーとcalledOnContractWith()
マッチャーが搭載されています。 これらを使えば、作成したコントラクトがモックアップのERC-20コントラクトを呼び出したかどうかを確認できるのです。 いずれかのマッチャーを使用した基本的なテストは、次のようになります:
1it("checks if contract called balanceOf on the ERC20 token", async () => {2 await mockERC20.mock.balanceOf.returns(utils.parseEther("999999"))3 await contract.check()4 expect("balanceOf").to.be.calledOnContract(mockERC20)5})
もう一方のマッチャーを使用することで、さらにこのテストを充実させることができます:
1it("checks if contract called balanceOf with certain wallet on the ERC20 token", async () => {2 await mockERC20.mock.balanceOf3 .withArgs(wallet.address)4 .returns(utils.parseEther("999999"))5 await contract.check()6 expect("balanceOf").to.be.calledOnContractWith(mockERC20, [wallet.address])7})
テストがうまく行ったか確認しましょう:
幸いなことに、すべてのテストに合格しました。
Waffleを使えば、コントラクトの呼び出しをとても簡単にテストできます。 特にすばらしいのは、 これらのマッチャーを通常のコントラクトとモックアップのコントラクトの両方に使えることです! 他のテクノロジー向けの人気が高いテストライブラリと同じように、Waffleは、コードを挿入するのではなく、EVM呼び出しを記録し、フィルタ処理を行うアプローチを採用しています。
おわりに
おめでとうございます! これで、Waffleを使用して、コントラクトの呼び出しや、モックアップのコントラクトを動的にテストする方法を身に付けることができました。 この他にもたくさんの興味深い機能がありますので、 Waffleのドキュメンテーションに目を通すことをおすすめします。
Waffleのドキュメンテーションは、こちら(opens in a new tab)から入手できます。
このチュートリアルのソースコードは、こちら(opens in a new tab)からアクセスできます。
さらに、以下のチュートリアルをおすすめします:
最終編集者: @nhsz(opens in a new tab), 2024年2月27日