关于 gas
面向 Monad 应用开发者的 gas 机制参考技能,说明交易定价、限制和 opcode 成本差异。重点是 Monad 按 gas limit 而非实际消耗收费,以及如何设置限额和展示费用。
主要功能
- 01说明按 gas limit 收费
- 02介绍 EIP-1559 定价规则
- 03列出区块与交易限制
- 04解释基础费控制器
- 05汇总 opcode 与预编译成本差异
- 06提供 gas 限额与费用计算建议
适用场景
- 01为 Monad 交易设置 gas limit
- 02在界面计算并展示 gas 费用
- 03估算合约冷存储访问成本
- 04评估签名或 ZK 预编译费用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ therealharpaljadeja/ monskills --skill gas安装后在 Claude Code 中输入/gas确认已加载。
附带文件SKILL.zh.md
详细介绍
Monad 的 Gas 定价兼容 EIP-1559,但与 Ethereum 有一些关键差异。如果你正在构建会提交交易、估算 Gas 或显示 Gas 费用的应用,就需要了解这些差异。
关键差异:Monad 按 Gas 上限收费,而不是按 Gas 用量收费
在 Ethereum 上,用户按交易实际消耗的 Gas 付费。在 Monad 上,用户根据自己设置的 Gas 上限 付费:
gas_paid = gas_limit * price_per_gas
这是因为 Monad 使用异步执行:区块领导者会先构建区块,再执行区块,因此交易被纳入区块时无法得知实际 Gas 消耗量。这样可以防止拒绝服务(DOS)攻击,即交易声称 Gas 费用低廉,却消耗昂贵的计算资源。
这对开发者意味着:
- 设置过高的 Gas 上限会直接让用户多花 MON。
- 务必设置严格且准确的 Gas 上限,尤其是成本固定的交易。
- 对于原生 MON 转账,Gas 费用始终为 21,000。请直接将此值硬编码,而不要依赖
eth_estimateGas。
EIP-1559 交易定价
Monad 使用 type 2(EIP-1559)交易:
price_per_gas = min(base_price_per_gas + priority_price_per_gas, max_price_per_gas)
用户签名时需指定两个值:
priority_price_per_gas— 用于提高交易在区块中优先级的小费max_price_per_gas— Gas 总价格的安全上限
网络控制 base_price_per_gas,该值对区块中的所有交易都相同。
区块和交易上限
| 参数 | 值 |
|---|---|
| 区块 Gas 上限 | 200M Gas |
| 交易 Gas 上限 | 30M Gas |
| 最低基础费用 | 100 MON-gwei(100 x 10^-9 MON) |
这些上限远高于 Ethereum 的 30M 区块 Gas 上限,因此 Monad 区块可以容纳更多交易。
基础费用控制器
Monad 的基础费用控制器与 Ethereum 的不同。它提高得更慢、降低得更快,以避免定价过高导致区块空间利用不足。
控制器参数:
- max_step_size = 1/28
- target = 160M Gas(区块容量的 80%)
- beta = 0.96
- epsilon = 160M
基础费用会在每个区块中更新,并根据区块相对于目标值的使用程度进行指数调整。此公式使用指数平滑(beta = 0.96)追踪区块使用量的历史波动,因此费用变动比 Ethereum 的简单机制更平稳。
实际上,这意味着 Monad 上的 Gas 价格更稳定,并能在价格飙升后更快恢复。
交易排序
默认的 Monad 客户端使用优先 Gas 拍卖对交易排序,按 Gas 总价格(基础费用 + 优先费)降序排列。
开发者指南
务必为已知成本设置明确的 Gas 上限
对于 Gas 成本固定的操作,请在提交给钱包之前明确设置 Gas 上限:
// Good: explicit gas limit for a native transfer
const tx = {
to: recipient,
value: parseEther("1.0"),
gasLimit: 21000n,
};
这一点很重要,因为某些钱包(例如 MetaMask)会使用 eth_estimateGas 来确定 Gas 上限。如果该调用回滚(例如合约调用会失败),钱包可能会回退到一个非常高的 Gas 上限。在 Monad 上,用户会因此按整个虚高的上限付费,因为Gas 按上限收费,而非按实际用量收费。
前端代码中的 Gas 估算
使用 viem 或 wagmi 估算 Gas 时,请记住,估算值就是用户实际支付的数值,而不是下限。如有需要,只添加少量缓冲:
// If you must estimate, keep the buffer small
const estimate = await publicClient.estimateGas({ ... });
const gasLimit = estimate + (estimate / 10n); // 10% buffer at most
向用户显示 Gas 费用
在 UI 中显示 Gas 费用时,请根据 Gas 上限计算,而不是使用其他链上收据中的“Gas 用量”:
const gasCost = gasLimit * gasPrice;
智能合约注意事项
- Monad 的 30M 交易 Gas 上限与 Ethereum 的区块 Gas 上限相同,因此单笔交易可以非常大。
- 优化合约以减少其在 Monad 上的 Gas 用量,这会直接为用户省钱,因为用户按上限付费;更准确的估算也意味着可以设置更严格的上限。
- 如果合约中有 Gas 成本可预测的函数,请记录这些成本,以便前端设置明确的上限。
操作码定价差异
Monad 上调了少数操作码的价格,而不是对所有操作都打折。这样能以最小的改动达到相同的相对效果。这些调整反映了 Monad 高性能架构中资源稀缺程度的差异,尤其是基于磁盘的状态操作。
冷状态访问的成本高得多
| 操作 | Ethereum | Monad | 增幅 |
|---|---|---|---|
| 账户访问(冷) | 2,600 gas | 10,100 gas | +7,500 |
| 存储访问(冷) | 2,100 gas | 8,100 gas | +6,000 |
| 账户访问(热) | 100 gas | 100 gas | 不变 |
| 存储访问(热) | 100 gas | 100 gas | 不变 |
受影响的操作码:
- 账户访问:
BALANCE、EXTCODESIZE、EXTCODECOPY、EXTCODEHASH、CALL、CALLCODE、DELEGATECALL、STATICCALL、SELFDESTRUCT - 存储访问:
SLOAD、SSTORE
这对开发者意味着:
- 访问许多冷存储槽或调用许多外部合约的合约,在 Monad 上的成本会显著增加。
- 热访问与 Ethereum 完全相同,因此在一笔交易中重复访问同一个槽位或账户不会产生额外成本。
- 只访问一次存储槽(冷访问),随后重复使用该槽位(热访问)的批处理操作没有问题。但在许多不同槽位上分别进行单次冷读取的模式,成本会更高。
- 如果合约在一次调用中读取许多不同的存储槽,在 Monad 上的 gas 估算会更高。设置 gas 限额时请将此因素考虑在内。
预编译合约重新定价
在 Monad 上,密码学预编译合约的成本会提高 2 到 5 倍:
| 预编译合约 | 地址 | Ethereum | Monad | 倍数 |
|---|---|---|---|---|
| ecRecover | 0x01 | 3,000 | 6,000 | 2 倍 |
| ecAdd | 0x06 | 150 | 300 | 2 倍 |
| ecMul | 0x07 | 6,000 | 30,000 | 5 倍 |
| ecPairing | 0x08 | 45,000 | 225,000 | 5 倍 |
| blake2f | 0x09 | rounds x 1 | rounds x 2 | 2 倍 |
| 点求值 | 0x0a | 50,000 | 200,000 | 4 倍 |
这对开发者意味着:
- 严重依赖签名验证(
ecRecover)的合约,这些操作的 gas 消耗将增加到原来的 2 倍。 - 与 ZK 相关的操作(
ecMul、ecPairing、点求值)成本会提高到原来的 4 到 5 倍。如果合约在链上验证 ZK 证明,基于 Ethereum 得出的 gas 估算会有很大偏差。 - 如果合约使用这些预编译合约,请在计算 gas 限额时考虑更高的成本。