净Gas计量的结构化定义

该EIP(以太坊改进提案)旨在通过结构化的净Gas计量方式,实现EIP-1283和EIP-1706的结合。它重新定义了SSTORE操作码的Gas成本计算,从而降低了某些存储操作的Gas费用,支持了新的合约存储用途,并解决了EIP-1283可能引入的重入攻击风险。

简单总结

这是一个实现了净 Gas 计量(net gas metering)的 EIP。它是 EIP-1283EIP-1706 的结合版本,具有结构化定义,以便与其他 Gas 变更(例如 EIP-1884)互操作。

摘要

本 EIP 提供了 SSTORE 操作码的净 Gas 计量变更的结构化定义,为合约存储启用了新的用法,并减少了在与大多数实现的工作方式不符时出现的过高 Gas 成本。

这是 EIP-1283EIP-1706 的结合。

动机

本 EIP 提出了一种在 SSTORE 上进行 Gas 计量的方法,利用了大多数实现普遍可用的信息,并要求对实现结构进行尽可能小的更改。

  • 存储槽的原始值。
  • 存储槽的当前值。
  • 退款计数器。

受益于本 EIP 的 Gas 削减方案的用法包括:

  • 同一调用帧内的后续存储写入操作。这包括重入锁、同一合约多重发送等。
  • 在子调用帧和父调用帧之间交换存储信息,其中此信息不需要在事务之外持久化。这包括子帧错误代码和消息传递等。

EIP-1283 的原始定义造成了一种新的重入攻击现有合约的危险,因为 Solidity 默认会为简单的传输调用提供 2300 Gas 的“津贴”。如果 SSTORE 不允许在剩余 Gas 较低的状态下执行,这种危险可以很容易地缓解,而不会破坏向后兼容性和 EIP-1283 的初衷。

本 EIP 还通过参数替换了 EIP-1283 原始的 Gas 值定义,使其更加结构化,并更容易在未来定义更改。

规范

定义变量 $SLOAD_GAS$$SSTORE_SET_GAS$$SSTORE_RESET_GAS$$SSTORE_CLEARS_SCHEDULE$。这些变量的新旧值如下:

  • $SLOAD_GAS$:从 200 更改为 800
  • $SSTORE_SET_GAS$20000,未更改。
  • $SSTORE_RESET_GAS$5000,未更改。
  • $SSTORE_CLEARS_SCHEDULE$15000,未更改。

使用这些变量更改 EIP-1283 的定义。结合 EIP-1283 和 EIP-1706 的新规范将如下所示。术语 原始值当前值新值 在 EIP-1283 中定义。

用以下逻辑替换 SSTORE 操作码的 Gas 成本计算(包括退款):

  • 如果 gasleft 小于或等于 Gas 津贴,则当前调用帧因“Gas 不足”异常而失败。
  • 如果 当前值 等于 新值(这是空操作),则扣除 $SLOAD_GAS$
  • 如果 当前值 不等于 新值
    • 如果 原始值 等于 当前值(此存储槽未被当前执行上下文更改)
    • 如果 原始值 为 0,则扣除 $SSTORE_SET_GAS$
    • 否则,扣除 $SSTORE_RESET_GAS$。如果 新值 为 0,则将 $SSTORE_CLEARS_SCHEDULE$ 加到退款计数器中。
    • 如果 原始值 不等于 当前值(此存储槽为脏),则扣除 $SLOAD_GAS$。应用以下两条条款。
    • 如果 原始值 不为 0
      • 如果 当前值 为 0(也意味着 新值 不为 0),则从退款计数器中移除 $SSTORE_CLEARS_SCHEDULE$
      • 如果 新值 为 0(也意味着 当前值 不为 0),则将 $SSTORE_CLEARS_SCHEDULE$ 加到退款计数器中。
    • 如果 原始值 等于 新值(此存储槽被重置)
      • 如果 原始值 为 0,则将 $SSTORE_SET_GAS - SLOAD_GAS$ 加到退款计数器中。
      • 否则,将 $SSTORE_RESET_GAS - SLOAD_GAS$ 加到退款计数器中。

实现还应注意,根据上述定义,如果实现使用 call-frame 退款计数器,则计数器可能变为负数。如果实现使用 transaction-wise 退款计数器,则计数器始终保持正数。

基本原理

