This article explains gas cost calculation before and after this hard fork upgrade, how this will change with EIP-2929, and how to use the access list feature introduced by EIP-2930. The original author is Franco Victorio, a software developer at Nomic Labs. Note: This is a long article, but here are some of the key points:
Gas costs before the Berlin hard forkEach opcode executed by the EVM has an associated gas cost. For most opcodes, this cost is fixed: PUSH1 always costs 3 units of gas, MUL costs 5 units of gas, etc. For others, it is variable: for example, the cost of the SHA3 opcode depends on the size of its input. We will focus on the SLOAD and SSTORE opcodes, as they are the ones most affected by the Berlin hard fork. We will later discuss opcodes that target addresses, like all EXT* and CALL* opcodes, as their gas costs will also change. SLOAD before the Berlin Hard ForkWithout EIP-2929, the cost of SLOAD is simple: it always consumes 800 gas. SSTORE before the Berlin Hard ForkSSTORE is probably the most complex opcode in terms of gas, as its cost depends on the current value of the storage slot, the new value, and whether it has been modified before. We will only analyze a few scenarios to get a basic understanding. If you want to learn more, read the eip linked at the end of this article.
The details here are a bit dry, the important point is that SSTORE is very expensive and its cost depends on several factors. Gas costs after implementing EIP-2929EIP-2929 changes all of these values, but before we do that, we need to talk about an important concept that this EIP introduces: accessed addresses and accessed storage keys. An address or storage key is considered accessed if it was previously "used" during a transaction. For example, when you call another contract, the address of that contract is marked as accessed. Similarly, when you SLOAD or SSTORE some slot, it will be considered accessed for the rest of the transaction. It doesn't matter which opcode does it: if a SLOAD reads a slot, it will be considered accessed for the following SLOAD and SSTORE. One thing to note here is that the storage key is located “inside” an address. As the EIP explains: "While executing a transaction, maintain a set of accessed_addresses: Set[Address] and accessed_storage_keys: Set[Tuple[Address, Bytes32]]" That is, when we say a storage slot is accessed, we actually say a pair (address, storageKey) is accessed. That being said, let’s talk about the new gas costs. SLOAD after the Berlin Hard ForkBefore the Berlin hard fork, the fixed cost of SLOAD was 800 gas, now it depends on whether the storage slot has been visited. If not, the cost is 2100 gas, if visited, the cost is 100 gas. So, if the slot is in the list of visited storage keys, the cost of a SLOAD is reduced by 2000 gas. SSTORE after the Berlin Hard ForkLet’s revisit the previous SSTORE example in the context of deploying EIP-2929:
As you can see, if the slot being modified has been accessed before, the cost of the first SSTORE will be 2100 gas less. The following table summarizes all values changed so far: Note that in the last line, it doesn't make sense to talk about whether the slot was accessed, because if it was written to before, it would have been accessed as well. EIP-2930Another EIP we mentioned at the beginning of the article is EIP-2930, which adds a new type of transaction that can include an access list in the transaction payload. This means that you can declare in advance which addresses and slots should be considered visited before the transaction starts executing. For example, a SLOAD cost of an unvisited slot is 2100, but if the slot is included in the access list of the transaction, the same opcode costs 100. But if the gas cost is lower when an address or storage key has already been accessed, does that mean we can just add everything to the access list of the transaction and reduce the gas cost? Not exactly, because you still need to pay gas for each address and each storage key you add. Let's look at an example, assuming we are sending a transaction to contract A, the access list may look like this: If we sent a transaction with this access list, and the first opcode to use slot 0x0 was SLOAD, it would cost 100 gas (instead of 2100 gas), which is a reduction of 2000 gas. But each storage key included in the transaction access list costs 1900 gas, so we only saved 100 gas. (If the first opcode to access that slot was SSTORE, we would save 2100 gas, which means we would save 200 gas in total if we take into account the cost of storage keys.) Does this mean that we always save gas when using transactions with access lists? Not really, because we also pay the gas cost for accessing the addresses in the list (in our case, " ")Visited AddressAbove, we only discussed the SLOAD and SSTORE opcodes, but these are not the only opcodes that changed after the Berlin hard fork. For example, the original call opcode had a fixed cost of 700 gas. But after the implementation of EIP-2929, if the address is not in the access list, the cost is 2600 gas, but if it is in the visited list, the cost is 100 gas. And, as with the accessed storage key, it doesn’t matter which opcode accessed the address before (for example, if EXTCODESIZE is called first, then this opcode will cost 2600 gas, and any subsequent EXTCODESIZE, CALL, STATICCALL using the same address will cost 100 gas). How does this get affected by access list transactions? For example, if we send a transaction to contract A, which calls another contract B, we can include an access list like this: We have to pay 2400 gas to include this access list in the transaction, but the first opcode that uses B's address will cost 100 gas (instead of 2600 gas). So we save 100 gas by doing this, and if B uses its storage in some way, and we know which keys it will use, we can also include them in the access list and save 100/200 gas per key (depending on whether the first opcode is SLOAD or SSTORE). But why are we talking to another contract? What happened to the contract we called? Why don’t we do this? We could do this, but it’s not worth it because EIP-2929 specifies that the called contract address (i.e. tx.to) is always included in the accessed_addresses list, so this would just waste 2400 gas. Let's analyze the example from the previous section again: This is actually wasteful unless we include multiple storage keys. If we assume that a SLOAD always uses a storage key first, then we need at least 24 storage keys to break even. Obviously, analyzing and creating such an access list is pointless. Fortunately, there is a better way. eth_createAccessList RPC methodGeth (starting from version 1.10.2) includes a new eth_createAccessList RPC method that can be used to generate access lists. Its usage is similar to eth_estimateGas, but instead of estimating gas, it returns something like this: That is, it gives you a list of addresses and storage keys that this transaction will use, and the gas that would be consumed if the access list was included. (And, as with eth_estimateGas, this is an estimate, and the list may change by the time the transaction is actually made.) I guess over time we'll figure out what the correct way to do this is, and my pseudocode guess would be: Activate the contractIt is important to point out that the main purpose of access lists is not to use gas, as the EIP explains: “What EIP-2929 introduces is a mitigation of the risk of contract breakage, as transactions can pre-specify and pay for the accounts and storage slots that the transaction plans to access. So in practice, the SLOAD and EXT* opcodes only cost 100 gas, which is low enough to not only prevent breakage due to this EIP, but also “activate” any contracts that are stuck due to EIP 1884.” This means that if a contract makes assumptions about the cost of performing certain operations, then an increase in gas cost may cause it to not work. For example, if a contract calls another contract (e.g. someOtherContract.someFunction{gas: 34500}()) because it assumes that a certain function uses exactly 34500 gas, then it will break, but if the appropriate access list is included in the transaction, then the contract will work again. If you want to test these EIPs yourself, you can clone this repo, which has several examples that can be executed using Hardhat and geth. Check out the README file for instructions. |
>>: What exactly is IPFS and what is its market prospect?
Recently, an industry insider revealed that the n...
The People's Bank of China is getting closer ...
What does a solitary nose look like? In physiogno...
When judging their fortune, many people will obser...
Romantic luck is a reflection of a person's c...
We all hope that we are blessed people and that e...
Many people have forehead wrinkles, but some peopl...
In fact, everyone should take into account some o...
A face that is prone to extreme suicide There are...
OpenBazaar, an open-source Bitcoin-based marketpl...
There are often some moles on our faces, and each...
Produced by/ Blockchain Truth Self-narration/ Lao...
Baozou Commentary : Data shows that the scale and...
Blessed is a good compliment, and we all want to ...
As one of the traditional physiognomy techniques, ...