跳至内容
0%

什么是 Solidity 二进制文件?

Alchemy team headshot

作者 Alchemy

发布于 2023年7月10日1 分钟阅读

通过智能合约发布到 Ethereum 区块链的原始数据是字节码,即长串的十六进制字符。虽然开发者用人类可读的 Solidity 代码编写和阅读智能合约,但发布到区块链上的并不是这种文本。

同样,每一次智能合约“调用”——即对智能合约公开的外部可见函数发起的请求——形式上也是原始字节码,即“二进制数据”。

以一个上传到 Ethereum mainnet 的智能合约为例,其结构(Solidity 编码)如下:

假设有用户想调用函数 baz,参数为 69 和 true。

以下是这个请求以字节码形式传输时的实际样子:

0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450...

很难读懂,对吧?

本文将讨论为什么 Ethereum Virtual Machine 将所有内容编码为字节码,了解什么是 ABI 以及如何使用它,并学习一些将字节码反编译回人类可读的 Solidity 的基本工具。

说明: 本文中的示例借鉴自官方 Solidity ABI 文档

为什么 Solidity 将智能合约编码为二进制形式?

由于在 Ethereum 区块链上存储数据的成本极高,且上传的每一字节数据都需要复制到区块链上的所有全节点,因此读写原始字节码比上传 Solidity 代码更节省成本。

解析和存储人类可读的代码所耗费的数据量可能高出一个数量级,而智能合约在主网上的成本已经可能高达数千美元,这就成了一个问题。

什么是 Solidity ABI?为什么读取智能合约需要它?

智能合约发布时,会在发布到 Ethereum 之前自动转译为字节码。但问题是——一旦发布到网络上,某个人要如何知道该如何与这个智能合约交互?看着一长串字节码,几乎不可能理解其中有哪些函数可供调用。

答案就是应用二进制接口(Application Binary Interface,简称 ABI)

ABI 是一份人类可读的公开方法列表,描述了可以对某个特定智能合约发起的调用,以及每次调用会返回什么。

有了 ABI,智能合约的使用者就不需要阅读字节码,而是可以将调用转换为字节码来与智能合约交互。

ABI 与传统 Web2 架构中的 API(应用程序编程接口)非常相似。但主要区别在于,Solidity ABI 让用户能够访问以二进制编码的智能合约中的方法,而 API 则让用户能够访问在线服务器端点上的方法。

由于 ABI 是供人使用和阅读的,智能合约开发者不会将智能合约的 ABI 发布到区块链上,因为那样成本会非常高。

相反,你可以通过以下方式获取 ABI:

  1. 从智能合约开发者处获取该合约的公开源代码,并据此生成 ABI。
  2. 如果智能合约已在 Etherscan 上通过验证,可以从 Etherscan 的合约信息中获取。
  3. 从智能合约字节码反向工程出 ABI(不推荐)。

ABI 通常以 JSON 格式发布,编码了 Solidity 智能合约的公开函数声明。

以下面这个智能合约的函数定义为例:

对应的 JSON 编码大致如下:

如何解读 Solidity 的调用数据二进制

虽然你不会想手动解析 Solidity 二进制数据来调用函数——因为这既复杂又不直观,还容易出错——但大致了解 Solidity 中二进制数据是如何构成的,对于快速浏览调用数据或核对数值会很有帮助。

我们会在下一节为你介绍几款工具,它们能帮你完成大部分这类转录工作。

以上面的例子为例。

假设有用户想调用智能合约中的函数 baz,参数为 69 和 true。以下是这个请求以字节码形式呈现的样子,总共 68 字节:

0xcdcd77c0000000000000000000000000000000000000000000000000000000000000004500...

1. 使用调用数据的前 4 个字节来识别方法 ID。

在这个例子中,0xcdcd77c 标识了方法 baz,其推导方式是取签名 baz(uint32,bool) 的 ASCII 形式的 Keccak 哈希值的前 4 个字节。

2. 使用接下来的 32 个字节来识别第一个参数

0x00000000000000000000000000000000000000000000000000000000000000045 标识了第一个参数 69,它是一个被填充到 32 字节的 uint32 值。填充的意思是添加 0,以保证整个字符串长度为 32 字节(在这个例子中),不管实际数值有多大。

0x00000000000000000000000000000000000000000000000000000000000000045 

3. 使用最后 32 个字节来识别第二个参数

第二个参数是 true,它是一个被填充到 32 字节的 bool 值:

0x0000000000000000000000000000000000000000000000000000000000000001

对于包含动态类型的参数,编码方式会稍有不同,因为不同于 address、bool、uint32 这类原地编码的静态类型,动态类型会被编码在单独分配的位置

如何解读 Solidity 的事件数据二进制?

事件是智能合约在执行方法调用时发布的日志,事件以二进制数据的形式发布。

事件可以接收参数,这些参数有助于指定事件将输出的内容。这些参数可以被索引,也就是说,可以用该索引参数作为过滤条件来搜索该事件。这些索引参数在 Solidity 术语中也被称为 topics!

大致来说,一个 Solidity 事件遵循以下结构:

  • address:合约的地址
  • topics[n]:0 到 4 个 topic,即索引参数
  • 任意长度的二进制数据,可以根据 ABI 进行解析。

应该用什么工具来反编译 Solidity 二进制数据?

市面上有多种 EVM 反编译工具,可以帮你还原出可读性更高的 Solidity 二进制版本,包括 EtherVM DecompilerPanoramix decompiler

这些 EVM 反编译工具不会完美还原出原始源代码(为了减小二进制体积,名称或其他重要信息可能已被移除),但应该能让你对可用的 ABI 请求有一个大致的了解。

Background gradient

构建区块链应用

Alchemy 将最强大的 Web3 开发者产品和工具与资源、社区及专业支持结合在一起。