· EVM Gas 优化 · 5 min read
EVM Layer 2 智能合约 Gas 优化技巧与实践
虽然 Layer 2 的 Gas 费远低于以太坊主网,但在高频交互或批量清算场景下,Gas 优化依然能为项目方节省巨大成本。本文分享实用的 Solidity 内存与存储优化技巧。
引言
随着 Arbitrum、Optimism、Base 和 Scroll 等 EVM Layer 2 网络的高速普及,单次交易的 Gas 费已经降至美分甚至更低。这导致很多开发者在编写 Solidity 合约时完全忽略了 Gas 优化的环节。
然而,在高频的流动性矿池(Yield Farming)、游戏链上资产发放以及批量清算协议中,累计的 Gas 消耗仍然是一笔巨大的开支。尤其是在以太坊主网拥堵导致 L2 提交数据(L1 Calldata)成本上升时,低效的代码会导致用户流失。
Northstar Protocol Studio 整理了以下几种业界通用的 Solidity 合约 Gas 优化方案。
1. 优化变量存储:使用 Calldata 代替 Memory
在 Solidity 函数参数中,凡是只读类型的数组、结构体或字符串参数,应当优先声明为 calldata 替代 memory。
- Memory(内存):会将参数数据完整复制到内存空间中,以便后续读写,这会产生额外的分配和复制 Gas。
- Calldata(调用数据):它是只读的临时存储区,避免了内存分配,可以直接进行指针级别的数据拉取,极大节省 Gas 消耗。
// 低效做法
function checkUsers(User[] memory users) public pure { ... }
// 优化做法
function checkUsers(User[] calldata users) public pure { ... }2. 紧凑排列状态变量 (Variable Packing)
EVM 虚拟机每次读写状态变量都是以 32 字节(256 位)的 Slot(插槽)为基本单位进行操作。如果变量排列合理,可以被打包存放在同一个 Slot 中,从而将多次 SSTORE(写入存储)操作合并为一次。
在排列状态变量时,应该按照大小顺序从高到低连续声明变量:
// 低效布局:占用了 3 个 Slot
uint128 public variableA;
uint256 public variableB;
uint128 public variableC;
// 优化布局:A 和 C 打包在同一个 Slot 中,总共只占用 2 个 Slot
uint256 public variableB;
uint128 public variableA;
uint128 public variableC;3. 巧用 unchecked 块绕过数学检查
从 Solidity 0.8.0 版本开始,编译器默认在所有的加减乘除计算中自动注入溢出校验。这保障了安全性,但每一步计算都会增加额外的判断指令(Gas 损耗)。
如果您能通过逻辑 100% 确认某项操作不会发生溢出(例如在一个固定范围的 for 循环计数器中),可以使用 unchecked 块进行优化:
// 低效写法
for (uint256 i = 0; i < length; i++) {
// 每次 i++ 都会有溢出检查
}
// 优化写法:节省约 20% 的循环 Gas
for (uint256 i = 0; i < length; ) {
// 核心逻辑...
unchecked { i++; }
}4. 优化条件控制语句:将低成本检查置前
在逻辑判断(如 require 或 if 块)中,有多个判断条件并联时,应将计算成本最低、最可能拦截流量的条件放在最前面。
由于 Solidity 采用“短路求值(Short-Circuit Evaluation)”规则,如果第一个并联条件已经能确定表达式的值,后续高成本的条件(如查询其他合约或读取 State 变量)就不会执行,从而大幅节省 Gas 消耗。
结论
Gas 优化是一项细致入微的工程实践,不仅体现在编译器层面的调优,更考验着开发者对 EVM 存储布局的深度掌握。可在适用项目中使用 Foundry 的 gas-report 工具进行性能对比,具体结果以测试数据为准。