LoginSignup
1
1

More than 1 year has passed since last update.

Solidityの例外をcatchした時にerr.reasonの値がうまく取得できなかった

Last updated at Posted at 2022-12-30

背景

  • 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先生に聞いてやりました。

Screen Shot 2022-12-30 at 22.43.50.png

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1