乐闻世界logo
搜索文章和话题

Solidity 智能合约如何进行 Gas 优化?有哪些常见的优化技巧?

3月6日 21:51

Gas 优化是 Solidity 开发中的关键技能,直接影响合约的执行成本和用户体验。以下是系统性的 Gas 优化策略和技巧。

1. 存储优化

Storage 是最昂贵的资源,优化存储使用是 Gas 优化的首要任务。

使用合适的数据类型

solidity
contract 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 }

存储变量打包

solidity
contract 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

solidity
contract 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. 变量和计算优化

使用常量和不变量

solidity
contract 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; } }

短路求优

solidity
contract 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. 循环优化

循环变量优化

solidity
contract 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

solidity
contract 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; } }

函数修饰符优化

solidity
contract 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. 事件和日志优化

solidity
contract 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. 位运算优化

solidity
contract 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固定值
使用 calldata3,800 Gas/32字节外部函数参数
自定义错误~50 Gas错误处理
unchecked~80 Gas/操作数学运算
事件替代 storage~19,000 Gas日志记录
短路求值变量条件判断

11. 优化工具

  1. hardhat-gas-reporter: 报告每个测试的 Gas 消耗
  2. eth-gas-reporter: 详细的 Gas 分析报告
  3. Remix IDE: 内置 Gas 估算
  4. Tenderly: Gas 分析和优化建议
javascript
// hardhat.config.js require("hardhat-gas-reporter"); module.exports = { gasReporter: { enabled: true, currency: "USD", gasPrice: 21 } };

12. 最佳实践总结

  1. 优先优化存储:Storage 操作是最昂贵的
  2. 使用合适的数据类型:避免过度分配
  3. 利用编译器优化:启用优化器并调整 runs 参数
  4. 批量操作:减少函数调用次数
  5. 使用事件:代替不必要的 storage 写入
  6. 延迟计算:尽可能在需要时计算而非存储
  7. 定期审计:使用工具检查 Gas 消耗

记住:过度优化可能降低代码可读性,需要在性能和可维护性之间找到平衡。

标签:Solidity