---
eip: 2200
title: Net gas metering for SSTORE operations
author: Nick Johnson (@arachnid), Wei Tang (@sorpaas), Andrei Maiboroda
(@gumb0), Alexey Akhunox (@AlexeyAkhunov), Péter Szilágyi (@karalabe)
discussions-to: https://github.com/sorpaas/EIPs/issues/1
status: Draft
type: Standards Track
category: Core
created: 2019-07-18
---
This EIP proposes a change to how gas is charged for EVM SSTORE operations, in order to reduce excessive gas costs in situations where these are unwarranted, and to enable new use-cases for contract storage.
Presently, SSTORE operations are charged as follows:
- 20,000 gas to set a slot from 0 to non-0
- 5,000 gas for any other change
- A 10,000 gas refund when a slot is set from non-0 to 0. Refunds are applied at the end of the transaction.
In situations where a single update is made to a storage value in a transaction, these gas costs have been determined to fairly reflect the resources consumed by the operation. However, this results in excessive gas costs for sequences of operations that make multiple updates.
Some examples to illustrate the problem:
- If a contract with empty storage sets slot 0 to 1, then back to 0, it will be charged
20000 + 5000 - 10000 = 15000
gas, despite this sequence of operations not requiring any disk writes. - A contract with empty storage that increments slot 0 5 times will be charged
20000 + 5 * 5000 = 45000
gas, despite this sequence of operations requiring no more disk activity than a single write, charged at 20000 gas. - A balance transfer from account A to account B followed by a transfer from B to C, with all accounts having nonzero starting and ending balances, will cost
5000 * 4 = 20000
gas.
Addressing this issue would also enable new use-cases that are currently cost-prohibitive:
- Subsequent storage write operations within the same call frame. This includes reentry locks, same-contract multi-send, etc.
- Exchange storage information between sub call frame and parent call frame, where this information does not need to be persistent outside of a transaction. This includes sub-frame error codes and message passing, etc.
Definitions of terms are as below:
- Storage slot's original value: This is the value of the storage if a reversion happens on the current transaction.
- Storage slot's current value: This is the value of the storage before SSTORE operation happens.
- Storage slot's new value: This is the value of the storage after SSTORE operation happens.
Definitions of constants are as below:
SSTORE_NOOP_GAS
:800
- Once per op if the value doesn't changeSSTORE_DIRTY_GAS
:2300
- Once per op if the slot is already dirtySSTORE_DIRTY_REFUND
:1500
- Once per op if the slot is already dirtySSTORE_INIT_GAS
:20000
- Once per op from clean zero to non-zeroSSTORE_INIT_REFUND
:19200
- Once per op for resetting to the original zero valueSSTORE_CLEAN_GAS
:5000
- Once per op from clean non-zero to something elseSSTORE_CLEAN_REFUND
:4200
- Once per op for resetting to the original non-zero valueSSTORE_CLEAR_REFUND
:15000
- Once per op for clearing an originally existing storage slot
Replace SSTORE opcode gas cost calculation (including refunds) with the following logic:
- If current value equals new value (this is a no-op),
SSTORE_NOOP_GAS
gas is deducted. - If current value does not equal new value:
- If original value equals current value (this storage slot has not been changed by the current execution context):
- If original value is 0,
SSTORE_INIT_GAS
gas is deducted. - Otherwise,
SSTORE_CLEAN_GAS
gas is deducted. If new value is 0, addSSTORE_CLEAR_REFUND
to refund counter.
- If original value is 0,
- If original value does not equal current value (this storage slot is dirty),
SSTORE_DIRTY_GAS
gas is deducted,SSTORE_DIRTY_REFUND
is refunded. Apply both of the following clauses:- If original value is not 0:
- If current value is 0 (also means that new value is not 0), subtract
SSTORE_CLEAR_REFUND
gas from refund counter. We can prove that refund counter will never go below 0. - If new value is 0 (also means that current value is not 0), add
SSTORE_CLEAR_REFUND
gas to refund counter.
- If current value is 0 (also means that new value is not 0), subtract
- If original value equals new value (this storage slot is reset):
- If original value is 0, add
SSTORE_INIT_REFUND
to refund counter. - Otherwise, add
SSTORE_CLEAN_REFUND
gas to refund counter.
- If original value is 0, add
- If original value is not 0:
- If original value equals current value (this storage slot has not been changed by the current execution context):
Refund counter works as before -- it is limited to half of the gas consumed. On a transaction level, refund counter will never go below zero. However, there are some important notes depending on the implementation details:
- If an implementation uses "transaction level" refund counter (refund is checkpointed at each call frame), then the refund counter continues to be unsigned.
- If an implementation uses "execution-frame level" refund counter (a new refund counter is created at each call frame, and then merged back to parent when the call frame finishes), then the refund counter needs to be changed to signed -- at internal calls, a child refund can go below zero.
We believe the proposed mechanism represents the simplest way to reduce storage gas costs in situations where they do not reflect the actual costs borne by nodes. Several alternative designs were considered and dismissed:
- Charging a flat 800 gas for SSTORE operations, and an additional 19200 / 4200 at the end of a transaction for new or modified values is simpler, and removes the need for a dirty map, but pushes a significant source of gas consumption out of the EVM stack and applies it at the end of the transaction, which is likely to complicate debugging and reduces contracts’ ability to limit the gas consumption of callees, as well as introducing a new mechanism to the EVM.
- Keeping a separate refund counter for storage gas refunds would avoid the issue of refunds being limited to half the gas consumed (not necessary here), but would introduce additional complexity in tracking this value.
- Refunding gas each time a storage slot is set back to its initial value would introduce a new mechanism (instant refunds) and complicate gas accounting for contracts calling other contracts; it would also permit the possibility of a contract call with negative execution cost.
This EIP requires a hard fork to implement.
No contract should see an increase in gas cost for this change, and many will see decreased gas consumption, so no contract-layer backwards compatibility issues are anticipated.
Code | Used Gas | Refund | Original | 1st | 2nd | 3rd |
---|---|---|---|---|---|---|
0x60006000556000600055 |
1612 | 0 | 0 | 0 | 0 | |
0x60006000556001600055 |
20812 | 0 | 0 | 0 | 1 | |
0x60016000556000600055 |
22312 | 20700 | 0 | 1 | 0 | |
0x60016000556002600055 |
22312 | 1500 | 0 | 1 | 2 | |
0x60016000556001600055 |
20812 | 0 | 0 | 1 | 1 | |
0x60006000556000600055 |
5812 | 15000 | 1 | 0 | 0 | |
0x60006000556001600055 |
7312 | 5700 | 1 | 0 | 1 | |
0x60006000556002600055 |
7312 | 1500 | 1 | 0 | 2 | |
0x60026000556000600055 |
7312 | 16500 | 1 | 2 | 0 | |
0x60026000556003600055 |
7312 | 1500 | 1 | 2 | 3 | |
0x60026000556001600055 |
7312 | 5700 | 1 | 2 | 1 | |
0x60026000556002600055 |
5812 | 0 | 1 | 2 | 2 | |
0x60016000556000600055 |
5812 | 15000 | 1 | 1 | 0 | |
0x60016000556002600055 |
5812 | 0 | 1 | 1 | 2 | |
0x60016000556001600055 |
1612 | 0 | 1 | 1 | 1 | |
0x600160005560006000556001600055 |
42318 | 20700 | 0 | 1 | 0 | 1 |
0x600060005560016000556000600055 |
12318 | 20700 | 1 | 0 | 1 | 0 |
Geth: ethereum/go-ethereum#19964
Copyright and related rights waived via CC0.