【Gas优化】设计Gas优化模式

本文介绍了针对以太坊智能合约的Gas成本节约策略,提出了24种设计模式并归类为5大类:外部交易、存储、节约空间、操作和杂项。模式包括使用代理合约减少部署成本、限制存储使用、优化数据结构以节省空间等,旨在帮助开发者降低智能合约执行的Gas成本。
摘要由CSDN通过智能技术生成

💡 本次解读的文章是 2020 年 IWBOSE 的一篇与智能合约 Gas 优化相关的论文,这篇论文提供了一套设计模式和提示,用以帮助在以太坊上开发智能合约时节约 Gas 成本,并根据所提供模式的特点,将其分为 5 类。

一、本文贡献

(1)提供一套 Gas 节约的设计模式;
(2)根据模式特点,将其进行归纳和分类。

二、Gas 节约设计模式

在智能合约的开发和执行过程中,存在着以 gas / gwei 计算成本,可以将这些成本划分为:与创建智能合约或发送交易相关的固定成本、与智能合约状态永久存储相关的成本、与执行函数所必需的临时变量在内存中存储有关的成本以及与操作执行相关的成本。

为此,本文归纳整理了 24 种设计模式,并将其分为 5 类,分别是:外部交易(External Transactions)、存储(Storage)、节约空间(Saving Space)、操作(Operations)以及杂项(Miscellaneous)。有些模式可能属于不止一类,因此,本文将其分配给最合适的类别。

2.1 外部交易

这个类别包括使用 Web3. js 标准库从外部地址创建合约和发送交易的模式。

(1) Proxy

模式名称Proxy
问题描述智能合约一旦部署后是不可变的,如果某个智能合约由于 bug 或需要扩展而必须改变时,则必须部署一个新合约,同时更新所有直接调用旧合约的相关智能合约,并部署新版本的智能合约,这样可能是非常昂贵的。
解决方案使用代理委托模式(Proxy delegate pattern),一个代理持有被引用的智能合约地址,在其状态变量中,可以改变。这样,只需更新对新智能合约的引用即可。

(2)Data Contract

模式名称Data Contract
问题描述当一个持有大量数据的智能合约必须更新时,同时必须将其所有数据复制到新部署的智能合约时,需要消耗大量的气体。
解决方案将数据保存在单独的智能合约中,由一个或多个智能合约访问,使用数据并保持处理逻辑。如果必须更新这一逻辑,则数据保留在数据合同中,这种模式通常也包含在代理模式的实现中。

(3)Event Log

模式名称Event Log
问题描述事件往往维护着系统的重要信息,这些信息必须在之后被与区块链交互的外部系统所使用。在区块链中存储这些信息可能非常昂贵。
解决方案如果发生过的事件数据是外部系统需要的,而不是智能合约需要的,则让外部系统直接访问区块链中的事件日志。

2.2 存储

这个类别包括与使用 Storage 存储永久数据有关的模式。

(1)Limit Storage

模式名称Limit Storage
问题描述存储(Storage)使用远比最贵的内存(memory)使用昂贵,因此,需要尽量避免使用存储。
解决方案限制存储在区块链中的数据,对于非永久性数据总是使用内存,此外,可限制存储的变化,即在执行函数时,将中间结果保存在内存或堆栈中,并仅在所有计算结束时更新存储。

(2)Packing Variables

模式名称Packing Variables
问题描述在以太坊中,内存的最小单位是一个 256 位的槽(slot),而一些整数的二进制表示并不需要用到 256 位。
解决方案对变量进行打包,在声明存储变量时,应连续声明数据类型相同的存储变量,这样打包由 Solidity 编译器自动完成。(注意,该模式对无法打包的 Memory 和 Calldata 内存不起作用)

(3)Packing Booleans

模式名称Packing Booleans
问题描述在 Solidity 中,布尔变量存储为 uint8(8位无符号整数),然而,只用1 bit足够存储它们。如果最多需要32个布尔值在一起,可以只遵循 Packing Variables 模式,如果需要更多,则会使用比实际需要更多的插槽。
解决方案在单个 uint256 变量中打包布尔值,为此创建将布尔函数打包和解包为单个变量的函数,运行这些功能的成本比额外存储的成本要便宜。

2.3 节约空间

这个类别包括内存和存储中与节省空间相关的模式。

(1)Uint* vs Uint256

模式名称Uint* vs Uint256
问题描述EVM 每次运行在 256 位,因此使用一个 uint *(小于256位的无符号整数),它将首先转换为 uint256,这需要额外的气体。
解决方案在一个 slot 中封装更多变量时使用小于或等于 128 位的无符号整数,如果不是,最好使用 uint256 变量。

(2)Mapping vs Array

模式名称Mapping vs Array
问题描述在 Solidity 中只提供了两种数据类型来表示数据列表:数组和映射,映射更便宜,而数组是封装和可迭代的。
解决方案为了节省气体,建议使用映射来管理数据列表,除非需要迭代或者有可能封装数据类型,这对于存储和内存都是有用的。当然也可以使用整数索引作为键来管理带有映射的有序列表。

