Skip to content

Latest commit

 

History

History
354 lines (247 loc) · 25.3 KB

File metadata and controls

354 lines (247 loc) · 25.3 KB
id TIP-1016
title Exempt Storage Creation from Gas Limits
description Storage creation gas costs are charged but don't count against transaction or block gas limits, using a reservoir model aligned with EIP-8037 for correct GAS opcode semantics and EVM compatibility.
authors Dankrad Feist @dankrad
status Backlog
related TIP-1000, TIP-1010, EIP-8037, EIP-8011, EIP-7825, EIP-7623
protocolVersion TBD

TIP-1016: Exempt Storage Creation from Gas Limits

Abstract

Storage creation operations (new state elements, account creation, contract code storage) continue to consume and be charged for gas, this gas does not count against block gas limit but it is capped by max tx gas limit EIP-7825. Gas accounting uses a reservoir model (aligned with EIP-8037) that splits gas into regular and reservoir gas, ensuring the GAS opcode accurately reflects the regular execution budget. This allows increasing contract code pricing to 2,500 gas/byte without preventing large contract deployments, and prevents new account creation from reducing effective throughput.

Motivation

TIP-1000 increased storage creation costs to 250,000 gas per operation and 1,000 gas/byte for contract code. This created two problems:

  1. Contract deployment constraints: 24KB contracts require ~26M gas, forcing us to:

    • Keep transaction gas cap at 30M (would prefer 16M)
    • Keep general gas limit at 30M (would prefer lower)
    • Limit contract code to 1,000 gas/byte (would prefer 2,500)
  2. New account throughput penalty: TIP-20 transfer to new address costs ~300,000 gas total (~70k regular + 230k state) vs ~50,000 gas to existing. At 500M payment lane gas limit:

    • Without exemption (single dimension): only ~1,700 new account transfers/block = ~3,400 TPS
    • With reservoir model (block limits apply to regular gas only): ~7,150 new account transfers/block = ~14,300 TPS
    • Existing account transfers: ~10,000 transfers/block = ~20,000 TPS
    • ~4x throughput improvement for new accounts by exempting state gas from block limits

The root cause: state gas counts against limits designed for execution time constraints. Storage creation is permanent (disk) not ephemeral (CPU), and shouldn't be bounded by per-block execution limits.

Why a reservoir model

Simply exempting state gas from protocol limits without changing EVM internals creates two problems:

  1. GAS opcode inaccuracy: The GAS opcode would return remaining gas from tx.gas minus all gas consumed (regular + state), which doesn't reflect the actual regular gas budget. A transaction with a high gas limit that has used 15.9M regular gas with a 16M EIP-7825 per-transaction gas limit would see GAS report millions of gas remaining, but OOG after just ~100k more regular gas.

  2. Broken gas patterns: Contracts relying on gasleft() for loop guards, subcall gas forwarding (63/64 rule), and relay/meta-transaction patterns would see incorrect values, potentially leading to unexpected OOG reverts.

The reservoir model (from EIP-8037) solves this by maintaining three internal counters:

  • regular remaining gas is reflecting execution budget, used by cpu and state creation. Returned by GAS opcode.
  • reservoir is holding overflow can be only be used for state creation
  • state_gas is tracking cumulative state gas consumed during execution.

Specification

Gas Dimensions

All operations consume gas in two dimensions:

  • Regular gas (regular_gas): Compute, memory, calldata, and the computational cost of storage operations (writing, hashing). This is the execution-time resource.

  • State gas (state_gas): The permanent storage burden of state creation operations. This is the long-term state growth resource.

At the transaction level, the user pays for both. At the block level, only regular gas counts toward block and EIP-7825 max transaction gas limits; state gas is exempt.

Storage Gas Operations

Storage creation operations split their cost between regular gas (computational overhead) and state gas (permanent storage burden):

