Skip to content

Instantly share code, notes, and snippets.

@karalabe
Last active September 22, 2019 19:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save karalabe/adc43c07db9f03be82093cd5466562b0 to your computer and use it in GitHub Desktop.
Save karalabe/adc43c07db9f03be82093cd5466562b0 to your computer and use it in GitHub Desktop.
Error in user YAML: (<unknown>): could not find expected ':' while scanning a simple key at line 4 column 1
---
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
---

Abstract

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.

Motivation

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.

Specification

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 change
  • SSTORE_DIRTY_GAS: 2300 - Once per op if the slot is already dirty
  • SSTORE_DIRTY_REFUND: 1500 - Once per op if the slot is already dirty
  • SSTORE_INIT_GAS: 20000 - Once per op from clean zero to non-zero
  • SSTORE_INIT_REFUND: 19200 - Once per op for resetting to the original zero value
  • SSTORE_CLEAN_GAS: 5000 - Once per op from clean non-zero to something else
  • SSTORE_CLEAN_REFUND: 4200 - Once per op for resetting to the original non-zero value
  • SSTORE_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, add SSTORE_CLEAR_REFUND to refund counter.
    • 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 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.

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.

Rationale

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.

Backwards Compatibility

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.

Test Cases

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

Implementation

Geth: ethereum/go-ethereum#19964

Copyright

Copyright and related rights waived via CC0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment