Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions EIPS/eip-8037.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,11 @@ intrinsic_gas = intrinsic_regular_gas + intrinsic_state_gas
execution_gas = tx.gas - intrinsic_gas
regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic_regular_gas
gas_left = min(regular_gas_budget, execution_gas)
state_gas_reservoir = execution_gas - gas_left
state_gas_reservoir = execution_gas - gas_left + create_state_gas
```

Here `create_state_gas` is the contract-creation component of `intrinsic_state_gas` (`STATE_BYTES_PER_NEW_ACCOUNT × CPSB` for contract-creation transactions, `0` otherwise). It is counted in `intrinsic_gas` for transaction validation, but it is seeded into the reservoir rather than pre-consumed: the corresponding charge is applied conditionally at execution time (see [Gas accounting for new accounts](#gas-accounting-for-new-accounts)).

This means that the `state_gas_reservoir` holds gas that exceeds [EIP-7825](./eip-7825.md)'s budget. Additionally, three new counters are introduced:

- `execution_regular_gas_used` encodes the total regular-gas used by the transaction, and it is initialized as `execution_regular_gas_used = 0`.
Expand Down Expand Up @@ -140,18 +142,19 @@ Here, an account is *existent* if it already has a leaf in the state trie, i.e.,

In particular, an address that holds a balance but has no code and a zero nonce is existent and is a valid `CREATE`/`CREATE2` target under [EIP-684](./eip-684.md). Creating a contract at such an address updates the existing leaf rather than adding a new one, so only the code-deposit cost (`L × CPSB`) applies and the `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` account-creation charge does not.

State-gas accounting for `CALL*` and `CREATE`/`CREATE2` operations is performed right before the respective call frame, but the two families differ in *whether* the account-creation charge is conditional. This distinction is required to remain compatible with [EIP-7928](./eip-7928.md) (see [Account-creation charging and EIP-7928](#account-creation-charging-and-eip-7928)).
State-gas accounting for `CALL*` and `CREATE`/`CREATE2` operations is performed right before the respective call frame. In both families the `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` account-creation charge is applied *conditionally*: only when the operation is about to create a new account. The families differ only in *when* the destination account may be read, which [EIP-7928](./eip-7928.md) constrains (see [Account-creation charging and EIP-7928](#account-creation-charging-and-eip-7928)).

**`CALL*` family (conditional charge).** The `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` account-creation charge is applied *conditionally* right before entering the child frame: only when the target account does not exist and a positive value is transferred. If the operation is unsuccessful before entering the call frame (e.g., due to insufficient balance or due to the stack depth), or if the child frame reverts or halts exceptionally, the charged state-gas is refilled in LIFO order and `execution_state_gas_used` decreases by the same amount.

**`CREATE`/`CREATE2` family (unconditional charge with refill).** Deciding whether the deployment target pre-exists requires reading the computed address, which [EIP-7928](./eip-7928.md) defers until the EVM actually accesses it. To avoid that read before the frame is entered, the `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` account-creation portion is charged *unconditionally* in the parent frame right before entering the create frame, as if the target were a new account. Once the create frame exits, this portion is refilled in LIFO order (and `execution_state_gas_used` decreases by the same amount) if either:
**`CREATE`/`CREATE2` family (conditional charge).** [EIP-7928](./eip-7928.md) defers reading the computed destination address until creation has passed the checks that precede any state access: sender balance for the endowment, nonce overflow, and the call-stack depth limit. A creation failing those checks never reads the destination, and no account-creation state-gas is charged. Once the checks pass, the destination is accessed — a single read that also performs the [EIP-7610](./eip-7610.md) collision check:

- the destination account was pre-existing (so no new account leaf is created, per the existence rule above), or
- the create operation failed (reverted, halted exceptionally, encountered an address collision, or was unsuccessful before entering the frame, e.g., due to insufficient balance, nonce overflow, or the call-stack depth limit).
- If the destination is non-existent (per the existence rule above), `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` is charged in the creating frame, before 63/64ths of the remaining gas is forwarded: a portion drawn from `gas_left` reduces the gas forwarded to the create frame, and a failed charge exceptionally halts the creating frame — exactly as for `CALL*`. If the destination pre-exists (e.g., it holds a balance), nothing is charged. The charge is decided by existence alone, independently of the collision outcome below.
- On an address collision ([EIP-7610](./eip-7610.md)) the creation fails as an exceptional halt of the create frame, consuming the forwarded gas. A colliding destination is usually existent (non-zero nonce or non-empty code) and therefore was not charged; the one exception is a destination that collides on storage alone while having zero nonce, zero balance, and empty code — it is non-existent, so it was charged, and the charge is refilled by the failure rule below.
- If the create frame reverts or halts exceptionally (including an address collision and a failed code deposit), the account creation is rolled back and any charged account-creation state-gas is refilled in LIFO order, decreasing `execution_state_gas_used` by the same amount — exactly as for `CALL*`.

The code-deposit portion (`L × CPSB`) is unaffected by this timing rule and is charged at code-deposit time as usual.
The code-deposit portion (`L × CPSB`) is unaffected by this rule and is charged at code-deposit time as usual.

The same unconditional-charge-with-refill model applies to top-level contract-creation transactions. The `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` portion of `intrinsic_state_gas` is charged unconditionally and refilled in LIFO order (with `execution_state_gas_used` decreased by the same amount) if the destination account was pre-existing or if the transaction reverts or halts.
The same conditional rule applies to top-level contract-creation transactions. The `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` create component of `intrinsic_state_gas` participates in transaction validation unchanged — the transaction must be able to afford the worst case without any state access — but it is not pre-consumed: it is seeded into `state_gas_reservoir` (see the reservoir model above), and the charge is applied when transaction setup accesses the deployment address (an access [EIP-7928](./eip-7928.md) always records), only if the destination does not already exist. The seeding guarantees the charge can always be covered, so transaction validity is unaffected. If the transaction reverts or halts (including an address collision), any charged portion is refilled by the rules below, and the unspent reservoir is returned to the sender by the normal end-of-transaction settlement.

Charges for account creation with `SELFDESTRUCT` are charged at the point where it executes.

Expand All @@ -171,7 +174,7 @@ When a child frame **reverts**, all of its state changes are rolled back and the

When a child frame **halts exceptionally**, the same refill is applied, but the child's `gas_left` is consumed (set to zero) rather than returned. Because the portion refilled to `gas_left` is part of the consumed `gas_left`, only the portion refilled to `state_gas_reservoir` survives — which is exactly the reservoir's value at the start of the child frame. No separate reservoir-reset rule is therefore required: refilling in LIFO order makes the reservoir whole automatically, while the regular `gas_left` the parent forwarded to the child is consumed in full as usual.

The same rules apply when the top-level call frame reverts or halts, treating the frame as a child of the transaction boundary. In particular, for address collisions in contract-creation transactions, `gas_left` is consumed in full, `execution_regular_gas_used` is incremented by the initial `gas_left`, and the new-account portion of `intrinsic_state_gas` is refilled per the rule above.
The same rules apply when the top-level call frame reverts or halts, treating the frame as a child of the transaction boundary. In particular, for address collisions in contract-creation transactions, `gas_left` is consumed in full and `execution_regular_gas_used` is incremented by the initial `gas_left`; any account-creation state-gas charged for the destination (possible only for a storage-only collision, since a destination with nonce, code, or balance is existent) is refilled by the rules above, and the `state_gas_reservoir` is returned to the sender by the normal end-of-transaction settlement.

##### Gas accounting for [EIP-7702](./eip-7702.md) authorizations

Expand Down Expand Up @@ -222,7 +225,7 @@ The refund cap remains at 20% of gas used. `tx_gas_used` applies the [EIP-7623](
At block level, instead of tracking a single `gas_used` counter, we keep track of two counters, one for `state-gas` and one for `regular-gas`:

```python
tx_state_gas = intrinsic_state_gas + tx_output.execution_state_gas_used
tx_state_gas = intrinsic_state_gas - create_state_gas + tx_output.execution_state_gas_used
tx_regular_gas = tx_gas_used_after_refund - tx_state_gas