本 EIP 主要实现了瞬时存储(transient storage)试图做的事情(EIP-1087EIP-1153),但没有引入“脏映射”(dirty maps)概念或额外存储结构的复杂性。

  • 我们不受 EIP-1087 优化限制的影响。EIP-1087 要求为存储更改保留一个脏映射,并隐含地假设事务的存储更改在事务结束时提交到存储 Trie。这对于某些实现效果很好,但对于其他实现则不然。在 EIP-658 之后,高效的存储缓存实现可能会使用内存中的 Trie(不进行 RLP 编码/解码)或其他不可变数据结构来跟踪存储更改,并且只在区块结束时提交更改。对于它们而言,可以知道存储的原始值和当前值,但无法迭代所有存储更改而无需承担额外的内存或处理成本。
  • 与当前方案相比,它从不消耗更多的 Gas。
  • 它涵盖了瞬时存储的所有用法。易于实现 EIP-1087 的客户端也将易于实现此规范。其他一些客户端可能需要进行一些额外的重构。尽管如此,运行时不需要额外的内存或处理成本。

关于 SSTORE 的 Gas 成本和退款,请参阅附录中关于本 EIP 满足的属性证明。

  • 对于 实际使用的 Gas(即 实际使用的 Gas 减去 退款),本 EIP 在所有情况下都等同于 EIP-1087。
  • 对于一个特殊情况,即存储槽被更改,重置为其原始值,然后再次更改,EIP-1283 会将更多的 Gas 移至退款计数器,而不是 EIP-1087。

检查 EIP-1087 动机中提供的示例(其中 $SLOAD_GAS$200):

  • 如果一个空存储的合约将槽 0 设置为 1,然后再设置为 0,它将被收取 $20000 + 200 - 19800 = 400$ Gas。
  • 一个空存储的合约将槽 0 增加 5 次,将被收取 $20000 + 5 * 200 = 21000$ Gas。
  • 从账户 A 到账户 B 的余额转移,然后从 B 到 C 的转移,所有账户都有非零的起始和结束余额,将花费 $5000 * 3 + 200 - 4800 = 10400$ Gas。

为了保持现有合约隐式的重入保护,如果剩余 Gas 低于 Solidity 中“transfer”/“send”给予的 Gas 津贴,则不应允许事务修改状态。以下是其他建议的补救措施和反对实施它们的理由:

  • 放弃 EIP-1283 并避免修改 SSTORE 成本
    • EIP-1283 是一个重要的更新
    • 它已被接受并在测试网络和客户端中实现。
  • 添加一个新的调用上下文,允许 LOG 操作码但不允许更改状态。
    • 在现有常规/静态调用之外增加了另一种调用类型
  • SSTORE 写入脏槽的成本提高到 >=2300 Gas
    • 使净 Gas 计量作用大大降低。
  • 减少 Gas 津贴
    • 使津贴几乎无用。
  • 将写入脏槽的成本恢复到 5000 Gas,但将 4800 Gas 添加到退款计数器
    • 仍然没有明确不变性。
    • 要求调用者提供更多 Gas,只是为了退款
  • 添加合约元数据,指定每个合约的 EVM 版本,并且只将 SSTORE 更改应用于使用新版本部署的合约。

向后兼容性

本 EIP 需要硬分叉才能实现。预计不会增加 Gas 成本,许多合约将看到 Gas 减少。

执行 SSTORE 从未能在低于 5000 Gas 的情况下完成,因此它不会对以太坊主网引入不兼容性。Gas 估算应考虑到此要求。

测试用例

代码 已用 Gas 退款 原始 第一次 第二次 第三次
0x60006000556000600055 1612 0 0 0 0
0x60006000556001600055 20812 0 0 0 1
0x60016000556000600055 20812 19200 0 1 0
0x60016000556002600055 20812 0 0 1 2
0x60016000556001600055 20812 0 0 1 1
0x60006000556000600055 5812 15000 1 0 0
0x60006000556001600055 5812 4200 1 0 1
0x60006000556002600055 5812 0 1 0 2
0x60026000556000600055 5812 15000 1 2 0
0x60026000556003600055 5812 0 1 2 3
0x60026000556001600055 5812 4200 1 2 1
0x60026000556002600055 5812 0 1 2 2
0x60016000556000600055 5812 15000 1 1 0
0x60016000556002600055 5812 0 1 1 2
0x60016000556001600055 1612 0 1 1 1
0x600160005560006000556001600055 40818 19200 0 1 0 1
0x600060005560016000556000600055 10818 19200 1 0 1 0

实现

待补充。

附录:证明

由于 存储槽的原始值 被定义为 当前事务 发生回滚时的值,因此很容易看出调用帧不会干扰 SSTORE 的 Gas 计算。所以,尽管下面的证明没有讨论调用帧,但它适用于所有带有调用帧的情况。我们将分别讨论 原始值 为零和不为零的情况,并使用 归纳法 来证明 SSTORE Gas 成本的一些属性。

最终值 是事务结束时特定存储槽的值。实际使用的 Gas已用 Gas 减去 退款 的绝对值。我们用 $N$ 来表示对存储槽进行 SSTORE 操作的总次数。对于下面讨论的状态,请参阅“解释”部分中的 状态转换

