以太坊合同ABI
此规范现在作为Solidity文档的一部分进行维护。
功能
基本设计
我们假设应用程序二进制接口(ABI)是强类型的,在编译时和静态时是已知的。不会提供自检机制。我们断言,在编译是可调用的contract都具有接口定义。
本规范不涉及接口是动态的或者运行时的情况。如果这些情况变得重要,那么它们可以作为以太坊生态系统内的设施进行充分处理。
Function Selector
函数调用的调用数据的前四个字节指定要调用的函数。它是函数的Keccak(SHA-3)哈希签名中的第一个(左边的,高位的big-endian)四个字节。签名被定义为基本原型的规范表达式,即带有括号的参数类型列表的函数名称。参数类型由一个逗号分隔 - 不使用空格。
参数编码
编码的参数跟随, 从第五个字节开始。这种编码也用于其他地方,例如返回值和事件参数都以相同的方式编码,而不用四个字节来指定函数。
类型
存在以下基本类型:
uint<M>
:无符号整数类型,M个
位0 < M <= 256
,,M % 8 == 0
。例如uint32
,uint8
,uint256
。int<M>
:二进制补码有符号整数类型的M
位0 < M <= 256
,,M % 8 == 0
。address
:相当于uint160
,除了假设的解释和语言类型。uint
,int
:同义词uint256
,int256
分别(不被用于计算功能选择)。bool
:相当于uint8
限制为值0和1fixed<M>x<N>
:有符号的定点小数的M
比特,0 < M <= 256
,M % 8 ==0
,和0 < N <= 80
,其表示的值v
作为v / (10 ** N)
。ufixed<M>x<N>
:无符号的变体fixed<M>x<N>
。fixed
,ufixed
:同义词fixed128x19
,ufixed128x19
分别(不被用于计算功能选择)。bytes<M>
:M
字节的二进制类型0 < M <= 32
。function
:相当于bytes24
:一个地址,后跟一个功能选择器
以下(固定大小)数组类型存在:
<type>[M]
:给定的固定长度类型的固定长度数组。
存在以下非固定大小类型:
bytes
:动态大小的字节序列。string
:动态大小的unicode字符串假定为UTF-8编码。<type>[]
:给定的固定长度类型的可变长度数组。
编码的形式规范
此规范现在作为Solidity文档的一部分进行维护。
现在我们将正式指定编码,以便它具有以下属性,如果某些参数是嵌套数组,则这些属性特别有用:
属性:
访问数值所需的读取次数至多是参数数组结构中值的深度,即需要四次读取才能检索
a_i[k][l][r]
。在ABI的以前版本中,读数的数量与最坏情况下动态参数的总数成线性关系。变量或数组元素的数据不与其他数据交错,并且是可重定位的,即它只使用相对“地址”
我们区分静态和动态类型。静态类型就地编码,动态类型在当前块之后的单独分配的位置进行编码。
定义:以下类型被称为“动态”:
bytes
string
T[]
为任何T
T[k]
任何动态T
和任何k > 0
所有其他类型被称为“静态”。
定义: len(a)
是二进制字符串中的字节数a
。len(a)
假定的类型是uint256
。
我们将enc
实际编码定义为ABI类型的值到二进制字符串的映射,这len(enc(X))
取决于X
当且仅当该类型X
是动态的值。
定义:对于任何ABI值X
,我们递归定义enc(X)
,根据类型X
是
T[k]
对于任何T
和k
:enc(X) = head(X[0]) ... head(X[k-1]) tail(X[0]) ... tail(X[k-1])
其中
head
和tail
被定义为X[i]
静态类型head(X[i]) = enc(X[i])
和tail(X[i]) = ""
(空字符串),head(X[i]) = enc(len(head(X[0]) ... head(X[k-1]) tail(X[0]) ... tail(X[i-1])))
tail(X[i]) = enc(X[i])
否则。请注意,在动态情况下,
head(X[i])
由于头部长度仅取决于类型而不取决于值,因此定义良好。它的值是开始tail(X[i])
相对于开始的偏移量enc(X)
。T[]
哪里X
有k
元素(k
假定是类型的uint256
):enc(X) = enc(k) enc([X[1], ..., X[k]])
即它被编码,就像它是一个静态大小的数组
k
,前缀的元素数量。bytes
,长度k
(假定是类型uint256
):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
,在高位(左侧)填充0xff
负数X
,正数为零X
,字长为32字节的倍数。bool
:在这种uint8
情况下,1
用于true
和0
用于false
fixed<M>x<N>
:enc(X)
是enc(X * 2**N)
在哪里X * 2**N
被解释为一个int256
。fixed
:就像fixed128x19
这样ufixed<M>x<N>
:enc(X)
是enc(X * 2**N)
在哪里X * 2**N
被解释为一个uint256
。ufixed
:就像ufixed128x19
这样bytes<M>
:enc(X)
是X
用零字节填充的长度为32 的字节序列。
请注意,对于任何X
,len(enc(X))
是32的倍数。
函数选择器和参数编码
总而言之,f
使用参数调用函数a_1, ..., a_n
被编码为
function_selector(f) enc([a_1, ..., a_n])
和返回值v_1, ..., v_k
的f
被编码为
enc([v_1, ..., v_k])
其中的类型[a_1, ..., a_n]
和[v_1, ..., v_k]
被认为是固定长度的数组长度n
和k
分别。请注意严格地说,它 [a_1, ..., a_n]
可以是一个包含不同类型元素的“数组”,但是编码仍然可以很好地定义,因为假定的通用类型T
(上面)并没有被实际使用。
例子
此规范现在作为Solidity文档的一部分进行维护。
下面的contract
contract Foo {
function bar(fixed[2] xy) {}
function baz(uint32 x, bool y) returns (bool r) { r = x > 32 || y; }
function sam(bytes name, bool z, uint[] data) {}
}
因此,对于我们的Foo
例子,如果我们想call baz
与参数69
和true
,我们将通过68个字节总,可细分为:
0xcdcd77c0
:方法ID。这是作为签名的ASCII形式的Keccak散列的前4个字节而得出的baz(uint32,bool)
。0x0000000000000000000000000000000000000000000000000000000000000045
:第一个参数,一个uint32值69
填充到32个字节0x0000000000000000000000000000000000000000000000000000000000000001
:第二个参数 - 布尔值true
,填充为32个字节
总共:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
它返回一个单一的bool
。例如,如果它返回false
,它的输出将是单字节数组0x0000000000000000000000000000000000000000000000000000000000000000
,单个布尔值。
如果我们想bar
用这个参数进行调用[2.125, 8.5]
,我们将总共传送68个字节,分解为:
0xab55044d
:方法ID。这是从签名派生的bar(fixed128x19[2])
。请注意,它将fixed
被其规范表示所替代fixed128x19
。0x0000000000000000000000000000000220000000000000000000000000000000
:第一个参数的第一部分,一个固定的128x19的值2.125
。0x0000000000000000000000000000000880000000000000000000000000000000
:第一个参数的第二部分,一个固定的128x19的值8.5
。
总共:
0xab55044d00000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000880000000000000000000000000000000
如果我们想叫sam
的论据"dave"
,true
而[1,2,3]
我们会通过292个字节总,细分为:
0xa5643bf2
:方法ID。这是从签名派生的sam(bytes,bool,uint256[])
。请注意,它将uint
被其规范表示所替代uint256
。0x0000000000000000000000000000000000000000000000000000000000000060
:第一个参数(动态类型)的数据部分的位置,从参数块的开始字节开始测量。在这种情况下0x60
。0x0000000000000000000000000000000000000000000000000000000000000001
:第二个参数:布尔值true。0x00000000000000000000000000000000000000000000000000000000000000a0
:第三个参数(动态类型)的数据部分的位置,以字节为单位。在这种情况下0xa0
。0x0000000000000000000000000000000000000000000000000000000000000004
:第一个参数的数据部分,它以元素中的字节数组的长度开始,在这种情况下是4。0x6461766500000000000000000000000000000000000000000000000000000000
:第一个参数的内容:UTF-8(在这种情况下等于ASCII)的编码"dave"
,在右边填充到32个字节。0x0000000000000000000000000000000000000000000000000000000000000003
:第三个参数的数据部分,它以元素中数组的长度开始,在这个例子中是3。0x0000000000000000000000000000000000000000000000000000000000000001
:第三个参数的第一个条目。0x0000000000000000000000000000000000000000000000000000000000000002
:第三个参数的第二个条目。0x0000000000000000000000000000000000000000000000000000000000000003
:第三个参数的第三个条目。
总共:
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
使用动态类型
此规范现在作为Solidity文档的一部分进行维护。
对f(uint,uint32[],bytes10,bytes)
具有值的签名的函数的调用(0x123, [0x456, 0x789], "1234567890", "Hello, world!")
按以下方式进行编码:
我们把前四个字节sha3("f(uint256,uint32[],bytes10,bytes)")
,即0x8be65246
。然后我们编码所有四个参数的头部分。对于静态类型uint256
和bytes10
,这些是直接我们想要传递的值,而对于动态类型uint32[]
和bytes
,我们使用在字节偏移到它们的数据区的开始,从值编码开始测量(即,不计数前四个字节包含函数签名的散列)。这些是:
0x0000000000000000000000000000000000000000000000000000000000000123
(0x123
填充到32个字节)0x0000000000000000000000000000000000000000000000000000000000000080
(第二个参数的数据部分的起始偏移,4 * 32个字节,正好是头部的大小)0x3132333435363738393000000000000000000000000000000000000000000000
("1234567890"
填充到右边的32个字节)0x00000000000000000000000000000000000000000000000000000000000000e0
(第四个参数的数据部分的起始偏移量=第一个动态参数的数据部分的起始偏移量+第一个动态参数的数据部分的大小= 4 * 32 + 3 * 32(见下文))
在此之后,第一个动态参数的数据部分[0x456, 0x789]
如下:
0x0000000000000000000000000000000000000000000000000000000000000002
(数组的元素个数,2)0x0000000000000000000000000000000000000000000000000000000000000456
(第一元素)0x0000000000000000000000000000000000000000000000000000000000000789
(第二元素)
最后,我们编码第二个动态参数的数据部分"Hello, world!"
:
0x000000000000000000000000000000000000000000000000000000000000000d
(元素数量(在这个例子中是字节数):13)0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000
("Hello, world!"
填充到右边的32个字节)
总之,编码是(换行符之后的换行符,每个32字节为了清晰起见):
0x8be65246
0000000000000000000000000000000000000000000000000000000000000123
0000000000000000000000000000000000000000000000000000000000000080
3132333435363738393000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000456
0000000000000000000000000000000000000000000000000000000000000789
000000000000000000000000000000000000000000000000000000000000000d
48656c6c6f2c20776f726c642100000000000000000000000000000000000000
活动
此规范现在作为Solidity文档的一部分进行维护。
事件是以太坊记录/事件监视协议的抽象。日志条目提供合同的地址,一系列最多四个主题和一些任意长度的二进制数据。事件利用现有的ABI函数来解释这个(连同一个接口规范)作为一个正确的类型结构。
给定一个事件名称和一系列的事件参数,我们将它们分成两个子系列:索引的和不是的。那些被编入索引的,可能数目最多为3的,与事件签名的Keccak散列一起使用,以形成日志条目的主题。那些没有索引的事件形成字节数组。
实际上,使用这个ABI的日志条目被描述为:
address
:合同地址(本质上由以太坊提供);topics[0]
:keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")
(canonical_type_of
就是简单地返回所述规范类型给定的参数,例如一个函数uint indexed foo
,它会返回uint256
)。如果该事件被声明为anonymous
在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序列化的功能,如上文所述)。
JSON
此规范现在作为Solidity文档的一部分进行维护。
合约接口的JSON格式由一组函数和/或事件描述给出。函数描述是一个带有字段的JSON对象:
type
:"function"
,,"constructor"
或"fallback"
(未命名的“默认”功能);name
:函数的名字;inputs
:一个对象数组,每个对象都包含:name
:参数的名称;type
:参数的规范类型。
outputs
:类似于对象的数组,inputs
如果函数不返回任何东西,则可以省略;constant
:true
如果函数被指定为永不修改区块链状态 ;payable
:true
如果函数接受ether,则默认为false
。
type
可以省略,默认为"function"
。
构造函数和回退函数从来没有name
或outputs
。回退函数也没有inputs
。
发送非零以太币到非付款功能将抛出。不要这样做。
事件描述是一个JSON对象,具有相当类似的字段:
type
:总是"event"
name
:事件的名称;inputs
:一个对象数组,每个对象都包含:name
:参数的名称;type
:参数的规范类型。indexed
:true
如果该字段是日志主题的一部分,false
是否是其中的一个日志的数据段。
anonymous
:true
如果事件被声明为anonymous
。
例如,
contract Test { function Test(){ b = 0x12345678901234567890123456789012; } event Event(uint indexed a, bytes32 b); event Event2(uint indexed a, bytes32 b); function foo(uint a) { 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": []
}]
Javascript使用示例
此规范现在作为Solidity文档的一部分进行维护。
var Test = eth.contract(
[{
"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": []
}]);
var theTest = new Test(addrTest);
// examples of usage:
// every log entry ("event") coming from theTest (i.e. Event & Event2):
var f0 = eth.filter(theTest);
// just log entries ("events") of type "Event" coming from theTest:
var f1 = eth.filter(theTest.Event);
// also written as
var f1 = theTest.Event();
// just log entries ("events") of type "Event" and "Event2" coming from theTest:
var f2 = eth.filter([theTest.Event, theTest.Event2]);
// just log entries ("events") of type "Event" coming from theTest with indexed parameter 'a' equal to 69:
var f3 = eth.filter(theTest.Event, {'a': 69});
// also written as
var f3 = theTest.Event({'a': 69});
// just log entries ("events") of type "Event" coming from theTest with indexed parameter 'a' equal to 69 or 42:
var f4 = eth.filter(theTest.Event, {'a': [69, 42]});
// also written as
var f4 = theTest.Event({'a': [69, 42]});
// options may also be supplied as a second parameter with `earliest`, `latest`, `offset` and `max`, as defined for `eth.filter`.
var options = { 'max': 100 };
var f4 = theTest.Event({'a': [69, 42]}, options);
var trigger;
f4.watch(trigger);
// call foo to make an Event:
theTest.foo(69);
// would call trigger like:
//trigger(theTest.Event, {'a': 69, 'b': '0x12345678901234567890123456789012'}, n);
// where n is the block number that the event triggered in.
执行:
// e.g. f4 would be similar to:
web3.eth.filter({'max': 100, 'address': theTest.address, 'topics': [ [69, 42] ]});
// except that the resultant data would need to be converted from the basic log entry format like:
{
'address': theTest.address,
'topics': [web3.sha3("Event(uint256,bytes32)"), 0x00...0045 /* 69 in hex format */],
'data': '0x12345678901234567890123456789012',
'number': n
}
// into data good for the trigger, specifically the three fields:
Test.Event // derivable from the first topic
{'a': 69, 'b': '0x12345678901234567890123456789012'} // derivable from the 'indexed' bool in the interface, the later 'topics' and the 'data'
n // from the 'number'
活动结果:
[ {
'event': Test.Event,
'args': {'a': 69, 'b': '0x12345678901234567890123456789012'},
'number': n
},
{ ...
} ...
]