3MIKAN
仮想通貨直コン

execution revertedとcustom errorをviemでデコードする方法

execution revertedで返るエラーデータをABIとviemでデコードし、Error(string)・Panic・custom errorを切り分ける方法を解説します。

エラーデータを4バイトの目印と引数へ分け、Error・Panic・custom errorを判定する流れ

execution revertedは、コントラクトの処理が条件を満たせず、途中までの変更も取り消されたという意味です。

たとえば、残高が足りない、利用許可額が足りない、期限を過ぎた、操作する権限がない、といった場合に発生します。ただし、この表示だけではどの条件に引っかかったかまでは分かりません。

そこで、返されたエラーの中身を表すデータ(revert data)をABIと照合します。

エラーデータを目印と引数へ分け、Error・Panic・custom errorを判定する流れ

エラーデータは関数呼び出しと似た形で読める

custom errorのデータは、先頭4バイトの目印(selector)と、ABI形式で並べた引数で構成されます。

0x | 4-byte error selector | 32-byte word | 32-byte word | ...

これは、封筒の先頭に「どの種類の通知か」を示す番号があり、その後ろに詳細情報が続くイメージです。

エラー定義を含むABIがあれば、decodeErrorResultでエラー名と引数を復元できます。

import { decodeErrorResult } from 'viem'

const decoded = decodeErrorResult({
  abi,
  data: revertData,
})

console.log(decoded.errorName, decoded.args)

ABIにはfunctionだけでなくerror項目も必要です。 実装コントラクトがproxyの背後にある場合、proxy側のABIだけではcustom errorを解決できないことがあります。

3種類のエラーを分けて考える

Error(string)

revert("message")や文字列付きrequireで使われる形式です。selectorは0x08c379a0で、その後ろにABI形式の文字列が続きます。

人が読めるメッセージを返せますが、メッセージが短すぎる場合や、途中の外部呼び出しからそのまま返ってきた場合は、追加の確認が必要です。

Panic(uint256)

assertの失敗、ゼロ除算、配列の範囲外など、Solidityが定義する内部的な異常を番号で返す形式です。selectorは0x4e487b71です。

番号だけで判断せず、使用しているSolidityのバージョンに対応するpanic codeの表と、失敗した処理を照合します。

custom error

error InsufficientBalance(uint256 available, uint256 required);

custom errorは、開発者がエラー名と引数を定義する形式です。上の例なら、「利用できる残高」と「必要な残高」を数値で返せます。

文字列より少ないデータ量で詳しい情報を返せる一方、正しいABIがなければ元のエラー名へ戻せません。

simulateContractの例外から読む

viemでは、simulateContractでシミュレーションしたときの例外からContractFunctionRevertedErrorを探し、デコード済みの情報を参照できます。

import { BaseError, ContractFunctionRevertedError } from 'viem'

try {
  await publicClient.simulateContract({
    address,
    abi,
    functionName: 'transfer',
    args: [to, amount],
    account,
  })
} catch (error) {
  if (error instanceof BaseError) {
    const reverted = error.walk(
      (cause) => cause instanceof ContractFunctionRevertedError,
    )
    if (reverted instanceof ContractFunctionRevertedError) {
      console.log(reverted.data?.errorName, reverted.data?.args)
    }
  }
}

シミュレーションは実際の取引を送信しないため、まず原因を調べる用途に向いています。送信元、送る数量、関数、引数、ブロック条件を元の取引と揃えることが重要です。

デコードできないときの確認順

  1. dataが空ではないか
  2. 0xを含む完全なエラーデータか
  3. 呼び出し先がproxyなら、実装アドレスと実装側ABIを確認したか
  4. delegatecall先や外部呼び出し先のerrorもABIへ含めたか
  5. 同じaccountvalue、ブロック条件でシミュレーションしたか
  6. ノードやRPCがエラーデータを省略していないか

関数入力のABI確認はABI・function selector・calldata・event logsを手で読む方法、トランザクション全体の入口はviemで失敗トランザクションを調べるを参照してください。

エラー名だけを信用しない

エラーデータは外部呼び出しからそのまま返ってくることがあり、任意のコントラクトが同じ形のバイト列を返すこともできます。そのため、表示されたエラー名だけで送信元や安全性を証明することはできません。

次の情報も合わせて確認します。

  • 実行結果に記録された送信先
  • proxyの実装アドレス
  • 検証済みソースコードとABI
  • シミュレーションやtraceで実際に呼び出されたコントラクト

直コンの基本を使う場合も、署名画面だけで判断せず、対象アドレスと入力データを先に検証してください。

確認した一次情報