EIPs/EIPS/eip-1283.md

9.9 KiB

eip title author discussions-to status type category created
1283 Net gas metering for SSTORE without dirty maps Wei Tang (@sorpaas) https://github.com/sorpaas/EIPs/issues/1 Draft Standards Track Core 2018-08-01

Abstract

This EIP proposes net gas metering changes for SSTORE opcode, as an alternative for EIP-1087. It tries to be friendlier to implementations that uses different opetimiazation strategies for storage change caches.

Motivation

EIP-1087 proposes a way to adjust gas metering for SSTORE opcode, enabling new usages on this opcodes where it is previously too expensive. However, EIP-1087 requires keeping a dirty map for storage changes, and implictly makes the assumption that a transaction's storage changes are committed to the storage trie at the end of a transaction. This works well for some implementations, but not for others. After EIP-658, an efficient storage cache implementation would probably use an in-memory trie (without RLP encoding/decoding) or other immutable data structures to keep track of storage changes, and only commit changes at the end of a block. For them, it is possible to know a storage's original value and current value, but it is not possible to iterate over all storage changes without incur additional memory or processing costs.

This EIP proposes an alternative way for gas metering on SSTORE, using information that is more universially available to most implementations:

  • Storage slot's original value.
  • Storage slot's current value.
  • Refund counter.

For the specification provided here:

  • We don't suffer from the optimization limitation of EIP-1087, and it never costs more gas compared with current scheme.
  • It covers all usages for a transient storage. Clients that are easy to implement EIP-1087 will also be easy to implement this specification. Some other clients might require a little bit extra refactoring on this. Nonetheless, no extra memory or processing cost is needed on runtime.

Usages that benefits from this EIP's gas reduction scheme includes:

  • 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.

Replace SSTORE opcode gas cost calculation (including refunds) with the following logic:

  • If current value equals new value (this is a no-op), 200 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, 20000 gas is deducted.
      • Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter.
    • If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. 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), remove 15000 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 15000 gas to refund counter.
      • If original value equals new value (this storage slot is reset)
        • If original value is 0, add 19800 gas to refund counter.
        • Otherwise, add 4800 gas to refund counter.

Refund counter works as before -- it is limited to half of the gas consumed.

Explanation

The new gas cost scheme for SSTORE is divided to three different types:

  • No-op: the virtual machine does not need to do anything. This is the case if current value equals new value.
  • Fresh: this storage slot has not been changed, or has been reset to its original value. This is the case if current value does not equal new value, and original value equals current value.
  • Dirty: this storage slot has already been changed. This is the case if current value does not equal new value, and original value does not equal current value.

We can see that the above three types cover all possible variations of original value, current value, and new value.

No-op is a trivial operation. Below we only consider cases for Fresh and Dirty.

All initial (not-No-op) SSTORE on a particular storage slot starts with Fresh. After that, it will become Dirty if the value has been changed. When going from Fresh to Dirty, we charge the gas cost the same as current scheme. A Dirty storage slot can be reset back to Fresh via a SSTORE opcode. This will trigger a refund.

When a storage slot remains at Dirty, we charge 200 gas. In this case, we would also need to keep track of R_SCLEAR refunds -- if we already issued the refund but it no longer applies (current value is 0), then removes this refund from the refund counter. If we didn't issue the refund but it applies now (new value is 0), then adds this refund to the refund counter. It is not possible where a refund is not issued but we remove the refund in the above case, because all storage slot starts with Fresh state.

State Transition

Below is a graph (by @Arachnid) showing possible state transition of gas costs. We ignore No-op state because that is trivial:

State Transition

Below are table version of the above diagram. Vertical shows the new value being set, and horizontal shows the state of original value and current value.

When original value is 0:

A (current=orig=0) B (current!=orig)
~0 B; 20k gas B; 200 gas
0 A; 200 gas A; 200 gas, 19.8k refund

When original value is not 0:

X (current=orig!=0) Y (current!=orig) Z (current=0)
orig X; 200 gas X; 200 gas, 4.8k refund X; 200 gas, -10.2k refund
~orig, ~0 Y; 5k gas Y; 200 gas Y; 200 gas, -15k refund
0 Z; 5k gas, 15k refund Z; 200 gas, 15k refund Z; 200 gas

Rationale

This EIP mostly archives what a transient storage tries to do (EIP-1087 and EIP-1153), but without the complexity of introducing the concept of "dirty maps", or an extra storage struct.

  • For absolute gas used (that is, actual gas used minus refund), this EIP is equivalent to EIP-1087 for all cases.
  • For one particular case, where a storage slot is changed, reset to its original value, and then changed again, EIP-1283 would move more gases to refund counter compared with EIP-1087.

Examine examples provided in EIP-1087's Motivation:

  • If a contract with empty storage sets slot 0 to 1, then back to 0, it will be charged 20000 + 200 - 19800 = 400 gas.
  • A contract with empty storage that increments slot 0 5 times will be charged 20000 + 5 * 200 = 21000 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, it will cost 5000 * 3 + 200 - 4800 = 10400 gas.

Backwards Compatibility

This EIP requires a hard fork to implement. No gas cost increase is anticipated, and many contract will see gas reduction.

Test Cases

Below we provide 17 test cases. 15 of them covering consecutive two SSTORE operations are based on work by @chfast. Two additional case with three SSTORE operations is used to test the case when a slot is reset and then set again.

Code Used Gas Refund Original 1st 2nd 3rd
0x60006000556000600055 412 0 0 0 0
0x60006000556001600055 20212 0 0 0 1
0x60016000556000600055 20212 19800 0 1 0
0x60016000556002600055 20212 0 0 1 2
0x60016000556001600055 20212 0 0 1 1
0x60006000556000600055 5212 15000 1 0 0
0x60006000556001600055 5212 4800 1 0 1
0x60006000556002600055 5212 0 1 0 2
0x60026000556000600055 5212 15000 1 2 0
0x60026000556003600055 5212 0 1 2 3
0x60026000556001600055 5212 4800 1 2 1
0x60026000556002600055 5212 0 1 2 2
0x60016000556000600055 5212 15000 1 1 0
0x60016000556002600055 5212 0 1 1 2
0x60016000556001600055 412 0 1 1 1
0x600160005560006000556001600055 40218 19800 0 1 0 1
0x600060005560016000556000600055 10218 19800 1 0 1 0

Copyright and related rights waived via CC0.