注意:存储项的键是“内在于”某些地址中的,一如该EIP所解释的: 执行事务时,保持一个集合: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(相比于从未访问过)。 汇总一下 EIP-2930:可选“访问清单”的事务类型 另一个“柏林”升级包含的EIP是2930。该EIP加入了一种新的类型的事务,可以在事务的负载中包含一个“访问清单”,意思是,你可以在事务执行前就声明哪些地址和存储槽应被认为是“访问过的”。举个例子,对一个未访问过的槽执行SLOAD需要耗费2100 gas,但如果该存储槽被包含在了事务的“访问清单”中,则操作的消耗量机会降为100 gas。 但如果只要地址和槽被当成“已访问过的”就可以降低操作的Gas消耗量;而访问清单可以把地址和槽标记为“已访问过的”;那岂不是说我们可以把这些东西都放在访问清单中,来获得Gas消耗量的减免?真棒,天赐Gas! 额,并不完全如此,因为你每添加一个地址或存储项键,都要支付额外的Gas。 举个例子。假如我们要向合约A发送了一条事务。 这是不是说,每次使用访问清单我们都能节省gas呢?很遗憾,也不是,因为在访问清单中填入地址也需要支付gas。(也就是我们示例中的"<address of A>") 访问过的地址 迄今为止,我们只讨论了SLOAD和SSTORE操作码,但“柏林”升级还改变了别的操作码。举个例子,CALL操作码原来的Gas消耗量为固定的700,但2929实施后,如果所调用的地址不在访问清单中,消耗量将提高到2600;如果在,则降低为100。而且,就像访问过的存储键一样,到底哪个操作码访问过那个地址并不重要(比如,如果用户最先调用的是EXTCODESIZE,这一个操作的消耗量是2600,但后续的调用,只要是对同一个地址的,无论是EXTCODESIZE、CALL还是STATICCALL,都只消耗100 gas。 (责任编辑:admin) |