Operation Execution Gas Storage Gas Total
Cold SSTORE (zero → non-zero) 22,200 230,000 252,200
Hot SSTORE (non-zero → non-zero) 2,900 0 2,900
Account creation (nonce 0 → 1) 25,000 225,000 250,000
Contract code storage (per byte) 200 2,300 2,500
Contract creation (fixed upfront cost) 32,000 468,000 500,000
EIP-7702 delegation (per auth) 25,000 225,000 250,000

For zero-to-non-zero SSTORE, Tempo keeps revm's decomposed Berlin accounting: GAS_WARM_ACCESS (100) plus sstore_set_without_load_cost (20,000), for a 20,100 regular-gas write path. When the slot is cold, the existing Berlin cold-slot access charge (GAS_COLD_SLOAD = 2,100) is retained on top of that write component, for a total of 22,200 regular gas before state gas.

EIP-7702 Delegation Pricing

Each EIP-7702 authorization writes a 23-byte delegation designator (0xef0100 || address) to the authority account's code field. This is permanent state: redelegation overwrites the account's code pointer but the old code entry persists in the code database.

The base cost per authorization is 25,000 regular gas + 225,000 state gas = 250,000 total, matching account creation. This reverts the TIP-1000 reduction to 12,500 gas per authorization.

For authorizations where auth.nonce == 0 (new account), the account creation cost (25,000 regular + 225,000 state) applies in addition to the delegation cost, for a total of 500,000 gas.

Keychain Authorization Pricing

Keychain authorize_key is charged as intrinsic gas (T1B+). The SSTORE components use the same regular/state split as standard EVM SSTOREs:

Component Regular Gas State Gas Notes
Signature verification 3,000+ 0 ecrecover + P256/WebAuthn if applicable
Existing key check (SLOAD) 2,100 0 Cold SLOAD
Key slot write (SSTORE) 20,000 230,000 Zero-to-non-zero write component only; cold-slot access charged separately
Per spending limit (SSTORE × N) 20,000 × N 230,000 × N Zero-to-non-zero write component only per token limit; cold-slot access charged separately
Buffer (TSTORE, keccak, event) 2,000 0 Computational overhead

Total per authorization: ~27,100 + 20,000 × N regular gas, 230,000 × (1 + N) state gas.

The table above isolates the write component itself. Any first access to a cold storage slot still incurs the standard Berlin cold-access charge separately.

Precompile and Intrinsic Storage Operations

The regular/state gas split applies uniformly to all SSTORE and code deposit operations regardless of call site. Precompile storage operations route through the same path as standard EVM SSTOREs and inherit the split automatically. Intrinsic gas charges that include SSTORE costs (e.g. keychain authorization) use the same split.

Opcode-level CREATE/CREATE2 follows the deployment flow above, including HASH_COST(L) for deployed bytecode.

Exception: Expiring nonce writes (TIP-1009) use WARM_SSTORE_RESET (2,900 gas) with zero state gas because they are ephemeral — entries are evicted from a fixed-size circular buffer and do not contribute to permanent state growth.

Notes:

  • Regular gas reflects computational cost (writing, hashing) and counts toward protocol limits
  • State gas reflects permanent storage burden and does NOT count toward protocol limits
  • All gas (regular + state) counts toward user's gas_limit and is charged at base_fee_per_gas
  • All other operations (non-state-creating) are charged entirely as regular gas
  • Regular gas is set to at least the pre-TIP-1000 (standard EVM) cost for each operation, ensuring that exempting state gas from limits never makes an operation cheaper against protocol limits than it was before TIP-1000

Transaction Validation

Before transaction execution, calculate_intrinsic_cost returns three values:

  • intrinsic_regular_gas: Base transaction cost, calldata, access lists, and other non-state-creating intrinsic costs
  • intrinsic_state_gas: State gas components of intrinsic cost (e.g., account creation for contract deployment transactions)
  • calldata_floor_gas_cost: The EIP-7623 calldata floor, defined as TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata + 21000

validate_transaction rejects transactions where:

tx.gas < intrinsic_regular_gas + intrinsic_state_gas

or where:

max(intrinsic_regular_gas, calldata_floor_gas_cost) > max_transaction_gas_limit

