Gas 优化是 Solidity 开发中的关键技能,直接影响合约的执行成本和用户体验。以下是系统性的 Gas 优化策略和技巧。
1. 存储优化
Storage 是最昂贵的资源,优化存储使用是 Gas 优化的首要任务。
使用合适的数据类型
soliditycontract StorageOptimization { // 不推荐:使用 uint256 存储小数值 uint256 public smallValue; // 占用 32 字节 // 推荐:使用更小的数据类型 uint128 public value128; // 占用 16 字节 uint64 public value64; // 占用 8 字节 uint32 public value32; // 占用 4 字节 uint8 public value8; // 占用 1 字节 // 最佳:打包存储变量 uint128 public balance; // slot 0: 16 字节 uint32 public timestamp; // slot 0: 4 字节 uint16 public status; // slot 0: 2 字节 address public owner; // slot 0: 20 字节 - 需要 slot 1 // 注意:uint128 + uint32 + uint16 = 22 字节,address 需要新的 slot }
存储变量打包
soliditycontract PackingExample { // 未优化:占用 3 个 storage slot struct Unoptimized { uint256 a; // slot 0 uint128 b; // slot 1 uint128 c; // slot 2 } // 优化后:只占用 2 个 storage slot struct Optimized { uint128 b; // slot 0 (16 bytes) uint128 c; // slot 0 (16 bytes) - 打包在一起 uint256 a; // slot 1 } // 最佳实践:按大小排序 struct BestPractice { uint128 a; // 16 bytes uint128 b; // 16 bytes - 与 a 共用一个 slot uint64 c; // 8 bytes uint64 d; // 8 bytes - 与 c 共用 uint32 e; // 4 bytes uint32 f; // 4 bytes - 与 e 共用 } }
使用 memory 代替 storage
soliditycontract MemoryVsStorage { uint256[] public data; // 昂贵:多次 storage 访问 function expensiveSum() public view returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; // 每次循环都访问 storage } return sum; } // 优化:先加载到 memory function optimizedSum() public view returns (uint256) { uint256[] memory localData = data; // 一次性加载 uint256 sum = 0; for (uint i = 0; i < localData.length; i++) { sum += localData[i]; // 访问 memory,便宜很多 } return sum; } }
2. 变量和计算优化
使用常量和不变量
soliditycontract ConstantOptimization { // 昂贵:storage 变量 uint256 public constantValue = 1000; // 优化:常量(不占用 storage) uint256 public constant CONSTANT_VALUE = 1000; // 更优:不可变量(编译时确定,不占用 storage) uint256 public immutable immutableValue; constructor(uint256 _value) { immutableValue = _value; } // 使用常量计算 function calculate(uint256 amount) public pure returns (uint256) { return amount * CONSTANT_VALUE / 10000; } }
短路求优
soliditycontract ShortCircuit { // 优化:利用短路求值 function checkConditions(bool a, bool b, bool c) public pure returns (bool) { // 将最可能为 false 的条件放在前面 return a && b && c; } // 优化:|| 运算符 function checkOr(bool a, bool b, bool c) public pure returns (bool) { // 将最可能为 true 的条件放在前面 return a || b || c; } }
3. 循环优化
循环变量优化
soliditycontract LoopOptimization { uint256[] public data; // 未优化:每次循环都访问 storage function unoptimizedLoop() public view returns (uint256) { uint256 sum = 0; for (uint256 i = 0; i < data.length; i++) { sum += data[i]; } return sum; } // 优化 1:缓存数组长度 function optimizedLoop1() public view returns (uint256) { uint256 sum = 0; uint256 len = data.length; // 缓存长度 for (uint256 i = 0; i < len; i++) { sum += data[i]; } return sum; } // 优化 2:使用 unchecked 和 ++i function optimizedLoop2() public view returns (uint256) { uint256 sum = 0; uint256 len = data.length; for (uint256 i = 0; i < len; ) { sum += data[i]; unchecked { ++i; } // 使用 unchecked 和前置递增 } return sum; } // 优化 3:使用 memory 数组 function optimizedLoop3() public view returns (uint256) { uint256[] memory localData = data; uint256 sum = 0; uint256 len = localData.length; for (uint256 i = 0; i < len; ) { sum += localData[i]; unchecked { ++i; } } return sum; } }
4. 函数优化
使用 calldata
soliditycontract CalldataOptimization { // 昂贵:使用 memory function processMemory(uint256[] memory data) external pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; } return sum; } // 优化:使用 calldata(只读,不复制) function processCalldata(uint256[] calldata data) external pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; } return sum; } }
函数修饰符优化
soliditycontract ModifierOptimization { // 昂贵:修饰符代码内联到每个函数 modifier expensiveCheck() { require(msg.sender == owner, "Not owner"); require(!paused, "Paused"); require(balance > 0, "No balance"); _; } // 优化:将检查提取为内部函数 modifier optimizedCheck() { _checkAccess(); _; } function _checkAccess() internal view { require(msg.sender == owner, "Not owner"); require(!paused, "Paused"); require(balance > 0, "No balance"); } // 这样编译器可以更有效地优化 function action1() external optimizedCheck { } function action2() external optimizedCheck { } function action3() external optimizedCheck { } }
5. 错误处理优化
自定义错误(Solidity 0.8.4+)
solidity// 文件: ErrorOptimization.sol // 开源许可: MIT pragma solidity ^0.8.4; contract ErrorOptimization { address public owner; uint256 public balance; // 定义自定义错误(比 require 字符串更省 Gas) error NotOwner(address caller); error InsufficientBalance(uint256 requested, uint256 available); error InvalidAmount(uint256 amount); modifier onlyOwner() { if (msg.sender != owner) { revert NotOwner(msg.sender); } _; } function withdraw(uint256 amount) external onlyOwner { if (amount == 0) { revert InvalidAmount(amount); } if (amount > balance) { revert InsufficientBalance(amount, balance); } balance -= amount; payable(msg.sender).transfer(amount); } }
6. 事件和日志优化
soliditycontract EventOptimization { // 昂贵:存储大量数据 mapping(uint256 => Transaction) public transactions; struct Transaction { address from; address to; uint256 amount; uint256 timestamp; string metadata; } // 优化:使用事件代替 storage event TransactionExecuted( address indexed from, address indexed to, uint256 amount, uint256 timestamp ); function executeTransaction(address to, uint256 amount) external { // 执行业务逻辑... // 使用事件记录(比 storage 便宜得多) emit TransactionExecuted(msg.sender, to, amount, block.timestamp); } }
7. 库的使用
solidity// 使用库减少代码重复 library SafeMath { function add(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { uint256 c = a + b; require(c >= a, "Addition overflow"); return c; } } } contract UsingLibrary { using SafeMath for uint256; function calculate(uint256 a, uint256 b) public pure returns (uint256) { return a.add(b); // 使用库函数 } }
8. 位运算优化
soliditycontract BitwiseOptimization { // 使用位运算代替数学运算 // 乘以/除以 2 的幂 function multiplyBy2(uint256 x) public pure returns (uint256) { return x << 1; // 等同于 x * 2 } function divideBy2(uint256 x) public pure returns (uint256) { return x >> 1; // 等同于 x / 2 } // 检查是否为 2 的幂 function isPowerOf2(uint256 x) public pure returns (bool) { return x > 0 && (x & (x - 1)) == 0; } // 使用位掩码存储多个布尔值 uint256 private flags; uint256 constant FLAG_PAUSED = 1 << 0; // 1 uint256 constant FLAG_FINALIZED = 1 << 1; // 2 uint256 constant FLAG_APPROVED = 1 << 2; // 4 function setFlag(uint256 flag) internal { flags |= flag; } function clearFlag(uint256 flag) internal { flags &= ~flag; } function hasFlag(uint256 flag) internal view returns (bool) { return (flags & flag) != 0; } }
9. 编译器优化
使用优化器
json// hardhat.config.js module.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200 // 根据合约调用频率调整 } } } };
10. Gas 优化对比表
| 优化技术 | Gas 节省 | 适用场景 |
|---|---|---|
| 存储打包 | 20,000 Gas/slot | 多个小变量 |
| 使用常量 | 20,000 Gas | 固定值 |
| 使用 calldata | 3,800 Gas/32字节 | 外部函数参数 |
| 自定义错误 | ~50 Gas | 错误处理 |
| unchecked | ~80 Gas/操作 | 数学运算 |
| 事件替代 storage | ~19,000 Gas | 日志记录 |
| 短路求值 | 变量 | 条件判断 |
11. 优化工具
- hardhat-gas-reporter: 报告每个测试的 Gas 消耗
- eth-gas-reporter: 详细的 Gas 分析报告
- Remix IDE: 内置 Gas 估算
- Tenderly: Gas 分析和优化建议
javascript// hardhat.config.js require("hardhat-gas-reporter"); module.exports = { gasReporter: { enabled: true, currency: "USD", gasPrice: 21 } };
12. 最佳实践总结
- 优先优化存储:Storage 操作是最昂贵的
- 使用合适的数据类型:避免过度分配
- 利用编译器优化:启用优化器并调整 runs 参数
- 批量操作:减少函数调用次数
- 使用事件:代替不必要的 storage 写入
- 延迟计算:尽可能在需要时计算而非存储
- 定期审计:使用工具检查 Gas 消耗
记住:过度优化可能降低代码可读性,需要在性能和可维护性之间找到平衡。