背景
-
Soldityにて、コントラクトの状態変数のsetterを書く。
-
コントラクトの所有者のみsetterが実行できることをテストで確認したい。
↓
SetterをOwner以外で実行すると例外が発生することを確認する。
問題
例外を取得しても、例外の理由をうまく取得できなかった。
具体的な状況
▼ Contract
pragma solidity ^0.8.9;
import "openzeppelin-solidity/contracts/access/Ownable.sol";
class FundRaiser is Ownable {
address payable public beneficiary;
function setBeneficiary(address payable _beneficiary) public onlyOwner {
beneficiary = _beneficiary;
}
}
▼ Test (TypeScript)
describe("setBeneficiary" , () => {
it("throws an error when called from a non-owner account", async () => {
const accounts = await ethers.getSigner();
try {
// Deployなどのコードは省略
await
fundRaiser.connect(accounts[3]).setBeneficiary(accounts[2].address);
} catch (err) {
const expectedError = "Ownable: caller is not the owner";
const actualError = err.reason;
// テストに失敗する
expect(actualError).to.equal(expectedError, "should not be permitted");
}
}
});
});
err.reason が undefined になり、テストに失敗する。
なぜ上記のようなコードを書いたのか?
オライリー社のSolidityとEthereumによる実践スマートコントラクト開発
を参考にしながらコードを書いたため。
書籍の中では、Truffule Suite を利用しているが、
私は、周囲のエンジニアがおすすめしていた hardhat を利用して書いたため、
動作が異なり、例外の理由がうまく取得できなかった。
なぜ例外がうまく取得できないのか?
errの中身を調べたところ、Solidityから返ってくる例外をそのまま受け取っていた。
メッセージも16進数文字列で返ってきて、人間には読めない。
「Solidity undecoded error reason」などで検索をしたら、同様のことで困っている人が散見された。
例)Custom contract error does not contain decoded error messages
解決
Error Reasonをパースする独自関数を作りました。
(1) errを文字列に変換する
// Error: VM Exception while processing transaction: reverted with reason string 'Ownable: caller is not the owner'
const errString = err.toString();
(2) reason string以降の文字列を切り出す
export const parseReason = (errString: string) => {
const reason = errString.split("reason string '")[1];
return reason.substring(0, reason.length - 1);
};
// Ownable: caller is not the owner
const actualReason = parseReason(err.toString());
(3) テストを完成させる
it("throws an error when called from a non-owner account", async () => {
try {
await fundraiser
.connect(accounts[3])
.setBeneficiary(accounts[2]);
} catch (err) {
const expectedReason = "Ownable: caller is not the owner";
const actualReason = parseReason(err.toString());
expect(actualReason).to.equal(
expectedReason,
"should not be permitted"
);
}
});
補足
Errorの文字列が Error: VM Exception while processing transaction: reverted with reason string 'Ownable: caller is not the owner'
でした。
reason string
以降の文字列を取る関数を作ろうと思った時、ぱっと思い浮かばなかったので、chatGPT先生に聞いてやりました。