The max ensures that calldata-heavy transactions cannot pass validation when their floor cost exceeds the per-transaction regular gas limit. The calldata floor is a regular gas concept — it does not interact with intrinsic_state_gas or state_gas_reservoir.

validate_transaction also returns intrinsic_regular_gas, intrinsic_state_gas, and calldata_floor_gas_cost.

Transaction-Level Gas Accounting (Reservoir Model)

Since transactions have a single gas limit parameter (tx.gas), gas accounting is enforced through a reservoir model, in which gas_left and state_gas_reservoir are initialized as follows:

intrinsic_gas = intrinsic_regular_gas + intrinsic_state_gas
execution_gas = tx.gas - intrinsic_gas
regular_gas_budget = max_transaction_gas_limit - intrinsic_regular_gas
gas_left = min(regular_gas_budget, execution_gas)
state_gas_reservoir = execution_gas - gas_left

The state_gas_reservoir holds gas that exceeds the per-transaction regular gas budget (max_transaction_gas_limit, per EIP-7825). The two counters operate as follows:

  • Regular gas charges deduct from gas_left only.
  • State gas charges deduct from state_gas_reservoir first; when the reservoir is exhausted, from gas_left.
  • When an opcode requires both regular and state gas, the regular gas charge MUST be applied first. If the regular gas charge triggers an out-of-gas error, the state gas charge is not applied.
  • The GAS opcode returns gas_left only (excluding the reservoir).
  • The reservoir is passed in full to child frames (no 63/64 rule). On child success, the remaining state_gas_reservoir is returned to the parent.
  • On child revert or exceptional halt, all state gas consumed by the child, both from the reservoir and any that spilled into gas_left, is restored to the parent's reservoir. On child exceptional halt, only gas_left is consumed (zeroed). State gas is fully preserved on failure because state changes are reverted, so no state was actually grown.
    • Note: State gas that originally spilled from the reservoir into gas_left is restored as reservoir gas, not as gas_left. A child frame that performs cold SSTOREs drawing from gas_left (because the reservoir was exhausted) and then reverts will return that gas to the parent's reservoir, where it can only be used for future state operations — not for regular execution. This is a known consequence of the EIP-8037 design that avoids tracking the original source of state gas charges per frame. The effect is bounded: it can only convert gas_left that was spent on state operations into reservoir gas, and only on child failure paths.
  • On exceptional halt, remaining gas_left is attributed to execution_regular_gas_used and set to zero (all regular gas consumed), consistent with existing EVM out-of-gas semantics. The state_gas_reservoir is not consumed — it is returned to the parent frame or preserved at the top level, consistent with the principle that state gas pays for long-term state growth which does not occur on failure.
  • System transactions are not subject to the max_transaction_gas_limit cap; their entire execution_gas is placed in gas_left with state_gas_reservoir = 0.

The two counters are returned by the transaction output. Besides the two counters, the EVM also keeps track of execution_state_gas_used and execution_regular_gas_used during block execution. state_gas costs are added to execution_state_gas_used while regular_gas costs are added to execution_regular_gas_used. These two counters are also returned by the transaction output.

Transaction Gas Used

At the end of transaction execution, the gas used before and after refunds is defined as:

