Contract ABI Specification¶
Basic Design¶
コントラクトにおけるアプリケーションバイナリインターフェース(Application Binary Interface, ABI)はイーサリアムエコシステム内でコントラクトに接続するスタンダードなインターフェースです。コントラクトもABIもブロックチェーン外で作成され、ABIはコントラクトとコントラクトを接続するために使用されます。このドキュメントで紹介されているように、データはABIに従う形でエンコーディングされます。このエンコーディングのやり方は自己記述的なものではありません。そのため、デコードを行う際には、あるスキーマで行うことが必要になります。
コントラクトのインターフェースは型付けされていることが強制されます。このことはコンパイル時に静的型付けであることとしてすでに知られていることです。すべてのコントラクトは、コンパイル時に呼び出し可能なすべてのコントラクトのインターフェース定義を内包します。
この仕様は動的なインターフェースやランタイムにのみ認識されるインターフェースを持つコントラクトには対応しません。
Function selector¶
関数呼び出し時のコールデータの最初の4バイトは呼び出す関数を指定しています。関数シグネチャにおけるKeccak-256(SHA-3)ハッシュの最初の4バイト(左から4バイトでビッグ・エンディアンの順序になっています)のことです。このシグネチャはデータ位置指定子(data location specifier)を含まない基本プロトタイプの正規表現として定義されています。例えば、かっこで囲まれたパラメータ型のリストを含む関数名などが挙げられます。パラメータの型は単一のコンマで区切られ、スペースは使用されません。
注釈
関数の戻り値の型はこのシグニチャの一部ではありません。 Solidityの関数のオーバーロード においては、戻り値の型は考慮されません。これは関数呼び出しを特定のコンテキストに依存しないようにするためです。 しかし、ABIのJSON記述 は入力値と出力値の両方を含むことに注意が必要です。
Argument Encoding¶
先頭から数えて5バイト目から、エンコーディングされた引数が続きます。このエンコーディングされた引数は、特定の関数を指定する4バイトを除いて、戻り値などの別の場所でも使用されます。また、イベント引数も同やり方でエンコーディングされます。
Types¶
基本の型は以下です:
uint<M>:Mビットのunsigned int型。0 < M <= 256とM % 8 == 0である必要があります。uint32、uint8、uint256が挙げられます。int<M>: 2の補数がつくMビットのsigned int型。0 < M <= 256、M % 8 == 0である必要があります。address:uint160と同等です。ただしこの場合、uint160が暗黙的な値や型であるときを除きます。関数セレクタを計算する際に、addressは使用されます。uint,int:uint256やint256と同じ意味です。関数セレクタを計算する際に、uint256とint256は必ず使用されなくてはなりません。bool:uint8と同等です。ただしこの場合、値は0と1のみである必要があります。 関数セレクタを計算する際に、boolが使用されます。fixed<M>x<N>:Mビットの符号付き固定小数点。8 <= M <= 256、M % 8 ==0、0 < N <= 80である必要があり、vasv / (10 ** N)であることを示します。ufixed<M>x<N>:fixed<M>x<N>の符号付き変数。fixed,ufixed:fixed128x18やufixed128x18と同じ意味です。関数セレクタを計算する際に、fixed128x18とufixed128x18は必ず使用されなくてはなりません。bytes<M>:Mバイトのバイナリ型。0 < M <= 32です。function: アドレス(20バイト)とそれに続く関数セレクタ(4バイト)。bytes24と同じエンコーディングです。
固定長な型は以下です:
<type>[M]:M個要素の固定長配列です。与えられた型のM >= 0になります。
可変長な型は以下です:
bytes: 動的サイズのバイトシーケンス。string: UTF-8でエンコードされている動的サイズのUnicode文字列。<type>[]: 指定されたタイプの要素の可変長配列。
データ型はカンマで区切って括弧で囲むとタプルにまとめることができます:
(T1,T2,...,Tn): 同じデータ型T1...,Tn,n >= 0で構成されたタプル。
また、タプルのタプル、タプルの配列なども形成することができます。ゼロタプル( n == 0 )を作ることもできます。
Mapping Solidity to ABI types¶
Solidityは上記のすべての型を同じ名前でサポートしています(ただしタプルは例外です)。一方で、SolidityタイプのいくつかはABIにはサポートされていません。次の表は、左列にABIにサポートされていないSolidityのデータ型を、右列にはABI側でのそのデータ型の表記を表しています。
| Solidity | ABI |
|---|---|
| address payable | address |
| contract | address |
| enum | smallest For example, an |
| struct | tuple |
Design Criteria for the Encoding¶
エンコーディングは次のプロパティを持つように設計されています。これは、引数がネストされた配列の場合に特に便利です。
- 値にアクセスするのに必要な読み込みの数は、引数となる配列構造内の値の最大の深さに依ります。すなわち、
a_i [k] [l] [r]を取得するには4回の読み込みが必要になるということです。以前のバージョンのABIでは、最悪の場合、読み取り数は動的パラメータの総数に比例して増減しました。- 変数または配列要素のデータは他のデータとインターリーブされません。またこれらのデータは再配置可能です。つまり、相対的な「アドレス」のみを使用します。
Formal Specification of the Encoding¶
静的型と動的型を区別するようにします。静的型はインプレースでエンコードされ、動的型はカレントブロック後に別々に割り当てられた場所でエンコードされます。
定義: 次の型は「動的型」です:
bytesstring- 任意の
TにおけるT[] - 任意の動的な
Tと任意のk >= 0におけるT[k] Tiが1 <= i <= kに対して動的である場合の(T1,...,Tk)
上記以外のすべてのすべての型は「静的型」です。
定義: len(a) はバイナリ文字列 a のバイト数です。len(a) の型は uint256 であると仮定されます。
実際のエンコーディングである enc は、ABI型の値を以下のようなバイナリ文字列にマッピングするものとして定義します。len(enc(X)) は X の型が動的である場合に限り、X の値に依存します。
定義: 任意のABI値 X に対して、下記の X の型によって再帰的に enc(X) を定義します。
k >= 0かつ任意の型T1, ...,Tkにおける(T1,...,Tk)のときは以下の様になります:enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))Tiが静的型である場合、X = (X(1)), ..., X(k))とheadとtailは以下のようにhead(X(i)) = enc(X(i))およびtail(X(i)) = ""(空の文字列)や
head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)) ))tail(X(i)) = enc(X(i))となります。
上記以外の場合は、
Tiは動的型です。動的な場合、
head(X(i))は ヘッド部分がそのデータ型にのみ依存し、値には依存しません。またその値はenc(X)の先頭からtail(X(i))の先頭までの範囲でのオフセットです。任意の
TとkにおけるT[k]のときは以下になります:enc(X) = enc((X[0], ..., X[k-1]))すなわち、同じ型の
k要素を持つタプルであるかのようにエンコードされます。Xがk要素を持つとき(kはuint256型であると仮定されます)のT[]のときは以下になります:
enc(X) = enc(k) enc([X[0], ..., X[k-1]])すなわち、要素数を前に付けた静的サイズの
kの配列であるかのようにエンコードされます。
長さが
k(これはuint256型であると仮定されます)のbytesのときは以下になります:enc(X) = enc(k) pad_right(X)。すなわち、バイト数はuint256の後にバイトシーケンスとしてのXの実際の値が続き、その後にlen(enc(X))が32の倍数になるような最小ゼロバイト数が続きます。stringのときは以下になります:enc(X) = enc(enc_utf8(X))。すなわち、XはUTF-8でエンコードされており、この値はbytes型として解釈され、さらにエンコードされます。この後のエンコーディングで使用される長さは、その文字数ではなく、utf-8でエンコードされた文字列のバイト数です。uint<M>:enc(X)はXのビッグエンディアンエンコーディングで、長さが32バイトになるように高位(左側)にゼロバイトが埋め込まれます。address:uint160の場合と同じです。int<M>:enc(X)は、Xのビッグエンディアンの2の補数エンコーディングで、負のXの場合は高位(左側)に0xffがパディングされ、長さが32バイトになるような正のXの場合はゼロバイトがパディングされます。bool:uint8の場合のように、1はtrueが使われ、0はfalseが使われます。fixed<M>x<N>:enc(X)はenc(X * 10**N)です。ここでX * 10**Nはint256として解釈されます。fixed:fixed128x18の場合と同じです。ufixed<M>x<N>:enc(X)はenc(X * 10**N)です。ここでX * 10**Nはuint256として解釈されます。ufixed:fixed128x18の場合と同じです。bytes<M>:enc(X)は末尾の0バイトを32バイトの長さまで埋め込んだX内のバイトの並びです。
任意のXにおいて、len(enc(X))は32の倍数であることを念頭に入れてください。
Function Selector and Argument Encoding¶
たいていの場合、a_1, ..., a_n をパラメータとして関数 f をコールするときには次のようにエンコーディングされます。
function_selector(f) enc((a_1, ..., a_n))
また、f の返り値である v_1, ..., v_k は次のようにエンコーディングされます。
enc((v_1, ..., v_k))
すなわち、値はタプルに組み合わされてエンコードされます。
Examples¶
次のコントラクトがあります:
pragma solidity >=0.4.16 <0.6.0;
contract Foo {
function bar(bytes3[2] memory) public pure {}
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function sam(bytes memory, bool, uint[] memory) public pure {}
}
このように Foo の例では、もしパラメータに「69」と「true」を指定して「baz」を呼び出すなら、合計68バイトを渡します。このことは次のように分割できます。
0xcdcd77c0: 指定のメソッドID。これはbaz(uint32,bool)の署名のASCII形式のKeccakハッシュにおける最初の4バイトとして導出されます。0x0000000000000000000000000000000000000000000000000000000000000045: 32バイトにパディングされた最初のパラメータ69のuint32値。0x0000000000000000000000000000000000000000000000000000000000000001: 32バイトにパディングされた2番目のパラメータtrueのブール値。
これらを連結すると:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
一つの bool を返します。たとえば、false を返すことになっていたら、その出力はシングルバイト配列 0x000000000000000000000000000000000000000000000000000000000000、シングルブールになります。
これは1つの bool 値を返します。 もしここで例えばこの値が false を返すなら、その出力はバイト配列の 0x0000000000000000000000000000000000000000000000000000000000000000 となるでしょう。
引数 ["abc"、"def"] と共に bar を呼び出したい場合は、合計68バイトを渡します。これは次のように分割されます:
0xfce353f6: 指定のメソッドID。これはbar(bytes3[2])の署名から導出されます。0x6162630000000000000000000000000000000000000000000000000000000000: 最初の引数の最初のパート、bytes3の値"abc"(左寄せ)。0x6465660000000000000000000000000000000000000000000000000000000000: 最初の引数の2番目のパート、bytes3の値"def"(左寄せ)。
これらを連結すると:
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
引数として "dave"、true および [1,2,3] を付けて sam を呼び出したい場合、合計292バイトを渡します。
0xa5643bf2: 指定のメソッドID。 これはsam(bytes,bool,uint256[])の署名から導出されます。uintはその正規表現uint256に置き換えられていることを念頭に入れてください。0x0000000000000000000000000000000000000000000000000000000000000060: 引数ブロックの先頭からのバイト単位で測定された、最初のパラメータ(動的型)のデータ部分の位置。この場合は0x60です。0x0000000000000000000000000000000000000000000000000000000000000001: 2番目のパラメータ:trueのブール値。0x00000000000000000000000000000000000000000000000000000000000000a0: 3番目のパラメータ(動的型)のデータ部分の位置(バイト単位)。この場合、0xa0です。0x0000000000000000000000000000000000000000000000000000000000000004: 最初の引数のデータ部分。要素のバイト配列の長さで始まります。この場合は4です。0x6461766500000000000000000000000000000000000000000000000000000000: 最初の引数の内容:32バイトまで右側にパディングしたUTF-8(この場合はASCIIに等しくなります)エンコーディング。0x0000000000000000000000000000000000000000000000000000000000000003: 3番目の引数のデータ部分。要素の配列の長さから始まります。この場合は3です。0x0000000000000000000000000000000000000000000000000000000000000001: 3番目のパラメータの最初のデータ0x0000000000000000000000000000000000000000000000000000000000000002: 3番目のパラメータの2番目のデータ0x0000000000000000000000000000000000000000000000000000000000000003: 3番目のパラメータの3番目のデータ
これらを連結すると:
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
Use of Dynamic Types¶
(0x123、[0x456、0x789]、"1234567890"、"Hello、world!")の値を持つシグネチャf(uint、uint32[]、bytes10、bytes)を持つ関数コールは次のようにエンコードされます:
sha3("f(uint256,uint32[],bytes10,bytes)")の最初の4バイトを取ります。すなわち、0x8be65246です。
それから、4つの引数すべての先頭部分をエンコードします。静的型である uint256 と bytes10 の場合、これらは直接渡したい値ではありますが、動的型の uint32[] と bytes の場合は、値エンコーディングの開始から測定された、データ領域の開始までのバイト単位のオフセットを使用します(つまり、関数シグネチャのハッシュを含む最初の4バイトはカウントしません)。これらは:
0x0000000000000000000000000000000000000000000000000000000000000123(32バイトにパディングされた0x123の値)0x0000000000000000000000000000000000000000000000000000000000000080(2番目のパラメータのデータ部の先頭までのオフセット。4 * 32バイト。正確には先頭部分のサイズ)0x3132333435363738393000000000000000000000000000000000000000000000(32バイトに右詰めにパディングされた"1234567890"の値)0x00000000000000000000000000000000000000000000000000000000000000e0(4番目のパラメータのデータ部の先頭までのオフセット = 最初の動的パラメータのデータ部の先頭までのオフセット + 最初の動的パラメータのデータ部のサイズ = 4 * 32 + 3 * 32(下記を参照してください)))
このあと、最初の動的引数のデータ部分である [0x456, 0x789] は次のようになります:
0x0000000000000000000000000000000000000000000000000000000000000002(配列の要素数、2)0x0000000000000000000000000000000000000000000000000000000000000456(最初の要素)0x0000000000000000000000000000000000000000000000000000000000000789(2番目の要素)
最後に、2番目の動的引数のデータ部分、"Hello, world!" をエンコードします:
0x000000000000000000000000000000000000000000000000000000000000000d(要素数(この場合バイトになります): 13)0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000(32バイトになるように右側にパディングした"Hello, world!")
こうして、エンコーディングは次のとおりです(関数セレクタの後に改行、わかりやすくするために各32バイト):
0x8be65246
0000000000000000000000000000000000000000000000000000000000000123
0000000000000000000000000000000000000000000000000000000000000080
3132333435363738393000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000456
0000000000000000000000000000000000000000000000000000000000000789
000000000000000000000000000000000000000000000000000000000000000d
48656c6c6f2c20776f726c642100000000000000000000000000000000000000
([[1、2]、[3]]、["one"、"two"、"three"]) の値で署名付きの関数 g(uint[][],string[]) に同じ原則を適用します。しかし、エンコーディングの最も基本的な部分から始めましょう:
まずはじめに、最初のルート配列 [[1, 2], [3]] の最初の動的な要素配列 [1, 2] の長さとデータをエンコードします。
0x0000000000000000000000000000000000000000000000000000000000000002(最初の配列の要素数である2。ただし要素自体は1と2です)0x0000000000000000000000000000000000000000000000000000000000000001(最初の要素)0x0000000000000000000000000000000000000000000000000000000000000002(2番目の要素)
次に、最初のルート配列 [[1、2]、[3]] の2番目の動的な要素配列 [3] の長さとデータをエンコードします。
0x0000000000000000000000000000000000000000000000000000000000000001(2番目の配列の要素数である1。要素は3です)0x0000000000000000000000000000000000000000000000000000000000000003(最初の要素)
それから、それぞれの動的配列 [1、2] と [3] に対するオフセット a と b を見つける必要があります。オフセットを計算するために、エンコーディングの各行を列挙している最初のルート配列 [[1、2]、[3]] のエンコードされたデータを見ることができます。
0 - a - [1, 2]のオフセット
1 - b - [3]のオフセット
2 - 0000000000000000000000000000000000000000000000000000000000000002 - [1, 2]の要素数
3 - 0000000000000000000000000000000000000000000000000000000000000001 - 1のエンコーディング
4 - 0000000000000000000000000000000000000000000000000000000000000002 - 2のエンコーディング
5 - 0000000000000000000000000000000000000000000000000000000000000001 - [3]の要素数
6 - 0000000000000000000000000000000000000000000000000000000000000003 - 3のエンコーディング
オフセット a は、2行目(64バイト)の配列 [1、2] の始まりを指しています。したがって、a = 0x0000000000000000000000000000000000000000000000000000000000000040 です。
オフセット b は、配列 [3] の5行目(160バイト)の先頭を指します。したがって、b = 0x00000000000000000000000000000000000000000000000000000000000000a0 です。
そして、2番目のルート配列の埋め込み文字列をエンコードします:
0x0000000000000000000000000000000000000000000000000000000000000003(単語"one"の中の文字数)0x6f6e650000000000000000000000000000000000000000000000000000000000(単語"one"のutf8での表現)0x0000000000000000000000000000000000000000000000000000000000000003(単語"two"の中の文字数)0x74776f0000000000000000000000000000000000000000000000000000000000(単語"two"のutf8での表現)0x0000000000000000000000000000000000000000000000000000000000000005(単語"three"の中の文字数)0x7468726565000000000000000000000000000000000000000000000000000000(単語"three"のutf8での表現)
最初のルート配列と並行して、文字列は動的要素なので、それらのオフセット c 、d および e を見つける必要があります:
0 - c - "one"のオフセット
1 - d - "two"のオフセット
2 - e - "three"のオフセット
3 - 0000000000000000000000000000000000000000000000000000000000000003 - "one"の文字列数
4 - 6f6e650000000000000000000000000000000000000000000000000000000000 - "one"のエンコーディング
5 - 0000000000000000000000000000000000000000000000000000000000000003 - "two"の文字列数
6 - 74776f0000000000000000000000000000000000000000000000000000000000 - "two"のエンコーディング
7 - 0000000000000000000000000000000000000000000000000000000000000005 - "three"の文字列数
8 - 7468726565000000000000000000000000000000000000000000000000000000 - "three"のエンコーディング
オフセット c は、文字列 "one" の3行目(96バイト)の先頭を指します。したがって、c = 0x0000000000000000000000000000000000000000000000000000000000000060 です。
オフセット d は、文字列 "two" の先頭を指します。これは5行目(160バイト)です。したがって、d = 0x00000000000000000000000000000000000000000000000000000000000000a0 です。
オフセット e は、文字列 "three" の7行目(224バイト)の先頭を指します。したがって、e = 0x00000000000000000000000000000000000000000000000000000000000000e0 です。
ルート配列の埋め込み要素のエンコーディングは互いに依存関係がなく、シグネチャが g(string[],uint[][]) の関数に対して同じエンコーディングを持つことに注意してください。
次に、最初のルート配列の長さをエンコードします:
0x0000000000000000000000000000000000000000000000000000000000000002(最初のルート配列の要素数である2。要素自体は[1, 2]と[3]です)
次に、2番目のルート配列の長さをエンコードします:
0x0000000000000000000000000000000000000000000000000000000000000003(2番目のルート配列の文字列数である3。文字列自体は"one"、"two"及び"three"です)
最後に、それぞれのルート動的配列 [[1, 2], [3]] と ["one", "two", "three"] に対するオフセット f と g を見つけます。そして、正しい順序でこれらのパーツを組み立てます。
0x2289b18c - 関数シグニチャ
0 - f - [[1, 2], [3]]のオフセット
1 - g - ["one", "two", "three"]のオフセット
2 - 0000000000000000000000000000000000000000000000000000000000000002 - [[1, 2], [3]]の要素数
3 - 0000000000000000000000000000000000000000000000000000000000000040 - [1, 2]のオフセット
4 - 00000000000000000000000000000000000000000000000000000000000000a0 - [3]のオフセット
5 - 0000000000000000000000000000000000000000000000000000000000000002 - [1, 2]の要素数
6 - 0000000000000000000000000000000000000000000000000000000000000001 - 1のエンコーディング
7 - 0000000000000000000000000000000000000000000000000000000000000002 - 2のエンコーディング
8 - 0000000000000000000000000000000000000000000000000000000000000001 - [3]の要素数
9 - 0000000000000000000000000000000000000000000000000000000000000003 - 3のエンコーディング
10 - 0000000000000000000000000000000000000000000000000000000000000003 - ["one", "two", "three"]の要素数
11 - 0000000000000000000000000000000000000000000000000000000000000060 - "one"のエンコーディング
12 - 00000000000000000000000000000000000000000000000000000000000000a0 - "two"のオフセット
13 - 00000000000000000000000000000000000000000000000000000000000000e0 - "three"のオフセット
14 - 0000000000000000000000000000000000000000000000000000000000000003 - "one"の文字列数
15 - 6f6e650000000000000000000000000000000000000000000000000000000000 - "one"のエンコーディング
16 - 0000000000000000000000000000000000000000000000000000000000000003 - "two"の文字列数
17 - 74776f0000000000000000000000000000000000000000000000000000000000 - "two"のエンコーディング
18 - 0000000000000000000000000000000000000000000000000000000000000005 - "three"の文字列数
19 - 7468726565000000000000000000000000000000000000000000000000000000 - "three"のエンコーディング
オフセット f は、配列 [[1、2]、[3]] の2行目(64バイト)の先頭を指します。したがって、f = 0x0000000000000000000000000000000000000000000000000000000000000040 です。
オフセット g は、配列 ["one"、"two"、"three"] の先頭を指します。これは10行目(320バイト)です。したがって、g = 0x0000000000000000000000000000000000000000000000000000000000000140 です。
Events¶
イベントはEthereumロギング/イベント監視プロトコルを抽象化したものです。ログエントリは、コントラクトアドレス、最大4つまでの一連のトピック、及び任意の長さのバイナリデータを提供します。イベントは既存の関数ABIを(インターフェース仕様とともに)正しく型付けされた構造体として解釈するために利用します。
イベント名と一連のイベントパラメータが与えられたとき、それらを2つのサブシリーズに分けます。それはインデックスされているものとそうでないものです。インデックス付けされたもの(最大3まで)は、イベント署名のKeccakハッシュと一緒に使用されて、ログエントリのトピックを形成します。インデックスが付けられていないものはイベントのバイト配列を形成します。
実際には、このABIを使用するログエントリは次のように記述されています:
address: コントラクトアドレス(Ethereumから提供されるものです)topics[0]:keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")(canonical_type_ofは与えられた引数の標準型を単純に返す関数です。uint indexed fooの場合は、uint256を返します。) イベントが匿名として宣言されている場合、topics[0]は生成されません。topics[n]:EVENT_INDEXED_ARGS[n - 1](EVENT_INDEXED_ARGSはインデックスが付けられた一連のEVENT_ARGSです)data:abi_serialise(EVENT_NON_INDEXED_ARGS)(EVENT_NON_INDEXED_ARGSはインデックスされていない一連のEVENT_ARGS、abi_serialiseは上述のように関数から一連の型付き値を返すために使用されるABIシリアライゼーション関数です)
すべての固定長のSolidityの型において、EVENT_INDEXED_ARGS 配列は32バイトでエンコードされた値を直接含みます。しかし、string、bytes、配列を含む 動的長 型の場合、EVENT_INDEXED_ARGS はパックされたエンコード値の Keccakハッシュ を含みます( Strict Encoding Mode を参照ください)。これにより、アプリケーションは(エンコードされた値のハッシュをトピックとして設定することによって)動的長の型の値を効率的に照会できますが、照会していない索引付きの値をデコードできません。動的長型の場合、アプリケーション開発者は、事前定義された値の高速検索(引数がインデックス付けされている場合)と任意の値の読みやすさ(引数にインデックスが付けられていないことが必要)との間のトレードオフに直面します。開発者はこのトレードオフを克服し、同じ値を保持することを目的とした2つの引数(1つはインデックス付き、1つはない)でイベントを定義することによって、効率的な検索と任意の読みやすさの両方を実現できます。
JSON¶
コントラクトインターフェースのJSONフォーマットは、関数やイベントのデスクリプションの配列によって指定されます。
関数の説明はフィールドを持つJSONオブジェクトです:
type:"function"、"constructor"、及び"fallback"( unnamed "default" function)name: 関数の名前inputs: それぞれが含むオブジェクトの配列:name: パラメータの名前type: パラメータの標準型(詳細は後述)components: タプル型に使用されます(詳細は後述)
outputs: functionが何も返さない場合は、inputに似たオブジェクトの配列を省略することができます。stateMutability: 以下のいずれかの値を持つ文字列:pure(specified to not read blockchain state),view(specified to not modify the blockchain state),nonpayable(function does not accept Ether) andpayable(function accepts Ether);payable: もし関数がEtherを受け付けるなら、true。そうでないなら、false。constant: もし関数がpureかviewのいづれかなら、true。そうでないなら、false。
type は省略することができ、"function" にデフォルト設定され、同様に payable と constant は省略することができ、両方とも false にデフォルト設定されます。
コンストラクタとフォールバック関数は、name や outputs などを持つことはありません。フォールバック関数も inputs を持つこともありません。
警告
constant や payable は今後廃止予定であり、今後取り除かれます。代わりに、stateMutability 領域が今後は同じプロパティを決定するために使うことができます。
注釈
ゼロ以外の額のイーサリアムをnon-payable functionに送金するときは、トランザクションにリバートが発生します。
イベントの詳細は似たフィールドを持つJSONオブジェクトです:
type: 常に"event"です。name: イベントの名前;inputs: 以下の値を含むオブジェクトの配列:name: パラメータの名前type: 標準的なパラメータの型 (詳細は以下を参照してください)components: タプルに使用する(詳細は以下を参照してください)indexed: もしフィールドがログトピックスの一部であるなら、true。もしログデータセグメントであるならfalse
anonymous: もしイベントがanonymousと宣言されていたならtrue
例えば、
pragma solidity ^0.5.0;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
これはJSONで表現すると以下のようになります:
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]
Handling tuple types¶
名前(names)はABIエンコーディングの一部にあたるわけではないですが、ABIに含めることには意味があります。これはJSON形式でエンドユーザーに表示できるという点にあります。 その構造は次のようにネストされています:
nameやtype、潜在的にはcomponentsなどと共にオブジェクトは型付きの変数を記述します。
正規型はタプル型に達するまで決定され、それまでの文字列の説明は type プレフィックスに tuple という語で格納されます。つまり、tuple の後に整数の k を持つ [] と [k] のシーケンスが続きます。
タプルの構成要素はメンバの構成要素に格納されます。これは配列型で、インデックスが許可されていないことを除いて最上位オブジェクトと同じ構造を持ちます。
例として、次のコードは
pragma solidity >=0.4.19 <0.6.0;
pragma experimental ABIEncoderV2;
contract Test {
struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public;
function g() public returns (S memory s, T memory t, uint a);
}
以下のようなJSONとなります:
[
{
"name": "f",
"type": "function",
"inputs": [
{
"name": "s",
"type": "tuple",
"components": [
{
"name": "a",
"type": "uint256"
},
{
"name": "b",
"type": "uint256[]"
},
{
"name": "c",
"type": "tuple[]",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "uint256"
}
]
}
]
},
{
"name": "t",
"type": "tuple",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "uint256"
}
]
},
{
"name": "a",
"type": "uint256"
}
],
"outputs": []
}
]
Strict Encoding Mode¶
Strict encoding modeは上記の仕様で定義されているものとまったく同じ符号化につながるモードです。 これは、データ領域にオーバーラップが発生しないようにしながらオフセットをできるだけ小さくする必要があるため、あらゆるギャップが許容されないことを意味します。
大抵の場合、ABIデコーダはオフセットポインタの直後に書かれていますが、Strict encoding modeを強制するデコーダもあります。 SolidityのABIデコーダは現在Strict encoding modeを強制しませんが、エンコーダは常にStrict encoding modeでデータを作成します。
Non-standard Packed Mode¶
abi.encodePacked()を通じて、SolidityはNon-standard Packed Modeをサポートしています:
- 32バイトより短い型はゼロパディングも符号拡張もされず、
- 動的型はその場でlengthを持たずにエンコードされます
Non-standard Packed Modeは主にインデックス付きイベントパラメータに使用されます。
例として、int16(-1), bytes1(0x42), uint16(0x03), string("Hello, world!") のエンコーディングは次のようになります:
0xffff42000348656c6c6f2c20776f726c6421
^^^^ int16(-1)
^^ bytes1(0x42)
^^^^ uint16(0x03)
^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field
- より詳細にいうと:
- 各値型は、その範囲と同じ数のバイトを取ります。
- 構造体または固定長配列のエンコーディングは、そのメンバー/要素のエンコーディングを連結したもので、区切り文字やパディングはありません。
- 構造体のマッピングメンバーは通常どおり認識されません。
string、bytes、uint []のような動的型はそれらのlengthフィールドなしでエンコードされます。
一般に、lengthフィールドが欠落しているため、2つの動的な要素があるとすぐに、エンコードはあいまいなものになってしまいます。
もしパディングが必要であれば、次のような明示的な型変換が使えるでしょう: abi.encodePacked(uint16(0x12)) == hex"0012"
パックエンコーディングは関数呼び出しの際に使われるものではないため、関数セレクタを準備するためのサポートがあるわけではありません。 また、エンコーディングが曖昧であるため、復号化のための関数は存在しません。