简介:本文将介绍Solidity语言的调用数据的布局和ABI详解。其中调用数据的布局将主要介绍以太坊合约间调用时的消息格式ABI。

好久时间没有更新文章,前文中我们介绍了Solidity的特性与内部机制,本文我们将Solidity的调用数据的布局和ABI详解。

用数据的布局(Layout of CallData)

当Solidity合约被部署后,从某个帐户调用这个合约,需要输入的数据是需要符合the ABI specification, ABI是以太坊的一种合约间调用时的一个消息格式。类似Webservice里的SOAP协议一样;也就是定义操作函数签名,参数编码,返回结果编码等。

ABI详解

函数 基本设计思想

使用ABI协议时必须要求在编译时知道类型,也就是说不支持动态语言那样的声明的变量还会变类型的情况。由于协议假设合约在编译期间知道另一个合约的接口定义,所以协议内没有明确定义存的内容类型(协议非类型内省)。 所以这个协议并不支持合约接口是动态的,或者是仅在运行时才知道类型的情况。如果这些情况很重要,可以使用以太坊生态系统建立自己的基础设施来解决这个问题。

函数选择器

一个函数调用的前四个字节数据指定了要调用的函数签名。计算方式是使用函数签名的keccak256的哈希,取4个字节。 bytes4(keccak256("foo(uint32,bool)")) 函数签名使用基本类型的典型格式(canonical expression)定义,如果有多个参数使用,隔开,要去掉表达式中的所有空格。

参数编码

由于前面的函数签名使用了四个字节,参数的数据将从第五个字节开始。参数的编码方式与返回值,事件的编码方式一致,后面一起介绍。

支持的类型

支持的类型可参考原文2。支持的类型里面有一些比较特殊的是动态内容的类型,比如string,需要存储的空间是不固定的。

编码方式

针对数组参数中的嵌套数组的优化: 访问一个参数属性需要的读取次数,在一个数组结构中最多是数组的深度,比如a_i[k][l][r],最多4次。在之前的ABI协议版本中,在最差情况下读取次数会随着总的动态类型的参数量线性增长。 变量的值或数组的元素间不应该是隔开存储的,可支持重定位,比如使用相对地址来定位。 区分了动态内容类型和固定大小的类型。固定大小的类型按原位置存储到当前块。动态类型的数据则独立存储在其它数据块。

动态内容类型的定义

bytes

string

T[] 某个类型的不定长数组

T[k] 某个类型的定长数组

所有其它类型则称为固定大小的类型。

长度函数的定义

len(a)是二进制字符串a的中的字节数。len(a)的结果类型是uint256。 我们定义enc,编码函数,是一个ABI类型值到二进制串的映射函数,也就是ABI类型到二进制字符串的映射函数。由此len(enc(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]) 对于X[i],如果其为固定大小的类型,head函数定义为,head(X[i]) = enc(X[i])。tail函数定义为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]) 而对于是动态长度的类型的X[i],虽然其长度不确定,但head(X[i])所存值其实是非常明确的,头部中只是存的一个偏移值(offset),这是偏移是实际存储内容处相对enc(X)整个编码内容开始位置来定义的。 上面这个表达式看得有点云里雾里的,但如果没有理解错的话,固定大小的类型在head里就依次编码了,动态内容类型只在head里放了一个从开始到真正内容开始的偏移,在偏移处才真正放内容,内容如果是变长的,就用len(enc(X))函数计算一个值放在前面,标识这个值有多大的内容。 T[] 其中X有k个元素。其中k被认为是uint256,所以enc(k)实际是编码一个uint256。 enc(X) = enc(k) enc(X[1], ..., X[k]) 它被以一个静态长度的数据来编码,但将数组所含元素的个长度作为前缀。

具体类型的编码方式

具体编码方式由于细节太多,不完全保证翻译正确,如果要自己实现这样的细节,建议再仔细研究原文文档,下面翻译仅做参考。

bytes,长度k,长度值k是uint256。 enc(X) = enc(k) pad_right(X),先将长度按uint256编码,然后紧跟字节序列格式的X,再用零补足,来保证len(enc(X))是32字节(256位)的倍数。

string enc(X) = enc(enc_UTF8(X)),这里的utf-8编码被按字节解释及编码;所以后续涉及的长度都是指按字节算的,不是按字符计算。

uint:enc(X)是按大端序编码X,并在左侧高位补足0,使之为32字节的倍数。

address:按uint160编码。

int: enc(X) 是X的按大端序值2的补码表示,如果是负数左侧用1补足,正数左侧用0被足,直到是32的倍数。

bool:按uint8编码。1代表true,0代表false。

fixedx: enc(X) is enc(X * 2N) where X * 2N is interpreted as a int256.

fixed: as in the fixed128x128 case

ufixedx: enc(X) is enc(X * 2N) where X * 2N is interpreted as a uint256.

ufixed: as in the ufixed128x128 case

bytes: enc(X) 是将字节序列用0补足为32位。

