Solidity如何优化Gas费用

时间:2022-12-22 14:14:57

Solidity如何优化Gas费用

在区块链中,每一笔智能合约的运行,都要根据复杂度消耗一笔GAS费;如果你要将值写入存储,则需要花费很多。如果你只是使用堆栈,它的成本会低一些;智能合约solidity语言的编写,不仅要考虑安全,也要考虑语言的优化,在Ethereum上的交易gas是有上限的,特别是针对一些复杂业务的处理,有可能会导致gas超高,严重者导致此笔交易失败。

0、Struct结构

      如果一个struct中有多个uint,则尽可能使用较小的uint,solidity会将这些uint打包在一起,从而占用较小的存储空间

1、选择变量数据类型

       不同数据类型的存储GAS消耗不同,我们应该选择 gas 消耗更小的数据类型。

       尽量使用256位的变量,如果你只存储一个uint8,则EVM将用零填充所有缺少的数字,这会耗费gas、此外,EVM执行计算也会转化为 uint256 ,因此除uint256之外的任何其他类型也必须进行转换。

       通常情况下我们不会考虑使用 uint 变种,因为无论如何定义 uint的大小,Solidity 为它保留256位的存储空间。例如,使用 uint8 而不是uint(uint256)不会节省任何 gas。

        有一种情况例外:把 uint 绑定到 struct 里面。如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间。

        当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。

        为了避免存储操作,可以在数组后面加上 memory关键字, 表明这个数组是仅仅在内存中创建,不需要写入外部存储,并且在函数调用结束时它就解散了。与在程序结束时把数据保存进 storage 的做法相比,内存运算可以大大节省gas开销 – 把这数组放在view里用,完全不用花钱。

2、布尔类型

         布尔类型 bool 实际为 uint8,即使用 8 位的存储空间,每个存储插槽能装入 32 个布尔类型值。而布尔值只能有两个值:True 或 False,其实只需要在单个存储位中就可以保存布尔值。

3、使用短路规则

操作符 || 和 && 适用常见的短路规则。

这意味着,假设f(x) 和 g(y) 返回 true 的概率一样,那么:

在表达式 f(x) || g(y) 中,如果 f(x) 的计算结果为真,则不会执行 g(y)。因此应该将贵的方法放在后面。

在表达式 f(x) && g(y) 中,如果 f(x) 的计算结果为假,则不会执行 g(y)。因此应该将贵的方法放在后面

4、通过汇编将变量打包到单个插槽中

将多个变量堆叠在一起

5、使用简短的原因字符串

       将错误原因字符串与require语句一起附加,以便更容易理解contract调用还原的原因。但是,这些字符串在部署的字节码中占用空间。每个原因字符串至少需要32个字节,因此请确保您的字符串符合32个字节,否则会变得更加昂贵。

6、调用内部函数更便宜

        从智能合约内部调用其内部函数比调用其公共函数便宜,因为当您调用公共函数时,所有参数将再次复制到内存中并传递给该函数。相反,当您调用内部函数时,将传递这些参数的引用,并且它们不会再次复制到内存中。这节省了一点gas。

7、使用代理模式进行大规模部署

        如果您希望部署同一contract的多个副本,则考虑仅部署一个实现contract和多个代理contract,将其逻辑委派给实现contract。这将允许这些contract共享相同的逻辑但不同的数据。

8、尽量使用批量操作

        因为一笔交易的基础 gas 消耗是 21000,批量操作相比多次操作,能减少多次的交易带来的基础 gas 消耗。

9、事件

没有参数的事件是750 GAS。理论上每个附加参数将增加256个GAS,但事实上,它会更多。

10、哈希

       你可以使用智能合约中的几个内置哈希函数:keccak256,sha256和ripemd160。参数越多,消耗的气体越多。耗气量:ripemd160> sha256> keccak256。因此,如果没有其他目的,建议使用keccak256函数。

11、删减不必要的Solidity库

        我们引入的库通常只需要用到其中的部分功能,这意味着其中可能会包含大量对于你的智能合约而言其实是冗余的solidity代码。如果可以在你自己的合约里安全有效地实现所依赖的库功能,那么就能够达到优化solidity合约的gas利用的目的。

12、显式声明Solidity合约函数的可见性

        显式声明函数的可见性不仅可以提高智能合约的安全性,同时也有利于优化合约执行的gas成本。例如,通过显式地标记函数为外部函数(External),可以强制将函数参数的存储位置设置为calldata,这会节约每次函数执行时所需的Ethereum 的 gas成本。

13、编译合约时使用优化器

       使用编译器 solc 编译合约时启动优化器 optimizer,它将简化复杂的表达方式,能减小编译后的合约字节码大小,从而减少部署时的 gas 消耗,同时也能减少合约调用时的消耗。

14、常量

      在 solidity 中,声明为 constant 或者 immutable 的状态变量即常量。constant 修饰的常量的值在编译时确定,而 immutable 修饰的常量的值在部署时确定。尽量使用常量,常量是合约字节码的一部分,不占用存储插槽,使用常量比变量更省 gas。在部署时,常量消耗的 gas 更少。

15、默克尔树

        使用默克尔树。在合约中保存一组数据的 merkleRoot,提供 verify 方法验证某条数据在这组数据中。相比使用一个 mapping 或数组来保存全部数据,减少了 gas 消耗。