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

因为我们要深入研究以太坊,所以我将在这里留下它的黄皮书的片段,其中指定了操作码的 gas 成本,在文章中我们将参考它们。



提示 1:冷访问 VS 热访问

冷负荷:2100 gas

Gwarmaccess:100 气体

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





在 Solidity 中,将数据缓存在函数内可以降低 gas 消耗,即使需要更多行代码。在这种情况下,通过切换数组的位置,而不是从存储中使用它,从而每次在循环中都进行冷访问,它将数组存储在访问成本更低的内存中。

提示 #2:零值与非零值以及 gas 退款

Gsset = 20,000 gas

Rsclear = {执行价格折扣}

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

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





  • 在第一个示例 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

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

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

您可以将存储视为一个数组,这将有助于直观地理解这一点。此存储“数组”中的每个空间称为一个槽,可容纳 32 个字节(256 位)的数据,并且智能合约中声明的每个状态变量将根据其声明位置和类型占用一个槽。

并非所有数据类型都会占用每个插槽的全部 32 个字节,因为有些数据类型(bool、uint8、address……)占用的字节数少于这个数字。

这里的技巧是,如果两个/三个或更多变量加起来不超过 32 个字节,solidity 的编译器将尝试将它们打包在一个插槽中,但这些变量需要彼此相邻地定义。





这里我们使用的数据类型为 bool (1 字节)、address (20 字节)和 uint256 (32 字节)。因此,了解这些变量的大小后,您可以轻松理解,在 TwoSlots 合约的第一个示例中,由于 bool 和 address 加在一起(1 + 20 = 21 字节,小于 32 字节),因此它们将占用一个插槽。在 ThreeSlots 合约中,由于 bool 和 uint256 不能位于同一个插槽中(1 + 32 = 33 字节,大于插槽容量),因此我们总共将使用三个插槽。

那么,为什么这如此重要?

SLOAD 操作码消耗 2100 gas,用于从存储槽读取,因此如果您可以将变量存储在更少的槽中,最终将节省一些 gas。

提示 #4:uint256 比 uint8 便宜

我们在技巧 3 中了解到,uint256(256 位 = 32 字节)本身占用一个位置,并且我们还了解到 uint8 小于 32 字节。因此,虽然 8 位小于 256 位是显而易见的,但为什么 uint256 更便宜呢?

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

EVM 执行的这个“0”的加法是会消耗 gas 的,也就是说,为了节省交易 gas,最好使用 uint256 而不是 uint8。

__________________

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

__________________

Twitter @TheBlockChainer 可以找到更多关于智能合约、Web3 安全、Solidity、审计智能合约等的每日更新。

__________________