首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >《纸上谈兵·solidity》第 21 课:Gas 优化与成本分析 —— 写出便宜的智能合约

《纸上谈兵·solidity》第 21 课:Gas 优化与成本分析 —— 写出便宜的智能合约

原创
作者头像
孟斯特
发布2025-08-27 16:46:05
发布2025-08-27 16:46:05
1030
举报
文章被收录于专栏:EthereumEthereum

课程目标

  • 理解智能合约的主要 Gas 消耗来源
  • 掌握常见的 优化技巧与模式
  • 通过 Foundry 对比 优化前后 Gas 成本
  • 建立“写出高效合约”的思维框架

1、Gas 的构成

在 EVM 中,Gas 主要分为三类:

  1. 计算消耗:算术运算、函数调用等
  2. 存储消耗:对 storage 的读写最昂贵
  3. 交易消耗:部署合约、转账、事件日志

其中最关键的是 storage 写入,单次写入约 20,000 gas(如果从 0 改为非 0)。


2、优化点概览

优化点

建议做法

原因

存储变量访问

先读入 memory 变量,再多次使用

减少重复 SLOAD 成本

storage vs memory vs calldata

尽量使用 calldata 作为函数参数

最便宜,避免复制

变量打包

将多个 uint128 合并到一个 uint256 slot

节省存储槽

固定长度数组

bytes32 替代 string/bytes

减少动态存储开销

循环

避免无限增长数组迭代

每次循环线性增加 Gas

事件

只记录必要字段

日志存储在链上也要付费

External call

批处理、懒执行

减少重复调用


3、常见优化技巧

1. 避免重复 SLOAD

代码语言:solidity
复制
// 差的写法
function bad(uint256 x) external {
    for (uint i = 0; i < 10; i++) {
        storageVar += x;
    }
}

// 优化写法
function good(uint256 x) external {
    uint256 tmp = storageVar;
    for (uint i = 0; i < 10; i++) {
        tmp += x;
    }
    storageVar = tmp;
}

在循环中访问 storage 十次,成本远高于一次写回。


2. 使用 calldata 代替 memory

代码语言:solidity
复制
// 差的写法
function sum(uint256[] memory arr) external pure returns (uint256 s) {
    for (uint i = 0; i < arr.length; i++) {
        s += arr[i];
    }
}

// 优化写法
function sum(uint256[] calldata arr) external pure returns (uint256 s) {
    for (uint i = 0; i < arr.length; i++) {
        s += arr[i];
    }
}

memory 会复制数组,而 calldata 直接引用,节省大量 gas。


3. 变量打包

代码语言:solidity
复制
// 差的写法
struct Bad {
    uint128 a;
    uint128 b;
    uint128 c;
}

// 优化写法(两个 slot -> 一个 slot)
struct Good {
    uint128 a;
    uint128 b;
    uint256 c;
}

Solidity 会将多个小于 256bit 的变量打包进一个存储槽。


4. 事件优化

代码语言:solidity
复制
// 差的写法
event Deposit(address indexed user, uint256 amount, string memo);

// 优化写法
event Deposit(address indexed user, uint256 amount);

string 会额外消耗存储空间,除非业务必须,尽量避免。


4、Foundry 实战:Gas 对比测试

合约:未优化 vs 优化

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BadContract {
    uint256 public value;

    function addMany(uint256 x) external {
        for (uint i = 0; i < 10; i++) {
            value += x; // 每次都访问 storage
        }
    }
}

contract GoodContract {
    uint256 public value;

    function addMany(uint256 x) external {
        uint256 tmp = value;
        for (uint i = 0; i < 10; i++) {
            tmp += x;
        }
        value = tmp; // 只写一次 storage
    }
}

测试:比较 Gas 消耗

代码语言:solidity
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/Gas.sol";

contract GasTest is Test {
    BadContract bad;
    GoodContract good;

    function setUp() public {
        bad = new BadContract();
        good = new GoodContract();
    }

    function testGasBad() public {
        bad.addMany(1);
    }

    function testGasGood() public {
        good.addMany(1);
    }
}

执行测试:

代码语言:bash
复制
➜  tutorial git:(main) ✗ forge test --match-path test/Gas.t.sol --gas-report -vvv 

[⠊] Compiling...
[⠒] Compiling 2 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 506.84ms
Compiler run successful!

Ran 2 tests for test/Gas.t.sol:GasTest
[PASS] testGasBad() (gas: 53693)
[PASS] testGasGood() (gas: 51716)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.57ms (894.88µs CPU time)

╭----------------------------------+-----------------+-------+--------+-------+---------╮
| src/Gas.sol:BadContract Contract |                 |       |        |       |         |
+=======================================================================================+
| Deployment Cost                  | Deployment Size |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
| 152487                           | 489             |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
|                                  |                 |       |        |       |         |
|----------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                    | Min             | Avg   | Median | Max   | # Calls |
|----------------------------------+-----------------+-------+--------+-------+---------|
| addMany                          | 48203           | 48203 | 48203  | 48203 | 1       |
╰----------------------------------+-----------------+-------+--------+-------+---------╯

╭-----------------------------------+-----------------+-------+--------+-------+---------╮
| src/Gas.sol:GoodContract Contract |                 |       |        |       |         |
+========================================================================================+
| Deployment Cost                   | Deployment Size |       |        |       |         |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| 153147                            | 492             |       |        |       |         |
|-----------------------------------+-----------------+-------+--------+-------+---------|
|                                   |                 |       |        |       |         |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| Function Name                     | Min             | Avg   | Median | Max   | # Calls |
|-----------------------------------+-----------------+-------+--------+-------+---------|
| addMany                           | 46257           | 46257 | 46257  | 46257 | 1       |
╰-----------------------------------+-----------------+-------+--------+-------+---------╯


Ran 1 test suite in 6.50ms (3.57ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

5、进阶优化策略

  1. 批处理:合并多次转账为一次
  2. 懒执行:只在必要时更新状态变量(例如延迟结算)
  3. 预计算:在链下完成复杂运算,把结果上链
  4. 库函数优化:使用 unchecked {} 避免多余溢出检查(适合已知安全场景)

6、总结

  • Gas 优化的关键:减少 storage 写入,避免不必要的复制,合理打包变量
  • 使用 Foundry 的 --gas-report 工具可以直观对比优化效果
  • 优化要 兼顾安全与可读性,不要为省 gas 牺牲合约清晰度

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 课程目标
  • 1、Gas 的构成
  • 2、优化点概览
  • 3、常见优化技巧
    • 1. 避免重复 SLOAD
    • 2. 使用 calldata 代替 memory
    • 3. 变量打包
    • 4. 事件优化
  • 4、Foundry 实战:Gas 对比测试
    • 合约:未优化 vs 优化
    • 测试:比较 Gas 消耗
  • 5、进阶优化策略
  • 6、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档