diff --git a/EIPS/eip-7609.md b/EIPS/eip-7609.md index c5862a9615e1c8..045b7f6e6cbe1d 100644 --- a/EIPS/eip-7609.md +++ b/EIPS/eip-7609.md @@ -1,19 +1,19 @@ --- eip: 7609 -title: Decrease base cost of TLOAD/TSTORE +title: Decrease cost of TLOAD/TSTORE/Warm SLOAD description: Improve the efficiency of TLOAD/TSTORE by decreasing the base cost and introducing a superlinear pricing model. -author: Charles Cooper (@charles-cooper), James Prestwich (@prestwich), brockelmore (@brockelmore) +author: Charles Cooper (@charles-cooper), James Prestwich (@prestwich), brockelmore (@brockelmore), Ben Adams (@benaadams) discussions-to: https://ethereum-magicians.org/t/eip-7609-reduce-transient-storage-pricing/18435 status: Draft type: Standards Track category: Core created: 2024-02-01 -requires: 1153 +requires: 1153, 2929 --- ## Abstract -Decrease the base cost of TLOAD/TSTORE while introducing a superlinear pricing model. This increases the efficiency of TLOAD/TSTORE for common use cases, while providing a pricing model to prevent DoS vectors. +Decrease the base cost of warm SLOAD, TLOAD and TSTORE while introducing a superlinear pricing model. This increases the efficiency of TLOAD/TSTORE for common use cases, while providing a pricing model to prevent DoS vectors. ## Motivation @@ -21,7 +21,7 @@ Decrease the base cost of TLOAD/TSTORE while introducing a superlinear pricing m One of the most important use cases that EIP-1153 enables is cheap reentrancy protection. In fact, if transient storage is cheap enough for the first few slots, reentrancy protection can be enabled by default at the language level without too much burden to users, while simultaneously preventing the largest—and most expensive—class of smart contract vulnerabilities. -Furthermore, it seems that transient storage is fundamentally overpriced. Its pricing does not interact with refunds, it only requires a new allocation on contract load (as opposed to memory, which requires a fresh allocation on every call), and has no interaction with the physical database. +Furthermore, it seems that transient storage and warm SLOAD are fundamentally overpriced. Its pricing does not interact with refunds, it only requires a new allocation on contract load (as opposed to memory, which requires a fresh allocation on every call), and has no interaction with the physical database. This EIP proposes a pricing model which charges additional gas per allocation, which is cheaper for common cases (fewer than ~95 slots are written per contract), while making DoS using transient storage prohibitively expensive. @@ -29,21 +29,35 @@ This EIP proposes a pricing model which charges additional gas per allocation, w The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. -The gas cost for `TLOAD` is proposed to be 5 gas. The gas cost for `TSTORE` is proposed to be 8 gas + `expansion_cost`, where `expansion_cost` is calculated as `1 gas * len(transient storage mapping)` if the key is not yet in the transient storage mapping, and otherwise 0 gas. +The gas cost for `TLOAD` is proposed to remain as warm `SLOAD` gas; but warm `SLOAD` reduced to 6 (e.g. `MLOAD * 2`). The gas cost for `TSTORE` is proposed to be 10 gas + `expansion_cost`, where `expansion_cost` is calculated as `1 gas * len(transient storage mapping)` if the key is not yet in the transient storage mapping, and otherwise 0 gas. + + +### Parameters + +| Constant | Value | +| - | - | +| `WARM_STORAGE_READ_COST` | 6 | +| `TLOAD` | 6 | +| `WARM_TSTORE` | 10 | +| `COLD_TSTORE` | 10 + N | + +Where `N` is the number of existing `TSTORE` entries In pseudo-code: ```python -G_LOW = 5 -G_MID = 8 +G_VLOW = 3 +G_MLOAD = G_VLOW +G_WARM_STORAGE_READ_COST = G_MLOAD * 2 +G_HIGH = 10 SLOPE = 1 def gas_tload(_key): - return G_LOW + return G_WARM_STORAGE_READ_COST def gas_tstore(key, transient_mapping): - cost = G_MID + cost = G_HIGH if key not in transient_mapping: cost += SLOPE * transient_mapping.size() return cost @@ -53,7 +67,7 @@ def gas_tstore(key, transient_mapping): ### Gas -In benchmarking, `TLOAD` was found to cost a similar amount of CPU time as `MUL`, while `TSTORE` was found to cost about 1.5x that. The values `G_low` and `G_mid` were therefore chosen for `TLOAD` and `TSTORE`, respectively. +In benchmarking, `TLOAD` was found to cost a similar amount of CPU time as `MUL`, while `TSTORE` was found to cost about 1.5x that. The logic for `TLOAD` and warm `SLOAD` are essentially identical, however in recognition that a hash lookup is slightly more complicated than an offset lookup the twice `gas MLOAD` pricing was chosen (or `gas CALLDATACOPY + 1`). For `TSTORE` in recognition that has an associated key the value `G_high` and additional expansion cost was therefore chosen. ## Backwards Compatibility @@ -61,19 +75,19 @@ No backward compatibility issues found. ## Security Considerations -The maximum number of transient slots which can be allocated on a single contract given 30m gas is approximately 7,739 (solution to `x(x-1)/2*1 + 8*x = 30_000_000`), which totals 248KB. +The maximum number of transient slots which can be allocated on a single contract given 30m gas is approximately 7,739 (solution to `x(x-1)/2*1 + 10*x = 30_000_000` `x≈7736.5`), which totals 242KB (7736.5 * 32). The maximum number of transient slots which can be allocated in a transaction if you use the strategy of calling new contracts (which each are designed to maximize transient storage allocation) once the cost of `TSTORE` is more than the cost of calling a cold contract (2600 gas), can be solved for as follows: ``` solve for SLOPE * == 2600, => num_slots == 2600 -gas_used_by_contract = 2600 + SLOPE * num_slots * (num_slots - 1) / 2 + G_MID * num_slots == 3402100 +gas_used_by_contract = 2600 + SLOPE * num_slots * (num_slots - 1) / 2 + G_HIGH * num_slots == 3407300 block_gas_limit = 30_000_000 -num_calls_per_txn = block_gas_limit // gas_used_by_contract ~= 8.8 -max_transient_slots = num_calls_per_txn * num_slots == 22927 +num_calls_per_txn = block_gas_limit // gas_used_by_contract ~= 8 +max_transient_slots = num_calls_per_txn * num_slots == 20800 ``` -Thus, the maximum number of transient slots which can be allocated in a single transaction with this method is roughly 23,000, which totals 736KB. Compared to the current gas schedule (which allows allocating approximately `30_000_000 / 100 * 32 == 9.6MB` worth of memory), this is a net *reduction* in the total memory which can be allocated on a client using transient storage, so this EIP presents a stronger bound on the resources which can be used by transient storage. As a comparison point, the total amount of memory which can be allocated on a client by `SSTORE`s in a given transaction is `30_000_000 / 20_000 * 32`, or 48KB. +Thus, the maximum number of transient slots which can be allocated in a single transaction with this method is roughly 20,800, which totals 650KB. Compared to the current gas schedule (which allows allocating approximately `30_000_000 / 100 * 32 == 9.6MB` worth of memory), this is a net *reduction* in the total memory which can be allocated on a client using transient storage, so this EIP presents a stronger bound on the resources which can be used by transient storage. As a comparison point, the total amount of memory which can be allocated on a client by `SSTORE`s in a given transaction is `30_000_000 / 20_000 * 32`, or 48KB. Note that this cap scales linearly with the gas limit, which is a useful property when considering future block gas limit increases.