Solidity で、スマートコントラクトのテスト用モックアップを作成する方法
モック・オブジェクト(opens in a new tab) は、オブジェクト指向プログラミングにおいて一般的なデザインパターンです。 「モック」という言葉は、古フランス語で「からかう」という意味を持つ「mocquer」が由来で、「本物を模倣する」という意味に発展しました。これこそ、プログラミングにで「モックアップ」を作成する作業です。 あなたが作成したスマートコントラクトについて「からかう」必要はありませんが、「モックアップ」の作成はできる限り必ず行ってください。 後々、楽になります。
コントラクトのモックアップを使った単体テスト
コントラクトのモックアップを作成するとは、究極的に、オリジナルとほぼ同様に動作するものの、デベロッパが簡単に管理できる第2のバージョンを作成することです。 複雑なコントラクトを開発すると、コントラクト全体の一部のみを単体テストする必要が発生する場合が少なくありません。 問題となるのは、この一部分をテストする上で、非常に具体的なコントラクトの状態を再現する必要があるものの、再現するのが難しい場合です。
テストに求められる状態を再現するために、テストを設定するための複雑なロジックを作成する代わりに、モックアップを作成すればよいのです。 コントラクトのモックアップは、継承を使って簡単に作成できます。 オリジナル版を継承したモックアップのコントラクトを作成するだけです。 モックアップでは、機能をオーバーライドすることができます。 この点については、具体例で見てみましょう。
例:プライベートのERC-20コントラクト
ここでは、当初にプライベート期間が設定された ERC-20 コントラクトを使って説明します。 トークン所有者はプライベートユーザーを管理でき、当初トークンを受け取ることができるのはプライベートユーザーのみになります。 設定した時間が経過した後は、すべてのユーザーがトークンを使用できるようになります。 参考までに、この例では新しいOpenZeppelinコントラクトv3に含まれている_beforeTokenTransfer
(opens in a new tab)フックを使用しています。
1pragma solidity ^0.6.0;23import "@openzeppelin/contracts/token/ERC20/ERC20.sol";4import "@openzeppelin/contracts/access/Ownable.sol";56contract PrivateERC20 is ERC20, Ownable {7 mapping (address => bool) public isPrivateUser;8 uint256 private publicAfterTime;910 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {11 publicAfterTime = now + privateERC20timeInSec;12 }1314 function addUser(address user) external onlyOwner {15 isPrivateUser[user] = true;16 }1718 function isPublic() public view returns (bool) {19 return now >= publicAfterTime;20 }2122 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {23 super._beforeTokenTransfer(from, to, amount);2425 require(_validRecipient(to), "PrivateERC20: invalid recipient");26 }2728 function _validRecipient(address to) private view returns (bool) {29 if (isPublic()) {30 return true;31 }3233 return isPrivateUser[to];34 }35}すべて表示コピー
さっそく、このコントラクトのモックアップを作成しましょう。
1pragma solidity ^0.6.0;2import "../PrivateERC20.sol";34contract PrivateERC20Mock is PrivateERC20 {5 bool isPublicConfig;67 constructor() public PrivateERC20(0) {}89 function setIsPublic(bool isPublic) external {10 isPublicConfig = isPublic;11 }1213 function isPublic() public view returns (bool) {14 return isPublicConfig;15 }16}すべて表示コピー
以下のエラーメッセージのどちらかが表示されます:
PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.
Solidityの新しいバージョン(0.6)を使用しているため、オーバーライド可能な関数についてはvirtual
のキーワードを追加して、オーバーライド関数をオーバーライドする必要があります。 このため、両方のisPublic
関数にこのキーワードを追加します。
これで、単体テストでPrivateERC20Mock
を使えるようになりました。 プライベート利用期間中の動作をテストしたい場合は、setIsPublic(false)
を使用し、パブリック利用期間についてはsetIsPublic(true)
を使ってテストしてください。 もちろん、time helpers(opens in a new tab)を使って、対象時間を変更することもできます。 しかし、すでにモックアップの有用性について理解できたでしょう。単に時間を進めるよりも、モックアップを利用した方が望ましいシナリオがすぐに思い浮かぶはずです。
複数のコントラクトに対してモックアップを作成する場合
モックアップごとに新たなコントラクトを作成するのは煩雑な作業ですね。 これを避けたい場合は、MockContract(opens in a new tab)ライブラリを参照してください。 このライブラリを用いることで、コントラクトの動作をその場でオーバーライドし、変更することができます。 ただしこのライブラリは、別のコントラクトに対する呼び出しのみに対応していますので、今回は使用できません。
モックアップは、その他にも利点があります
モックアップ作成のメリットは、他にもあります。
- 関数の追加:特定の関数をオーバーライドする機能だけでなく、関数を追加できることも有益です。 トークンを対象とする場合のよい例としては、
ミント
機能を追加することで、ユーザーが新規トークンを無料で手に入れられるようにすることができます。 - テストネットでの使用:Dapp と共に、テストネット上でコントラクトをデプロイ、テストする際は、モックアップの活用を検討すべきです。 本当に必要な場合を除き、関数をオーバーライドすることは避けるべきです。 結局のところ、実際のロジックをテストする必要があるからです。 しかし例えば、コントラクトのステートを、新たにデプロイする必要なしで開始時点にリセットするだけのリセット機能は、有益な場合があるでしょう。 言うまでもなく、メインネット用のコントラクトにはこのようなリセット機能を含めてはなりません。
最終編集者: @nhsz(opens in a new tab), 2023年8月15日