Solidity Gas 优化:理解 EVM 工作原理助你节省 Gas

  • zealynx
  • 发布于 2023-03-02 10:40
  • 阅读 31

本文探讨了如何通过理解以太坊虚拟机(EVM)来优化Solidity智能合约的Gas成本。主要介绍了四个方面的优化技巧:冷热存储访问的成本差异、归零存储的Gas退款机制、状态变量排序以实现存储槽打包以及uint256相比uint8更经济的原因,旨在帮助开发者编写更节省Gas的合约。

你可能遇到过一些Solidity技巧,旨在提高你的代码技能以节省gas,但今天我想更多地关注如何理解以太坊虚拟机才能有效地节省智能合约的gas成本。

既然我们要深入以太坊,我将在这里留下其黄皮书的片段,其中详细说明了操作码的gas成本,在文章中我们也会参考它们。

EVM Yellow Paper Opcodes


Tip #1: 冷访问 (Cold Access) VS 热访问 (Warm Access)

Gcoldsload: 2100 gas

Gwarmaccess: 100 gas

这是我们的第一个操作码,第一个指定了首次访问变量(或冷访问)的成本,而第二个指定了第二次及后续访问变量(热访问)的成本。正如你所看到的,价格差异相当大,因此理解这一点可以对你的智能合约交易成本产生巨大影响。让我们看一个例子。

Cold Access Example 1

Cold Access Example 2

在Solidity函数中缓存数据可以降低gas使用量,即使它需要更多的代码行。在这个例子中,通过改变数组的位置,而不是每次在循环中从存储(storage)进行冷访问,它将数组存储在内存(memory)中,从而访问成本更低。


Tip #2: 零值与非零值以及 Gas 退款

Gsset = 20,000 gas

Rsclear = {执行价格折扣}

在以太坊区块链上,将值从0更改为非零值是昂贵的,正如我们在Gsset的价格中看到的,但将值从非零值更改为0,可以为你带来gas值的退款,依据操作码Rsclear。为了避免滥用退款机制,规定你最多只能获得总交易成本20%的退款。

你可以在区块链上一个非常常见的场景中找到这种情况,即更新智能合约中地址的余额。让我们看每个例子:

Zero to Non-Zero Example

Non-Zero to Zero Example

在第一个例子ZeroToNonZero合约中,非零到非零 (5,000 gas*) + 零到非零 (20,000 gas) = 25,000 gas

在第二个例子NonZeroToZero合约中,非零到零 (5,000 gas*) + 零到非零 (20,000 gas) — 退款 (4,800 gas) = 21,200 gas

*2,100 (Gcolssload) + 2,900 (Gsreset) = 5,000 gas


Tip #3: 状态变量的顺序很重要

存储 (Storage) 就像一个键值数据结构,它保存着 Solidity 智能合约的状态变量值。

你可以将存储想象成一个数组,这有助于可视化。这个存储“数组”中的每个空间都称为一个插槽 (slot),并容纳 32 字节(256 位)的数据,智能合约中声明的每个状态变量将根据其声明位置和类型占用一个插槽。

并非所有数据类型都占用全部 32 字节的插槽,因为有些数据类型(booluint8address...)占用的空间小于 32 字节。

这里的诀窍是,如果两个、三个或更多变量加起来是 32 字节或更少,Solidity 的编译器会尝试将它们打包到一个插槽中,但这些变量需要连续定义。

Storage Slots Example - Two Slots

Storage Slots Example - Three Slots

这里我们使用了数据类型 bool (1 字节)、address (20 字节) 和 uint256 (32 字节)。所以,了解这些变量的大小,你可以很容易理解,在第一个例子 TwoSlots 合约中,由于我们有 booladdress 在一起 (1 + 20 = 21 字节,小于 32 字节),它们将占用一个插槽。在 ThreeSlots 合约中,由于 booluint256 不能在同一个插槽中 (1 + 32 = 33 字节,大于插槽容量),所以总共会使用三个插槽。

现在,这为什么如此重要?

SLOAD 操作码需要 2100 gas,它用于从存储插槽中读取数据,因此如果能将变量存储在更少的插槽中,你最终会节省一些 gas。


Tip #4: uint256 比 uint8 更便宜

我们已经在技巧 #3 中了解到,uint256(256 位 = 32 字节)本身就占用一个插槽,我们也了解到 uint8 小于 32 字节。那么,尽管 8 位显然比 256 位小,为什么 uint256 却更便宜呢?

为了理解这一点,重要的是要知道,如果一个变量本身没有填满整个插槽,并且这个插槽也没有被任何其他变量填满,EVM 将会用“0”填充剩余的位,以便能够操作它。

EVM 执行的这种“0”填充会产生gas成本,这意味着为了节省交易gas,最好使用 uint256 而不是 uint8


希望在了解这些降低智能合约gas成本的技巧时,你也对 EVM 的工作原理有了一些了解。


需要 Gas 效率高的智能合约审计吗?

Zealynx Security,我们不仅仅发现漏洞——我们还帮助你交付 gas 优化、安全的智能合约。如果你的协议需要进行彻底的 安全审计,以捕捉效率低下之处和关键漏洞,请联系我们。

联系方式:

  • 原文链接: zealynx.io/blogs/solidit...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
zealynx
zealynx
江湖只有他的大名,没有他的介绍。