solidity

应用二进制接口描述

2017-08-02  本文已影响22人  gaoer1938

翻译原文

date:20170801

基本设计

在以太坊生态系统中,应用二进制接口是跟合约交互的标准途径。从区块链外部或者合约之间的交互都可以通过这个方式。数据根据它的类型编码,我们将在这个文章中详细论述。编码不是自我描述的,所以如果要解码就需要模式(schema)。

我们假设合约的接口函数是强类型的,在编译的时候推断,且是静态的。没有提供反省机制。我们假设所有的合约都定义这样的接口,使得任何合约都可以在编译的时候调用。

该描述文档不描述接口是动态的合约或者另一种情况--在运行的时候才知道。这些情况是很重要的,因为他们可以构建以太坊生态系统的丰富的内建设施。(?This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem.)

函数选择器

函数调用的调用数据的前四个字节指出了所调用的函数。它是函数签名的keccak(SHA-3)的前四个字节(左边,高位,大端存储)。签名被定义为基本原型的权威表达式。例如,函数名称和用括号括起来的参数类型。参数类型通过一个逗号隔开-没有使用空格。

参数编码

从第五个字节开始,就是参数的编码。这个编码除了用在前四个字节指定函数外,也用在了其他地方。例如,返回值和时间参数也是用同样的方式。

类型

有下面几种基础类型:

下面是(固定大小的)数组类型:

下面是非固定大小的类型:

类型可以结合为匿名结构体,通过把有限数量的参数用圆括号包围,通过逗号隔开:

可以组合成结构体有结构体,结构体数组等结构。

编码的正式描述(?该章节尚未理解,翻译出错的概率很大,读者可以直接查看原文)

我们现在开始正式描述编码,它遵循下面的准则。如果参数中有嵌套数组,它们非常有用:

属性:

1. 为了获取一个值而读取的次数,差不多是值在参数数组结构里的深度。例如,如果要获取a_i[k][l][r],就要读取4次。在之前版本的ABI中,最坏的情况下,读取次数和动态参数的总数线性相关。
2.变量的值或者数组元素不会插入其他值,而且可以重新定位。例如,它只使用相关的“addresses”。

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

定义:以下的类型被称为是“动态的”:* bytes* string* 对于任意类型T的数组T[] 任意动态类型T的数组T[k],且K > 0* (T1,....,Tk),对于1 <= i <= k,如果Ti都是动态的。

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

定义:len(a)是任意字符串 a 的字节数。len(a)的类型假设为uint256

我们定义enc,真实的编码,作为ABI类型的映射值到二进制字符串,所以len(enc(X)),当且仅当X是动态的时候,依赖于X的值。

定义:对于任意的ABI的值X,我们递归定义enc(X)依赖于X的类型

注意,对于任意X,len(enc(x))都是32的整数倍。

函数选择器和参数编码

总之,调用f函数,并传递a_1,...,a_n参数会被编码为

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

并且f的返回值v_1,...,v_k会被编码为

enc((v_1,...v_k))

例如,返回值会组合为一个数组并且编码。

例子

给定的合约如下所示:

pragma solidity ^0.4.0;

contract Foo {
  function bar(bytes3[2] xy) {}
  function baz(uint32 x, bool y) returns (bool r) { r = x > 32 || y; }
  function sam(bytes name, bool z, uint[] data) {}
}

因此,对于Foo这个例子,如果我们想要调用baz,参数为69true。我们就得传递总共68字节,可以分解为:

合并为

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001

返回值为一个布尔量,例如,如果返回false,它的输出为单个字节数组
,0x0000000000000000000000000000000000000000000000000000000000000000,一个布尔量。

如果我们想要调用bar,参数为["abc","def"],我们就要传递总共68个字节,可以分解为

合并为

0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000

如果我们想要调用sam,参数为"dave",true和[1,2,3]。我们总共会传递292个字节,可以分解为:

合并为:

0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003

动态类型的使用

调用一个函数,f(uint,uint32[],bytes10,bytes),参数为(0x123,[0x456,0x789],"1234567890","Hello,world!"),会通过下面的方式编码:

我们取sha3(“f(uint256,uint32[],bytes10,bytes)”)的前四个字节,例如0x8be65246。然后我们编码四个参数的头部。对于静态类型的uint256bytes10,直接传递他们的值。但是对于动态类型uint32[]bytes10,我们使用他们在数据区域的偏移,测量值编码的起始位置(例如,不包含函数签名hash的前四个字节)。它们是:

这之后,后面紧跟第一个动态参数的数据部分,[0x456,0x789]:

最后我们会编码第二个动态参数的数据部分,”Hello world!“

合而为一,编码如下(函数选择器之后要换行,为了清晰,每行32字节):

0x8be65246
  0000000000000000000000000000000000000000000000000000000000000123
  0000000000000000000000000000000000000000000000000000000000000080
  3132333435363738393000000000000000000000000000000000000000000000
  00000000000000000000000000000000000000000000000000000000000000e0
  0000000000000000000000000000000000000000000000000000000000000002
  0000000000000000000000000000000000000000000000000000000000000456
  0000000000000000000000000000000000000000000000000000000000000789
  000000000000000000000000000000000000000000000000000000000000000d
  48656c6c6f2c20776f726c642100000000000000000000000000000000000000

事件

事件是以太坊日志/事件监听协议的抽象。日志实体提供了合约的地址,一系列但最多4个主题和任意长度的二进制数据。事件整合已存在的ABI,将他(和接口描述一起)翻译为合适类型的结构体。(?Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure.)

给定一个事件名称和一系列的事件参数,我们可以将它分为两个子系列:被索引的和没有被索引的。被索引的,最多3个,通过事件签名的Keccak哈希来组成日志实体的主题。另外没有索引的组成事件的byte数组。

实际上,使用ABI的日志实体被描述为:

JSON

合约接口的JSON形式通过一个函数数组 和/或 事件描述 给定。函数描述是一个JSON对象,具有如下的字段:

type可以删除,默认为"function".

构造函数和fallback函数没有名称或者输出。fallback函数也没有输入。

发送非零个以太币到非payable的函数会有异常,不要这么干。

一个事件的描述,是一个差不多字段的JSON对象。

例如,

pragma solidity ^0.4.0;

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": []
}]
上一篇 下一篇

猜你喜欢

热点阅读