Hardhat gas 优化报告全攻略:从零配置到实战省费

·

关键词:Hardhat、gas reporter、智能合约优化、Solidity、ETH 费用预估、SLOAD/SSTORE、变量打包、存储读写

背景:为什么你必须关注 gas 优化

在以太坊网络,一笔交易所消耗的 gas 直接决定用户最终需要支付的 ETHUSD 费用。测试阶段不摸清函数成本,主网一上线就可能因为高昂的转账费用吓跑用户。借助 Hardhat gas reporter 插件,开发者可以在跑单元测试时同步拿到清晰的 gas 消耗报告,提前优化并避免返工。


安装与配置:3 分钟搞定

步骤 1:安装插件

npm install --save-dev hardhat-gas-reporter

步骤 2:在 hardhat.config.js 添加最小化配置

require("hardhat-gas-reporter");

module.exports = {
  solidity: "0.8.20",
  gasReporter: {
    enabled: true,           // 只有本地跑测试时才计算 gas
    currency: "USD",         // 支持 ETH、USD、EUR
    gasPrice: 20,            // 默认 20 Gwei,可按市场行情调整
    coinmarketcap: process.env.COINMARKETCAP_KEY, // 必填,实时 ETH 价
    outputFile: "gas-report.txt", // 把结果保存到文件,方便 CI
    noColors: true,          // 结果纯文本,防止 CI 中出现颜色转义
    excludeContracts: ["MockToken"] // 测试合约不统计
  }
};

👉 完整配置模板与常见问题解决方案


生成报告:两行命令可见成效

  1. 把测试代码写好后,直接执行:

    npx hardhat test
  2. 控制台立即输出如下:
·----------------------------|----------------------------|-------------|----------------------------·
| Solidity Contract · Method    · Min (Gas) · Max (Gas) · Avg (Gas) · Cost (USD)
·----------------------------|----------------------------|-------------|-------------|-------------·
| MyToken   · transfer          · 28 912   · 51 234  · 43 210  · 0.22
| MyToken   · approve           · 2 345    · 2 345   · 2 345   · 0.01
·----------------------------|----------------------------|-------------|-------------|-------------·

报告同步写入 gas-report.txt,可做版本比较或制成看板。


报告解读:5 个字段决定优化方向

字段你能做什么
Method立刻定位调用频繁的函数,优先砍“大头”。
Avg Gas越偏离最小值的函数越需要检查逻辑分支差异。
Cost (USD)营销团队常用的“每张优惠券发行成本”就是这里来的。
Network · Gas Price当测试网 Gas 价远低于主网时,需要在主网做沙箱测试。
SLOAD / SSTORE 数量逐个对比 storagememory,用下面优化技巧立刻切。

实战技巧:5 分钟让你的合约少花 30% gas

1. 减少存储操作(多数项目最大消耗来源)

最佳做法:批量写 storage,集中读 memory。

// 差:两次独立 SSTORE
uint256 public score;
uint256 public lastUpdate;
function setScore(uint256 newScore) external {
    score = newScore;          // SSTORE 1
    lastUpdate = block.number; // SSTORE 2
}

// 好:一次打包写 storage
struct Stats {
    uint128 score;      // 注意用 uint128 以节省字节
    uint128 updateBlk;
}
Stats private stats;
function setScore(uint128 newScore) external {
    stats = Stats(newScore, uint128(block.number)); // 1 SSTORE
}

2. 用 constant / immutable 代替公共变量

// 差:每次需要一次 SLOAD
address public feeTo;

// 好:编译期就确定,运行期 gas=0
address public constant FEE_TO = 0x...;

3. 变量打包(Variable Packing)

把较小的整数放相邻位置,使 EVM 只用 1 个 storage slot(32 字节)

// 差:占 3 slot
uint128 a;
uint256 b;
uint128 c;

// 好:仅占 2 slot——a+c 打包,b 单占
uint128 a;
uint128 c;
uint256 b;

4. 合理使用 unchecked

当逻辑已经保证不会溢出时,显式关闭溢出检查

function incUnchecked(uint256 x) external pure returns (uint256) {
    unchecked { return x + 1; }
}

可省 46–50 gas,高频合约能省下一笔可观费用。

5. 避免重复计算 & 缓存中间变量

// 差:重复加 a+b
function compute(uint256 a, uint256 b) external pure returns (uint256) {
    require(a + b > 100, "Invalid");
    return (a + b) * 2;
}

// 好:一次计算、缓存复用
function compute(uint256 a, uint256 b) external pure returns (uint256) {
    uint256 sum = a + b;
    require(sum > 100, "Invalid");
    return sum * 2;
}

关键流程示例:如何在一次迭代中节省 40 USD

假设你的 NFT 项目有 1000 次 mint,平均一次 mint 花费 0.0002 ETH(约 0.4 USD),总费用 400 USD。通过以下优化:

  1. 把循环中多余的 storage 读写合并,减少 20% gas。
  2. 打包 uint96 地址列表(节省 10 slot)。
  3. 常量设置手续费地址为 immutable,读写 cost 为 0。

实际测算,report 显示平均 mint 成本降到 0.00012 ETH(约 0.24 USD)。节约 40% 费用,总值约 160 USD,足够再发一轮优惠码。

👉 注册后免费领取每日实时 Gas Price 动态图


FAQ:关于 hardhat-gas-reporter 的高频问题

Q1:不配置 coinmarketcap 会怎样?
A1:不会报错,但报告里 “USD 成本”字段为 0,无法与其他非技术同事共享“真实费用”。

Q2:测试结果波动很大,如何避免?
A2:本地网络使用 固定 gasPrice(20 Gwei) 并关闭异步交易,再把测试设为 serial 运行即可。

Q3:CI 环境中想隐藏颜色输出应该怎么做?
A3:在 gasReporter 里加 noColors: true 即可,日志文件干净易读。

Q4:能把报告上传到 Notion 或 Jira 吗?
A4:可以。利用 outputFile 选项生成 txt 后,定时脚本抓文件内容通过 API 推送即可。

Q5:UPGRADEABLE 合约与 delegatecall 会不会影响数据?
A5:will Proxy 不会,但 delegatecall 的函数在代理合约上下文中被执行,report 会统计 proxy 而非实现 的数据,注意对比合约名。


小结:提效把成本降到底线

通过 hardhat-gas-reporter 插件,你可在 运行单元测试时同步获得可读的 gas 优化报告

学会将 “成本” 当作 “业务指标” 放在 OKR,你的智能合约才能在主网环境里越来越轻、越来越受用户欢迎。