0%

solidity合约中如何减少gas消耗

从设计原则、编码技巧、数据存储优化和具体示例四个方面,系统地讲解如何在 Solidity 合约中节省 Gas 费用。

设计原则

减少合约调用次数

  • 将多个逻辑步骤合并到单一函数中,避免多次外部调用。
  • 尽量减少合约之间的交互,因为每次外部调用都涉及高 Gas 消耗。

避免频繁修改状态变量

  • 修改状态变量(如 storage)比操作本地变量(如 memory)耗费更多 Gas。
  • 在逻辑中优先使用本地变量,计算完后一次性更新状态变量。

简化复杂计算

  • 将复杂计算移到链下处理,通过链上存储结果代替。
  • 在链下生成哈希值等,只存储结果以减少链上计算开销。

编码技巧

使用 view 和 pure 函数

view 函数不修改状态,pure 函数不依赖链上状态,调用它们不消耗 Gas。

1
2
3
function add(uint a, uint b) public pure returns (uint) {
return a + b;
}

精简数据类型

  • 使用适合的数据类型,比如 uint8 而非 uint256(仅当值范围确定小于 256)。
  • 避免布尔值,因为布尔操作实际存储在 256 位。

利用 calldata 代替 memory

函数的外部输入参数可用 calldata,节省内存分配成本。

1
2
3
function processData(uint[] calldata data) external {
// calldata 参数直接使用,无需复制
}

事件替代状态变量

使用 emit 事件记录某些数据,而非存储状态变量。事件存储在日志中,成本低于 storage。

1
event DataProcessed(address indexed user, uint amount);

合理使用 immutable 和 constant

  • immutable:部署后值不可更改,但比普通变量读取快。
  • constant:在编译时决定值,不占用存储空间。
1
2
uint256 public immutable deployTime = block.timestamp;
uint256 public constant FEE_RATE = 100;

数据存储优化

批量更新状态变量

将多次状态变量更新合并为一次。

1
2
3
4
5
6
7
// 不推荐
balance = balance + 10;
counter = counter + 1;

// 推荐
balance += 10;
counter += 1;

使用结构体或映射存储数据

将多个相关变量合并到一个 struct 或映射中。

1
2
3
4
5
struct User {
uint256 balance;
uint256 lastUpdated;
}
mapping(address => User) users;

减少存储变量数量

优化存储布局,减少存储槽的使用(每个槽 32 字节)。多个变量可以合并到一个存储槽中。

1
2
3
4
5
6
7
8
9
10
// 不推荐
uint128 public var1;
uint128 public var2;

// 推荐
struct Combined {
uint128 var1;
uint128 var2;
}
Combined public data;

删除不必要的状态变量

使用 delete 删除已用完的存储变量,释放存储空间。

1
delete users[userAddress];

其他优化建议

  1. 代码复用
  • 将重复逻辑封装为内部函数,减少代码重复和部署大小。
  • 使用库(library)减少代码复制。
  1. 优化循环
  • 避免大规模循环操作,因为每次迭代都会增加 Gas。
  • 可以分阶段处理数据,减少单次调用的负载。
  1. Gas 预言机
  • 部署前使用工具(如 Remix、Hardhat)模拟和估算 Gas。
  • 优化部署的 Gas 成本。