EIP-7973 Warm account write charging (Warm Account Write Metering)

Declined for Inclusion · Fees & gas · EIP status Draft · created 2025-06-14

A gas repricing that discounts the write cost from the second time the same account is modified within one transaction, because the state root update happens only once anyway.

Why it is needed

One of the most expensive tasks when building a block is updating the state root (a value summarizing the state of the entire network). Currently, repeated storage writes (the space a contract uses to store data) within the same transaction become cheaper from the second one thanks to net gas metering, but the cost of modifying the account itself (balance, nonce, code hash) is the same every time no matter how many times it is repeated. So sending a token such as WETH several times in one transaction gets cheaper each time, while sending native ETH several times gets no discount. Because of this gap, native ETH transfers fall out of use, and future instructions that include value transfers, such as PAY and GAS2ETH, are also put at a disadvantage. This EIP reflects in gas costs the fact that multiple writes within one transaction can be processed together, bringing fees closer to the actual execution cost.

What changes

Today

After EIP-7973

Who is affected

In depth

Gas parameter reorganization

The existing parameters GAS_CALL_VALUE and GAS_STORAGE_UPDATE are removed and three new ones are introduced. GAS_COLD_STORAGE_WRITE is the cost of updating the storage trie once, GAS_COLD_ACCOUNT_WRITE is the cost of updating the account trie once, and GAS_WARM_WRITE is the cost of a warm update that does not trigger a new state root computation. All three values are still TBD. Separating the cost of updating the storage state root from the cost of updating the account state root makes it possible to apply warm charging to account updates as well.

Charging rules for CREATE and CALL family

For the instructions that update accounts, CREATE, CREATE2, and *CALL, the following is charged in place of GAS_CALL_VALUE. If the account fields (nonce, value, codehash) equal the transaction start values, meaning the account has not yet been updated in this transaction, GAS_COLD_ACCOUNT_WRITE is charged. If they differ from the start values, meaning the account has already been updated, GAS_WARM_WRITE is charged.

Two-part split of the SSTORE cost

The SSTORE cost is divided into an account tuple update share and a state trie update share. If storage has been updated even once, the account tuple is already dirty, so the cost of updating it again can be batched. If the account differs from its original value, GAS_WARM_WRITE is charged first, and if it is the same, GAS_COLD_ACCOUNT_WRITE is charged first. If the slot is accessed for the first time, GAS_COLD_SLOAD is added. If the original value and current value are the same and the slot changes to a new value, GAS_STORAGE_SET is added when the original value is 0, and otherwise GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD is added. In all other cases, GAS_WARM_ACCESS is added.

Refund logic update

The refund counter calculation also changes to match the new parameters. When a slot is cleared to 0 for the first time in a transaction, GAS_STORAGE_CLEAR_REFUND is added, and if an earlier refund needs to be reversed, the same value is subtracted. When a slot is restored to its original value, GAS_STORAGE_SET - GAS_WARM_ACCESS is refunded if the original value was 0, and GAS_COLD_STORAGE_WRITE - GAS_COLD_SLOAD - GAS_WARM_ACCESS if it was not 0. If the account is still cold on restoration, GAS_COLD_ACCOUNT_WRITE is also refunded. Full net metering, which refunds when the end-of-transaction value equals the start value, was also considered but left out for simplicity.

Compatibility with EIP-7928 BAL and parallel execution

EIP-7928 defines a Block-Level Access List (BAL) and includes rules for parallel execution. If an accessed account appears in the BAL as updated by a transaction earlier in the block than the current one, the reference value for the cold/warm determination is taken from that BAL entry instead of the account trie.

Why account updates can be treated as warm

In Ethereum, an account is represented as the tuple (nonce, balance, storage_root, codehash) and is a leaf of the Merkle Patricia Trie (MPT). storage_root is the root of the MPT holding that account's key-value storage. Modifying storage requires updating two MPTs (the account's storage_root and the global state root), but modifying any other account field requires updating only one MPT. When the same account is modified several times in one transaction, the state root computation can be batched and done once, so the real cost of the second and later updates is not the same as that of the first.

Open numbers and benchmarking

This proposal does not yet have final gas numbers. Setting them requires a stateful benchmark, which is currently under development, and the source says the final values will be set after data is collected. On the security side, lowering the cost of account access may open new attack paths, so more analysis of the impact on dApps and user behavior is needed.

Status and fork discussion

The source status is Draft, so the specification may still change. On the 08.13 ACDE #243 call, EIP 7971, 8058, 7609, 7973 and 8360 were not accepted as candidates (DFI). Their champions (the people responsible for the proposal and pushing it forward) were not on the call, so nixo agreed to contact the proposers and close the PRs if they cannot be reached.

Original EIP · ← EIPs