(3)Fixed Size

模式名称Fixed Size
问题描述在 Solidity 中,任何固定大小的变量(fixed size variable)都比可变变量(variable size)更便宜。
解决方案每当有可能设定一个数组大小的上界时,使用固定大小的数组而不是动态数组。

(4)Default Value

模式名称Default Value
问题描述在创建变量时对其进行初始化是一个很好的习惯,然而,这需要消耗以太坊中的 gas。
解决方案在 Solidity 中,所有变量默认设置为零。因此,不要显式地初始化一个变量,如果它的值需要设置为零。

(5)Minimize on-chain data

模式名称Minimize on-chain data
问题描述Storage 的 gas 成本非常高,远高于 Memory 的成本。
解决方案最小化链上数据:链上的存储变量数据越少,gas 成本就越少,即只存储智能合约的关键数据并保留所有可能的链外数据。这种模式是在源头减少存储的使用,而 Limit Storage 模式是在使用存储时减少 gas 消耗。

(6)Explicitly mark external function

模式名称Explicitly mark external function
问题描述公共函数的输入参数自动复制到内存中,需要耗费气体。
解决方案外部函数的输入参数从 Calldata 内存中直接读取,显示标记为只对外调用的外部函数。

2.4 操作

这个类别包括与在智能合约函数内执行操作所使用 gas 有关的模式。

(1)Limit External Calls

模式名称Limit External Calls
问题描述每次调用外部智能合约都相当昂贵,甚至可能不安全。
解决方案在 Solidity 中,限制外部调用,最好是调用一个单一的、多用途的、参数较多的函数并返回所需的结果,而不是对每个数据进行不同的调用。

(2)Internal Function Calls

模式名称Internal Function Calls
问题描述调用公共函数比调用内部函数更昂贵,因为在前者中所有的参数都被复制到内存中。
解决方案在可能的情况下,优先选择内部函数调用。

(3)Fewer functions

模式名称Fewer functions
问题描述在以太坊智能合约中实现一个函数需要消耗 gas。
解决方案尽量拥有较少的函数,但又不能太少,平衡函数数量与复杂度。(实现一个具有许多小函数的智能合约是昂贵的,但是,过大的函数会使测试变得复杂,并可能危及安全性)

(4)Use Libraries

模式名称Use Libraries
问题描述如果一个智能合约倾向于用自己的代码来执行所有的任务,那么它将会非常昂贵。
解决方案均衡地使用库,外部库的字节码不是智能合约的一部分,从而节省了气体,然而,调用它们存在安全问题。

(5)Short Circuit

模式名称Short Circuit
问题描述每个操作都需要耗费气体。
解决方案短路判断,在使用逻辑运算符时,对表达式进行排序,以降低评估第二个表达式的概率。

(6)Short Constant Strings

模式名称Short Constant Strings
问题描述存储字符串是昂贵的。
解决方案保持短的常量字符串,确保常量字符串适合32字节。例如,可以使用字符串澄清错误,但必须保持较短的长度以避免浪费内存。

(7)Limit Modifiers

模式名称Limit Modifiers
问题描述修饰器(Modifiers)的代码内联在被修改的函数内部,从而增加了大小和 gas 成本。
解决方案限制修饰器的使用。内部函数不是内联的,而是被称为单独的函数。它们在运行时稍微昂贵一些,但是如果多次使用,在部署时可以节省大量的冗余字节码。

(8)Avoid redundant operations

模式名称Avoid redundant operations
问题描述每个操作都需要耗费气体。
解决方案避免冗余操作。例如,避免双重检查;使用 SafeMath 库可以防止下溢和溢出,因此不需要进行检查。

(9)Single Line Swap

模式名称Single Line Swap
问题描述每个赋值和定义变量都需要耗费气体。
解决方案Solidity 允许在一条指令中交换两个变量的值,因此,不使用辅助变量就可进行交换:(a, b) = (b, a)

(10)Write Values

模式名称Write Values
问题描述每个操作都需要耗费气体。
解决方案写入值而不是计算它们,如果在编译时已经知道某些数据的值,则直接写出这些值。在初始化时不要使用 Solidity 函数来推导数据的值。

2.5 杂项

这个类别包括上述类别中未涉及到的模式。

(1)Freeing storage

模式名称Freeing storage
问题描述有时,不再使用存储变量。
解决方案为了保持区块链的规模较小,每次释放存储资源都会得到 gas 退款。因此,可以方便地使用关键字删除,只要它们不再是必要的。

(2)Optimizer

模式名称Optimizer
问题描述以穷举方式优化 Solidity 代码来节约 gas 是困难的。
解决方案始终开启 Solidity 优化器,它是所有 solidity 编译器的一个选项,执行编译器可以进行优化,但是,这种优化是有限的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值