コンテンツへスキップ
0%

Solidityバイナリとは?

Alchemy team headshot

執筆者 Alchemy

2023年7月10日 公開読了時間 1 分

スマートコントラクトを通じてEthereumブロックチェーンに公開される生データは、バイトコード、つまり長い16進数の文字列です。開発者は人間が読めるSolidityコードでスマートコントラクトを記述・読解しますが、ブロックチェーンに公開されるのはそのテキストではありません。

同様に、スマートコントラクトへの「コール」、つまりスマートコントラクトが公開している外部から見える関数へのリクエストは、すべて生のバイトコード、または「バイナリ」の形式です。

以下の(Solidity でエンコードされた)構造を持つ、Ethereum mainnetにアップロードされたスマートコントラクトを例に見てみましょう。

ユーザーが関数 baz に対して、パラメータ 69 と true を指定して呼び出しを行いたいとします。

バイトコードとして送信されるリクエストの実際の姿は以下の通りです。

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450...

かなり読みにくいですよね。

この記事では、Ethereum Virtual Machineがなぜすべてをバイトコードでエンコードするのかを解説し、ABI とは何か、その使い方を学び、バイトコードを人間が読めるSolidityに戻すための基本的なツールをいくつか紹介します。

注: この記事の例は、公式のSolidity ABI ドキュメントから引用しています。

なぜ Solidity はスマートコントラクトをバイナリでエンコードするのか?

Ethereum ブロックチェーンにデータを保存するのは非常にコストが高く、アップロードされるデータの1バイトごとにブロックチェーン上のすべてのフルノードに複製する必要があるため、Solidity コードをアップロードするよりも生のバイトコードで読み書きする方がコスト効率が良くなります。

人間が読めるコードを解析して保存すると、データ量が桁違いに増える可能性があり、mainnet 上ではスマートコントラクトのデプロイだけですでに数千ドルかかることもあるため、これは問題になります。

Solidity ABI とは何か、スマートコントラクトを読むためになぜ必要なのか

スマートコントラクトが公開される際、Ethereum への公開前に自動的にバイトコードへとトランスパイルされます。しかし、いったんネットワーク上に公開されると、個々の利用者はそのスマートコントラクトとどのように対話すればよいのでしょうか。長いバイトコードの文字列を見て、どの関数が呼び出し可能かを理解するのはほぼ不可能です。

その答えがApplication Binary Interface、つまり ABIです。

ABI は、特定のスマートコントラクトに対して行える呼び出しと、各呼び出しが何を返すかを記述した、人間が読める公開メソッドのリストです。

ABI があれば、スマートコントラクトの利用者はバイトコードを読む必要がなく、自分の呼び出しをバイトコードに変換してスマートコントラクトと対話できます。

ABI は、従来の Web2 アーキテクチャにおける API (Application Programming Interface) と非常によく似ています。ただし主な違いは、Solidity ABI がバイナリでエンコードされたスマートコントラクト内のメソッドへのアクセスをユーザーに可能にするのに対し、API はオンラインのサーバーエンドポイントからメソッドへのアクセスをユーザーに可能にするという点です。

ABI は人間が使用し、読むことを目的としているため、スマートコントラクト開発者はスマートコントラクトの ABI をブロックチェーンに公開しません。それは非常にコストがかかるためです。

代わりに、ABI は以下の方法で入手できます。

  1. スマートコントラクト開発者が公開しているコントラクトのソースコードから、ABI を生成する。
  2. スマートコントラクトが Etherscan で verify されている場合は、Etherscan のコントラクト情報から取得する。
  3. スマートコントラクトのバイトコードから ABI をリバースエンジニアリングする(推奨されません)。

ABI は通常、Solidity スマートコントラクトの公開関数宣言を JSON 形式でエンコードしたものとして公開されます。

以下のスマートコントラクトの関数定義を例に見てみましょう。

対応する JSON エンコーディングは以下のようになります。

Solidity のコールデータのバイナリを解釈する方法

Solidity のバイナリを手作業で解析して関数呼び出しに戻すのは、複雑で直感的でなく、ミスをしやすいため避けたいところですが、バイナリが Solidity 内でどのように構成されるかをおおまかに理解しておくことは非常に役立ちます。そうすればコールデータをざっと確認したり、値を再確認したりできます。

次のセクションでは、このような変換作業のほとんどを処理してくれるツールをいくつか紹介します。

上記の例を見てみましょう。

ユーザーがスマートコントラクト内の関数 baz に対して、パラメータ 69 と true を指定して呼び出しを行いたいとします。以下が、合計 68 バイトのバイトコードで表されたリクエストです。

0xcdcd77c0000000000000000000000000000000000000000000000000000000000000004500...

1. コールデータの最初の 4 バイトを使ってメソッド ID を特定する

この場合、0xcdcd77c がメソッド baz を特定します。これはシグネチャ baz(uint32,bool)の ASCII 形式の Keccak ハッシュの最初の 4 バイトから導出されます。

2. 続く 32 バイトを使って最初のパラメータを特定する

0x00000000000000000000000000000000000000000000000000000000000000045 は最初のパラメータ 69 を表しており、これは 32 バイトにパディングされた uint32 値です。パディングとは、実際の数値の大きさに関わらず、文字列全体が(この場合)32 バイトになるよう 0 を追加することを意味します。

0x00000000000000000000000000000000000000000000000000000000000000045 

3. 最後の 32 バイトを使って2つ目のパラメータを特定する

2つ目のパラメータは true で、これは 32 バイトにパディングされた bool 値です。

0x0000000000000000000000000000000000000000000000000000000000000001

address、bool、uint32 のような静的な型はその場でエンコードされるのに対し、動的な型は別に確保された場所にエンコードされるため、動的な型を含むパラメータのエンコーディングは少し異なる見た目になります。

Solidity のイベントデータのバイナリはどう解釈すればよいか

イベントとは、メソッド呼び出しを実行する際にスマートコントラクトが発行するログのことで、イベントはバイナリデータとして公開されます。

イベントはパラメータを受け取ることができ、これによりイベントが何を出力するかを指定できます。これらのパラメータはインデックス化することができ、インデックス化されたパラメータをフィルタとして使うことで、そのイベントを検索できるようになります。これらのインデックス化されたパラメータは、Solidity の用語では topics とも呼ばれます。

おおまかに言うと、Solidity のイベントは以下のような構造を持ちます。

  • address: コントラクトのアドレス
  • topics[n]: 0~4個の topics、つまりインデックス化されたパラメータ
  • ABI に従って解析可能な、任意の長さのバイナリデータ

Solidity のバイナリを逆コンパイルするにはどのツールを使うべきか

Solidity のバイナリをより読みやすい形式に復元する助けとなる EVM デコンパイラは各種存在し、EtherVM DecompilerPanoramix decompiler などがあります。

これらの EVM デコンパイラは元のソースコードを完璧に再現するわけではありません(バイナリサイズを最小限にするために、名前などの重要な情報が削除されている場合があります)が、許可されている ABI リクエストについて大まかに理解する助けにはなるはずです。

Background gradient

ブロックチェーンで魔法を生み出す

Alchemyは、最も強力なweb3開発者向けプロダクトとツールを、豊富なリソース、コミュニティ、そして卓越したサポートと組み合わせて提供します。