This proposal introduces block-level address and storage key warming, allowing accessed addresses and storage keys to maintain their “warm” status throughout an entire block’s execution. Accessed slots can be effectively cached at the block level, allowing for this opimization.
Motivation
Currently, the EVM’s storage slot warming mechanism operates at the transaction level, requiring each transaction to “warm up” slots independently, even when accessing the same storage locations within the same block. This design does not take advantage of the fact that modern node implementations can effectively cache storage access patterns at the block level. By extending the slot warming duration to the block level, we can:
Reduce redundant warming costs for frequently accessed slots
Better align gas costs with actual computational overhead
Improve overall network throughput without compromising security.
As of Jan. 2025, the empirically messured efficiency gains are around 5%.
Specification
Mechanics
When a storage slot is accessed within a block:
The first access to a slot in a block incurs the cold access cost as of EIP-2929.
All subsequent accesses to the same slot within the same block incur only the warm access cost as of EIP-2929.
The warm/cold status resets at block boundaries
Block Processing
At the start of each block:
Initialize two empty sets block_level_accessed_addresses and block_level_accessed_storage_keys
For each transaction in the block:
Before processing storage access:
Check if touched address is in block_level_accessed_addresses
If yes: charge WARM_STORAGE_READ_COST
If no:
Charge COLD_ACCOUNT_ACCESS_COST
Add address to block_level_accessed_addresses
Check if storage key is in block_level_accessed_storage_keys
If yes: charge WARM_STORAGE_READ_COST
If no:
Charge COLD_SLOAD_COST
Add storage key to block_level_accessed_storage_keys
Implementation Details
The implementation modifies the block execution process to maintain block-level sets of accessed addresses and storage slots.
Block-Level Storage Management
defapply_body(...):# Initialize block-level tracking sets
block_level_accessed_addresses=set()block_level_accessed_storage_keys=set()fori,txinenumerate(map(decode_transaction,transactions)):# Create environment with block-level context
env=vm.Environment(# ... other parameters ...
block_level_accessed_addresses=block_level_accessed_addresses,block_level_accessed_storage_keys=block_level_accessed_storage_keys)# Process transaction and update block-level sets
gas_used,accessed_addresses,accessed_storage_keys,logs,error=process_transaction(env,tx)block_level_accessed_addresses+=accessed_addressesblock_level_accessed_storage_keys+=accessed_storage_keys
This code shows how block-level slot warming is implemented at the execution layer. The block_level_accessed_addresses and block_level_accessed_storage_keys sets are maintained throughout the block’s execution and passed to each transaction’s environment.
This adds the block-level-accessed addresses and storage keys to the preaccessed addresses and storage keys.
As a result, from the perspective of a transaction, block-level accessed addresses and storage keys are treated the same as precompiles or the coinbase address.
defprocess_message_call(message:Message,env:Environment)->MessageCallOutput:returnMessageCallOutput(# ... other fields ...
accessed_addresses=evm.accessed_addresses,accessed_storage_keys=evm.accessed_storage_keys)
The message call processing tracks accessed addresses and storage keys during execution, which are then propagated back up to the transaction level and ultimately to the block level.
Rationale
The proposal builds on several key observations:
Caching Efficiency: Modern Ethereum clients already implement sophisticated caching mechanisms at the block level. Extending address and storage key warming to match this caching behavior better aligns gas costs with actual computational costs.
Backward Compatibility: The worst-case scenario for any transaction remains unchanged - it will never pay more than the current cold access cost for its first access to a slot.
Handling Reverts: In EIP-2929, if a sub-call reverts, all accessed addresses and slots are reverted to a cold state. Under block-level warming, it remains an open question whether a failing transaction (i.e., one that reverts) should ‘unwarm’ previously warmed addresses and slots for subsequent transactions within the same block. Some propose removing this revert-induced “cold reset” altogether to maintain consistency with block-level warming. This point warrants further discussion, particularly around whether or not preserving a warmed state even upon failure aligns with the broader goal of better matching gas costs to actual client caching behavior.
First Access Warms For All: Under the proposed mechanism, the first transaction in a block that accesses and warms multiple addresses or storage slots bears the entire cost of warming, even if subsequent transactions benefit from those already-warmed slots without incurring additional costs. This approach is deemed acceptable because early execution within a block—where warming typically occurs—is generally occupied for transactions placed by professional builders aiming for top-of-block execution. Since the resulting cost discrepancy is relatively minor, a more complex cost-sharing model is avoided in favor of maintaining simplicity.
An alternative to the one-warms-for-all mechanism involves distributing the warming costs across all accesses within a block. In this approach, each transaction must at least be able to pay the cold access cost. Once all transactions in the block are executed, the total warming costs are evenly distributed, and transaction originators are refunded accordingly.
Alternative Block Warming Windows: Instead of applying warming at the block level, more advanced multi-block warming approaches can be considered. Potential options include:
Warming addresses and storage keys over the duration of an epoch
Using a ring buffer spanning x blocks
Since the Execution Layer (EL) currently operates without regard to epoch boundaries, it may be preferable to maintain this design. Therefore, the second option, involving a ring buffer of size x, could be more suitable. For the ring buffer, one approach might be to store the relevant warmed information in the block header for those x blocks, but this introduces additional overhead and can complicate statelessness. Careful design would be required to manage these trade-offs (e.g., header size, client complexity) if a multi-block warming solution were adopted.
Backwards Compatibility
This change is not backward compatible and requires a hard fork activation. However, it does not introduce any breaking changes for existing contracts, as:
All existing transactions will continue to work as before
Gas costs will only decrease, never increase
Contract logic depending on gas costs will remain valid, as the worst-case scenario is unchanged
Security Considerations
Memory Usage: The set of warm slots is still bounded by block gas limit / minimum gas cost per slot access, preventing DoS attacks through excessive memory usage.
MEV Considerations: Block producers can optimize transaction ordering to maximize slot warming benefits for themselves. Third-party block builders can backrun transactions that warmed specific addresses or storage keys.