本文介绍了作者和合作者构建的一款高性能自动化Solidity智能合约Gas优化工具,该工具通过在源代码和中间表示层进行优化,从而有效地节省Gas。该工具提供了命令行界面和Web应用程序两种部署方式,支持结构体打包、存储变量缓存和调用数据优化等功能,旨在帮助开发者编写更高效的Solidity代码,并最大限度地降低智能合约在区块链上的执行成本。
一个高性能的自动化工具,可优化 Solidity 智能合约中的 gas 使用,重点关注存储和函数调用效率。
本文探讨了 Xian Lin (CHAD dev) 和 我 如何开发智能合约优化器,该优化器通过在源代码和中间表示级别执行优化来提高 gas 节省。
如果你懒得阅读,我们构建了一个 web gas 优化器应用程序,可以根据你选择的优化来优化你的 solidity 代码——结构体打包、存储变量缓存 和 Call Data 优化。在此处找到代码here。
只需插入你的 solidity 代码,BAM 你就可以获得优化的代码作为输出 🙌
Gas 是基于以太坊虚拟机 (EVM) 的区块链上的关键资源。执行智能合约的成本(以 gas 衡量)直接影响区块链操作的效率和费用。
Solidity 编译器将 Solidity 代码转换为 EVM 字节码,并结合各种优化技术来减少部署和执行期间的 gas 消耗。但是,这些内置优化具有局限性。为了实现最佳 gas 效率,开发人员必须编写高度优化的代码。
虽然有一些 linters 和优化器可用,但能够分析并在源代码和中间表示级别提出改进建议的工具明显不足。
这种差距促使我们创建了一个 开发工具,帮助 Solidity 开发人员编写 gas 高效的代码,最终最大限度地降低智能合约在区块链上的执行成本。
如何在 EVM 中测量 Gas?
Solidity 编译器的作用
中间表示 — 抽象语法树 (AST)
我们的 Solidity Gas 优化器提供两种部署选项:独立的命令行界面 (CLI) 用于高效的合约优化,以及 Web 应用程序,提供用户友好的可视化界面以实现相同的目的。
Web 应用程序由两个主要组件组成:Go 后端 Web 服务器和 Next.js/React 前端。
高级架构
当用户首次打开网站时,会看到一个屏幕,允许他输入要优化的合约以及要选择的优化。当按下“优化代码”按钮时,前端将有效负载发送到后端,后端与优化器模块交互以获取优化的代码。
未优化和优化的代码并排显示给用户,并可以选择查看两个版本之间的差异。当用户按下“切换差异”按钮时,视图会高亮显示优化过程所做的更改。
按下“切换差异”按钮后,用户会看到此视图,其中高亮显示了优化所做的更改。
优化合约后,会出现一个“估算 Gas”窗口,允许用户编写代码来调用其合约函数。此功能可以估算未优化和优化合约的 gas 使用情况,从而实现直接比较。
测试代码会发送到后端,后端会将其合并到模板中,以生成两个测试文件:gas 估算过程所需的 Unoptimized.t.sol
和 Optimized.t.sol
。
Gas 估算器的结果
运行 gas 估算后,会显示一个窗口,其中显示每个合约的函数调用的 gas 成本。例如,优化合约的函数调用可能比其未优化的对应合约的 gas 成本低约 4%。
在此处找到代码here。
现在你已经了解了我们应用程序的用途和功能,让我们继续了解我们 Solidity 优化器背后的思路和设计。
设计阶段包括规划 Solidity 优化器的架构和关键组件。此阶段确保该工具能够有效地分析和优化源代码和中间表示级别的 Solidity 智能合约。
遵循编译器实现的更简单的设计,我们提出了如下所示的初始架构。
以下架构显示了我们的优化器应用程序的高级工作流程
Gas 优化器架构的初始设计
我们使用 solgo 库进行 solidity 语言的词法分析和解析。解析器是通过使用 Antlr 从 Solidity 语法文件生成的,它使用 AntlrGo 生成词法分析器、解析器和监听器。
因此,它可以对 Solidity 代码进行语法分析,将其转换为解析树,从而提供代码的详细语法表示,从而可以进行复杂的导航和操作。
我们有很多不同的 gas 优化器可供选择,我们想选择那些成本最低的优化——以最小的努力带来最大的 gas 节省。以下是关于优化的部分。
在通过词法分析器和解析器馈送 Solidity 程序并获得 AST 树之后。我们使用 DFS 和访问者等技术按顺序将选定的优化应用于 AST,以遍历树并直接修改 AST。
以下是优化过程的一般步骤:
但主要思想是优化应该是模块化的,并且可以独立地完成添加进一步的 gas 优化。
在应用优化之后,我们将抽象语法树 (AST) 转换回其 Solidity 代码表示,此过程称为漂亮打印或反解析。鉴于 Solidity 语言的广泛语法,我们有选择性地为最常用的节点类型实现了漂亮打印。
虽然我们计划将来扩展对剩余节点类型的支持,但我们当前的实现足以打印大多数 Solidity 合约。
以下是当前支持漂亮打印的节点类型列表:
支持的节点类型
Foundry
我们工具中的 gas 估算利用 Foundry,这是一个全面的智能合约开发框架。Foundry 通过管理依赖项、编译项目、执行测试、促进部署以及通过命令行工具和 Solidity 脚本启用与区块链的交互来简化开发过程。就我们的目的而言,我们主要使用 Foundry 的测试执行和 gas 报告功能,从而可以准确地进行 gas 使用估算。
用法
利用 Foundry 的测试功能,我们建立了一个 Forge 项目来构建和执行 Solidity 脚本以进行 gas 估算。Forge 项目中的单元测试通过跟踪功能提供了对 gas 使用情况的宝贵见解,使我们能够准确地估算合约的 gas 消耗。
存储打包减少了必要的 SLOAD(加载数据)或 SSTORE(存储数据)操作的数量,从而将访问存储变量的成本降低了一半或更多,尤其是在一次读取或写入同一存储槽中的多个值时。
有关结构体打包样子的示例代码,请在此处找到代码here,或在此处找到入门读物here。
Solidity 编译器采用一种节省空间的策略来存储合约的状态变量。每个存储槽最多可容纳 32 字节(256 位)的数据。编译器有效地将多个变量打包到这些槽中,前提是它们的组合大小保持在 32 字节的限制之下。这种方法优化了存储利用率,最大限度地减少了不必要的空间分配。
根据官方的 Solidity 编译器文档:
通过对结构体中使用的变量进行重新排序,使其小于 32 字节,使其彼此相邻,我们可以节省以太坊虚拟机 (EVM) 上的存储空间。
按照上述规则,我们决定使用离线最优装箱算法,而不是贪婪算法来决定如何打包结构体成员。首先,我们将所有 32 位或更大的结构体成员移动到结构体的顶部,然后我们使用该算法穷尽地搜索问题空间,最终获得最佳解决方案,从而得到一个将使用最少槽数的结构体。在得出此结构体之后,我们操作 AST 并按照此新格式对结构体定义中的结构体成员进行重新排序。
为什么它有用?
存储变量的缓存在需要多次读取状态变量(具有存储位置的变量)值的操作中,可以大大减少 gas 的使用量。与多次调用相比,将其缓存在内存中更具 gas 效率。
与缓存数据相关的主要操作码是 SLOAD 和 MLOAD。这些操作码分别处理从存储和内存加载数据。
MLOAD 始终产生 3 gas 的成本,使其相对便宜。另一方面,SLOAD 具有更复杂的成本结构:在交易期间初始访问值需要花费 2100 gas,而在同一交易中每次后续访问仅需要 100 gas。这种定价结构表明,从内存 (MLOAD) 访问数据比从存储 (SLOAD) 访问数据便宜得多,成本差异可能超过 97%。
一些学术来源支持通过将经常访问的存储变量缓存到内存中来优化数据访问的重要性:
关键思想是,如果对全局存储变量的调用超过 2 次,我们将声明一个临时局部变量作为缓存值
完整的实现可以在这里找到here。
在 Solidity 智能合约中,对于外部函数参数,优化 calldata
而不是 memory
的使用可以显着节省 gas。Calldata 是一种特殊的数据位置,其中包含在交易中传递的函数参数。它是只读的,并且与内存相比,访问成本更低。
通常,使用 calldata 比使用内存更便宜,原因如下:
示例 1:简单参数处理
在第一个示例中,使用 calldata 可以直接读取数据,而无需复制到内存,从而节省 gas。
示例 2:函数中的数组处理
在第二个示例中,使用 calldata 消除了将数组数据从 calldata 复制到内存的开销,从而降低了 gas 成本和计算开销。
此优化的正确性取决于保证参数不会在函数中被修改。这可以通过手动分析以及使用纯或 view 修饰符来验证,如下所示:
对于不需要修改的外部函数参数,利用 calldata 可以显着降低与调用这些函数相关的 gas 成本。由于 calldata 是一个不可修改的临时位置,函数的数据输入驻留在其中,因此避免了与将数据复制到内存相关的 gas 成本。
我们的项目依赖于 solgo 库,这是一个功能强大的 Solidity 静态分析器。该库有助于Token化、解析以及我们遍历和操作 AST 的能力,使我们的工具能够优化代码。在开发过程中,我们充分理解了该库的代码库,可以做出自己的贡献,并且我们现在正在与该库的作者紧密合作,以改进 solgo。
值得注意的是,在开发过程中,我们发现 solgo 的功能存在不足——缺少 AST 到源代码的打印函数。为了解决这个问题,我们实现了缺少的功能并将其作为 pull request 贡献回了项目。
我们对 AST 打印函数的实现产生了宝贵的辅助收益。此过程揭示了 solgo 库中的几个关键错误,尽管之前进行了单元和集成测试,但这些错误仍然未被检测到。通过通过打印 AST 来显示这些解析错误,我们可以为库的改进做出重大贡献。
我们对现有 Solidity gas 优化器的调查显示,该领域令人惊讶地缺乏开源解决方案。虽然探索 gas 优化技术的研究论文很容易获得,但用于自动 gas 优化的实用、开源工具仍然难以捉摸。
这种形势的差距促使我们开发了这个项目,我们相信我们的项目代表了第一个致力于自动 Solidity gas 优化的开源产品。通过这个项目,我们的目标是为开发人员提供有价值的工具,以简化合约效率并增强他们的工作流程。
我们目前的工作重点是完成 solgo 库中“漂亮打印”pull request 的集成。此外,我们正在探索状态变量打包的实现,该功能与我们现有的结构体变量打包优化类似。我们项目的模块化架构有助于无缝集成新的优化,使其成为持续开发的关键重点。最后,我们致力于通过提高网站的用户友好性并整合工具提示等功能来增强用户体验。
经过进一步的完善,以确保强大的用户体验和提高网站安全性,我们计划启动我们的 Web 应用程序以进行公开 Beta 测试。此计划将使我们能够收集 Solidity 开发社区的宝贵反馈,并贡献一个用于优化智能合约 gas 使用的实用工具。
在公开 Beta 发布的同时,我们计划通过 LinkedIn 和 Twitter 等平台上的有针对性的外展活动,积极与 Solidity 开发社区互动。这种多管齐下的方法将有助于收集宝贵的用户反馈,同时提高目标受众对我们项目的认识。
我们的愿景超出了当前的产品。我们设想该工具发展成为一个通用的 VS Code 扩展。与代码格式化程序类似,此扩展将处理 Solidity 源代码,提供优化的版本以供查看,或者只是覆盖该代码。此外,我们正在探索与现有静态分析器库的潜在集成。这种合作可以释放有价值的协同效应,并扩展该工具在 Solidity 开发生态系统中的功能。
开发 Solidity Gas 优化器提出了一个独特的挑战。开源工具的稀缺和有限的参考资料需要创造力和足智多谋的结合。克服这些障碍非常有意义,强调了开源贡献和社区参与的价值。
通过让自己沉浸在 Solidity 开发人员社区中,我们不仅提供了一个服务于社区的实用工具,而且还利用了我们在整个软件工程过程中磨练的各种技能。这个项目是一次重要的学习体验,促使我们适应和创新,最终导致创建了一个支持开发人员优化其智能合约的工具。
- 原文链接: extremelysunnyyk.medium....
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!