-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
EIP-2200: Structured Definitions for Net Gas Metering (#2200)
* Rebalance net-metered SSTORE gas cost with consideration of SLOAD gas cost change * Rename to EIP-2200 * Update eip-2200.md * Update eip-2200.md * Update eip-2200.md * Copy sections from EIP-1283 and EIP-1706 to EIP-2200 * Fix spelling * Escape the "SSTORE" word * Make EIP1283/1706/1884 a link (as customary) * Update eip-2200.md * Update eip-2200.md * Update eip-2200.md * Update eip-2200.md * Update eip-2200.md * Update eip-2200.md
- Loading branch information
Showing
1 changed file
with
322 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
--- | ||
eip: 2200 | ||
title: Structured Definitions for Net Gas Metering | ||
author: Wei Tang (@sorpaas) | ||
discussions-to: https://github.com/sorpaas/EIPs/issues/1 | ||
status: Draft | ||
type: Standards Track | ||
category: Core | ||
created: 2019-07-18 | ||
--- | ||
|
||
## Simple Summary | ||
|
||
This is an EIP that implements net gas metering. It's a combined | ||
version of [EIP-1283] and [EIP-1706], with a structured definition so as | ||
to make it interoperable with other gas changes such as [EIP-1884]. | ||
|
||
## Abstract | ||
|
||
This EIP provides a structured definition of net gas metering changes | ||
for `SSTORE` opcode, enabling new usages for contract storage, and | ||
reducing excessive gas costs where it doesn’t match how most | ||
implementation works. | ||
|
||
This is a combination of [EIP-1283] and [EIP-1706]. | ||
|
||
## Motivation | ||
|
||
This EIP proposes a way for gas metering on `SSTORE`, using information | ||
that is more universally available to most implementations, and | ||
require as little change in implementation structures as possible. | ||
|
||
* Storage slot’s original value. | ||
* Storage slot’s current value. | ||
* Refund counter. | ||
|
||
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. | ||
|
||
The original definition of EIP-1283 created a danger of a new kind of | ||
reentrancy attacks on existing contracts as Solidity by default grants | ||
a "stipend" of 2300 gas to simple transfer calls. This danger is | ||
easily mitigated if `SSTORE` is not allowed in low gasleft state, | ||
without breaking the backward compatibility and the original intention | ||
of EIP-1283. | ||
|
||
This EIP also replaces the original EIP-1283 value definitions of gas | ||
by parameters, so that it's more structured, and easier to define | ||
changes in the future. | ||
|
||
## Specification | ||
|
||
Define variables `SLOAD_GAS`, `SSTORE_SET_GAS`, `SSTORE_RESET_GAS` and | ||
`SSTORE_CLEARS_SCHEDULE`. The old and new values for those variables | ||
are: | ||
|
||
* `SLOAD_GAS`: changed from `200` to `800`. | ||
* `SSTORE_SET_GAS`: `20000`, not changed. | ||
* `SSTORE_RESET_GAS`: `5000`, not changed. | ||
* `SSTORE_CLEARS_SCHEDULE`: `15000`, not changed. | ||
|
||
Change the definition of EIP-1283 using those variables. The new | ||
specification, combining EIP-1283 and EIP-1706, will look like | ||
below. The terms *original value*, *current value* and *new value* are | ||
defined in EIP-1283. | ||
|
||
Replace `SSTORE` opcode gas cost calculation (including refunds) with | ||
the following logic: | ||
|
||
* If *gasleft* is less than or equal to gas stipend, fail the current | ||
call frame with 'out of gas' exception. | ||
* If *current value* equals *new value* (this is a no-op), `SLOAD_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_SET_GAS` is deducted. | ||
* Otherwise, `SSTORE_RESET_GAS` gas is deducted. If *new value* is | ||
0, add `SSTORE_CLEARS_SCHEDULE` gas to refund counter. | ||
* If *original value* does not equal *current value* (this storage | ||
slot is dirty), `SLOAD_GAS` 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 `SSTORE_CLEARS_SCHEDULE` gas from refund | ||
counter. | ||
* If *new value* is 0 (also means that *current value* is not | ||
0), add `SSTORE_CLEARS_SCHEDULE` gas to refund counter. | ||
* If *original value* equals *new value* (this storage slot is | ||
reset) | ||
* If *original value* is 0, add `SSTORE_SET_GAS - SLOAD_GAS` to | ||
refund counter. | ||
* Otherwise, add `SSTORE_RESET_GAS - SLOAD_GAS` gas to refund | ||
counter. | ||
|
||
An implementation should also note that with the above definition, if | ||
the implementation uses call-frame refund counter, the counter can go | ||
negative. If the implementation uses transaction-wise refund counter, | ||
the counter always stays positive. | ||
|
||
## Rationale | ||
|
||
This EIP mostly achieves 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. | ||
|
||
* We don't suffer from the optimization limitation of | ||
EIP-1087. EIP-1087 requires keeping a dirty map for storage changes, | ||
and implicitly 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 | ||
incurring additional memory or processing costs. | ||
* It never costs more gas compared with the 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. | ||
|
||
Regarding `SSTORE` gas cost and refunds, see Appendix for proofs of | ||
properties that this EIP satisfies. | ||
|
||
* 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 (with `SLOAD_GAS` being | ||
`200`): | ||
|
||
* 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. | ||
|
||
In order to keep in place the implicit reentrancy protection of | ||
existing contracts, transactions should not be allowed to modify state | ||
if the remaining gas is lower then the gas stipend given to | ||
"transfer"/"send" in Solidity. These are other proposed remediations | ||
and objections to implementing them: | ||
|
||
* Drop EIP-1283 and abstain from modifying `SSTORE` cost | ||
* EIP-1283 is an important update | ||
* It was accepted and implemented on test networks and in clients. | ||
* Add a new call context that permits LOG opcodes but not changes to state. | ||
* Adds another call type beyond existing regular/staticcall | ||
* Raise the cost of `SSTORE` to dirty slots to >=2300 gas | ||
* Makes net gas metering much less useful. | ||
* Reduce the gas stipend | ||
* Makes the stipend almost useless. | ||
* Increase the cost of writes to dirty slots back to 5000 gas, but add | ||
4800 gas to the refund counter | ||
* Still doesn’t make the invariant explicit. | ||
* Requires callers to supply more gas, just to have it refunded | ||
* Add contract metadata specifying per-contract EVM version, and only | ||
apply `SSTORE` changes to contracts deployed with the new version. | ||
|
||
## Backwards Compatibility | ||
|
||
This EIP requires a hard fork to implement. No gas cost increase is | ||
anticipated, and many contracts will see gas reduction. | ||
|
||
Performing `SSTORE` has never been possible with less than 5000 gas, so | ||
it does not introduce incompatibility to the Ethereum mainnet. Gas | ||
estimation should account for this requirement. | ||
|
||
## Test Cases | ||
|
||
| Code | Used Gas | Refund | Original | 1st | 2nd | 3rd | | ||
|------------------------------------|----------|--------|----------|-----|-----|-----| | ||
| `0x60006000556000600055` | 1612 | 0 | 0 | 0 | 0 | | | ||
| `0x60006000556001600055` | 20812 | 0 | 0 | 0 | 1 | | | ||
| `0x60016000556000600055` | 20812 | 19200 | 0 | 1 | 0 | | | ||
| `0x60016000556002600055` | 20812 | 0 | 0 | 1 | 2 | | | ||
| `0x60016000556001600055` | 20812 | 0 | 0 | 1 | 1 | | | ||
| `0x60006000556000600055` | 5812 | 15000 | 1 | 0 | 0 | | | ||
| `0x60006000556001600055` | 5812 | 4200 | 1 | 0 | 1 | | | ||
| `0x60006000556002600055` | 5812 | 0 | 1 | 0 | 2 | | | ||
| `0x60026000556000600055` | 5812 | 15000 | 1 | 2 | 0 | | | ||
| `0x60026000556003600055` | 5812 | 0 | 1 | 2 | 3 | | | ||
| `0x60026000556001600055` | 5812 | 4200 | 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` | 40818 | 19200 | 0 | 1 | 0 | 1 | | ||
| `0x600060005560016000556000600055` | 10818 | 19200 | 1 | 0 | 1 | 0 | | ||
|
||
## Implementation | ||
|
||
To be added. | ||
|
||
## Appendix: Proof | ||
|
||
Because the *storage slot's original value* is defined as the value | ||
when a reversion happens on the *current transaction*, it's easy to | ||
see that call frames won't interfere `SSTORE` gas calculation. So | ||
although the below proof is discussed without call frames, it applies | ||
to all situations with call frames. We will discuss the case | ||
separately for *original value* being zero and not zero, and use | ||
*induction* to prove some properties of `SSTORE` gas cost. | ||
|
||
*Final value* is the value of a particular storage slot at the end of | ||
a transaction. *Absolute gas used* is the absolute value of *gas used* | ||
minus *refund*. We use `N` to represent the total number of `SSTORE` | ||
operations on a storage slot. For states discussed below, refer to | ||
*State Transition* in *Explanation* section. | ||
|
||
Below we do the proof under the assumption that all parameters are | ||
unchanged, meaning `SLOAD_GAS` is `200`. However, note that the proof | ||
still applies no matter how `SLOAD_GAS` is changed. | ||
|
||
### Original Value Being Zero | ||
|
||
When *original value* is 0, we want to prove that: | ||
|
||
* **Case I**: If the *final value* ends up still being 0, we want to charge `200 * | ||
N` gases, because no disk write is needed. | ||
* **Case II**: If the *final value* ends up being a non-zero value, we want to | ||
charge `20000 + 200 * (N-1)` gas, because it requires writing this | ||
slot to disk. | ||
|
||
#### Base Case | ||
|
||
We always start at state A. The first `SSTORE` can: | ||
|
||
* Go to state A: 200 gas is deducted. We satisfy *Case I* because | ||
`200 * N == 200 * 1`. | ||
* Go to state B: 20000 gas is deducted. We satisfy *Case II* because | ||
`20000 + 200 * (N-1) == 20000 + 200 * 0`. | ||
|
||
#### Inductive Step | ||
|
||
* From A to A. The previous gas cost is `200 * (N-1)`. The current | ||
gas cost is `200 + 200 * (N-1)`. It satisfy *Case I*. | ||
* From A to B. The previous gas cost is `200 * (N-1)`. The current | ||
gas cost is `20000 + 200 * (N-1)`. It satisfy *Case II*. | ||
* From B to B. The previous gas cost is `20000 + 200 * (N-2)`. The | ||
current gas cost is `200 + 20000 + 200 * (N-2)`. It satisfy | ||
*Case II*. | ||
* From B to A. The previous gas cost is `20000 + 200 * (N-2)`. The | ||
current gas cost is `200 - 19800 + 20000 + 200 * (N-2)`. It satisfy | ||
*Case I*. | ||
|
||
### Original Value Not Being Zero | ||
|
||
When *original value* is not 0, we want to prove that: | ||
|
||
* **Case I**: If the *final value* ends up unchanged, we want to | ||
charge `200 * N` gases, because no disk write is needed. | ||
* **Case II**: If the *final value* ends up being zero, we want to | ||
charge `5000 - 15000 + 200 * (N-1)` gas. Note that `15000` is the | ||
refund in actual definition. | ||
* **Case III**: If the *final value* ends up being a changed non-zero | ||
value, we want to charge `5000 + 200 * (N-1)` gas. | ||
|
||
#### Base Case | ||
|
||
We always start at state X. The first `SSTORE` can: | ||
|
||
* Go to state X: 200 gas is deducted. We satisfy *Case I* because | ||
`200 * N == 200 * 1`. | ||
* Go to state Y: 5000 gas is deducted. We satisfy *Case III* because | ||
`5000 + 200 * (N-1) == 5000 + 200 * 0`. | ||
* Go to state Z: The absolute gas used is `5000 - 15000` where 15000 | ||
is the refund. We satisfy *Case II* because `5000 - 15000 + 200 * | ||
(N-1) == 5000 - 15000 + 200 * 0`. | ||
|
||
#### Inductive Step | ||
|
||
* From X to X. The previous gas cost is `200 * (N-1)`. The current gas | ||
cost is `200 + 200 * (N-1)`. It satisfy *Case I*. | ||
* From X to Y. The previous gas cost is `200 * (N-1)`. The current gas | ||
cost is `5000 + 200 * (N-1)`. It satisfy *Case III*. | ||
* From X to Z. The previous gas cost is `200 * (N-1)`. The current | ||
absolute gas cost is `5000 - 15000 + 200 * (N-1)`. It satisfy *Case | ||
II*. | ||
* From Y to X. The previous gas cost is `5000 + 200 * (N-2)`. The | ||
absolute current gas cost is `200 - 4800 + 5000 + 200 * (N-2)`. It | ||
satisfy *Case I*. | ||
* From Y to Y. The previous gas cost is `5000 + 200 * (N-2)`. The | ||
current gas cost is `200 + 5000 + 200 * (N-2)`. It satisfy *Case | ||
III*. | ||
* From Y to Z. The previous gas cost is `5000 + 200 * (N-2)`. The | ||
current absolute gas cost is `200 - 15000 + 5000 + 200 * (N-2)`. It | ||
satisfy *Case II*. | ||
* From Z to X. The previous gas cost is `5000 - 15000 + 200 * | ||
(N-2)`. The current absolute gas cost is `200 + 10200 + 5000 - | ||
15000 + 200 * (N-2)`. It satisfy *Case I*. | ||
* From Z to Y. The previous gas cost is `5000 - 15000 + 200 * | ||
(N-2)`. The current absolute gas cost is `200 + 15000 + 5000 - | ||
15000 + 200 * (N-2)`. It satisfy *Case III*. | ||
* From Z to Z. The previous gas cost is `5000 - 15000 + 200 * | ||
(N-2)`. The current absolute gas cost is `200 + 5000 - 15000 + 200 * | ||
(N-2)`. It satisfy *Case II*. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). | ||
|
||
[EIP-1283]: https://eips.ethereum.org/EIPS/eip-1283 | ||
[EIP-1706]: https://eips.ethereum.org/EIPS/eip-1706 | ||
[EIP-1884]: https://eips.ethereum.org/EIPS/eip-1884 | ||
[EIP-1087]: https://eips.ethereum.org/EIPS/eip-1087 | ||
[EIP-1153]: https://eips.ethereum.org/EIPS/eip-1153 | ||
[EIP-658]: https://eips.ethereum.org/EIPS/eip-658 |