From ac1a50936cd3b5b22943a8354ce31912191d1c34 Mon Sep 17 00:00:00 2001 From: Wei Tang Date: Sat, 26 Oct 2019 01:41:40 +0200 Subject: [PATCH] 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 --- EIPS/eip-2200.md | 322 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 EIPS/eip-2200.md diff --git a/EIPS/eip-2200.md b/EIPS/eip-2200.md new file mode 100644 index 0000000000000..959a00a9635ea --- /dev/null +++ b/EIPS/eip-2200.md @@ -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