# Reth: EIP-2780 / EIP-7976 / EIP-7981 intrinsic gas review (Amsterdam)

Scope: I read the code only. Nothing was run against a network or a node. Amsterdam is not live on mainnet.

## Pins
- reth `main`: **e0378eb691aa2770bb96fd4ecec2f4445a2a3e7c**. This matches api.github.com/repos/paradigmxyz/reth/commits/main and the shallow clone.
- Reth's intrinsic gas logic is not in the reth repo. It is in the `evm2` dependency, pinned in reth `Cargo.toml#L440-L443` and `#L676-L679` at **alloy-rs/evm2 (patched as DaniPopes/evm2) rev 4a53ec080dfbb904e98e3ca8f0abcc11ab8abae0**. Every `evm2` citation below is at that rev, under `crates/evm2/src/`.
- execution-specs `forks/bogota`: **7f2f4c9bf6d73b09ecb8b24eb14d08bf0cf0247b**
- go-ethereum `master`: **8817f99f92852f8ac53e596de6202dd9e3494065**

## Candidates

**None.** I found no case where Reth (through evm2) gives a different intrinsic gas, floor gas, gas used or validity result from the spec at these pins. The disproof attempts are in the table below.

The only difference I found is in the txpool, not in consensus, and the spec does not define txpool behaviour, so I record it as an observation rather than a candidate:

- **Observation O1 (txpool, fork boundary).** The pool picks its gas rules from the header of the current tip: `crates/transaction-pool/src/validate/eth.rs#L961-L963` calls `transaction_validation_rules(&self.evm_config, new_tip_block)`, and the function is defined at `#L1478-L1492`. While the tip is the last Osaka block, the pool still applies Osaka intrinsic rules to transactions that will be included under Amsterdam.
  - A self-transfer or zero-value call sent with gas between 12,000/15,000 and 21,000 is rejected until the first Amsterdam block is imported.
  - In the other direction, an access-list transaction can pass the Osaka rules (2,400 per address) and then fail Amsterdam's 4,180 per address. Block building then skips it.
  - This has no effect on block validity.
  - Severity: cosmetic (mempool admission). Confidence: medium. I did not trace whether another path re-validates against the next timestamp.
  - Known check: related issue https://github.com/paradigmxyz/reth/issues/27619 ("txpool keeps transactions a fork made invalid, and peers get banned for them", closed).

## Rules checked and found correct

Spec references are in `src/ethereum/forks/amsterdam/`. "Spec tx" means `transactions.py#L689-L798` (`calculate_intrinsic_cost`).

