📌 核心概念

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++;
}
}
}

🎯 总结要点

  1. 存储是最贵的 - 尽量减少和优化存储操作
  2. 变量打包 - 利用32字节的存储槽
  3. 使用constant/immutable - 避免存储开销
  4. 缓存storage - 多次读取时缓存到memory
  5. 测试验证 - 使用Remix或hardhat测试gas消耗