从设计原则、编码技巧、数据存储优化和具体示例四个方面,系统地讲解如何在 Solidity 合约中节省 Gas 费用。
设计原则
减少合约调用次数
- 将多个逻辑步骤合并到单一函数中,避免多次外部调用。
- 尽量减少合约之间的交互,因为每次外部调用都涉及高 Gas 消耗。
避免频繁修改状态变量
- 修改状态变量(如 storage)比操作本地变量(如 memory)耗费更多 Gas。
- 在逻辑中优先使用本地变量,计算完后一次性更新状态变量。
简化复杂计算
- 将复杂计算移到链下处理,通过链上存储结果代替。
- 在链下生成哈希值等,只存储结果以减少链上计算开销。
编码技巧
使用 view 和 pure 函数
view 函数不修改状态,pure 函数不依赖链上状态,调用它们不消耗 Gas。
1 | function add(uint a, uint b) public pure returns (uint) { |
精简数据类型
- 使用适合的数据类型,比如 uint8 而非 uint256(仅当值范围确定小于 256)。
- 避免布尔值,因为布尔操作实际存储在 256 位。
利用 calldata 代替 memory
函数的外部输入参数可用 calldata,节省内存分配成本。
1 | function processData(uint[] calldata data) external { |
事件替代状态变量
使用 emit 事件记录某些数据,而非存储状态变量。事件存储在日志中,成本低于 storage。
1 | event DataProcessed(address indexed user, uint amount); |
合理使用 immutable 和 constant
- immutable:部署后值不可更改,但比普通变量读取快。
- constant:在编译时决定值,不占用存储空间。
1 | uint256 public immutable deployTime = block.timestamp; |
数据存储优化
批量更新状态变量
将多次状态变量更新合并为一次。
1 | // 不推荐 |
使用结构体或映射存储数据
将多个相关变量合并到一个 struct 或映射中。
1 | struct User { |
减少存储变量数量
优化存储布局,减少存储槽的使用(每个槽 32 字节)。多个变量可以合并到一个存储槽中。
1 | // 不推荐 |
删除不必要的状态变量
使用 delete 删除已用完的存储变量,释放存储空间。
1 | delete users[userAddress]; |
其他优化建议
- 代码复用
- 将重复逻辑封装为内部函数,减少代码重复和部署大小。
- 使用库(library)减少代码复制。
- 优化循环
- 避免大规模循环操作,因为每次迭代都会增加 Gas。
- 可以分阶段处理数据,减少单次调用的负载。
- Gas 预言机
- 部署前使用工具(如 Remix、Hardhat)模拟和估算 Gas。
- 优化部署的 Gas 成本。