文章从比较全面介绍各种优化gas 的方法,如何优化存储、利用退款、数据类型和打包、使用事件、设计函数等。
- 原文链接: https://hacken.io/discover/solidity-gas-optimization/
- 译文出自:登链翻译计划
- 译者:翻译小组 校对:Tiny 熊
- 本文永久链接:learnblockchain.cn/article…
以太坊Gas费用多年来一直是一个重大关注点。虽然从工作量证明到权益证明的过渡使网络更加节能,但并没有实质性地影响Gas费用。这一变化,以及其他正在进行的更新,如 EIP-1559、分片和各种一二层扩展解决方案,都是朝着更高效的以太坊生态系统迈出的步伐。然而,对于希望创建成本效益高、安全的智能合约的开发人员来说,掌握 Solidity Gas优化技术仍然至关重要。
本文将为你提供 Solidity Gas优化的实用策略,重点强调在降低成本和安全性之间取得平衡的重要性,以避免在合约中引入漏洞。
以太坊中的Gas是推动智能合约执行和交易的“燃料”。它是一种衡量执行操作所需计算工作量的单位。以太坊网络上的每个操作,从简单的转账到复杂的合约交互,都需要Gas。因此,Gas是一种防止计算永远运行并向网络发送垃圾数据的机制。
它也是一种奖励节能开发的机制。两个智能合约可能实现相同的目标,但执行复杂度较低的合约将以较低的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 中的最小长度。 如果你处理的是可变长度的字节数组,建议使用最小的大小来...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!