tx_gas_used_before_refund = tx.gas - tx_output.gas_left - tx_output.state_gas_reservoir
tx_gas_refund = min(tx_gas_used_before_refund // 5, tx_output.refund_counter)
tx_gas_used_after_refund = max(
    tx_gas_used_before_refund - tx_gas_refund,
    calldata_floor_gas_cost
)

The refund cap remains at 20% of gas used. The max with calldata_floor_gas_cost (EIP-7623) ensures the user always pays at least the calldata floor, even if refunds would bring the total below it. Refunds apply only to user-paid gas; block-level accounting uses tx_regular_gas (regular gas only, no refund subtracted) — see Block-Level Gas Accounting.

Note: EIP-8037 uses tx_gas_used in the refund and post-refund formulas, but that variable is not defined in the same code block. TIP-1016 uses tx_gas_used_before_refund consistently to avoid ambiguity.

Block-Level Gas Accounting

At block level, only regular gas counts toward block gas limits. State gas is exempt — it is not tracked at the block level and does not constrain block capacity.

tx_regular_gas = intrinsic_regular_gas + tx_output.execution_regular_gas_used

block_output.block_regular_gas_used += max(tx_regular_gas, calldata_floor_gas_cost)

The max with calldata_floor_gas_cost (EIP-7623) ensures calldata-heavy transactions consume at least the floor cost worth of block capacity. The floor applies to regular gas only — state gas remains fully exempt from block limits.

Per EIP-7778, tx_regular_gas is the pre-refund value: tx_gas_refund is not subtracted from block accounting. This prevents block gas limit circumvention via refundable operations while preserving user incentives to clean up state.

The block header gas_used field is set to:

gas_used = block_output.block_regular_gas_used

The block validity condition uses this value:

assert gas_used <= block.gas_limit, 'invalid block: too much gas used'

The base fee update rule uses this same value:

gas_used_delta = parent.gas_used - parent.gas_target

Note: Tempo has two block limits — general gas limit (~25M) for contracts and payment lane limit (500M) for simple transfers. In both lanes, only regular gas counts toward the limit; state gas is exempt.

Divergence from EIP-8037: EIP-8037 uses a bottleneck model where gas_used = max(block_regular_gas, block_state_gas), effectively capping state gas at the block gas limit. TIP-1016 instead exempts state gas entirely from block limits, relying on fixed high prices (250,000 gas per state element) as the economic deterrent for state growth.

SSTORE Refund for Slot Restoration

When a storage slot is set to a non-zero value and then restored to zero within the same transaction (0→X→0 pattern), the following are refunded via refund_counter:

  • State gas: 230,000 (the full state creation charge; EIP-8037 equivalent: 32 × cost_per_state_byte)
  • Regular gas: GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - GAS_WARM_ACCESS (EIP-8037 equivalent: 2,800; Tempo: 20,000 − 2,100 − 100 = 17,800)

The refund mechanism is identical to EIP-8037. The numeric values differ because Tempo uses fixed pricing (see Storage Gas Operations table) rather than EIP-8037's dynamic cost_per_state_byte. The net cost after refund is GAS_WARM_ACCESS (100), consistent with pre-EIP-8037 SSTORE restoration behavior. Refunds use refund_counter rather than direct gas accounting decrements, so that reverted frames do not benefit from the refund.

Revert Behavior for State Gas

State gas charged for account creation (CREATE, CALL to new account, and EOA delegation) is consumed even if the frame reverts — state changes are rolled back but gas is not refunded. This is consistent with pre-EIP-8037 behavior where GAS_NEW_ACCOUNT was consumed on revert.

This is achieved structurally: GAS_NEW_ACCOUNT state gas is charged in the parent frame before creating the child frame. On child revert, handle_reservoir_remaining_gas restores only the child's state_gas_spent to the parent's reservoir — the parent's prior charge is preserved. Similarly, GAS_CREATE state gas for contract deployment is charged in the parent before the child initcode runs.

Receipt Semantics

Receipt cumulative_gas_used tracks the cumulative sum of tx_gas_used_after_refund (post-refund, post-floor) across transactions. This means receipt[i].cumulative_gas_used - receipt[i-1].cumulative_gas_used equals the gas paid by transaction i.

Contract Creation Pricing

Contract code storage cost increases from 1,000 to 2,500 gas/byte (200 regular + 2,300 state).

Contract Deployment Cost Calculation

When a contract creation transaction or opcode (CREATE/CREATE2) is executed, gas is charged differently based on whether the deployment succeeds or fails. Given bytecode B (length L) returned by initcode and H = keccak256(B):

When opcode execution starts: Always charge GAS_CREATE (Tempo: 32,000 regular + 468,000 state; EIP-8037: 9,000 regular + 112 × cpsb state)

During initcode execution: Charge the actual gas consumed by the initcode execution

Success path (no error, not reverted, and L ≤ MAX_CODE_SIZE):

  • Charge GAS_CODE_DEPOSIT * L (200 regular + 2,300 state per byte) and persist B under H, then link codeHash to H
  • Charge HASH_COST(L) where HASH_COST(L) = 6 × ceil(L / 32) to compute H

Failure paths (REVERT, OOG/invalid during initcode, OOG during code deposit, or L > MAX_CODE_SIZE):

  • Do NOT charge GAS_CODE_DEPOSIT * L or HASH_COST(L)
  • No code is stored; no codeHash is linked to the account
  • The account remains unchanged or non-existent

This is aligned with EIP-8037's deployment flow, where GAS_CODE_DEPOSIT is charged only on the success path.

Example: 24KB Contract Deployment

Operation Regular State gas
Contract code 24,576 × 200 = 4,915,200 24,576 × 2,300 = 56,524,800
Contract fixed upfront 32,000 468,000
Deployment logic ~2M 0
---------- --------- ----------
Totals: ~7M (counts toward protocol limits via gas_left) ~57M (served from state_gas_reservoir, doesn't count toward protocol limits)

Total gas: ~64M (user must authorize with gas_limit >= 64M)

Can deploy with protocol max_transaction_gas_limit = 16M (only ~7M regular gas counts)

Examples

TIP-20 Transfer to New Address

  • Transfer logic: ~50,000 regular gas
  • New balance slot: 20,000 regular gas + 230,000 state gas
  • Total: ~70,000 regular gas + 230,000 state gas = ~300,000 gas
  • User must authorize: gas_limit >= 300,000
  • Counts toward block limit: ~70,000 regular gas
  • Reservoir initialization (assuming max_transaction_gas_limit = 16M):
    • intrinsic_gas = intrinsic_regular + intrinsic_state ≈ 21,000 + 0 = 21,000
    • execution_gas = 300,000 - 21,000 = 279,000
    • regular_gas_budget = 16M - 21,000 ≈ 15,979,000
    • gas_left = min(15,979,000, 279,000) = 279,000
    • state_gas_reservoir = 279,000 - 279,000 = 0
    • Since total < max_transaction_gas_limit, all gas fits in gas_left; state gas draws from gas_left
  • GAS opcode accurately reflects execution budget (~279,000 before execution)
  • Block accounting: adds ~70,000 to block_regular_gas_used (state gas is exempt from block limits)
  • Total cost: ~300,000 gas

TIP-20 Transfer to Existing Address

  • Transfer logic: ~50,000 regular gas
  • Update existing slot: included in transfer logic
  • Total: ~50,000 regular gas
  • User must authorize: gas_limit >= 50,000
  • Counts toward block limit: ~50,000 regular gas
  • Total cost: ~50,000 gas

Block Throughput

At 500M payment lane gas limit (only regular gas counts toward block limits):

  • New account transfers: ~70k regular gas each → ~7,150 transfers/block ≈ 14,300 TPS
  • Existing account transfers: ~50k regular gas each → ~10,000 transfers/block ≈ 20,000 TPS
  • Mixed workload: Only regular gas constrains capacity. A block can contain any mix of new and existing transfers as long as total regular gas ≤ 500M. State gas doesn't reduce block capacity.
  • vs TIP-1000: ~7,150 new account transfers/block vs ~1,700 without exemption (~4x improvement)

Invariants

  1. User Authorization: Total gas used (regular + state) MUST NOT exceed transaction.gas_limit (prevents surprise costs)
  2. Protocol Transaction Limit: Regular gas (via gas_left) MUST NOT exceed max_transaction_gas_limit (EIP-7825 limit, e.g. 16M)
  3. Protocol Block Limits: Block regular_gas MUST NOT exceed applicable limit:
    • General transactions: general_gas_limit (25M target, currently 30M)
    • Payment lane transactions: payment_lane_limit (500M)
  4. State Gas Exemption: State gas MUST NOT count toward protocol limits (transaction or block). State gas is uncapped at the block level.
  5. Reservoir Model: Gas accounting MUST use the reservoir model — gas_left and state_gas_reservoir initialized from tx.gas, with state gas drawing from reservoir first
  6. GAS Opcode: The GAS opcode MUST return gas_left only (excluding state_gas_reservoir)
  7. Reservoir Passing: The state_gas_reservoir MUST be passed in full to child frames (no 63/64 rule). Unused reservoir MUST be returned to parent on child completion
  8. Exceptional Halt: On exceptional halt, gas_left MUST be set to zero; state_gas_reservoir MUST be preserved (returned to parent or kept for refund)
  9. Regular Gas Component: Storage creation operations MUST charge regular gas for computational overhead (writing, hashing)
  10. Total Cost: Transaction cost MUST equal (regular_gas + state_gas) × (base_fee_per_gas + priority_fee)
  11. Gas Split: Storage creation operations MUST split cost into regular gas (computational) and state gas (permanent burden)
  12. Hot vs Cold: Hot SSTORE (non-zero → non-zero) has NO state gas component; cold SSTORE (zero → non-zero) has both
  13. Refund via Counter: SSTORE slot restoration refunds MUST use refund_counter, not direct gas decrements
  14. Revert Behavior: On child revert or exceptional halt, all state gas consumed by the child MUST be restored to the parent's state_gas_reservoir, except state gas for account creation (GAS_NEW_ACCOUNT) which MUST be consumed even on revert
  15. Regular Gas Floor: The regular gas component of each storage creation operation MUST be at least the pre-TIP-1000 (standard EVM) cost for that operation (SSTORE: 20,000, account creation: 25,000, CREATE base: 32,000, code deposit: 200/byte)
  16. EIP-7702 Delegation: Each EIP-7702 authorization MUST charge 25,000 regular gas + 225,000 state gas (250,000 total). Authorizations with auth.nonce == 0 MUST additionally charge the account creation cost (25,000 regular + 225,000 state)
  17. Precompile Consistency: All precompile storage operations MUST use the same gas accounting path as standard EVM SSTORE, inheriting the regular/state gas split automatically
  18. Keychain Authorization: Keychain authorize_key intrinsic gas MUST split SSTORE costs using the same regular/state ratio as standard EVM SSTOREs (20,000 regular + 230,000 state per new slot)
  19. Calldata Floor (EIP-7623): The calldata floor (TOTAL_COST_FLOOR_PER_TOKEN * tokens_in_calldata + 21000) MUST apply to regular gas only — it MUST NOT interact with state_gas_reservoir. Transaction validation MUST reject when max(intrinsic_regular_gas, calldata_floor_gas_cost) > max_transaction_gas_limit. Post-execution tx_gas_used_after_refund and block regular_gas_used MUST be at least calldata_floor_gas_cost

Alignment with EIP-8037

This TIP adopts the reservoir model from EIP-8037 for transaction-level gas accounting, with the following Tempo-specific differences:

Aspect EIP-8037 TIP-1016
State gas pricing Dynamic cost_per_state_byte scaling with block gas limit Fixed costs (e.g., 230,000 per slot) — Tempo uses fixed high prices for state growth protection
Gas cost harmonization Harmonizes all state creation to uniform cost-per-byte Maintains Tempo-specific pricing from TIP-1000
Target state growth 100 GiB/year dynamic target Economic deterrence via fixed high costs
Block-level gas accounting Bottleneck model: max(block_regular_gas, block_state_gas) Regular gas only; state gas fully exempt from block limits
Block gas limit range 60M–300M+ (Ethereum L1 scaling) 25M general + 500M payment lane (Tempo dual-lane)
Quantization Top-5 significant bits with offset for cost_per_state_byte Not applicable (fixed costs)

The core EVM mechanism — reservoir model, GAS opcode semantics, SSTORE refund/revert behavior, contract deployment flow, and receipt semantics — is shared with EIP-8037, minimizing implementation divergence from upstream. The key divergence is at the block level: TIP-1016 exempts state gas entirely from block limits rather than using EIP-8037's bottleneck model.