Skip to content
Draft
28 changes: 25 additions & 3 deletions EIPS/eip-7778.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
eip: 7778
title: Block Gas Accounting without Refunds
description: Prevent Block Gas Limit Circumvention by Excluding Refunds from Block Gas Accounting
description: Prevent Block Gas Limit Circumvention by Excluding Refunds from Block Gas Accounting and Introduce Refund Routing based on Their Origin
author: Ben Adams (@benaadams), Toni Wahrstätter (@nerolation)
discussions-to: https://ethereum-magicians.org/t/eip-7778-prevent-block-gas-smuggling/21234
status: Draft
Expand All @@ -12,7 +12,9 @@ created: 2024-10-01

## Abstract

This EIP modifies the block gas accounting mechanism to prevent the circumvention of block gas limits. It proposes that gas refunds, particularly those from SSTORE operations setting storage slots to zero, should not reduce the gas counted toward the block gas limit, while still being applied to transaction gas costs for users.
This EIP modifies the block gas accounting mechanism to prevent the circumvention of block gas limits. It proposes that gas refunds, particularly those from `SSTORE` operations setting storage slots to zero, should not reduce the gas counted toward the block gas limit, while still being applied to transaction gas costs for users.

Additionally, this EIP introduces refund routing based on the origin of refunds in order to further improve gas accounting. These rules apply retroactively to existing gas refunds but do not modify the value of any refund mechanism.

## Motivation

Expand All @@ -26,6 +28,8 @@ This mechanism can be exploited to perform more operations in a block than the g
2. Denial-of-service vectors
3. Computational loads exceeding the intended block gas limit

