13 Gas-model inconsistencies in Ethereum
This post summarizes 13 inconsistencies in Ethereum’s gas model.
All the symbols come from the Ethereum Yellow Paper (esp. Appendix G. Fee Schedule).
1. The external transaction does not incur new-account charges (G_{newaccount}) when creating an account, but internal transactions do
Example
Suppose you want a contract C
to transfer ETH to a brand-new account A
that does not yet exist.
- If
C
transfers directly, the internal new-account path charges 25,000 gas (G_{newaccount}). - Alternative: first send an external tx from an EOA to
A
with 1 wei. This tx costs the standard base 21,000 gas. After that,A
exists. NowC
’s transfer toA
no longer incurs the new-account charge (Save ~4,000 gas).
Conclusion
If you rely on a contract to create a new account, you pay 25,000 gas.
But if you first send an external transaction (21,000 gas) and then let the contract send funds, you effectively save ~4,000 gas.
Cause
External and internal creation paths use different charging hooks; only the contract invoking triggers the explicit new-account fee.
2. Precompile calls sometimes skip transaction input-byte fees
Example
Calling a precompiled contract (e.g., ECRECOVER
) may skip charging for transaction input bytes. Normally, input bytes cost 4 gas (zero) or 16 gas (non-zero) each. Take two real transaction examples:
* 1. A transaction 0x6b01 calling ECRECOVER
with 24,276 gas, where 276 gas is for the input bytes.
* 2. A transaction 0x1fb0 calling ECRECOVER
with 24,000 gas, where 0 gas is for the input bytes.
If you read the source code of execution client, you can find that the second transaction can also execute ECRECOVER
without charging for input bytes. The client will pad the input with zero data to match the expected size.
Extra note
I guess you are smart enough to notice that the two transactions I mentioned are external transactions.
So why do they invoke precompiled contracts?
I guess part of the reason is that some explorers mislabel precompile addresses (0x01–0x0A) as “burn addresses,” further confusing users (see here with the below snapshot).
Besides, deploying the precompile addresses in these special addresses (0x01–0x0A) is a failed design.
Sometimes, people just want to call these special addresses directly.
Cause
The poor address design of precompiled contracts and the misleading of block explorers leads to confusion and mislabeling.
3. Access-list entries are charged even if never accessed (i.e., G_{accesslistaddress}, G_{accessliststorage})
Example
EIP-2930 introduces access lists, allowing transactions to specify which addresses and storage slots they intend to access. However, a transaction can include addresses and slots in its access list but never touches them.
For example, a transaction 0x0dd0c sets an access list but never accesses the specified slots due to the address.
Cause
The protocol charges on inclusion to simplify execution, regardless of whether the entries are used. If you trust your users can provide you with correct input, you might as well trust Taylor Swift is your wife.
4. Self-transfer still charges transfer gas
Example
Account A sends ETH to itself.
No balance change occurs, yet charges still include 9,000 gas (G_{callvalue}) for the value transfer. According to this post from @vbuterin.
Two account writes (a balance-editing CALL normally costs 9000 gas)
Why does one account writing still cost 9,000 gas? Actually, if you read the source of execution client, you will find that when the from address is the same as the to address, the client will do nothing.
The above cases can happen when the transaction is a self-transfer or uses CALLCODE to transfer value.
Cause
Execution charges trigger regardless of whether the transfer is a no-op.
5. Calldata vs. contract bytecode disk pricing mismatch
Example
- Tx calldata: 16 gas/byte (non-zero) or 4 gas/byte (zero).
- Contract bytecode: 200 gas/byte.
Both occupy disk, yet pricing is inconsistent. It’s very confusing for me, as tx calldata is cheaper than contract bytecode as it should consider the actual disk usage and network overhead.
Cause
Gas schedule separates “calldata” and “code deposit” without aligning them to actual disk usage.
6. Reverted transactions are charged as if they wrote to disk
Example
A reverted transaction modifies state in memory, but no changes persist, yet charges for writes are still applied, the following gas fees are affected:
- 25,000 gas (new account, G_{newaccount})
- 9,000 gas (value transfer, G_{callvalue})
- 2,100 gas (cold slot, G_{coldslot})
- 200 gas (code deposit, G_{codedeposit})
Actual memory-only cost would have been ~100 gas.
Cause
Gas is charged during execution; a later revert cancels state changes but not fees. Implementations conservatively charge to prevent DoS.
7. Multiple ETH transfers in a single transaction are mischarged as cold
Example
Suppose a contract sends ETH to different accounts multiple times within a single transaction.
- The first transfer correctly incurs the G_{callvalue} (9,000 gas) to write to the account’s balance.
- Subsequent transfers to the other account in the same transaction should be charged the warm access fee (100 gas + 4,500 gas), but sometimes are still billed as cold (9,000 gas).
Cause
The warm/cold access bookkeeping is not consistently updated for multiple value transfers within a single transaction.
8. Miner/validator reward or withdrawal writes are uncharged
Example
Protocol-level balance updates (e.g., rewards, withdrawals) modify state on disk but cost 0 gas.
Cause
System-level bookkeeping bypasses the gas accounting hooks.
9. SSTORE’s first disk read is uncharged (per EIP-2200)
Example
When the SSTORE
opcode is executed, it first reads the current value from disk (contract storage) before deciding whether to write a new value. According to EIP-2200, if the value being stored matches the existing value, no disk write occurs and only a minimal gas fee is charged. However, the initial disk read itself is not charged any gas—the protocol only charges for the subsequent write if the value changes.
Cause
EIP-2200’s logic focuses on charging for state changes, but omits charging for the disk read that always happens first. This means the first access to the storage slot is free, even if it’s a cold read.
10. Storage-read optimizations reduced I/O but gas remained unchanged
Example
Ethereum clients have adopted flat storage/snapshot optimizations (e.g., Snapshot acceleration structure for geth), which organize state as a flat key-value store and allow direct disk reads, bypassing the intermediate nodes required by the legacy Merkle-Patricia Trie (MPT). This optimization significantly reduces disk I/O for cold storage reads. For instance, Geth and other clients now use SAS or similar structures, but the gas fees for cold accesses—2,600 / 2,100 / 2,400 / 1,900 gas—remain unchanged.
Cause
Gas constants for cold access were originally calibrated for MPT, where disk reads were more expensive due to traversing multiple trie nodes. With SAS, the actual disk resource consumption is much lower, but the protocol has not updated the corresponding gas fees.
Mitigation
Recalibrate gas constants to reflect the reduced disk I/O when clients switch to SAS or similar optimized storage backends.
11. SLOAD vs. MLOAD pricing mismatch
Example
SLOAD
(warm) → 100 gasMLOAD
→ 3 gas
Both are memory reads, but prices differ greatly.
Cause
Legacy distinction between state and memory operations; optimizations have blurred the actual cost gap.
12. Internal transactions sometimes update accounts without gas
Example
When account updates in disk occur without charging a gas fee for those updates. Specifically, this issue arises in scenarios where a user sends an external transaction to contract A, which in turn makes an internal call to contract B. If contract B modifies a slot in its storage, the corresponding storage root in contract B’s account must be updated on disk. However, no gas fees are charged for this account B update, leading to an inconsistency.
Cause
The bug occurs because the storage trie modification of contract B incurs no additional gas fee for updating its account state. This results from the protocol not charging for account state updates triggered by internal transactions, even though disk writes are performed.
13. EXT* opcodes priced too coarsely
Example
EXTCODESIZE
may read more data than BALANCE
, but both are charged the same cold-account fee (2,600 gas).
Cause
Opcode pricing buckets are coarse and ignore variable work.
Closing Note
This issue comes from my paper as follows, I share it with this link.
He, Z., Li, Z., Luo, J., Luo, F., Duan, J., Li, J., … & Zhang, X. (2025, February). Auspex: Unveiling Inconsistency Bugs of Transaction Fee Mechanism in Blockchain. In Proceedings of the 23rd USENIX Conference on File and Storage Technologies.
I would be glad if you could cite my paper.
All this highlights the need for a comprehensive review and adjustment of gas pricing mechanisms within the Ethereum protocol. By addressing these inconsistencies, we can ensure a more efficient and fair gas market that accurately reflects the underlying resource costs of various operations.