📌 核心概念
Gas费是执行以太坊智能合约所需的计算费用,优化Gas费可以大幅降低用户交互成本和合约部署成本。
一、基础优化策略
1.1 变量类型优化
solidity
1 2 3 4 5 6 7 8 9 10
| // ❌ 不推荐 uint256 public number1 = 100; uint256 public number2 = 200;
// ✅ 推荐:使用更小的类型 uint128 public number1 = 100; uint128 public number2 = 200; // 打包存储,节省存储槽
// 或者使用uint8处理小数值 uint8 public status = 1; // 状态值用最小的类型
|
1.2 变量打包(Packing)
solidity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // ❌ 不推荐 - 占用3个存储槽 uint128 public a; // 槽1 uint128 public b; // 槽2 uint8 public c; // 槽3
// ✅ 推荐 - 占用2个存储槽 struct PackedVars { uint128 a; uint128 b; uint8 c; } // 或直接排列 uint128 public a; uint128 public b; uint8 public c; // a和b已经占用槽1,c可以放在槽1剩余空间? // 注意:uint128 a和b分别占用不同的槽,因为128+128=256,正好一个槽? // 更正:uint128 a和b可以放在一个槽里 uint128 public a; uint128 public b; // a和b共用一个槽 uint8 public c; // c可以放在槽2
|
1.3 存储位置选择
solidity
1 2 3 4 5 6 7 8 9 10 11
| // ❌ 不推荐:频繁操作storage function badExample(uint256[] memory _ids) public { uint256[] storage ids = _ids; // 错误:不能将memory赋值给storage // ✅ 推荐:明确指定存储位置 uint256[] memory ids = _ids; // 使用memory // 复杂数据结构示例 uint256[] storage localArray = myArray; // 引用storage,修改会影响原数据 uint256[] memory localArrayCopy = myArray; // 复制到memory,独立修改 }
|
二、进阶优化技巧
2.1 使用常量(Constant)
solidity
1 2 3 4 5 6 7
| // ❌ 不推荐 uint256 public fee = 100;
// ✅ 推荐:常量不占用存储槽,直接内嵌到字节码 uint256 public constant FEE = 100; string public constant TOKEN_NAME = "MyToken"; address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;
|
2.2 使用不可变量(Immutable)
solidity
1 2 3 4 5 6 7 8 9 10 11
| // ❌ 不推荐 address public owner; constructor(address _owner) { owner = _owner; // 占用存储槽 }
// ✅ 推荐:immutable变量部署后不可变,节省gas address public immutable owner; constructor(address _owner) { owner = _owner; // 存储在代码中,不占用存储槽 }
|
2.3 事件索引优化
solidity
1 2 3 4 5 6
| solidity// ❌ 不推荐:全部不索引 event Transfer(address from, address to, uint256 amount);
// ✅ 推荐:关键字段索引,便于查询 event Transfer(address indexed from, address indexed to, uint256 amount); // 注意:最多3个索引参数
|
三、存储优化策略
3.1 存储读写优化
solidity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // ❌ 不推荐:多次读取storage function badRead(uint256 _index) public view returns(uint256) { uint256 sum = 0; for(uint i = 0; i < myArray.length; i++) { sum += myArray[i]; // 每次循环都读取storage } return sum; }
// ✅ 推荐:缓存到memory function goodRead(uint256 _index) public view returns(uint256) { uint256[] memory localArray = myArray; // 一次性读取到memory uint256 sum = 0; for(uint i = 0; i < localArray.length; i++) { sum += localArray[i]; // 从memory读取 } return sum; }
|
3.2 批量操作
solidity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| // ❌ 不推荐:多次独立写入 function badBatch(address[] calldata _users) external { for(uint i = 0; i < _users.length; i++) { balances[_users[i]] = 100; // 每次写入都消耗gas } }
// ✅ 推荐:批量操作并不能减少存储写入次数,但可以减少外部调用 // 示例:使用位图存储多个状态 contract BitmapExample { uint256 public states; // 一个uint256存储32个bool状态 function setState(uint8 _index, bool _value) external { require(_index < 32, "超出范围"); if(_value) { states |= (uint256(1) << _index); // 设置某位为1 } else { states &= ~(uint256(1) << _index); // 设置某位为0 } } function getState(uint8 _index) external view returns(bool) { return (states >> _index) & 1 == 1; } }
|
四、函数优化技巧
4.1 函数修饰符使用
solidity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // ❌ 不推荐:多次检查 function withdraw() external { require(balances[msg.sender] > 0, "余额不足"); require(block.timestamp > unlockTime, "未到解锁时间"); require(!paused, "合约暂停"); // 转账逻辑 }
// ✅ 推荐:使用修饰符复用检查逻辑 modifier onlyWhenActive() { require(!paused, "合约暂停"); require(block.timestamp > unlockTime, "未到解锁时间"); _; }
function withdraw() external onlyWhenActive { require(balances[msg.sender] > 0, "余额不足"); // 转账逻辑 }
|
4.2 短路运算
solidity
1 2 3 4 5
| // ❌ 不推荐:消耗多的放在前面 require(expensiveCheck() && simpleCheck());
// ✅ 推荐:简单的检查放前面,利用短路特性 require(simpleCheck() && expensiveCheck());
|
五、实用优化清单
🔧 部署优化
- 使用构造函数初始化immutable变量
- 使用constant常量代替storage变量
- 优化合约继承结构,减少继承层级
- 移除未使用的函数和变量
🔧 执行优化
- 将storage变量缓存到memory
- 使用unchecked块处理不会溢出的计算
- 使用require尽早检查失败条件
- 合并多次外部调用
🔧 数据结构优化
- 使用mapping替代数组(如果不需要遍历)
- 使用bytes32替代string(如果可能)
- 紧凑打包存储变量
六、Gas消耗对比表
| 操作类型 |
Gas消耗 |
优化建议 |
| SLOAD (读storage) |
~800 |
缓存到memory |
| SSTORE (写storage) |
~5000-20000 |
减少写入次数 |
| 普通运算 |
~3-10 |
影响较小 |
| 外部调用 |
~700 |
尽量减少 |
| 创建合约 |
~32000+ |
优化构造函数 |
📝 实战案例:优化前VS优化后
优化前(约消耗150,000 gas):
solidity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| contract BadExample { uint256 public counter; uint256 public timestamp; address public owner; constructor() { owner = msg.sender; timestamp = block.timestamp; } function increment() external { require(owner == msg.sender); counter++; } }
|
优化后(约消耗90,000 gas):
solidity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| contract GoodExample { uint256 public counter; uint256 public immutable timestamp; address public immutable owner; constructor() { owner = msg.sender; timestamp = block.timestamp; } function increment() external { require(owner == msg.sender, "Not owner"); unchecked { // 确认不会溢出 counter++; } } }
|
🎯 总结要点
- 存储是最贵的 - 尽量减少和优化存储操作
- 变量打包 - 利用32字节的存储槽
- 使用constant/immutable - 避免存储开销
- 缓存storage - 多次读取时缓存到memory
- 测试验证 - 使用Remix或hardhat测试gas消耗