Forgeを使ったスマートコントラクトの構築について
※当記事はこちらの記事を翻訳したものです。
Foundryは、開発者がプロジェクトを構築し、スマートコントラクトをテストすることができるEthereum開発フレームワークです。ForgeはFoundryに同梱されているツールで、Solidityでテストを記述することができます。
Paradigm の開発者によって Rust で書かれた Foundry は、Hardhat やその他のテスト環境に代わる、非常に高速なテスト環境です。
このガイドでは、thirdweb と Foundry を使用して Solidity プロジェクトをセットアップしますので、まずはFoundry Book の手順に従ってFoundry のインストールを行ってください。
新しい Forge プロジェクトを作成する
まず、新しいディレクトリを作成し、次のコマンドを実行します。
npx thirdweb@latest create contract
プロジェクトをセットアップするための一連の質問が出るので、それに返答していきます。
- プロジェクトに名前を付ける
- フレームワークとして
Forge
を選択する - 基本コントラクトとして
ERC721
を選択する - オプションの拡張機能として
Drop
を選択する
最後に、以下をプロジェクト ディレクトリ内で実行します。
forge clean && foundryup && forge update
すべてがインストールされたら、プロジェクト ディレクトリに移動し、Solidity を書き始めましょう。
コントラクトのセットアップ
src
フォルダーにある Contract.sol
ファイル内にコントラクトを記述します。
最初は、次のようなコードになります。
pragma solidity ^0.8.13;
import "@thirdweb-dev/contracts/base/ERC721Drop.sol";
contract Contract is ERC721Drop {
constructor(
string memory _name,
string memory _symbol,
address _royaltyRecipient,
uint128 _royaltyBps,
address _primarySaleRecipient
)
ERC721Drop(
_name,
_symbol,
_royaltyRecipient,
_royaltyBps,
_primarySaleRecipient
)
{}
}
今回テストを行う、標準的なERC721のmint
関数を作成しましょう。
以下の関数をコントラクトに追加してください。
function mint(address _to, uint256 _amount) external {
require(_amount > 0, 'You must mint at least one token!');
_safeMint(_to, _amount);
}
この関数は、
-
_to
: トークンをミントするアドレス -
_amount
: ミントするトークンの数
という2つの引数を受け取ります。また、mint
を呼び出す際に、少なくとも1つのトークンをミントしていることを確認するrequire
文も追加しています。
テストの作成
コントラクトが書けたので、Forgeを使ってmint
関数をテストしてみましょう。
Foundry の利点の 1 つは、Solidity でコントラクトを記述するだけでなく、Solidity を使用してテストできるため、開発者のワークフローが単純化されることです。
Contract.t.sol
内に、
- テストするコントラクト
- 標準ライブラリのテストコントラクト(こちらは初めから書かれていると思います)
の両方のインポートが必要であることを確認します。
import "forge-std/Test.sol";
import "../src/Contract.sol";
次に、Test.sol
からインポートされたTest
コントラクトを継承したContractTest
という新しいコントラクトを作成します。
まず、setUp()
関数内でContract
のインスタンスを作成します。
また、コントラクトをテストするためのアドレスが必要です。このために、Forge標準ライブラリのヘルパー関数のmakeAddr
を使用します。
contract ContractTest is Test {
Contract drop;
address testAddr = makeAddr("Test");
function setUp() public {
drop = new Contract("MintTest", "MT", testAddr, 500, testAddr);
}
}
ここでは次の2つのテストを書きます。
-
testDropWithZeroTokens()
:
0個のトークンをミントしようとした場合にトランザクションがリバートされる -
testDropWithTokensMinted()
:
0個より多いトークンをミントしようとした場合に正しい数のトークンがミントされる
function testDropWithZeroTokens() public {
drop.mint(testAddr, 0);
}
function testDropWithTwoTokens() public {
drop.mint(testAddr, 2);
}
テストが合格するかを確認するには、次のコマンドを実行します。
forge test
なんと、テストの1つが失敗してしまいました!
テストを実行すると、コンソールにエラーが表示され、ひとつは合格、もうひとつは不合格と表示されます。
テストを書くときには、トランザクションがリバートされることを想定してもテストがパスするように設計することが重要です。
testDropWithZeroTokens
関数は、0トークンで mint
を呼び出したので、リバートされました。
このテストに合格するためには、Forge のチートコードである vm.expectRevert(<insert error message>)
を使用し、引数として提供されたエラーメッセージがコントラクト内の revert 文のメッセージと一致することを確認します。
この関数の動作は、次の呼び出し時に、与えられたエラーメッセージか戻ってくるかどうかを確認することです。したがって、この行を mint
を呼び出す直前に追加すれば、テストは両方とも成功するはずです。
最後に、テスト関数内でmint
を呼び出した後、トークンの残高が正確かどうかをチェックしてみましょう。
assertEq(param_1, param_2)
を利用することで、パラメータ1とパラメータ2が等しいかどうかをチェックし、等しくない場合はエラーを返します。mint
を呼び出したら、両方のテストに以下を追加してみましょう。
assertEq(drop.balanceOf(testAddr), <expected balance>)
最終的にテストコントラクト Contract.t.sol は以下のようになります。
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Contract.sol";
contract ContractTest is Test {
Contract drop;
address testAddr = makeAddr("Test");
function setUp() public {
drop = new Contract("MintTest", "MT", testAddr, 500, testAddr);
}
function testDropWithZeroTokens() public{
vm.expectRevert("You must mint at least one token!");
drop.mint(testAddr, 0);
assertEq(drop.balanceOf(testAddr), 0);
}
function testDropWithTokensMinted() public{
drop.mint(testAddr, 2);
assertEq(drop.balanceOf(testAddr), 2);
}
}
テストを再実行すると、両方とも合格しました。
デプロイする
mint関数のテストが終わったので、このコントラクトをデプロイしてみましょう! 次のコマンドを実行します。
npx thirdweb@latest deploy
コンストラクタのパラメータを入力します。
これでダッシュボードが開き、コントラクトの表示・管理ができるようになります。
forgeでテスト済みの新しいERC721 Dropコントラクトのデプロイ、おめでとうございます 🎉
質問がある場合や、あなたの素晴らしいプロジェクトを私たちと共有したい場合は、私たちのDiscordにぜひ飛び込んで来てください!
Discussion