block_output.block_regular_gas_used += tx_regular_gas
Expand Down Expand Up @@ -254,7 +257,7 @@ gas_used_delta = parent.gas_used - parent.gas_target
[EIP-7778](./eip-7778.md) excludes transactions refunds from the block gas accounting. If implemented together with [EIP-7778](./eip-7778.md), the block level would be updated to:

```python
tx_state_gas = intrinsic_state_gas + tx_output.execution_state_gas_used
tx_state_gas = intrinsic_state_gas - create_state_gas + tx_output.execution_state_gas_used
tx_regular_gas = tx_gas_used_before_refund - tx_state_gas

block_output.block_regular_gas_used += tx_regular_gas
Expand Down Expand Up @@ -402,11 +405,14 @@ Another advantage of metering contract creation separately is that increasing th

[EIP-7928](./eip-7928.md) (Block-Level Access Lists) constrains *when* the target address of `CREATE`/`CREATE2` may be accessed: only once the EVM reaches the computed address (on post-access out-of-gas, on creation collision, or when creation setup increments the new account's nonce), and not on address computation alone, nor when creation fails before access (e.g., insufficient sender balance for the endowment, nonce overflow, or the call-stack depth limit).

This conflicts with charging the account-creation state cost *conditionally* before the create frame, because deciding whether to charge `STATE_BYTES_PER_NEW_ACCOUNT × CPSB` requires reading whether the target already exists — exactly the access [EIP-7928](./eip-7928.md) defers. This EIP resolves the conflict by charging the account-creation portion unconditionally before the create frame and refilling it on frame exit when the destination pre-existed or when creation failed. The rationale for this choice over the alternatives:
The conditional account-creation charge is compatible with this by construction. The existence read that decides the charge is the *same single access* that performs the [EIP-7610](./eip-7610.md) collision check, and it happens after the pre-access failure checks — exactly the access [EIP-7928](./eip-7928.md) already sanctions and records. Creations that fail before access charge nothing and appear nowhere in the block access list; creations that reach the access point are recorded and pay exactly for the state they are about to create. The recorded access set is identical to that of an unconditional-charge design: the charge decision introduces no additional read.

An earlier revision of this EIP instead charged the account-creation portion unconditionally before the create frame (before the balance, nonce, and depth checks) and refilled it on frame exit when the destination pre-existed or creation failed. The conditional model was adopted because it removes the pre-existing-destination refill and the pre-access-failure refills (nothing was charged), aligns `CREATE`/`CREATE2` with the `CALL*` family (state-dependent charges are applied when the state is read, conditionally on what is actually created), and avoids temporarily under-budgeting the create frame of a deployment onto a pre-existing account (e.g., a pre-funded deterministic-deployment address). The remaining refill — on failure of the create frame — is shared with `CALL*`. At the transaction level the worst-case charge stays in `intrinsic_state_gas`, so transaction validity remains decidable without any state access; only the moment the gas is actually consumed changes.

Placement of the charge among the remaining alternatives:

- **Why not charge conditionally?** It would require the deferred existence check before the frame. Pre-existing deployment destinations are also rare, so deployers should generally budget for account creation; treating the pre-existing case as a refill (rather than a saved charge) matches that expectation.
- **Why not charge after the create frame exits?** Up to 63/64 of the parent's regular gas is forwarded to the child, and deployment code may legitimately consume nearly all of it. Deferring the charge until the frame returns could make an otherwise-successful deployment fail with out-of-gas at frame exit, introducing semantic uncertainty.
- **Why not charge inside the create frame?** That would silently move the out-of-gas condition from the parent frame (the current behavior) to the child frame, a behavior change that could break existing on-chain contracts.
- **Why not charge inside the create frame?** That would silently move the out-of-gas condition from the parent frame (the pre-existing behavior for account-creation charges) to the child frame. Charging in the creating frame, right after the destination access and before the 63/64ths split, keeps the out-of-gas locus and the forwarded-gas computation identical to the `CALL*` family.

For the `CALL*` family the charge remains conditional and is unchanged: the callee is already resolved (including [EIP-7702](./eip-7702.md) delegation resolution) before the child frame, so there is no deferred-access conflict, and unconditionally pre-charging every call would create significant compatibility concerns (e.g., Solidity's `address.transfer()` forwards no gas to the child frame).

Expand Down
Loading