Ethereum Contract ABI

以太坊合同ABI

William Entriken 编辑此页面 

此规范现在作为Solidity文档的一部分进行维护

功能

基本设计

我们假设应用程序二进制接口(ABI)是强类型的,在编译时和静态时是已知的。不会提供自检机制。我们断言,在编译是可调用的contract都具有接口定义

本规范不涉及接口是动态的或者运行时的情况。如果这些情况变得重要,那么它们可以作为以太坊生态系统内的设施进行充分处理。

Function Selector

函数调用的调用数据的前四个字节指定要调用的函数。它是函数的Keccak(SHA-3)哈希签名中的第一个(左边的,高位的big-endian)四个字节。签名被定义为基本原型的规范表达式,即带有括号的参数类型列表的函数名称。参数类型由一个逗号分隔 - 不使用空格。

参数编码

编码的参数跟随, 从第五个字节开始。这种编码也用于其他地方,例如返回值和事件参数都以相同的方式编码,而不用四个字节来指定函数。

类型

存在以下基本类型:

  • uint<M>:无符号整数类型,M个0 < M <= 256,,M % 8 == 0例如uint32uint8uint256
  • int<M>:二进制补码有符号整数类型的M0 < M <= 256,,M % 8 == 0
  • address:相当于uint160,除了假设的解释和语言类型。
  • uintint:同义词uint256int256分别(不被用于计算功能选择)。
  • bool:相当于uint8限制为值0和1
  • fixed<M>x<N>:有符号的定点小数的M比特,0 < M <= 256M % 8 ==0,和0 < N <= 80,其表示的值v作为v / (10 ** N)
  • ufixed<M>x<N>:无符号的变体fixed<M>x<N>
  • fixedufixed:同义词fixed128x19ufixed128x19分别(不被用于计算功能选择)。
  • bytes<M>M字节的二进制类型0 < M <= 32
  • function:相当于bytes24:一个地址,后跟一个功能选择器

以下(固定大小)数组类型存在:

  • <type>[M]:给定的固定长度类型的固定长度数组。

存在以下非固定大小类型:

  • bytes:动态大小的字节序列。
  • string:动态大小的unicode字符串假定为UTF-8编码。
  • <type>[]:给定的固定长度类型的可变长度数组。

编码的形式规范

此规范现在作为Solidity文档的一部分进行维护

现在我们将正式指定编码,以便它具有以下属性,如果某些参数是嵌套数组,则这些属性特别有用:

属性:

  1. 访问数值所需的读取次数至多是参数数组结构中值的深度,即需要四次读取才能检索a_i[k][l][r]在ABI的以前版本中,读数的数量与最坏情况下动态参数的总数成线性关系。

  2. 变量或数组元素的数据不与其他数据交错,并且是可重定位的,即它只使用相对“地址”

我们区分静态和动态类型。静态类型就地编码,动态类型在当前块之后的单独分配的位置进行编码。

定义:以下类型被称为“动态”:

  • bytes
  • string
  • T[] 为任何 T
  • T[k]任何动态T和任何k > 0

所有其他类型被称为“静态”。

定义: len(a)是二进制字符串中的字节数alen(a)假定的类型uint256

我们将enc实际编码定义为ABI类型的值到二进制字符串的映射,这len(enc(X))取决于X当且仅当该类型X 是动态的值。

定义:对于任何ABI值X,我们递归定义enc(X),根据类型X

  • T[k]对于任何Tk

    enc(X) = head(X[0]) ... head(X[k-1]) tail(X[0]) ... tail(X[k-1])

    其中headtail被定义为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[]哪里Xk元素(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)),即XUTF-8编码,这个值被解释为bytes类型和进一步编码。请注意,此后续编码中使用的长度是utf-8编码字符串的字节数,而不是其字符数。

  • uint<M>enc(X)是大端编码X,用零字节填充到较高(左)侧,长度是32字节的倍数。

  • address:就像uint160这样

  • int<M>enc(X)是大端的二进制补码编码X,在高位(左侧)填充0xff负数X,正数为零X,字长为32字节的倍数。

  • bool:在这种uint8情况下,1用于true0用于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 的字节序列

请注意,对于任何Xlen(enc(X))是32的倍数。

函数选择器和参数编码

总而言之,f使用参数调用函数a_1, ..., a_n被编码为

function_selector(f) enc([a_1, ..., a_n])

和返回值v_1, ..., v_kf被编码为

enc([v_1, ..., v_k])

其中的类型[a_1, ..., a_n][v_1, ..., v_k]被认为是固定长度的数组长度nk分别。请注意严格地说,它 [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与参数69true,我们将通过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然后我们编码所有四个参数的头部分。对于静态类型uint256bytes10,这些是直接我们想要传递的值,而对于动态类型uint32[]bytes,我们使用在字节偏移到它们的数据区的开始,从值编码开始测量(即,不计数前四个字节包含函数签名的散列)。这些是:

  • 0x00000000000000000000000000000000000000000000000000000000000001230x123填充到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)。如果该事件被声明为anonymoustopics[0]不产生;
  • topics[n]EVENT_INDEXED_ARGS[n - 1]EVENT_INDEXED_ARGS在一系列的EVENT_ARGS被索引);
  • dataabi_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如果函数不返回任何东西,则可以省略;
  • constanttrue如果函数被指定为永不修改区块链状态 ;
  • payabletrue如果函数接受ether,则默认为false

type可以省略,默认为"function"

构造函数和回退函数从来没有nameoutputs回退函数也没有inputs

发送非零以太币到非付款功能将抛出。不要这样做。

事件描述是一个JSON对象,具有相当类似的字段:

  • type:总是 "event"
  • name:事件的名称;
  • inputs:一个对象数组,每个对象都包含:
    • name:参数的名称;
    • type:参数的规范类型。
    • indexedtrue如果该字段是日志主题的一部分,false是否是其中的一个日志的数据段。
  • anonymoustrue如果事件被声明为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
  },
  { ...
  } ...
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值