Meter the bytes each opcode adds to the EIP-7928 Block Access List in an explicit per-transaction counter, and fold that count, at 64 gas/byte, into the transaction’s floor accumulator — checked at runtime before the BAL grows. Today an attacker can pack ~1.55 MB into a 60M-gas block: 75% of gas on cold SLOADs (32 BAL bytes per 2,100 gas) + 25% on calldata at 16 gas/byte. On top of EIP-8131’s tx-content floor (same rate), block content is capped at block_gas_limit / 64 ≈ 0.89 MB (~42% reduction). Neither EIP alone closes the bypass: 8131 does not price BAL bytes; 8279 reuses 8131’s floor. Typical transactions are unaffected: the runtime BAL floor never binds in isolation.
Motivation
EIP-7623 caps worst-case block size by charging at least 64 gas per non-zero calldata byte. EIP-7981 extended that to access-list entries, and EIP-8131 generalises the floor to a uniform per-byte rule over all tx-content fields, including EIP-7702 authorization tuples and EIP-4844 blob versioned hashes. EIP-7928 introduces a new source of block bytes, the BAL, populated by runtime opcodes. None of those static floors cover it.
The cheapest BAL contributor is a cold SLOAD: 32 bytes for 2,100 gas. Combined with all-non-zero calldata, an attacker pushes intrinsic gas above the calldata floor, pays intrinsic, and gets calldata at 16 gas/byte while loading the rest of the block via SLOAD keys.
At a 60M gas limit:
Attack
Worst-case block bytes
Pure calldata at floor
0.894 MB (target)
Auth-tuple bypass under EIP-8131 alone
1.067 MB
Cold-SLOAD bypass under EIP-7928
1.548 MB
SSTORE, address access, CREATE, and successful EIP-7702 delegations give further vectors. This EIP closes them with one uniform mechanism.
Specification
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.
Clients keep two internal, per-transaction counters on the execution environment for the duration of the transaction:
bal_data_bytes — an explicit count of the bytes each opcode contributes to the BAL, and
floor_gas_used — the floor accumulator, derived from the static seed plus the metered bytes.
Neither is part of the signed transaction, RLP-encoded, gossiped, or persisted; no new transaction field or type is introduced. floor_gas_used is seeded with the static floor (below) with bal_data_bytes = 0, and both are extended at runtime. No gas is reserved or deducted from the execution budget; the derived floor is checked against tx.gas only to ensure the user can pay it if it ends up binding.
meter_bal_data MUST be called BEFORE the matching BAL insertion or state mutation. An OutOfGasError aborts the operation before any unpaid BAL byte exists. The metered count is a deliberate upper bound on the transaction’s actual BAL contribution (see Reverts and Repeated writes to the same slot); over-counting only raises the floor, never lowers it, so the bound below is never violated.
Static floor seed
The tx-content portion of the floor (calldata, access-list entries, authorization tuples, blob versioned hashes) is defined by EIP-8131 as tx_floor. This EIP adds the per-auth BAL contribution that arises when an authorization is processed:
tx_env.static_floor holds this value and tx_env.floor_gas_used is initialised to it (with bal_data_bytes = 0) at the start of execution. The per-auth term covers each authorization’s worst-case BAL contribution statically, so set_delegation (which runs before the EVM’s OOG handler) never meters at runtime.
Per-transaction accounting always adds further BAL entries that no opcode places there: sender, coinbase, and target (each with address plus the relevant balance/nonce changes); recipient balance for value transfers; contract nonce on creation; optional top-level EIP-7702 delegated target. These total at most 184 bytes, fully covered by the TX_BASE / FLOOR_GAS_PER_BYTE = 328 bytes of headroom already in TX_BASE.
Runtime data metering
Each trigger calls meter_bal_data with the byte count below, before the corresponding BAL entry is added:
SSTORE first moving a slot off its pre-transaction value
+32
SSTORE returning a slot to its pre-transaction value
−32 (refund)
CALL with non-zero value to a different account
32
SELFDESTRUCT with non-zero balance to a different beneficiary
32
CREATE / CREATE2 after the collision check (new contract’s nonce)
8
CREATE / CREATE2 with non-zero endowment (new contract’s balance)
+32
Successful CREATE / CREATE2 deploy, just before set_code
len(deployed_code)
EIP-7702 delegations are covered by the static auth_bytes term. CALLCODE, DELEGATECALL, and STATICCALL transfer no value out of the executing account and therefore add no balance bytes.
The 32 storage-value bytes are metered at most once per slot, tracking whether the slot’s current value differs from its pre-transaction value: charged on the first SSTORE that makes it differ, refunded by a later SSTORE that restores it. The refund subtracts from bal_data_bytes and recomputes floor_gas_used = static_floor + bal_data_bytes * FLOOR_GAS_PER_BYTE. This mirrors EIP-7928, which records a single post-value per changed slot and demotes a slot whose post-value equals its pre-value to a no-op write in storage_reads — the slot key remains (covered by the cold-access row above), only the value entry disappears. The condition is the BAL’s no-op-write rule, not the EIP-3529SSTORE gas refund: clearing a non-zero slot to zero earns a gas refund but is still a recorded BAL write, so its value bytes are never refunded.
Transaction validation continues to require tx.gas >= max(intrinsic, static_floor).
System transactions and withdrawals
System-contract calls (EIP-2935, EIP-4788, EIP-7002, EIP-7251) and withdrawals do NOT consume from a per-transaction floor. Their BAL bytes are absorbed by the existing EIP-7928 block-level buffer bal_items <= block_gas_limit / ITEM_COST.
Rationale
Floor side, not intrinsic side
Raising opcode intrinsic costs would penalise every user, including ones who never bypass. Charging on the floor only bites when execution is too cheap to cover the floor, which is exactly the bypass case.
A pure intrinsic surcharge also fails to cap the block at gas_limit / 64: for any added per-SLOAD cost X, the optimum bypass still yields B = gas_limit / 64 + 24 · gas_limit / (2100 + X) bytes, with the residual only vanishing as X grows large. Floor-side charging gives a closed bound directly.
Runtime extensions cannot make the floor bind on their own
Every runtime trigger pairs its meter_bal_data call with an execution charge larger than the floor extension itself:
Trigger
Floor extension
Min execution charge
Cold account access
1,280
2,600
Cold SLOAD
2,048
2,100
SSTORE (cold, zero → non-zero)
4,096
22,100
CALL with non-zero value
2,048
9,000
SELFDESTRUCT with non-zero balance
2,048
5,000
CREATE / CREATE2 (nonce only)
512
32,000
Deployed code (per byte)
64
200
For every BAL-contributing opcode, floor_extension < execution_gas. Executing one more such opcode raises execution_gas_used by strictly more than it raises floor_gas_used. A transaction whose execution gas already exceeds its static floor therefore stays execution-dominated no matter how many BAL-contributing opcodes it runs: the gap between execution_gas_used and floor_gas_used can only widen. Floor-side OutOfGasError (new_floor > tx.gas inside meter_bal_data) requires the floor accumulator to climb past tx.gas; from an execution-dominated state, execution-side OOG fires first.
The floor only binds when the static seed defined by EIP-8131 (calldata, access-list entries, auths, blob hashes) already pushes static_floor close to tx.gas before any opcode runs. That is precisely the calldata + cold-SLOAD bypass this EIP targets. Transactions whose static seed is small, the common case, cannot reach a binding floor through opcode execution alone.
One per-byte rate
Pricing every BAL byte at the same FLOOR_GAS_PER_BYTE as non-zero calldata makes the worst case invariant to the attacker’s choice: calldata, auths, storage keys, addresses, code, and balances all yield 1/64 bytes per gas at the floor. The optimum collapses to B = gas_limit / 64.
Meter before; refund only when bytes leave the BAL
EIP-7928 keeps accessed addresses and storage keys in the BAL even when their call frame reverts. Metering each contribution before the insertion guarantees every BAL byte is accounted for, and an out-of-gas at the metering step aborts before the matching insertion. The meter is otherwise a deliberate upper bound, not an exact mirror of the BAL: changes from reverted frames are never rewound (see Reverts), because rewinding them would entangle the meter with the existing OOG and refund machinery for no benefit — the count feeds nothing but a floor that rarely binds, so over-counting is free.
The one refund is the storage-value byte, and it is safe precisely because it is keyed to the BAL rather than to gas. A slot restored to its pre-transaction value becomes a no-op write under EIP-7928, so its 32 value bytes genuinely leave the BAL; refunding only when bytes provably leave the BAL keeps the meter an upper bound and never under-counts, so the block-size bound holds. This is deliberately narrower than the EIP-3529SSTORE gas refund, which also fires when clearing a non-zero slot to zero — a case EIP-7928 still records as a write, and which therefore earns no byte refund. Keying the byte refund to the gas refund instead would drop bytes that remain in the BAL and break the bound.
Per-auth coverage is static
set_delegation runs outside the EVM’s exception handler. A runtime meter_bal_data raising OutOfGasError there would propagate uncaught and crash block processing. Folding the worst-case 51 BAL bytes per auth (on top of the 108-byte tuple priced by EIP-8131, for 159 B total) into the static floor lets set_delegation mutate state freely: any transaction whose tx.gas cannot cover it has already been rejected at validation.
Counter, not a reservation
The runtime mechanism does not deduct gas from the execution budget or pre-commit any gas. meter_bal_data only increments the explicit byte counter (bal_data_bytes) and the derived accumulator (floor_gas_used), and checks that the floor remains ≤ tx.gas. Execution gas accounting is untouched. At the end, tx.gasUsed = max(execution_gas_used, floor_gas_used); the floor accumulator decides only how much of the upfront-debited tx.gas × gas_price gets kept versus refunded.
max(execution, floor) preserved
The EIP-7623 / EIP-8131 charging shape is unchanged; only the floor side has a runtime tail. Transactions whose execution exceeds the floor pay exactly as today.
Backwards Compatibility
Hard fork.
The static floor takes tx_floor from EIP-8131 and adds 51 BAL bytes per authorization, plus runtime extensions of the floor accumulator for BAL bytes that opcodes contribute during execution. The combined floor per auth is (108 + 51) × 64 = 10,176 gas, still below the AUTH_PER_EMPTY_ACCOUNT = 25,000 intrinsic per auth. The minimum tx.gas for a transaction only changes when the floor side already dominated, i.e. for calldata-heavy or BAL-heavy transactions. A bare ETH transfer stays at 21,000.
Mainnet sample (1,500 random blocks, 441,271 transactions, 20,000 of those traced for BAL bytes, May 2026): EIP-8131 alone makes 3.24% of transactions pay more (+3.29% gas). EIP-8279 adds another 0.54 pp and +0.77 pp, for a combined 3.79% / +4.06%. The BAL piece never bites on its own: per-tx BAL bytes average 120-200 B after TX_BASE absorption, under 13 k floor gas, well below typical execution gas. EIP-8279’s job is to close the calldata + cold-SLOAD bypass when paired with EIP-8131, not to add a standalone floor.
Wallets and eth_estimateGas MUST compute the new static floor (including the per-auth term) and, when tracing execution, meter the runtime BAL bytes for cold accesses, value-bearing calls, and deployments alongside intrinsic gas.
Pre-EIP optimum at gas_limit = 60M: 937,500 non-zero calldata bytes plus 21,429 cold SLOADs in one transaction.
Static seed: 21,000 + 64 × 937,500 = 60,021,000.
Per cold SLOAD floor extension: 32 × 64 = 2,048.
Cumulative floor after 21,429 SLOADs: 60,021,000 + 21,429 × 2,048 ≈ 103.9M, exceeding gas_limit. The transaction fails validation or hits OutOfGasError mid-execution.
The largest feasible mix yields B = gas_limit / 64 = 937,500 block bytes regardless of the calldata / SLOAD split.
Cold SSTORE (zero → non-zero)
Single cold SSTORE writing a non-zero value to an empty slot.
Metered: 32 (key) + 32 (value) = 64 BAL bytes → 4,096 gas folded into floor_gas_used.
Intrinsic SSTORE cost: 22,100. max(intrinsic, floor) = intrinsic. The floor accumulator is dominated by execution gas.
Net-zero SSTORE round-trip
Slot starts at original value O; the transaction writes X ≠ O then writes O again.
First SSTORE (O → X): 32 (cold key) + 32 (value) metered.
Second SSTORE (X → O): slot is back at its pre-transaction value, a no-op write under EIP-7928; the 32 value bytes are refunded.
Net metered: 32 (key only). The BAL records the slot in storage_reads, with no storage_changes value entry — the meter matches.
Repeated distinct writes
Slot starts at O; the transaction writes A, then B, then C (all distinct, C ≠ O).
The 32 value bytes are metered once, on the O → A transition; A → B and B → C add nothing.
Net metered: 32 (key) + 32 (value) = 64, matching the single post-value C the BAL records.
Reverted CALL with value
CALL with value > 0 to a recipient that reverts.
32 balance bytes are metered (folded into floor_gas_used) before generic_call.
The recipient’s balance change is discarded on revert (per EIP-7928); neither bal_data_bytes nor the floor is rewound.
Over-accounting by 32 bytes; the bound still holds.
Security Considerations
Block-size bound
Per transaction, every byte the tx contributes to the block (tx blob + BAL) is accounted for at the floor rate via either the static seed or a runtime extension, with implicit per-tx bytes covered by TX_BASE:
System-contract and withdrawal contributions are bounded separately by the EIP-7928 block-level ITEM_COST buffer (~1,428 items at a 60M gas limit), sized to absorb existing system contract use.
Reverts
Accessed addresses and storage keys persist in the BAL across reverts (per EIP-7928); balance, nonce, code, and storage-value changes from reverted frames are discarded. Neither bal_data_bytes nor the derived floor is rewound for discarded entries. The storage-value refund fires only on an explicit SSTORE back to the pre-transaction value on the committed path — value bytes charged inside a frame that later reverts are left in place, not refunded. Safe: the meter over-counts, never under-counts.
Repeated writes to the same slot
The 32 storage-value bytes are metered once per slot — on the first SSTORE that moves it off its pre-transaction value — and refunded if it returns there, matching the single post-value the BAL records per changed slot. Repeated distinct writes (A → B → C) therefore meter 32 value bytes total, not 32 per write, and a net-zero round-trip (O → X → O) meters none. bal_data_bytes can still over-count where the BAL deduplicates further (e.g. value bytes charged in a frame that later reverts, never rewound); the bound holds.
Gas estimation
Wallets and eth_estimateGas MUST simulate floor_gas_used alongside intrinsic gas. Without it, estimates for floor-bound transactions are too low and submissions will be rejected at validation.