本文介绍了以太坊EVM中gas refund的机制,重点讲解了伦敦分叉前后,由于清空链上存储而返还gas的规则变化。伦敦分叉前,将变量设置为默认值可获得15000 gas的退款,但退款上限为交易消耗gas的一半。伦敦分叉后,退款额降至4800 gas,上限为gas消耗的五分之一。文章还探讨了降低gas退款的原因,包括GasToken的出现和区块大小差异的增加。
你知道 EVM 会奖励用户 gas 退款,以补偿清除合约数据占用的链上存储吗?
伦敦分叉后有一些变化。但为了更好地理解它,我们将看看伦敦升级之前的场景,然后我们将看到伦敦升级中提出的变化。
在开始讨论退款之前,我们应该对存储 gas 消耗有一些了解,这将使你更容易理解 gas 的计算。
我直接从以太坊黄皮书中复制了一些 gas 数量,让我们看看它们是什么。
1. Gcoldsload— 2100 - 首次访问冷存储的成本。
2. Gsset— 20000 - 当存储值从零设置为非零时,SSTORE 操作的支付费用。
3. Gsreset— 2900 - 当存储值的零值保持不变或设置为零时,SSTORE 操作的支付费用。
4. Rsclear— 15000 - 当存储值从非零设置为零时,给予的退款(添加到退款计数器中)。
解释:
1. Gcoldsload — 每次你在函数中首次访问任何存储变量时都必须支付费用,第二次或连续访问时费用为 100 gas.
2. Gsset — 每次你将任何变量从零值设置为非零值(布尔类型的情况下从 false 设置为 true)时都需要支付费用。简单来说,你正在更改默认值,节点现在必须跟踪该插槽。
3. Gsreset — 每次你将非零值设置为非零值或零值时都需要支付费用。
4. Rsclear — 无论何时你将任何值设置为其默认值,你都会获得退款。
在我们开始之前,请记住以下几点
每当你将某些内容设置为其默认值时,退款就会生效,对于 uint 来说是零,对于布尔值来说是 false 等。为了便于理解,我将以 uint 为例。
在两种情况下,EVM 会将 gas 退还给用户。首先,如果你调用 selfdestruct
函数,则会从总消耗的 gas 中退还 24000
gas。这很简单,没有什么好说的,因为 selfdestruct
的 gas 退款功能已在 EIP-3529 中删除,并且最近 selfdestruct
方法也被弃用了。
其次,如果你将任何变量设置为其默认值,你将获得 15000 的退款。为什么会这样?将值设置为默认值意味着你正在清除存储,节点不需要跟踪你刚刚清除的特定存储槽。
这是以太坊黄皮书的截图,说明了退款金额。
但是有一种正确的方法来计算和退还 gas。
这是以太坊黄皮书中的参考。
机制: 退款在任何交易结束时给出,并且有最大数量的上限。最大数量是总使用 gas 的一半。
让我们通过 uint 的例子来理解它。当我们把任何非零的 uint 值设置为零时会发生什么?
假设你在一个交易中将一个 uint 变量设置为零,因此总的适用退款将是 15000
,但是此交易中消耗的总 gas 是 24000
,其中一半是 12000
,现在 12000
成为可以退还的最大 gas 量,这意味着交易现在将花费你
24000 — MAXIMUM_GAS_REFUNDABLE = 24000–12000 = 12000
gas。
虽然我们期望获得 15000
gas 的退款,但上限被限制在 12000
,因此将金额减少到 12000
。如果我们的交易消耗 40000
gas 呢?在这种情况下,40000
的一半是 20000
,大于 15000
,因此我们将获得 15000
gas 的退款。
这是你仅将一个 uint 变量设置为零的情况,如果我们将多个 uint 变量设置为零会发生什么?例如
contract {
uint256 count = 1;
uint256 count2 = 2;
uint256 count3 = 3;
uint256 count4 = 3;
uint256 count5 = 3;
uint256 count6 = 5;
uint256 count7 = 6;
uint256 count8 = 6;
function setTOzero() external {
count = 0;
count2 = 0;
count3 = 0;
count4 = 0;
count5 = 0;
count6 = 0;
count7 = 0;
count8 = 0;
}
}
//Transaction cost will be 21000+ execution cost //交易成本将是 21000+ 执行成本
//execution cost = 8 * (Gcoldsload +Gsreset) = 8 * 5000 = 40000 //执行成本 = 8 * (Gcoldsload +Gsreset) = 8 * 5000 = 40000
// Transaction cost = 21000 + 40000 = 61000 // 交易成本 = 21000 + 40000 = 61000
无论你的交易成本是多少,都是 EXECUTION_COST+ 21000
。现在,退款是根据你设置为默认值的变量数量计算的。在这种情况下,我们将 8 个 uint 变量设置为零,因此可退款金额计数为 15000 * 8 = 1,20,000
。
等等,如果这是退款金额,那么我们的交易消耗了多少 gas?当你计算要消耗的 gas 时,你将获得大约 61000
的金额。现在,如果在 61000
的交易中退还 1,20,000
gas 会发生什么?矿工最终将为交易付费,这就是引入上限机制的原因。
根据公式,要退还的最大 gas 将是 61000/2 = 30500
。因此,消耗的 gas 将是 61000–30500 =30500
而不是 61000 – 1,20,000
。现在我认为这对你来说很清楚了,但是在伦敦升级之后,此方法不再有效。如果你想测试这一点,那么你可以在 Remix IDE 中切换到 VM 的 Berlin 版本,并观察一切正常运行。
在伦敦升级中引入 EIP-3529 之后,这些数字发生了变化。
退款 gas 数量现在减少到 4800
,并且可以退还的最大金额是消耗 gas 的五分之一。让我们理解与之前相同的示例。
在第一种情况下,如果我们仅将一个变量设置为零,则交易将消耗 26000
。当我们注意到 EIP-3529 中的最大 gas 规则时,它说 TOTAL_GAS_CONSUMED 除以 5,在我们的例子中是 26000/5
等于 5200
,这意味着我们有资格在此交易中获得的最大退款为 5200
,但是正如 EIP-3529 中提到的,退款金额为 4800
。这次,我们将 4800
退还给我们,并且该交易花费 21400 gas(一些额外的 gas)。
让我们来看第二种情况,我们将 8 个变量设置为零。消耗的 gas 仍然是 61000
。谈到退款,我们希望总退款为 4800 * 8 =38400
,这意味着我们期望的消耗是
61000–38400 = 22600
。但是,当你执行此交易时,你将注意到消耗的 gas 为 49000
,现在你明白了,要退还的 gas 上限为 61000/5 = 12200
,这意味着该交易总共将消耗 61000–12200 ~ 49000
。
你可能想知道是什么导致了伦敦升级中 gas 退款的降低。看看我从官方 EIP 网站复制的原因:
最初引入 SSTORE
和 SELFDESTRUCT
的 Gas 退款是为了激励应用程序开发人员编写实践“良好状态卫生”的应用程序,清除不再需要的存储槽和合约。但是,这种技术的好处已被证明远低于预期,并且 gas 退款产生了多个意想不到的有害后果:
现在,你必须清楚 gas 退款的概念。你在 Remix 上对此进行更多操作,你将更多地了解计算。为什么不尝试执行一些真实的函数?因为我上面写的那些只是示例。你可以尝试查看真实合约中的 gas 消耗,例如当余额即将变为零时 ERC20 的转移函数。让我知道你通过这些实验发现了什么:)
- 原文链接: decipherclub.com/clear-s...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!