Solidity Gas优化:高效的智能合约策略

文章从比较全面介绍各种优化gas 的方法,如何优化存储、利用退款、数据类型和打包、使用事件、设计函数等。

以太坊Gas费用多年来一直是一个重大关注点。虽然从工作量证明到权益证明的过渡使网络更加节能,但并没有实质性地影响Gas费用。这一变化,以及其他正在进行的更新,如 EIP-1559、分片和各种一二层扩展解决方案,都是朝着更高效的以太坊生态系统迈出的步伐。然而,对于希望创建成本效益高、安全的智能合约的开发人员来说,掌握 Solidity Gas优化技术仍然至关重要。

本文将为你提供 Solidity Gas优化的实用策略,重点强调在降低成本和安全性之间取得平衡的重要性,以避免在合约中引入漏洞

以太坊中的Gas机制

以太坊中的Gas是推动智能合约执行和交易的“燃料”。它是一种衡量执行操作所需计算工作量的单位。以太坊网络上的每个操作,从简单的转账到复杂的合约交互,都需要Gas。因此,Gas是一种防止计算永远运行并向网络发送垃圾数据的机制。

它也是一种奖励节能开发的机制。两个智能合约可能实现相同的目标,但执行复杂度较低的合约将以较低的Gas费用获得奖励。这种经济模型激励开发人员提高他们的 Solidity 技能,编写的代码不仅功能齐全,而且节约。在这个区块链生态系统中,Gas优化是成功的关键,确保你的智能合约不仅有效,而且对开发人员和用户都经济可行。

Solidity Gas优化技术

存储优化

在 Solidity 中,存储优化对于管理和减少Gas成本至关重要。与存储操作相关的基本成本包括存储新变量需要 20,000 Gas,重写现有变量需要 5,000 Gas,而从存储槽中读取只需要相对较少的 200 Gas。有趣的是,仅声明存储变量而不初始化它不会产生Gas成本,这为节约Gas提供了机会。

contract GasOptimization {

    // Storage variable declaration (no initialization cost)

    uint256 public storedData;

减少链上数据: 减少合约变量中存储的数据量。链上数据量较少意味着Gas消耗较低。在可能的情况下,将数据保存在链下,只在区块链上存储必要的信息。这种方法不仅节省Gas,还能通过整合链下数据实现更复杂的应用程序,如预测市场和稳定币。

高效数据管理: 在函数中将存储变量永久保存在内存中。在函数中策略性地使用内存,临时存储变量。

更新存储变量: 如果要更新存储变量,首先在内存变量中计算所有内容。这样可以最大程度地减少向区块链写入操作,有效降低Gas成本。

变量打包: 在可能的情况下,将多个变量合并到一个存储槽中。Solidity 允许有效地处理存储,特别是当较小的数据类型被组合在一起时。此外,在使用结构体时,尝试对其进行打包。

struct PackedData {

        uint8 data1; // Smaller data types can be packed together

        uint8 data2;

    }

    PackedData public packedData;

不要初始化零值: 在编写 for 循环时,避免将变量初始化为零(uint256 index = 0;)。而是使用uint256 index;,因为uint256的默认值是零。这种做法可以通过避免初始化来节省一些Gas。

尽可能使 Solidity 值保持不可变: 对于不变的静态值,使用 constant 常量。如果值在构造时被赋值并保持不变,使用 immutable。这些做法减少了访问这些变量所需的Gas成本。

基于事件的数据存储注意事项: 虽然在事件中存储数据比变量更便宜,但需要注意的是,这些数据对链上的其他智能合约是不可访问的。在使用事件进行数据存储时,应仔细考虑这一限制。

利用退款

了解并利用 Solidity 中的Gas退款机制是优化智能合约Gas使用的关键方面。

释放存储槽: 当不再需要存储槽时,将其值设置为零(实质上“清零”变量)可以显著的获得 Gas 退款。具体来说,此操作将退还 15,000 Gas。重要的是要在合约中策略性地确定可以安全清零存储变量的点。这样做不仅清理了合约的状态,还可以恢复存储值时花费的部分Gas。

使用自毁: Solidity 中的selfdestruct操作码可用于从区块链中删除合约。使用此操作码销毁合约将退还 24,000 Gas。然而,需要考虑一个重要的限制。从selfdestruct获得的退款不能超过正在进行的合约调用使用的Gas的一半。这个限制是为了防止滥用退款机制。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract GasRefundExample {
   // Example storage variables
   uint256 public value1;
   uint256 public value2;

   // Function to update values and free storage slot
   function updateValuesAndFreeStorageSlot(uint256 _newValue1, uint256 _newValue2) external {
       // Perform some operations with the values
       value1 = _newValue1;
       value2 = _newValue2;

       // Clear the storage slot by zeroing the variables
       // This refunds 15,000 gas
       assembly {
           sstore(value1.slot, 0)
           sstore(value2.slot, 0)
       }
   }

   // Function to selfdestruct and refund gas
   function destroyContract() external {
       // Ensure the refund doesn't surpass half the gas used
       require(gasleft() > gasleft() / 2, "Refund cannot surpass half the gas used");

       // Selfdestruct and refund 24,000 gas
       selfdestruct(payable(msg.sender));
   }
}

在这个例子中,updateValuesAndFreeStorageSlot函数更新两个存储变量(value1 和 value2),然后使用汇编清除它们对应的存储槽,以退还 15,000 Gas。

destroyContract函数使用selfdestruct操作码销毁合约,并将任何剩余资金发送给合约所有者(msg.sender)。这个操作退还 24,000 Gas,但require语句确保退款不会超过一半的Gas使用,以防止潜在的滥用。

数据类型和打包

在 Solidity 中,选择和打包数据类型对于优化存储和减少Gas成本至关重要。

尽可能使用 bytes32,因为它是最优化的存储类型。 bytes32是一种 32 字节的数据类型,是 Solidity 中最节省Gas的存储类型。当你处理的数据适合 32 字节时,建议使用bytes32以实现最佳的Gas使用。

如果字节长度可以限制,尽量使用从 bytes1 到 bytes32 中的最小长度。 如果你处理的是可变长度的字节数组,建议使用最小的大小来...

剩余50%的内容订阅专栏后可查看

点赞 2
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
翻译小组
翻译小组
0x9e64...7c84
大家看到好的文章可以在 GitHub 提 Issue: https://github.com/lbc-team/Pioneer/issues 欢迎关注我的 Twitter: https://twitter.com/UpchainDAO