在下面的证明中,我们假设所有参数都没有改变,这意味着 $SLOAD_GAS$200。然而,请注意,无论 $SLOAD_GAS$ 如何变化,该证明仍然适用。

原始值为零

原始值 为 0 时,我们想证明:

  • 情况一:如果 最终值 仍为 0,我们希望收取 $200 * N$ Gas,因为不需要磁盘写入。
  • 情况二:如果 最终值 为非零值,我们希望收取 $20000 + 200 * (N-1)$ Gas,因为这需要将此槽写入磁盘。

基本情况

我们总是从状态 A 开始。第一次 SSTORE 可以:

  • 进入状态 A:扣除 200 Gas。我们满足 情况一,因为 $200 * N == 200 * 1$
  • 进入状态 B:扣除 20000 Gas。我们满足 情况二,因为 $20000 + 200 * (N-1) == 20000 + 200 * 0$

归纳步骤

  • 从 A 到 A。之前的 Gas 成本是 $200 * (N-1)$。当前的 Gas 成本是 $200 + 200 * (N-1)$。它满足 情况一
  • 从 A 到 B。之前的 Gas 成本是 $200 * (N-1)$。当前的 Gas 成本是 $20000 + 200 * (N-1)$。它满足 情况二
  • 从 B 到 B。之前的 Gas 成本是 $20000 + 200 * (N-2)$。当前的 Gas 成本是 $200 + 20000 + 200 * (N-2)$。它满足 情况二
  • 从 B 到 A。之前的 Gas 成本是 $20000 + 200 * (N-2)$。当前的 Gas 成本是 $200 - 19800 + 20000 + 200 * (N-2)$。它满足 情况一

原始值不为零

原始值 不为 0 时,我们想证明:

  • 情况一:如果 最终值 保持不变,我们希望收取 $200 * N$ Gas,因为不需要磁盘写入。
  • 情况二:如果 最终值 变为零,我们希望收取 $5000 - 15000 + 200 * (N-1)$ Gas。请注意 $15000$ 是实际定义中的退款。
  • 情况三:如果 最终值 变为更改后的非零值,我们希望收取 $5000 + 200 * (N-1)$ Gas。

基本情况

我们总是从状态 X 开始。第一次 SSTORE 可以:

  • 进入状态 X:扣除 200 Gas。我们满足 情况一,因为 $200 * N == 200 * 1$
  • 进入状态 Y:扣除 5000 Gas。我们满足 情况三,因为 $5000 + 200 * (N-1) == 5000 + 200 * 0$
  • 进入状态 Z:绝对 Gas 使用量为 $5000 - 15000$,其中 15000 是退款。我们满足 情况二,因为 $5000 - 15000 + 200 * (N-1) == 5000 - 15000 + 200 * 0$

归纳步骤

  • 从 X 到 X。之前的 Gas 成本是 $200 * (N-1)$。当前的 Gas 成本是 $200 + 200 * (N-1)$。它满足 情况一
  • 从 X 到 Y。之前的 Gas 成本是 $200 * (N-1)$。当前的 Gas 成本是 $5000 + 200 * (N-1)$。它满足 情况三
  • 从 X 到 Z。之前的 Gas 成本是 $200 * (N-1)$。当前的绝对 Gas 成本是 $5000 - 15000 + 200 * (N-1)$。它满足 情况二
  • 从 Y 到 X。之前的 Gas 成本是 $5000 + 200 * (N-2)$。当前的绝对 Gas 成本是 $200 - 4800 + 5000 + 200 * (N-2)$。它满足 情况一
  • 从 Y 到 Y。之前的 Gas 成本是 $5000 + 200 * (N-2)$。当前的 Gas 成本是 $200 + 5000 + 200 * (N-2)$。它满足 情况三
  • 从 Y 到 Z。之前的 Gas 成本是 $5000 + 200 * (N-2)$。当前的绝对 Gas 成本是 $200 - 15000 + 5000 + 200 * (N-2)$。它满足 情况二
  • 从 Z 到 X。之前的 Gas 成本是 $5000 - 15000 + 200 * (N-2)$。当前的绝对 Gas 成本是 $200 + 10200 + 5000 - 15000 + 200 * (N-2)$。它满足 情况一
  • 从 Z 到 Y。之前的 Gas 成本是 $5000 - 15000 + 200 * (N-2)$。当前的绝对 Gas 成本是 $200 + 15000 + 5000 - 15000 + 200 * (N-2)$。它满足 情况三
  • 从 Z 到 Z。之前的 Gas 成本是 $5000 - 15000 + 200 * (N-2)$。当前的绝对 Gas 成本是 $200 + 5000 - 15000 + 200 * (N-2)$。它满足 情况二

版权

通过 CC0 放弃版权及相关权利。

  • 原文链接: github.com/nerolation/EI...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Nerolation
Nerolation
江湖只有他的大名,没有他的介绍。