Furthermore, existing refunds (for example, the authorization refunds introduced by [EIP-7702](./eip-7702.md) are routed through the single refund counter retained after [EIP-3529](./eip-3529.md). As a result, refunded gas remains unavailable until the end of the transaction and is subject to the 20% refund cap. Separating these refunds according to their origin improves gas accounting and can reduce transaction costs by making refunded gas available through more appropriate accounting mechanisms.

## Specification

### Gas Accounting Changes
Expand All @@ -41,6 +45,17 @@ This mechanism can be exploited to perform more operations in a block than the g
- Block gas accounting becomes: `block.gas_used += max(tx_gas_used, calldata_floor_gas_cost)` (incorporating the calldata floor from [EIP-7623](./eip-7623.md))
- Storage discounts that reflect actual reduced computational work (e.g., warm storage access, reverting to original values) remain applied to block gas accounting

### Refund Routing

This EIP defines two categories of gas refunds according to their origin:

- `external refund`: a refund originating from state changes performed by previous transactions. For example, transaction A creates a storage slot and transaction B subsequently clears it.
- `internal refund`: a refund originating from state changes or overcharges within the same transaction. For example, refunds resulting from the overcharging of [EIP-7702](./eip-7702.md) authorizations.

The refund counter only accepts refunds belonging to the `external refund` category.

Refunds belonging to the `internal refund` category MUST NOT be added to the refund counter and instead MUST be accounted for using alternative mechanisms, such as dedicated refund reservoirs or counters.

## Rationale

### Aligning Gas Limits with Computational Work
Expand All @@ -51,6 +66,10 @@ The block gas limit is designed to constrain the computational load per block. G

Users still receive gas refunds, maintaining incentives for efficient state management. Only the accounting for block-level constraints changes, not the economics for individual users

### Refund Routing

Separating refunds according to their origin further improves gas accounting while preserving the existing refund mechanisms. This proposal changes how refunds are accounted for but does not alter the economic value of existing refunds.

## Backwards Compatibility

- This change is not backwards compatible and requires a hard fork
Expand All @@ -71,12 +90,15 @@ Users still receive gas refunds, maintaining incentives for efficient state mana
- Verify that transaction costs for users remain unchanged
- Confirm block gas accounting properly excludes refunds

4. **Refund Routing:**
- Verify that only `external refunds` are added to the refund counter.

## Security Considerations

- Eliminates potential denial-of-service vectors that exploit gas refunds to exceed block computational limits
- Improves predictability of block processing times by enforcing stricter limits on computational work
- Prevents miners/validators from including transactions that collectively contain more computational work than intended

## Copyright
## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
2 changes: 1 addition & 1 deletion EIPS/eip-8037.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ The gas counters operate as follows:
- The `GAS` opcode returns `gas_left` only (excluding the reservoir).
- State-gas is metered at the end of all state-mutating opcodes, call-frame boundaries (in case of exceptional halts and reverts) and at the end of the transaction.

##### Gas accounting for SSTORE (opcode-level)
##### Gas accounting for `SSTORE` (opcode-level)

| Original value | Current value | New value | Description | State-gas charges/refills |
|---|---|---|---|---|
Expand Down
130 changes: 95 additions & 35 deletions EIPS/eip-8038.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Upon activation of this EIP, the following parameters of the gas model are intro
| `ACCOUNT_WRITE` | Surcharge for when writing to an account changes one account leaf value for the first time | 6,700¹ | 8,000 | +19% | `*CALL` opcodes, `SELFDESTRUCT`, EOA delegations and ETH transfers |
| `COLD_STORAGE_ACCESS` | Cold touch of a storage slot | 2,100 | 3,000 | +43% | `SSTORE` and `SLOAD` |
| `STORAGE_WRITE` | Surcharge for when writing to a storage slot changes its value for the first time | 2,800² | 10,000 | +257% | `SSTORE` |
| `WARM_ACCESS` | Touch of an already-warm account or storage slot | 100 | 100 | +0% | `SSTORE`, `SLOAD`, `*CALL` opcodes, `BALANCE` and `EXT*` opcodes |
| `STORAGE_CLEAR_REFUND` | Refund for clearing a storage slot | 4,800 | 12,480 | +160% | `SSTORE` |
| `LOW_WARM_ACCESS` | Warm access cost for a read-only opcodes | - | 5 | - | `SLOAD`, `BALANCE` and `EXT*` opcodes |
| `WARM_ACCESS` | Warm access cost for opcodes includes processing the `REVERT` path. | 100 | 100 | +0% | `SSTORE`, `*CALL` opcodes, and `CREATE*` opcodes |
| `CREATE_ACCESS` | State access costs for contract deployment (include account cold access and account write) | 7,000³ | 11,000 | +57% | `CREATE`/ `CREATE2` |
| `ACCESS_LIST_STORAGE_KEY_COST` | Gas charged per storage key included in a transaction's access list | 1,900 | 3,000 | +58% | `SSTORE` and `SLOAD` |
| `ACCESS_LIST_ADDRESS_COST` | Gas charged per address included in a transaction's access list | 2,400 | 3,000 | +25% | `*CALL` opcodes, `BALANCE`, `SELFDESTRUCT` and `EXT*` opcodes |
Expand All @@ -45,40 +45,70 @@ Upon activation of this EIP, the following parameters of the gas model are intro

³: 7,000 = `GAS_CREATE` (32,000) - `GAS_NEW_ACCOUNT` (25,000)

### `SSTORE` pricing
### Introduce `state_access_gas_reservoir`

A new counter, `state_access_gas_reservoir`, is introduced to replace the refund counter removed by [EIP-7778](./eip-7778.md).

The counter is initialized to zero at the beginning of each transaction. Its behavior is analogous to `state_gas_reservoir` defined in [EIP-8037](./eip-8037.md). It accumulates all gas refills associated with valid state-access operations.

Any state-access charge (excluding the base cost of an opcode) MUST first consume gas from `state_access_gas_reservoir` before consuming gas from `gas_left`.

### Introduce `balance_updated` and `account_updated`

Two new global sets are introduced:

- `balance_updated`
- `account_updated`

`balance_updated` tracks accounts whose balances have been modified during the transaction. These updates are temporary.

`account_updated` tracks accounts that have undergone permanent state changes, including account creation and account state-root updates.

In addition, the `SSTORE` cost formula is updated to include the following independent costs:
These sets are global transaction-scoped data structures and operate similarly to existing sets such as `accessed_addresses`.

1. Access costs: `COLD_STORAGE_ACCESS` or `WARM_ACCESS`, depending on whether the storage slot is cold or warm.
2. Write cost: additionally charge `STORAGE_WRITE` if the new value is different from the present value and the present value equals the original value (First time change).
3. State creation cost: additionally charge `GAS_STORAGE_SET` (per [EIP-8037](./eip-8037.md)) if the original value is zero, the current value is zero, and the new value is non-zero.
The rules for inserting and removing addresses from these sets are defined by the opcode semantics specified below.

Refunds are also updated as follows. Each rule is an adjustment to the transaction's refund counter and is evaluated on every `SSTORE`:
### `SLOAD` pricing

1. `STORAGE_CLEAR_REFUND` is added if the original value is non-zero, the current value is non-zero and the new value is zero (a slot is cleared).
2. `STORAGE_CLEAR_REFUND` is subtracted if the original value is non-zero, the current value is zero and the new value is non-zero (a slot that was cleared earlier in the same transaction is restored). This reverses the refund granted by rule 1, so that clearing and then restoring a slot within the same transaction is never net-profitable.
3. `STORAGE_WRITE` is refunded if the new value equals the original value and differs from the current value (a first-time change made earlier in the same transaction is reset back to its original value).
The base cost of `SLOAD` is set to `LOW_WARM_ACCESS`.

If the accessed storage slot is cold, an additional `COLD_STORAGE_ACCESS` charge is applied.

### `BALANCE` and `EXTCODEHASH` pricing

The base cost of `BALANCE` and `EXTCODEHASH` is set to `LOW_WARM_ACCESS`.

If the accessed account is cold, an additional `COLD_ACCOUNT_ACCESS` charge is applied.

### `EXT*` family update

The base cost of `EXTCODESIZE` and `EXTCODECOPY` is set to 2 * `LOW_WARM_ACCESS`.

If the accessed account is cold, an additional `COLD_ACCOUNT_ACCESS` charge is applied.

### `SSTORE` pricing

Rules 2 and 3 mirror the refund accounting of [EIP-2200](./eip-2200.md) and [EIP-3529](./eip-3529.md) for slots that are modified more than once within a transaction: a `STORAGE_WRITE` charge or `STORAGE_CLEAR_REFUND` granted on an earlier `SSTORE` to the same slot is undone when a later `SSTORE` returns the slot to its original value. Without these reversals, a round trip such as `x → 0 → x` would yield a net refund even though the slot ends the transaction unchanged.
The base cost of `SSTORE` is set to `WARM_ACCESS`.

These refunds go to the transaction's refund counter and are capped to 20% of the total gas used by the transaction, as per the current refund mechanism.
If the accessed storage slot is cold, an additional `COLD_STORAGE_ACCESS` charge is applied.

The rules for state creation cost charges and refills are addressed in [EIP-8037](./eip-8037.md).
[EIP-7778](./eip-7778.md) removes the refund counter and therefore removes refunds associated with clearing storage slots.

Cases and their corresponding costs are detailed in the table below:
Transaction-internal refunds are instead redirected to:

| Original value | Current value | New value | Access status | Description | Regular-gas charges/refunds | State-gas charges/refills |
- `state_access_gas_reservoir` for state-access costs.
- `state_gas_reservoir` for state-creation costs.

The corresponding charging and refill rules are shown below:

| Original value | Current value | New value | Access status | Description | Regular-gas charges/refills | State-gas charges/refills |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| 0 | 0 | x | Cold | New slot | `COLD_STORAGE_ACCESS` + `STORAGE_WRITE` charged | `GAS_STORAGE_SET` charged |
| 0 | 0 | x | Cold | New slot | `WARM_ACCESS` + `COLD_STORAGE_ACCESS` + `STORAGE_WRITE` charged | `GAS_STORAGE_SET` charged |
| 0 | 0 | x | Warm | New slot | `WARM_ACCESS` + `STORAGE_WRITE` charged | `GAS_STORAGE_SET` charged |
| x | x | y | Cold | Existing slot, first change | `COLD_STORAGE_ACCESS` + `STORAGE_WRITE` charged | no state-gas adjustments |
| x | x | y | Cold | Existing slot, first change | `WARM_ACCESS` + `COLD_STORAGE_ACCESS` + `STORAGE_WRITE` charged | no state-gas adjustments |
| x | x | y | Warm | Existing slot, first change | `WARM_ACCESS` + `STORAGE_WRITE` charged | no state-gas adjustments |
| x | x | 0 | Cold | Cleared slot, non-zero at transaction start | `COLD_STORAGE_ACCESS` + `STORAGE_WRITE` charged, `STORAGE_CLEAR_REFUND` refunded | no state-gas adjustments |
| x | x | 0 | Warm | Cleared slot, non-zero at transaction start | `WARM_ACCESS` + `STORAGE_WRITE` charged, `STORAGE_CLEAR_REFUND` refunded | no state-gas adjustments |
| 0 | x | 0 | Warm | Set slot reset to zero (zero at transaction start) | `WARM_ACCESS` charged, `STORAGE_WRITE` refunded | `GAS_STORAGE_SET` refilled |
| x | y | x | Warm | Previously-modified slot reset to original value | `WARM_ACCESS` charged, `STORAGE_WRITE` refunded | no state-gas adjustments |
| x | 0 | x | Warm | Cleared slot restored to original value | `WARM_ACCESS` charged, `STORAGE_WRITE` refunded, `STORAGE_CLEAR_REFUND` reversed | no state-gas adjustments |
| x | 0 | y | Warm | Cleared slot written to a new non-zero value | `WARM_ACCESS` charged, `STORAGE_CLEAR_REFUND` reversed | no state-gas adjustments |
| 0 | x | 0 | Warm | Set slot reset to zero (zero at transaction start) | `WARM_ACCESS` charged, `STORAGE_WRITE` refilled | `GAS_STORAGE_SET` refilled |
| x | y | x | Warm | Previously-modified slot reset to original value | `WARM_ACCESS` charged, `STORAGE_WRITE` refilled | no state-gas adjustments |
| x | y | z | Warm | Previously-modified slot written to again | `WARM_ACCESS` charged | no state-gas adjustments |

### Account access pricing
Expand All @@ -87,34 +117,64 @@ The rules that determine when `COLD_ACCOUNT_ACCESS` and `WARM_ACCESS` are charge

#### `CALL`/`CALLCODE`

For `CALL` and `CALLCODE` operations, `CALL_VALUE` is set to `ACCOUNT_WRITE + CALL_STIPEND`, where `CALL_STIPEND` = 2,300.
The base cost of the `*CALL` family is set to `WARM_ACCESS`.

If the target account is cold, an additional `COLD_ACCOUNT_ACCESS` charge is applied.

During transaction execution, each account maintains the following values:

-`original_balance`: the account balance at the beginning of the transaction. This value remains constant throughout the transaction.
-`current_balance`: the current account balance.
-`new_balance`: the balance after the pending update.

For `CALL` and `CALLCODE`, `CALL_VALUE` is defined as `CALL_VALUE = ACCOUNT_WRITE + CALL_STIPEND` where `CALL_STIPEND` = 2300. This represents the maximum state-access charge for a value-transferring call. Upon entering the call frame, gas accounting is performed according to the table below:

| `original_balance` | `current_balance` | `new_balance` | `account_updated` | `balance_updated` update | Description | Regular-gas charges/ State-access-gas refills |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| x | x | y | false | added | First temporary update | no adjustments |
| x | x | y | true | unchanged | First permanent update | refilled `ACCOUNT_WRITE` |
| x | y | x | false | removed | Temporary reset | refilled 2 * `ACCOUNT_WRITE` |
| x | y | x | true | unchanged | Permanent reset | refilled `ACCOUNT_WRITE` |
| x | y | z | false | unchanged | Subsequent temporary update | refilled `ACCOUNT_WRITE` |
| x | y | z | true | unchanged | Subsequent permanent update | refilled `ACCOUNT_WRITE` |

#### `CREATE`/`CREATE2`

`CREATE` and `CREATE2` operations don't have explicit warm/cold pricing. Instead, they are charged `CREATE_ACCESS` in regular-gas and `GAS_NEW_ACCOUNT` in state-gas (per [EIP-8037](./eip-8037.md)). `CREATE_ACCESS` is defined as `ACCOUNT_WRITE` + `COLD_STORAGE_ACCESS`.
The base cost of the `CREATE*` family is set to `WARM_ACCESS`.

This charge is applied uniformly to operations that require `REVERT`-path handling.

`CREATE` and `CREATE2` do not use explicit warm/cold pricing. Instead, they charge:

- `CREATE_ACCESS` in regular gas.
- `GAS_NEW_ACCOUNT` in state gas, as specified by [EIP-8037](./eip-8037.md).

`CREATE_ACCESS` is defined as `CREATE_ACCESS = ACCOUNT_WRITE + COLD_ACCOUNT_ACCESS`. This represents the maximum state-access charge associated with account creation.

Note: this definition does not exactly match the legacy decomposition shown in footnote ³ of the Parameters table (`GAS_CREATE` - `GAS_NEW_ACCOUNT` = 7,000). The pre-existing accounting in the protocol is not internally consistent here, and this EIP keeps the discrepancy rather than attempting to reconcile it.

#### `SELFDESTRUCT`
When entering a creation frame, refund accounting is performed if the target account was previously updated before becoming the destination of the creation operation (for example, an account that received ETH before contract creation).

For `SELFDESTRUCT`, an additional charge of `ACCOUNT_WRITE` is added if a positive balance is sent to an empty account.
The account MUST be permanently inserted into `account_updated`.

If the account exists in `balance_updated`, `ACCOUNT_WRITE` MUST be refilled into `state_access_gas_reservoir`, and the account MUST be removed from `balance_updated`.

#### `EXT*` family update
#### `SELFDESTRUCT`

Besides the current access costs, `EXTCODESIZE` and `EXTCODECOPY` are charged an additional `WARM_ACCESS`.
For `SELFDESTRUCT`, an additional charge of `ACCOUNT_WRITE` is added if a positive balance is sent to an empty account.

#### Overview

The cases for account-related operations and their corresponding costs are summarized in the table below:

| Operation | Access charge | `ACCOUNT_WRITE` charged | Additional charge |
| :--- | :---: | :--- | :--- |
| `CALL` / `CALLCODE` | `COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` | when value is transferred (via `CALL_VALUE` = `ACCOUNT_WRITE` + `CALL_STIPEND`) | — |
| `DELEGATECALL` / `STATICCALL` | `COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` | never (no value transfer) | — |
| `CREATE` / `CREATE2` | `CREATE_ACCESS` (= `ACCOUNT_WRITE` + `COLD_STORAGE_ACCESS`) | always (folded into `CREATE_ACCESS`) | `GAS_NEW_ACCOUNT` in state-gas (per [EIP-8037](./eip-8037.md)) |
| `CALL` / `CALLCODE` | `WARM_ACCESS + COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` | when value is transferred (via `CALL_VALUE` = `ACCOUNT_WRITE` + `CALL_STIPEND`) | — |
| `DELEGATECALL` / `STATICCALL` | `WARM_ACCESS + COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` | never (no value transfer) | — |
| `CREATE` / `CREATE2` | `WARM_ACCESS + CREATE_ACCESS` (= `WARM_ACCESS + ACCOUNT_WRITE` + `COLD_STORAGE_ACCESS`) | always (folded into `CREATE_ACCESS`) | `GAS_NEW_ACCOUNT` in state-gas (per [EIP-8037](./eip-8037.md)) |
| `SELFDESTRUCT` | `COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` | when a positive balance is sent to an empty account | — |
| `BALANCE` / `EXTCODEHASH` | `COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` | never | — |
| `EXTCODESIZE` / `EXTCODECOPY` | `COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` | never | `WARM_ACCESS` (second database read) |
| `BALANCE` / `EXTCODEHASH` | `LOW_WARM_ACCESS + COLD_ACCOUNT_ACCESS` or `LOW_WARM_ACCESS` | never | — |
| `EXTCODESIZE` / `EXTCODECOPY` | `LOW_WARM_ACCESS + COLD_ACCOUNT_ACCESS` or `LOW_WARM_ACCESS` | never | `LOW_WARM_ACCESS` (second database read) |
| EOA delegation ([EIP-7702](./eip-7702.md)) | `COLD_ACCOUNT_ACCESS` or `WARM_ACCESS` per authority | per authorization (intrinsic cost) | see authorizations table below |

### [EIP-7702](./eip-7702.md) authorizations pricing
Expand Down
Loading