以太坊的ABI编码

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了以太坊的ABI编码相关的知识,希望对你有一定的参考价值。

参考技术A

ABI全称Application Binary Interface, 是调用智能合约函数以及合约之间函数调用的消息编码格式定义,也可以理解为智能合约函数调用的接口说明. 类似Webservice里的SOAP协议一样;也就是定义操作函数签名,参数编码,返回结果编码等。

使用ABI协议时必须要求在编译时知道类型,即强类型相关.

当一个智能合约编译出来后, 他的abi接口定义就确定了. 比如下面的智能合约:

生成的字节码:

生成的abi定义:

可以看出, 生成abi包含了2个定义: 函数 lotus , 事件 Log_lotus , 各个字段含义见上. 根据该abi定义,就可以生成调用该智能合约函数的abi格式的数据了.

格式简单的可以表示为: 函数选择器+参数编码

一个函数调用的前四个字节数据指定了要调用的函数签名。计算方式是使用函数签名的 keccak256 的哈希,取4个字节。

函数名如果有多个参数使用,隔开,要去掉表达式中的所有空格。在geth客户端,通过命令可以得到hash:

由于前面的函数签名使用了四个字节,参数的数据将从第五个字节开始。

根据参数类型,编码规则有所区别:

除了bytes,和string, 其他类型的数据不足32字节长度的需要加0补足32字节. 动态长度的编码在例子中介绍.

函数: function baz(uint32 x, bool y) :

调用: baz(69, true)

生成的数据如下:

返回结果是一个bool值,在这里,返回的是false:

函数: f(uint,uint32[],bytes10,bytes)

调用: (0x123, [0x456, 0x789], "1234567890", "Hello, world!")

函数选择器: bytes4(sha3("f(uint256,uint32[],bytes10,bytes)"))

对于 固定大小的类型 值 uint256 和 bytes10 ,直接编码值。

对于 动态内容类型 值 uint32[] 和 bytes ,我们先 编码偏移值 ,偏移值是整个值编码的开始到真正存这个数据的偏移值(这里不计算头四个用于表示函数签名的字节)。

所以参数编码数据依次为:

尾部部分的第一个动态参数, [0x456, 0x789] 编码拆解如下:

最后我们来看看第二个动态参数的的编码, Hello, world! 。

所以最终结果是:

聊聊以太坊智能合约ABI

聊聊以太坊智能合约ABI

ABI的全称是 Application Binary Interface,所以它是一个接口。或者说是一个标准。这个标准描述了在以太坊生态中如何跟合约进行交互,这个交互包含外部客户端调用合约的接口,也包括合约之间的交互。

ABI假设所有的编码都是在编译阶段确定的,也就是静态的。而不是运行时动态生成的。

我们知道智能合约的本质也是程序,程序之间交互要用二进制码(binary code),ABI就是这样一个标准规定合约的代码如何进行编码和解码。

ABI的标准规范的内容包括合约的函数列表、函数名称、参数名称、参数类型、返回类型等。这些信息以JSON格式保存。这些JSON是可读的。

比如一个智能合约

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;


contract Test 
    constructor()  b = hex"12345678901234567890123456789012"; 
    event Event(uint indexed a, bytes32 b);
    event Event2(uint indexed a, bytes32 b);
    error InsufficientBalance(uint256 available, uint256 required);
    function foo(uint a) public  emit Event(a, b); 
    bytes32 b;

它的JSON如下:

[
"type":"error",
"inputs": ["name":"available","type":"uint256","name":"required","type":"uint256"],
"name":"InsufficientBalance"
, 
"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": []
]

我们看到的是一个json数组,数组的第一个元素是一个名为InsufficientBalance,类型是error的接口,同时还指明了它的入参。

其它元素也是类似,就不展开说了。

我们也可以在remix编译的时候看到ABI信息。

接下来看看具体的编码规则。

还是拿一个具体的合约举例子,

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.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 

比如我要调用bar这个方法,传入的参数是69和true,它会被编码为

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001

首先这个0xcdcd77c0,是函数的id,它占据了编码的前四个字节,它其实是下面这个函数签名的Keccak hash的前四个字节。

baz(uint32,bool)
```

![在这里插入图片描述](https://img-blog.csdnimg.cn/2da45e03e19c47bc91ca4f7427e45da3.png#pic_center)


```
0x0000000000000000000000000000000000000000000000000000000000000045
```
是入参69的十六进制数并且补齐到32字节。(ABI的编码会对所有的数据编码强制32字节对齐)

```
0x0000000000000000000000000000000000000000000000000000000000000001
```
是入参true(true就是1)补齐32字节的结果。



----

参考:

* https://docs.soliditylang.org/en/develop/abi-spec.html#abi

以上是关于以太坊的ABI编码的主要内容,如果未能解决你的问题,请参考以下文章

web3.js链接以太坊并查询钱包u余额

聊聊以太坊智能合约ABI

聊聊以太坊智能合约ABI

深入了解以太坊虚拟机第4部分——ABI编码外部方法调用的方式

以太坊的“分片”是指啥?

以太坊的“分片”是指啥?