Gas优化最佳实践清单
下面这份 Gas优化最佳实践,来自数十个上线在以太坊与 Binance 智能链项目的复盘,按「越靠前、收益越高」的顺序排列,方便团队按优先级落地。
1. 把状态变量从 storage 拉到 memory 操作
任何 SLOAD 都比 MLOAD 贵两个数量级。在循环中重复访问同一个状态变量时,先 copy 到 memory 变量,循环结束后再写回 storage,能直接砍掉 90% 的读 Gas。
2. 紧凑存储布局
把多个小于 32 字节的变量打包进同一个 storage slot。但要小心结构体字段顺序:Solidity 编译器并不会自动重排,必须显式调整顺序,让相邻字段总宽度小于等于 256 位。
3. 用 immutable 替代 constant 的运行时计算
immutable 变量在部署时写入字节码,运行期读取相当于读字面量,比 storage 读便宜得多。常见的合约 owner、token 地址、链 ID 都适合定义为 immutable。
4. 避免不必要的 SSTORE
如果某次写入与原值相同,仍会消耗约 2900 Gas。先做 if (old != new) 判断,再 SSTORE。这一条在 B安 智能链高频交互场景中尤其重要。
5. 使用 custom error 取代字符串 revert
custom error 编码后只占 4 字节 selector,比 revert string 节省 50–150 Gas,同时降低部署字节码。
6. 批量调用与 multicall
把多个独立读操作放入 multicall 合约一次性返回,能节省一次次握手与 21000 的交易基础 Gas。读侧使用 view multicall,写侧使用 try/catch 的 batch executor。
7. 仔细设计事件(event)
event 是 Gas 大户。原则:
- 仅索引必须被链下检索的字段;
- 大段 metadata 用 IPFS 哈希存储,事件中只发哈希;
- 跨多笔交易共享上下文时,用 nonce + 单一事件聚合,而不是逐笔发出。
8. 使用外部 library 复用算法
把通用代码(Merkle 校验、SafeCast、字符串处理)抽成 external library,部署一次后被多个合约 delegatecall。适合 BN 生态中跨多个合约共享代码的工厂模式。
9. unchecked 块仅用于可证明的内部循环
配合显式注释「为什么不会溢出」。所有用户输入仍需走默认溢出检查。
10. 自定义 fallback 与 receive 函数
如果合约不接收 ETH,应显式禁用 receive;如果只接收特定 calldata,应在 fallback 中尽早 revert,避免误转账消耗 Gas。
11. 汇编内联的边界
仅在 hot path 使用,并通过 Foundry fuzz、Slither、Echidna 三重验证。任何修改 free memory pointer 的 Yul 都必须显式注释。
12. 部署期优化
- 启用 via-IR + optimizer.runs = 1000000(高频合约)或 1(一次性部署);
- 把大段不可变常量塞进字节码而非 storage;
- 通过 CREATE2 在 必安 等多链复用同一地址。
持续度量
所有最佳实践都需要可被度量。建议在 CI 中固定 forge snapshot 与 gas-reporter 输出,任何 PR 引入超过 5% 的 Gas 增长,都必须有 reviewer 签字。把这条纪律坚持半年,整个团队的成本意识会显著提升,最终落在用户感知的费率上。