所以对于任意的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]) 这里的[a_1, ..., a_n]和[v_1, ..., v_k],是定长数组,长度分别是n和k。严格说来,[a_1, ..., a_n]是一个含不同类型元素的数组。但即便如此,编码仍然是明确的,因为实际上我们并没有使用这样一种类型T。

例子

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) {}
}
复制代码

如果要调用baz(69, true),要传的字节拆解如下: 0xcdcd77c0: 使用函数选择器确定的函数ID。通过bytes4(keccak256("baz(uint32,bool)"))。 0x0000000000000000000000000000000000000000000000000000000000000045。第一个参数,uint32位的值69,并补位到32字节。 0x0000000000000000000000000000000000000000000000000000000000000001。第二值boolean类型值true。补位到32字节。 所以最终的串值为: 0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001 返回结果是一个bool值,在这里,返回的是false。所以输出就是一个bool。 0x0000000000000000000000000000000000000000000000000000000000000000

动态类型的使用例子

如果我们要值用(0x123, [0x456, 0x789], "1234567890", "Hello, world!")调用函数f(uint,uint32[],bytes10,bytes),编码拆解如下: bytes4(sha3("f(uint256,uint32[],bytes10,bytes)"))计算MethodID值。对于固定大小的类型值uint256和bytes10,直接编码值。而对于动态内容类型值uint32[]和bytes,我们先编码偏移值,偏移值是整个值编码的开始到真正存这个数据的偏移值(这里不计算头四个用于表示函数签名的字节)。所以依次为:

0x0000000000000000000000000000000000000000000000000000000000000123,32字节的0x123。 0x0000000000000000000000000000000000000000000000000000000000000080 (第二个参数的由于是动态内容类型,所以这里存储偏移值,4*32 字节,刚好是头部部分的大小) 0x3132333435363738393000000000000000000000000000000000000000000000 ("1234567890" 在右侧补0到32字节大小) 0x00000000000000000000000000000000000000000000000000000000000000e0 (第四个参数的偏移 = 第一个动态参数的偏移值 + 第一个动态参数的大小 = 432 + 332 动态长度的计算见后) 尾部部分的第一个动态参数,[0x456, 0x789]编码拆解如下: 0x0000000000000000000000000000000000000000000000000000000000000002 (整个数组的长度,2)。 0x0000000000000000000000000000000000000000000000000000000000000456 (第一个元素) 0x0000000000000000000000000000000000000000000000000000000000000789(第二个元素) 最后我们来看看第二个动态参数的的编码,Hello, world!。 0x000000000000000000000000000000000000000000000000000000000000000d (元素的字节长度,13) 0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000 ("Hello, world!" 补位到32字节,里面是按ascii编码的,可以查查对应的编码。) 最终我们得到了下述的编码,为了清晰在函数签名的四个字节后,加了一个换行。 0x8be65246 0000000000000000000000000000000000000000000000000000000000000123 0000000000000000000000000000000000000000000000000000000000000080 3132333435363738393000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000000000000000e0 0000000000000000000000000000000000000000000000000000000000000002 0000000000000000000000000000000000000000000000000000000000000456 0000000000000000000000000000000000000000000000000000000000000789 000000000000000000000000000000000000000000000000000000000000000d 48656c6c6f2c20776f726c642100000000000000000000000000000000000000

Events 事件

Events是抽象出来的以太坊的日志,事件监听协议。日志实体包含合约的地址,一系列的最多可以达到4个的Topic,和任意长度的二进制数据内容。Events依赖ABI函数来解释,日志实体被当成为一个自定义数据结构。 事件有一个给定的事件名称,一系列的事件参数,我们将他们分为两个类别:需要索引的和不需要索引的。需要索引的,可以最多允许有三个,包含使用Keccak hash算法哈希过的事件签名,来组成现在日志实体的Topic。那些不需要索引的组成了Events的字节数组。 一个日志实体使用ABI描述如下:

address: 合约的地址。(由以太坊内部提供)

topics[0]: keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")"),其中的canonical_type_of是返回函数的规范型(Canonical form),如,uint indexed foo,返回的应该是uint256。如果事件本身是匿名定义的,那么Topic[0]将不会自动生成。

Topics[n]: EVENT_INDEXED_ARGS[n-1],其中的EVENT_INDEXED_ARGS表示指定成要索引的事件参数。

data: abi_serialise(EVENT_NON_INDEXED_ARGS)使用ABI协议序列化的没有指定为索引的其它的参数。abi_serialise()是ABI序列函数,用来返回一系列的函数定义的类型值。

JSON格式

合约接口的JSON格式。包含一系列的函数和或事件的描述。一个描述函数的JSON包含下述的字段:

type: 可取值有function,constructor,fallback(无名称的默认函数)

inputs: 一系列的对象,每个对象包含下述属性:

name: 参数名称

type: 参数的规范型(Canonical Type)。

outputs: 一系列的类似inputs的对象,如果无返回值时,可以省略。

constant: true表示函数声明自己不会改变区块链的状态。

payable: true表示函数可以接收ether,否则表示不能。

其中type字段可以省略,默认是function类型。构造器函数和回退函数没有name或outputs。回退函数也没有inputs。