| Rule | Spec | Reth / evm2 | Geth |
|---|---|---|---|
| TX_BASE 12000 | `vm/gas.py#L143` | `version/mod.rs#L869-L874` sets `TxBaseStipend: EIP2780_TX_BASE_COST`. The test at `version/gas_params.rs#L605` asserts 12_000. | `core/state_transition.go#L163-L184` uses `TxBaseCost2780` |
| Recipient: cold access 3000 for a call to another account, nothing for a self-transfer | spec tx `#L736-L747` | `ethereum/mod.rs#L892-L910` charges warm 100 + cold-additional 2900 = 3000 | `core/state_transition.go#L177-L184` |
| TX_VALUE_COST 6000, only for a non-creation, non-self call with value > 0 | spec tx `#L744-L747`; `vm/gas.py#L144` | `ethereum/mod.rs#L903-L907`; `interpreter/gas.rs#L132` | same function |
| Creation: CREATE_ACCESS = ACCOUNT_WRITE 9000 + COLD 3000 = 12000, and no value charge even when value is sent | `vm/gas.py#L77,#L85,#L90`; spec tx `#L741-L743` | `ethereum/mod.rs#L900-L902` (comment: "creates pay no value-based charge"); `interpreter/gas.rs#L67,#L81` | `CreateAccessAmsterdam`, `#L180-L181` |
| Init-code word cost 2 × ceil(len/32) | `vm/gas.py#L864-L883` | `ethereum/mod.rs#L861-L863`; `interpreter/gas.rs#L37` | (pre-existing) |
| Calldata cost: 4 per token, zero byte = 1 token, nonzero byte = 4 tokens | spec tx `#L732-L734,#L801-L810` | `version/gas_params.rs#L463-L471`; `ethereum/mod.rs#L845-L846` | (pre-existing) |
| EIP-7976 floor: every byte counts as 4 tokens, 16 gas per token | spec tx `#L775,#L782-L786`; `vm/gas.py#L146` | `TxFloorZeroByteMultiplier` = 4 and per-token = 16 (`version/mod.rs#L792-L799`; `interpreter/gas.rs#L36`) | `core/state_transition.go#L202-L210` |
| Floor base is the decomposed EIP-2780 base, not TX_BASE alone | spec tx `#L777-L786` | `ethereum/mod.rs#L874` uses `eip2780_base` | `core/state_transition.go#L243-L249` |
| Access list per entry: 2900 per address, 2000 per key (EIP-8038) | `vm/gas.py#L147-L152` (3000−100 and 2100−100) | `version/mod.rs` `TxAccessListAddressCost`/`StorageKeyCost` = 2900+20·64 and 2000+32·64. Test at `gas_params.rs#L624-L625`. | `#L127-L139`, `TxAccessListAddressGasAmsterdam` 2900 |
| EIP-7981 surcharge on the intrinsic side: 80 tokens per address and 128 per key, ×16 = 1280 / 2048 | spec tx `#L757-L766,#L789-L795` | Included in the per-item cost: 20·64 = 1280, 32·64 = 2048 (see the row above) | `#L141-…` adds it on top for Amsterdam |
| EIP-7981 surcharge on the floor side: same tokens | spec tx `#L782-L786` | `version/gas_params.rs#L497-L511`: (20·a + 32·k)·4 tokens ×16 | `core/state_transition.go#L211-L225` |
| EIP-7702 intrinsic per auth = 101·16 + 3000 + 3000 + 2·100 = 7816; not in the floor | `vm/gas.py#L157-L163`; spec tx `#L768-L772` | `ethereum/eip7702.rs#L68` adds it to intrinsic only; `gas_params.rs#L620` asserts 7_816; the pool path is `reth crates/evm/evm/src/lib.rs#L109-L112` | not re-checked |
| Gas limit must cover the execution intrinsic, and separately the floor | spec tx `#L674-L680` | `ethereum/mod.rs#L407-L426`. The Amsterdam `initial_state_gas` is 0 (`eip1559.rs#L61-L70`, `eip7702.rs#L69-L81`). | — |
| Intrinsic or floor above TX_MAX_GAS_LIMIT is rejected | spec tx `#L677-L684` | `ethereum/mod.rs#L347-L364` checks this only when `gas_limit > cap`. If `gas_limit ≤ cap`, the intrinsic and floor checks already bound both values, so the outcome is the same. | — |
| Check order (size, gas cap, fee, then intrinsic) | spec tx `#L630-L688` | `eip1559.rs#L44-L72`. The order differs only in which error is reported; validity is the same. | — |
| Floor applied after refund: gas used = max(spent − refund, floor); exactly equal counts as the floor | EIP-7623/7976 | `evm/tx.rs#L114-L118` (strict `>` then floor, so ties give the same value) | — |
| Txpool and block validation use one formula | — | The pool calls `evm2::ethereum::intrinsic_and_floor_gas` (`reth crates/evm/evm/src/lib.rs#L90-L114`; `crates/transaction-pool/src/validate/eth.rs#L1567-L1600`). See O1 for the fork boundary. | — |
| Legacy transactions: no access list counted | spec `has_access_list` | Legacy handler passes 0 | — |

Worked check: a zero-value call with 1000 bytes of `0x01` calldata, 1 address and 1 key.
- Spec floor = 15000 + (4000 + 80 + 128)·16.
- evm2 test `ethereum/mod.rs#L1099-L1125` asserts the same value, both for all-zero and for nonzero calldata.

I did not separately verify EIP-7981's prose ("flat surcharge") against the token form; the two give the same numbers (1280 / 2048).

## Known check
- paradigmxyz/reth searches (api.github.com/search/issues, `repo:paradigmxyz/reth`):
  - `2780` found https://github.com/paradigmxyz/reth/pull/26536 (txpool EIP-2780 rules, closed), #26068 and #26875 (RPC estimate, closed).
  - `7976` and `7981` found only https://github.com/paradigmxyz/reth/pull/23732 (devnet4 integration, closed).
  - `intrinsic gas` found https://github.com/paradigmxyz/reth/pull/27587 (closed) and issue #27619 (closed, related to O1).
- I did not fetch the execution-specs "Test Release Tracker" issues or `hive.ethpandaops.io/glamsterdam/listing.jsonl` in this run.
- The brief says hive consume-engine had 1626 failures on 2026-10-26. I did not attribute any of them to these rules.

## URLs read
- https://api.github.com/repos/paradigmxyz/reth/commits/main
- git clones: github.com/paradigmxyz/reth, github.com/alloy-rs/evm2, github.com/ethereum/execution-specs (forks/bogota), github.com/ethereum/go-ethereum
- https://api.github.com/search/issues?q=repo:paradigmxyz/reth+{2780,7976,7981,intrinsic+gas}
- I did not fetch the EIP pages (eips.ethereum.org/EIPS/eip-2780, -7976, -7981). The spec code at the pin was used as the reference.
