feat(eip8130): apply EIP-7623 calldata floor to intrinsic gas - #5073
chunter-cb wants to merge 2 commits into
Conversation
EIP-8130 priced tx_payload_cost at a flat EIP-2028 16/4 per byte with no floor, so a data-heavy 0x79 transaction could post data availability ~40% cheaper than an equivalent type-2 transaction on the same chain: standard txs get the EIP-7623 floor via revm, but the 8130 path bypasses revm through Eip8130Executor. This reopens the max-block-size hole EIP-7623 was created to close. Port EIP-7623's max(standard, floor) form over the serialized 8130 transaction (its bytes live in account_changes, sender_auth, calls, metadata, and payer_auth, so there is no single `data` field to floor): sender_floor = (sender_intrinsic - payload) + payload_floor sender_metered_gas = max(sender_intrinsic + execution_gas_used, sender_floor) payload_floor charges TX_TOTAL_COST_FLOOR_PER_TOKEN (10) per payload token instead of the standard 4, so computation-heavy transactions are unaffected and only data-heavy ones pay the floor. Enforced at mempool admission and inclusion (gas_limit >= sender_floor), and applied to the settled charge and to gas estimation. payer_auth is metered outside gas_limit and its bytes are already part of payload, so the transaction-level floor covers that padding vector too. The 10-per-token rate is pinned to revm's TOTAL_COST_FLOOR_PER_TOKEN by the gas_primitives_match_evm_reference drift tripwire. Warmth tests that carry little calldata are padded with JUMPDEST no-ops so their cold/warm SLOAD delta is not masked by the floor clamp. Mirrors ethereum/EIPs#12320.
🟡 Heimdall Review Status
|
|
✅ All benchmarks green — 14 within ±2% (deterministic instruction counts). View run Benchmark details (14)
|
| let execution_gas_available = | ||
| intrinsic.execution_gas_available(gas_limit).ok_or_else(|| { | ||
| BaseTransactionError::eip8130("EIP-8130 sender-intrinsic gas exceeds the gas limit") | ||
| })?; |
There was a problem hiding this comment.
Nit: after the gas_limit < sender_floor guard at line 1810, gas_limit >= sender_floor >= sender_intrinsic is guaranteed (since sender_floor >= sender_intrinsic is a documented invariant of the floor formula). That makes execution_gas_available(gas_limit) infallible here — the ok_or_else branch is dead code.
Not a bug, but worth noting: this silent dead path could mask a real invariant violation if the floor relationship ever broke. Consider either:
- replacing with an
unwrap/expectthat documents the invariant ("sender_floor >= sender_intrinsic, checked above"), or - adding a
debug_assert!(sender_floor >= intrinsic.sender_intrinsic())before the floor check to make the contract explicit.
There was a problem hiding this comment.
Agreed — after the floor guard, gas_limit >= sender_floor >= sender_intrinsic makes the execution_gas_available error branch unreachable.
Addressed in a10cb35: debug_assert!(sender_floor >= sender_intrinsic) plus expect documenting the same contract, so a broken floor relationship fails loudly instead of being masked by a dead error string.
Review SummaryPR: feat(eip8130): apply EIP-7623 calldata floor to intrinsic gas This PR correctly ports the EIP-7623 calldata floor to the EIP-8130 transaction type, closing an underpricing vector where data-heavy 8130 transactions could post DA ~40% cheaper than equivalent type-2 transactions. What I verified
FindingsOne minor finding posted inline: the No critical or blocking findings. |
`gas_limit >= sender_floor >= sender_intrinsic` is guaranteed past the EIP-7623 floor guard, so `execution_gas_available` cannot fail there. Replace the unreachable `ok_or_else` with a `debug_assert` on the floor relationship and an `expect` that documents it.
Review SummaryThis PR correctly ports EIP-7623's calldata floor to EIP-8130 transactions. The implementation is sound and the changes are consistent across all four surfaces: intrinsic gas computation, executor (execution + estimation), settlement, and mempool validation. What I verified
Block-production sensitivityThis PR touches the metering/gas path (a block-production-sensitive surface). I evaluated against the block production review guide:
No critical findings. |
Summary
Implements ethereum/EIPs#12320 — applies the EIP-7623 calldata floor to EIP-8130 (transaction type
0x79) intrinsic gas.Why this is needed
EIP-8130's
tx_payload_costprices the serialized transaction at a flat EIP-2028 16/4 per byte with no floor. Standard type-2 transactions get the EIP-7623 floor from revm, but the 8130 path bypasses revm entirely throughEip8130Executor, so the floor never binds on an0x79transaction. The practical effect: a data-heavy 8130 transaction can post data availability ~40% cheaper than an equivalent type-2 transaction on the same chain (measured on 20,000 zero bytes: type-2 pays the 221,000 EIP-7623 floor; a self-paid0x79pays ~129,000). That reopens the max-block-size hole EIP-7623 was created to close.An 8130 transaction has no single
datafield — its bytes live inaccount_changes,sender_auth,calls,metadata, andpayer_auth— so EIP-7623'stx.data-based floor can't reach it, and the L2 adoption profile makes the floor's structure normative (so it can't be fixed in chain config). It belongs in the intrinsic-gas path.What changed
Ports EIP-7623's
max(standard, floor)form over the serialized transaction:payload_floorchargesTX_TOTAL_COST_FLOOR_PER_TOKEN(10) per payload token instead of the standard 4, so computation-heavy transactions are unaffected and only data-heavy ones pay the floor.schedule.rs— addTX_TOTAL_COST_FLOOR_PER_TOKEN = 10, pinned to revm'sTOTAL_COST_FLOOR_PER_TOKENby thegas_primitives_match_evm_referencedrift tripwire.intrinsic.rs— count payload tokens in one pass, add thepayload_floorfield andsender_floor()(>= sender_intrinsic).eip8130.rs(executor) — threadsender_floorthroughEip8130Outcome; rejectgas_limit < sender_floorat inclusion; floor the settled charge inbillable_gas(mirrors revm'seip7623_check_gas_floor, comparing against post-refund gas) and the gas estimate.validator.rs— floor check at mempool admission.payer_authis metered outsidegas_limitand its bytes are already part ofpayload, so the transaction-level floor covers that padding vector too. (The EIP draft flags the exact placement of a separatepayer_authfloor as an open discussion point; this implementation follows the transaction-level option, consistent with how Base already meterspayer_authbytes intx_payload_cost.)Tests
payload_floor,sender_floor, growth with payload size) and for the settlement floor inbillable_gas.JUMPDESTno-ops (deployed code, not tx bytes, so tokens are unchanged) so their cold/warm SLOAD delta isn't masked by the floor clamp.base-common-eip8130,base-common-evm,base-execution-eip8130,base-execution-txpool,base-execution-eip8130-rpc,base-builder-core.