向不支持payable发送ether将会引发异常,禁止这么做。 事件用JSON描述时几乎是一样的:

type: 总是event

name: 事件的名称

inputs: 一系列的对象,每个对象包含下述属性:

name: 参数名称

type: 参数的规范型(Canonical Type)。

indexed: true代表这个这段是日志主题的一部分,false代表是日志数据的一部分。

anonymous: true代表事件是匿名声明的。

示例:

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": "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中的使用示例:

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},{ ...} ...
]
复制代码

智能合约从入门到精通:调用数据的布局和ABI相关推荐

  1. 智能合约从入门到精通:完整范例

    简介:前几篇文章我们一直在讨论Solidity语言的相关语法,从本文开始,我们将介绍智能合约开发.今天我们将介绍一个完整范例. 此章节将介绍一个完整案例来帮助开发者快速了解合约的开发规范及流程. 注意 ...

  2. 智能合约从入门到精通:Solidity Assembly

    简介:上一节,我们讲过Solidity汇编语言,这个汇编语言,可以不同Solidity一起使用.这个汇编语言还可以嵌入到Solidity源码中,以内联汇编的方式使用.下面我们将从内联汇编如何使用着手, ...

  3. 智能合约从入门到精通:JIDE集成开发工具

    简介:前面两节,我们介绍了JIDE的工具库,本节我们将介绍智能合约开发最后一段内容,JIDE集成开发工具.除了JIDE的安装,我们还将介绍智能合约的编写.编译.部署.运行以及查看日志等.您看过所有的内 ...

  4. 以太坊智能合约安全入门了解一下(下)

    作者:RickGray 作者博客:http://rickgray.me/2018/05/26/ethereum-smart-contracts-vulnerabilities-review-part2 ...

  5. 以太坊智能合约安全入门了解一下(上)

    作者:RickGray 作者博客:http://rickgray.me/2018/05/17/ethereum-smart-contracts-vulnerabilites-review/ (注:本文 ...

  6. java和以太坊交互_java类库web3j开发以太坊智能合约快速入门

    web3j简介 web3j是一个轻量级.高度模块化.响应式.类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成. 可以通过它进行以太坊 ...

  7. 阿希链-ASCH智能合约开发入门

    1. 概述 1.1. 设计目标 ASCH智能合约平台致力于成为一个开发者可以快速上手的安全.高效的智能合约开发和运行平台 智能合约容易学习.容易编写.容易验证 合约语言必须是图灵完备的,合约运行必须是 ...

  8. NFT合约 从入门到精通

    在<区块链杀手级应用落地畅想(上)>中我们提到,2021年被称为NFT"元年".在短时间内,NFT已不再局限于加密世界的投机价值,其释放的潜力吸引了越来越多的国际品牌, ...

  9. 原 EOS智能合约开发入门

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. EOS智能合约的开发需要使用llvm和abigen来生成abi文件. 为此eos提供了一个 名为eosiocpp的工具. ...

最新文章

  1. std::string的find问题研究
  2. html页面获取get传递过来的值,在HTML中用js获取通过GET、POST方法(就是在网址后加?a=bc=d之类)传过来的表单值...
  3. IE安全系列:脚本先锋(4)
  4. dos下打包整个java工程
  5. vuejs深入浅出—基础篇
  6. Swift 简单的通讯录
  7. javascript案例---简单的视觉效应
  8. 加载项目的时候提示:需要缺少的web组件才能进行加载
  9. OS51技术 GHOSTXP SP3 OEM V7.0 驱动增强纯净版(20130629)
  10. matlab画三维空间布朗运动,matlab绘制布朗运动的二维三维图.doc
  11. 使用Python实现搜索任意电影资源的磁力链接
  12. 用c加加创建c语言项目,如何使用visual studio 2017创建C语言项目
  13. 2021多校补题(8)
  14. Mac下使用imagemagick将PDF转长图
  15. Merge用法:Oracle 10g中对Merge语句的增强
  16. 柏林初创Meditopia​靠一个冥想App获1500万美元融资
  17. Manjaro KDE 18.1.2踩坑指南(含双显卡黑屏解决方案)
  18. 【转】图灵机与计算问题(张江)
  19. MATLAB程序到底怎么调试?
  20. 谷歌浏览器默认开启无痕浏览

热门文章

  1. postgresql在线备份与恢复(三)
  2. C# 实现将 PDF 转文本的功能
  3. lpk.dll是什么
  4. irobot擦地机器人故障_自己动手修复 iRobot braava380t 电机故障
  5. python orm benchmark_python 编写ORM
  6. JAVA SE学习day_11:集合的相关应用、增强型for循环、foreach方法、数组与集合的相互转换
  7. python配置opencv最简单_【萌新】面向(Windows10)python的opencv环境配置“个人向”报错总结...
  8. iOS Sprite Kit教程之xcode安装以及苹果帐号绑定
  9. php 5.6 iis7,IIS7 配置 PHP5.6 Web程序 - 贪吃蛇学院-专业IT技术平台
  10. python turtle库有什么用_Python中turtle库的使用