“柏林” 硬分叉将在 4 月 15 日激活,该硬分叉所包含 EIP 中的两个(EIP-2929 和 EIP-2930)都会影响事务的 Gas 开销。本文会解释 “柏林” 激活之前,一些操作码的 Gas 消耗量是如何计算的,而 EIP-2929 对此有何影响,以及,2930 引入的访问清单(Access List)功能应如何使用。 摘要 这篇文章很长,你要是只想知道结论,看完这部分就可以把网页关掉了: 柏林硬分叉改变了某些操作码的 Gas 开销。如果你在自己的应用中硬编码了一些操作可使用的 Gas 数量,这些操作可能会卡死。如果真的出现了这种情况,而你的智能合约又是没法升级的,用户就需要使用 “访问清单” 功能来使用你的应用。访问清单功能可略微减少 Gas 开销,但有些时候也可能会提高总的 Gas 消耗量。geth 客户端引入了一种新的 RPC 方法,叫做eth_createAccessList来简化访问清单的生成。“柏林” 升级以前的 Gas 开销 “柏林” 以前的SLOAD 在 EIP-2929 实施前,SLOAD开销的计算方式很简单:总是消耗 800 gas。所以,也没啥可展开的。“柏林” 以前的SSTORE 要讲到 Gas 消耗量的计算,SSTORE操作码可能是最复杂的了。因为消耗多少取决于该存储项槽当前的值、要写入的新值、该存储项是否已经修改过。我们只会分析少数几种场景,了解个大概。如果你想了解更多,请阅读本文末尾所附的 EIP 链接。如果存储项的值从 0 改为 1(或者任意非零的值),Gas 消耗量是 20000如果存储项的值从 1 改为 2(或者任意非零的值),Gas 消耗量是 5000如果存储项的值从 1(或任意非零的值) 改为 0,消耗量也是 5000,但你会在事务执行结束后获得 gas 补贴。我们这里也不讨论 gas 返还机制,因为它不会受到柏林的影响在一笔事务中,如果存储项已不是第一次修改,则后续每一次SSTORE都消耗 800 gas细节在这里并不重要,重要的是,SSTORE是昂贵的,具体消耗多少 gas 则依赖于多个因素。EIP-2929 之后的 Gas 消耗量 执行事务时,保持一个集合:accessed_addresses: Set[Address]以及accessed_storage_keys: Set[Tuple[Address, Bytes32]]也就是说,当我们说某个存储槽已被访问过了,我们的实际意思是:(address, storageKey)已被访问过了。搞清楚了这个概念,我们来谈谈新的 Gas 消耗量计算模式。 “柏林” 以后的SLOAD 升级前,SLOAD的 Gas 消耗量是固定的 800。但升级后,Gas 消耗量要看这个存储槽是否已经被访问过。还没访问过的,消耗量就是 2100 gas;访问过的,就是 100 gas。所以,如果某个存储项槽已经在 “已访问过的存储项键` 的集合里了,就可以省掉 2000 gas。“柏林” 以后的SSTORE 我们逐个逐个对比下,在 EIP-2929 实施后,上面的几个例子会发生什么样的变化:如果存储项的值从 0 改为 1(或者任意非零的值),Gas 消耗量是 20000如果该存储项键还未访问过,消耗 22100 gas若已访问过,消耗 20000 gas如果存储项的值从 1 改为 2(或者任意非零的值),Gas 消耗量是 5000如果该存储项键还未访问过,消耗 5000 gas若已访问过,消耗 2900 gas如果存储项的值从 1(或任意非零的值) 改为 0,消耗量保持不变,gas 返还机制也不变在一笔事务中,如果存储项已不是第一次修改,则后续每一次SSTORE都消耗 100 gas由此可见,如果某个槽此前已访问过,则对它的第一次SSTORE操作会节约 2100 gas(相比于从未访问过)。汇总一下 未访问过访问过SLOAD8002100100SSTORE from 0 to 1200002210020000SSTORE from 1 to 2500050002900SLOAD + SSTORE*580050003000SSTORE* + SLOAD580051003000SSTORE 一个已经被写过的槽800100100*从一个非零值改为另一个非零值,就像第三行所示的那样 EIP-2930:可选 “访问清单” 的事务类型 accessList: [{ address: \"<address of A>\", storageKeys: [ \"0x0000000000000000000000000000000000000000000000000000000000000000\" ]}] 如果我们发送了一条带有这条访问清单的事务,而使用0x0存储槽的第一个操作码就是SLOAD,则 Gas 消耗量会是 100 而非 2100,也就是减免了 2000 gas。但是,在访问列表中声明一个存储项键需要额外支付 1900 gas,所以我们只节约了 100 gas。(如果对该存储槽的第一个操作是SSTROE,我们在单个操作中就省下了 2100 gas,也就是总共省下了 200 gas,因为访问清单本身需要消耗 gas)。这是不是说,每次使用访问清单我们都能节省 gas 呢?很遗憾,也不是,因为在访问清单中填入地址也需要支付 gas。(也就是我们示例中的\"<address of A>\") 访问过的地址 accessList: [{ address: \"<address of B>\", storageKeys: [] }] 我们首先需要为在这条事务的访问清单中加入这个地址支付 2400 gas,但对 B 使用的第一个操作码就只需要消耗 100 gas 而不是 2600 gas,这就剩下了 100 gas。如果 B 也需要使用其存储项,我们又知道它将使用哪个键,我们也可以把这些键包含在访问列表中,然后为每个键的操作省下 100 或 200 gas(取决于第一个操作码是SLOAD还是SSTORE)。但为啥我们要加多一个合约来举例子?我们不是可以这样写吗? accessList: [ {address: \"<address of A>\", storageKeys: []}, {address: \"<address of B>\", storageKeys: []},] 你当然可以这样做,但不值得,因为 EIP-2929 指明了你一开始调用的合约(也即是tx.to的目的地)必定会被包含在accessed_addresses列表中,所以你就是额外花了 2400 gas,什么好处都没得到。所以,回头看我们上面举的例子: accessList: [{ address: \"<address of A>\", storageKeys: [ \"0x0000000000000000000000000000000000000000000000000000000000000000\" ]}] 这样做其实是浪费,除非你在里面加多几个存储项键。如果我们假设所有的存储项键的第一个操作都是SLOAD,那你要至少 24 个键,才能赚回来。而且,如你所见,自己一五一十地分析这些因素、手动生成访问清单,显然是极其繁琐而令人崩溃的事。好在,还有更好的办法。 eth_createAccessListRPC 方法 { \"accessList\": [ { \"address\": \"0xb0ee076d7779a6ce152283f009f4c32b5f88756c\", \"storageKeys\": [ \"0x0000000000000000000000000000000000000000000000000000000000000000\", \"0x0000000000000000000000000000000000000000000000000000000000000001\" ] } ], \"gasUsed\": \"0x8496\"} 也就是告诉你一笔事务将会用到的地址和存储项键的清单,以及,假定纳入这份访问清单将耗用多少 gas。跟eth_estimateGas一样,这也是估计出来的,该笔事务真正上链时,会访问到哪些数据仍有可能改变。但是,再说一遍,这绝不意味着你只要使用了访问清单,所用的 Gas 就会比不用清单更少!我估计随着时间推移,我们会越来越知道怎么利用这个功能,但我个人估计,方法的伪代码形式会像这样: let gasEstimation = estimateGas(tx)let { accessList, gasUsed } = createAccessList(tx)if (gasUsed > gasEstimation) { delete accessList[tx.to]}tx.accessList = accessList;sendTransaction(tx) 防止合约变砖 值得提醒,访问清单功能的主要目的不是节省 Gas。如该 EIP 自身所述: 缓解由 EIP-2929 带来的合约变砖风险,因为事务可以预先指定、预先支付自身尝试范文的账户和存储槽,因此,在实际的执行中,SLOAD 和 EXT* 操作码都只会消耗 100 gas:这个值低到既足以防止 2929 打破某些合约,也可以 “解封” 被 EIP-1884 封印的合约。原本,只要一个合约预设了执行的 Gas 开销,操作码的 Gas 消耗量变动就有可能导致它变砖。比如,如果一个合约预设另一个合约的someFunction只会用到 34500 gas,因此总是用someOtherContract.someFunction{gas: 34500}()调用那个合约,这个合约就有可能变砖。但只要你在事务中添加合适的访问清单,这个合约就还能工作。 自己验证 觉得有趣吗? 参考文献 EIP-2929 和 EIP-2930是两个跟本文有关的 “柏林” EIP。EIP-2930 依赖于 “柏林” 升级纳入的另一个 EIP:EIP-2718,也叫标准化的事务信封。EIP-2929 大量参考了 EIP-2200,如果你想更深入地理解 Gas 消耗量,你应该从那里开始。想了解更复杂的情形中 Gas 消耗量会如何变化,请看这里。原文链接: https://hackmd.io/@fvictorio/gas-costs-after-berlin 作者:Franco Victorio 翻译:阿剑—- 编译者/作者:EthFans 玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。 |
干货 | 搞懂 “柏林” 之后的合约 Gas 开销
2021-04-18 EthFans 来源:区块链网络
LOADING...
相关阅读:
- 4-18BTC空单布局精准抓住一波插针满满斩获8120个点位图2021-04-18
- 丁晓生:交易中等待合适进场机会和耐心拿单2021-04-18
- 担心什么来什么连跌三天昨天你做空了吗今日就有抄底机会2021-04-18
- 4-18BTC行情分析和操作思路供参考2021-04-18
- 独家:比特币如期再跌2000点局势扭转空头才是赢家?2021-04-18