foundryのテストを書くときにexpectRevertを使うとコケた件
おきたこと
foundryでテストを書く際、ローレベルコールcall
を使うときに、普通は成功するとステータスのtrueを返し、失敗するとfalseを返すのだ。
しかし、function expectRevert() external;
を使うとなぜか絶対失敗してsuccessがfalseやろうというときにも、trueを返すことがある。
(bool success, bytes memory returndata) = address(xx).call(abi.encodeWithSignature("xxoo()"));
解決法
これはあかんということで調べたらこちらにたどり着いた。
Gochaがあって、expectRevert()
を使うとexpectRevertの挙動が成功するかどうかを使って、ローレベルルコールが成功するか否かのsuccessを上書きしてしまうというらしい。
だから、expectRevert()
を使うときには、success
がfalse
になることは望めないというわけだ。
function testIfCallingFunctionDoesntExistThenRevert2() public {
// Expect the revert
// vm.expectRevert();
// Attempt to call a non-existing function
(bool success, bytes memory returndata) = address(tf).call(abi.encodeWithSignature("nonExistingFunction()"));
// Log the success status
console.log("success:", success);
console.logBytes(returndata);
// Ensure the call reverted
assert(success == false && returndata.length == 0);
}
こちらのcallを使った返り値は0x
で空っぽのdataとなった。
// This is the log of `// Log the success status`
success: false
0x
testするときに便利なコマンド
- コントラクトのregexで探してそれだけをテストする用
forge test --mc TokenTest
forge test --match-contract TokenTest
- テストのregexで探してそれだけをテストする用
forge test --mt testStateVariables
forge test --match-test testStateVariables
おきたこと
function testTransfer() public {
address to = makeAddr("to");
token.transfer(to, 1_000_000 * 1e18);
assertEq(token.balanceOf(to), 1_000_000 * 1e18);
assertEq(token.balanceOf(fakeHolder), 999_000_000 * 1e18);
}
上記のFoundryテストでtokenを送付する時に、テストが失敗した理由は、テストの途中で実行されたトランザクションが失敗したためです。具体的には、次のエラーが出ています:
[FAIL. Reason: ERC20InsufficientBalance(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, 0, 1000000000000000000000000 [1e24])]
このエラーは、「ERC20InsufficientBalance」というカスタムエラーで、アカウントの残高が不足しているた
めにトランザクションが失敗したことを示しています。以下に考えられる原因と解決策を示します:
原因
- デフォルトアカウントの残高不足:
• Foundryでは、デフォルトで最初のテストアカウント(0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496)からトランザクションが送信されます。このアカウントに十分なトークンがないため、トランザクションが失敗しています。 - 初期設定の不足:
• token.transfer(to, 1_000_000 * 1e18)を実行する前に、テストアカウントにトークンを割り当てる初期設定が不足している可能性があります。
このように修正したらOK:
function testTransfer() public {
address to = makeAddr("to");
vm.prank(fakeHolder);
token.transfer(to, 1_000_000 * 1e18);
assertEq(token.balanceOf(to), 1_000_000 * 1e18);
assertEq(token.balanceOf(fakeHolder), 999_000_000 * 1e18);
}
testを書く時に、明示的にexpectRevertされるときのエラーの署名も含めて、しかもエラーには引数がある状態でチェックしたいときに、どうしたらいいかというサンプルコードはこちらです。
function testIfCreateTokenFunctionFailsWhenFeeIsNotEnough() public {
address someone = makeAddr("someone");
vm.deal(someone, 1 ether);
// test something
bytes4 selector = bytes4(keccak256("NotEnoughFee(uint256)"));
vm.expectRevert(abi.encodeWithSelector(selector, 50000000000000));
// fee should be 645161290322580 / 1e18 = 0.000645161290322580
// when eth price is 3100
tf.createToken{value: 0.00005 ether}("Test Token Token", "TSTT", address(this), 1_000_000_000 * 1e18);
}
function testConstructorIfZeroAddressNotAllowed() public {
bytes4 selector = bytes4(keccak256("OwnableInvalidOwner(address)"));
vm.expectRevert(abi.encodeWithSelector(selector, address(0)));
new TokenFactory(address(0), address(1), address(2));
}
もう一つ簡単な書き方はこれ:
function testIfCreateTokenFunctionFailsWhenInitialHolderIsZero() public {
address someone = makeAddr("someone");
vm.deal(someone, 1 ether);
// test something
vm.expectRevert(ZeroAddressNotAllowed.selector);
contract.functionName{value: 0.01 ether}(address(0));
}
command例
vm.prank(<address>)
: 誰になりすます。
vm.startPrank(<address>)
: 誰かになりすます。ここまで同じだが、vm.stopPrank()
が出現するまでずっとなりすます。