---
title: "什么是 Solidity 二进制文件？"
description: "什么是 Solidity 二进制文件，以及如何用它优化智能合约代码"
---

# 什么是 Solidity 二进制文件？

通过智能合约发布到 Ethereum 区块链的原始数据是字节码，即长串的十六进制字符。虽然开发者用人类可读的 [Solidity](https://www.alchemy.com/overviews/solidity) 代码编写和阅读智能合约，但发布到区块链上的并不是这种文本。

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

以一个上传到 [Ethereum mainnet](https://www.alchemy.com/rpc/ethereum) 的智能合约为例，其结构（Solidity 编码）如下：

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

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

`0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450`...

很难读懂，对吧？

本文将讨论为什么 [Ethereum Virtual Machine](https://www.alchemy.com/overviews/what-is-the-ethereum-virtual-machine-evm) 将所有内容编码为字节码，了解什么是 ABI 以及如何使用它，并学习一些将字节码反编译回人类可读的 [Solidity](https://www.alchemy.com/dapps/solidity) 的基本工具。

**说明：** 本文中的示例借鉴自官方 [Solidity ABI 文档](https://docs.soliditylang.org/en/v0.8.13/abi-spec.html)。

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

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

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

## **什么是 Solidity ABI？为什么读取智能合约需要它？**

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

答案就是[应用二进制接口（Application Binary Interface，简称 ABI）](https://www.alchemy.com/overviews/what-is-an-abi-of-a-smart-contract-examples-and-usage)。

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

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

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

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

相反，你可以通过以下方式获取 ABI：

1. 从智能合约开发者处获取该合约的公开源代码，并据此生成 ABI。
1. 如果智能合约已在 Etherscan 上通过验证，可以从 Etherscan 的合约信息中获取。
1. 从智能合约字节码反向工程出 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 这类原地编码的静态类型，[动态类型会被编码在单独分配的位置](https://docs.soliditylang.org/en/v0.8.13/abi-spec.html#use-of-dynamic-types)。

## **如何解读 Solidity 的事件数据二进制？**

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

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

大致来说，一个 Solidity 事件遵循以下结构：

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

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

市面上有多种 EVM 反编译工具，可以帮你还原出可读性更高的 Solidity 二进制版本，包括 [EtherVM Decompiler](https://ethervm.io/decompile) 和 [Panoramix decompiler](https://github.com/palkeo/panoramix)。

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