Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a2e59a6
refactor(spec-tools): use testing pydantic models in t8n (#2924)
gurukamath Jul 23, 2026
04e7b0d
feat(tests): add EIP-2681 nonce-reaching-max regression tests (#3226)
chfast Jul 24, 2026
1646cf5
refactor(test): Make max balance < 2^128 wei (#3227)
kevaundray Jul 24, 2026
6463c0d
chore(tests): improve EIP-7708 coverage, checklist, and ref-spec pin …
spencer-tb Jul 24, 2026
6e02b3b
chore(tests): improve EIP-7843 coverage, checklist, and ref-spec pin …
spencer-tb Jul 24, 2026
3c15c23
Open fix(tests): search for parent fork that differs in gas costs fro…
kevaundray Jul 24, 2026
1ac58d7
fix: skip ported static tests for amsterdam and later (#3231)
kevaundray Jul 24, 2026
ca7cac5
fix(consume): make release resolution robust to GitHub API rate limit…
danceratopz Jul 24, 2026
85aa48c
tests(amsterdam): EIP-2780 - pin receipt status of top-frame OOG tx i…
qu0b Jul 24, 2026
00bd585
fix(tests): update withdrawal and consolidation tests to use Header f…
jsign Jul 26, 2026
853585f
fix: max balance < 2^128 in ported static tests (#3230)
kevaundray Jul 27, 2026
7c4177a
feat(tests): add ef_prefix deposit-halt mode to EIP-8037 state-gas te…
chfast Jul 27, 2026
2cc42a8
refactor(spec): make state interface fully implementation agnostic (#…
kevaundray Jul 27, 2026
d5d230c
bug(spec-tools): make t8n daemon module importable on Windows (#3212)
Kumarutkarsh9470 Jul 27, 2026
3d3afa6
bug(test-eest): reconfigure output streams to UTF-8 on Windows consol…
Kumarutkarsh9470 Jul 27, 2026
36fbbab
fix(test-specs): parenthesize walrus when counting failing txs (#3190)
cuiweixie Jul 27, 2026
85a36cc
fix(test-specs): use integer division for genesis base fee (#3189)
cuiweixie Jul 27, 2026
c69d54b
fix(test-ci): Skip `test_cli.py` until #3241 is resolved (#3242)
marioevz Jul 28, 2026
608f878
feat(tests): add EIP-7997 case where factory is not present at fork b…
jochem-brouwer Jul 28, 2026
3850ce5
refactor(tests): Remove `fork.gas_costs()` reconstruction from Amster…
marioevz Jul 28, 2026
e0ce65c
chore(tests): improve EIP-7981 coverage, checklist, and ref-spec pin …
spencer-tb Jul 28, 2026
44d2b9c
feat(test-types): Introduce SSZ model into base types (#3196)
RazorClient Jul 28, 2026
46364f7
refactor(ci): run geth benchmark CI only on `benchmarks/**` branches …
marioevz Jul 29, 2026
178d9f9
chore(tests): improve EIP-7976 coverage, checklist, and ref-spec pin …
spencer-tb Jul 29, 2026
f79c4c7
feat(tests): EIP-7928 - cover system-address zero-tip coinbase BAL (#…
flcl42 Jul 29, 2026
1311ff3
feat(specs,amsterdam): EIP-2780 - fold transfer log cost into value t…
gurukamath Jul 29, 2026
f1c3408
fix(tests): enhance & un-skip Amsterdam ported static tests (Pt. 1) (…
marioevz Jul 29, 2026
d3baec8
fix(test-execute): prune fork-less items before evaluating filter_com…
spencer-tb Jul 29, 2026
5930782
refactor(spec-specs,tests): rename EIP-8037 regular gas to execution …
spencer-tb Jul 29, 2026
3ae3d66
feat(tests): implement eip-8070 sparse blob pool tests (#2948)
LouisTsai-Csie Jul 29, 2026
af13747
feat(test-benchmark): on-chain account verification (#3197)
LouisTsai-Csie Jul 29, 2026
6caece9
fix(tests): un-skip Amsterdam gas_cost SSTORE case via fork-derived cost
spencer-tb Jul 29, 2026
0727c4b
fix(tests): un-skip Amsterdam stBadOpcode CREATE gas searches via for…
spencer-tb Jul 29, 2026
2ff70e1
fix(tests): consolidate & un-skip CREATE empty-contract-with-storage …
spencer-tb Jul 29, 2026
2e7516d
fix(tests): un-skip Amsterdam stStaticCall create/ask ported tests
spencer-tb Jul 29, 2026
5fa8217
fix(tests): un-skip Amsterdam stSStoreTest via metadata-derived costs…
spencer-tb Jul 29, 2026
63e0854
fix(tests): un-skip Amsterdam 63/64 ask-family ported tests via obser…
spencer-tb Jul 29, 2026
49a0686
fix(tests): un-skip Amsterdam stInitCodeTest OOG creation tests via d…
spencer-tb Jul 29, 2026
bd03c66
fix(tests): un-skip Amsterdam EIP-150 create/delegate/oog-cascade por…
spencer-tb Jul 29, 2026
e319431
fix(tests): un-skip Amsterdam name-registrator creation ported tests
spencer-tb Jul 29, 2026
7c77201
fix(tests): un-skip Amsterdam gas-price-for-codes and memory OOG port…
spencer-tb Jul 29, 2026
8def631
fix(tests): un-skip Amsterdam exp-empty, homestead-oog, and create-wa…
spencer-tb Jul 29, 2026
065e20d
fix(tests): un-skip Amsterdam attack-replay and callcode-suicide port…
spencer-tb Jul 29, 2026
debb521
fix(tests): un-skip Amsterdam stSolidityTest via fork-derived state-g…
spencer-tb Jul 29, 2026
28d4782
fix(tests): un-skip Amsterdam double-selfdestruct-touch ported test
spencer-tb Jul 29, 2026
b108707
fix(tests): un-skip Amsterdam stTransactionTest ported tests via deri…
spencer-tb Jul 29, 2026
d7b9c97
fix(tests): un-skip Amsterdam refund600 via derived refund-cap formula
spencer-tb Jul 29, 2026
89c727f
fix(tests): un-skip Amsterdam 1024-depth recursion and initcode-call …
spencer-tb Jul 29, 2026
2d191a7
fix(tests): un-skip Amsterdam stCreateTest remainder via derived budg…
spencer-tb Jul 29, 2026
8657e32
fix(tests): un-skip Amsterdam mutual-recursion and call-bomb ported t…
spencer-tb Jul 29, 2026
341b16c
fix(tests): un-skip remaining Amsterdam ported static tests, emptying…
spencer-tb Jul 29, 2026
ca79dcb
fix(tests): correct Account.NONEXISTENT annotations for mypy
spencer-tb Jul 29, 2026
13dc9bf
docs(claude): add refund-cap, collision, and depth-loop lessons to en…
spencer-tb Jul 29, 2026
1fc547c
fix(tests): reword docstring to satisfy codespell
spencer-tb Jul 29, 2026
bac43cc
fix(tests): apply execution-gas rename in ported create tests
spencer-tb Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
626 changes: 626 additions & 0 deletions .claude/commands/enhance-ported-test.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions .claude/commands/implement-eip.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ Each fork lives at `src/ethereum/forks/<fork_name>/`. Explore the latest fork di

## Gas Handling

Recent forks meter two gas dimensions: regular gas and state gas (for durable state growth). Key rules:
Recent forks meter two gas dimensions: execution gas and state gas (for durable state growth). Key rules:

1. Gas constants and calculations go in `vm/gas.py`; a frame's mutable gas state lives on `Evm.gas_meter`.
2. Extend the named helper vocabulary (`charge_*`, `credit_*`, `restore_*`, `withhold_*`, ...) instead of doing gas arithmetic by hand at call sites; encode each helper's invariant as an assert.
3. State gas is charged by the frame whose opcode causes the creation, before the child's regular-gas share is withheld; the whole reservoir passes to the child.
3. State gas is charged by the frame whose opcode causes the creation, before the child's execution-gas share is withheld; the whole reservoir passes to the child.
4. A failing frame settles its own meter before returning, so parents incorporate children unconditionally.
5. Opcodes that touch state use labeled stages, with all charging before the operation: `GAS (STATE-INDEPENDENT)` → `STATE ACCESS (STATE-DEPENDENT GAS)` → `STATE GAS` → `CHILD GRANT` → `OPERATION`. Simple opcodes keep the bare `GAS` marker. `generic_call`/`generic_create` contain no pricing; they run the child lifecycle: `PREFLIGHT` → `DESTINATION ACCESS` → `CHILD GRANT` → `DISPATCH` → `OUTCOME`.
6. Avoid "frame" in gas identifiers (a future EIP claims the term); when a name diverges from the spec's variable name, cross-reference the spec name in the docstring.
Expand Down
16 changes: 14 additions & 2 deletions .claude/commands/write-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,20 @@ Conventions and patterns for writing consensus tests. Run this skill before writ
## Fork-Aware Logic

- `fork >= Cancun` for conditional behavior based on fork
- `fork.gas_costs()` returns `GasCosts` dataclass with constants like `G_WARM_SLOAD`, `G_COLD_ACCOUNT_ACCESS`, `G_BASE`, etc.
- `fork.transaction_intrinsic_cost_calculator()` for computing tx intrinsic gas
- `fork.fork_at(timestamp=...)` gives the fork active before/after a transition boundary
- For gas amounts, see **Gas Cost Expectations** below — prefer framework cost constructs over reading `fork.gas_costs()` constants directly

## Gas Cost Expectations

Never hand-reconstruct a gas amount by summing `fork.gas_costs()` constants (`NEW_ACCOUNT`, `CALL_VALUE`, `COLD_STORAGE_WRITE`, `VERY_LOW`, ...). Re-deriving the schedule duplicates the framework's own calculation and silently breaks when a future fork reprices. Instead:

- **Read the cost off the bytecode under test.** Set the relevant opcode metadata (`account_new`, `value_transfer`, `address_warm`, `key_warm`/`original_value`/`current_value`/`new_value`, `init_code_size`, `code_deposit_size`, `new_memory_size`, ...) and use `bytecode.gas_cost(fork)` (execution + state), `.execution_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior — e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`.
- **Transaction-level costs:** `fork.transaction_intrinsic_cost_calculator()`; `fork.transaction_top_frame_state_gas(contract_creation=True)` for the created account's `NEW_ACCOUNT` (under EIP-2780 it is NOT part of the intrinsic — never subtract it from the intrinsic); `fork.transaction_data_floor_cost_calculator()`; `fork.call_value_stipend()`.
- **A single bare opcode/schedule cost** (e.g. an account-access constant) comes from a metadata-only opcode: `Op.BALANCE.with_metadata(address_warm=False).gas_cost(fork)`.
- **Fork-transition / cross-fork comparisons:** evaluate the same bytecode or intrinsic at each fork (`before = fork.fork_at(timestamp=...)`, `after = ...`) and compare `before` vs `after` costs — do not compare raw schedule constants.
- **Do not add "self-check" asserts** that compare a framework-computed value against a `fork.gas_costs()` decomposition of the same fork; they add no coverage over the runtime behavior the test already exercises and only break on repricing.
- **If the framework cannot express a cost, fix the framework** (wire the opcode into its gas/state map, add an accessor) rather than reconstructing it in the test. If the use case does not support the framework, the framework needs an update.
- **Exception:** a test whose *subject* is a specific schedule value (e.g. a regression that an opcode's cost is unchanged) may compare a runtime measurement (`CodeGasMeasure`) against `fork.gas_costs().OPCODE_*`. Even then, never hardcode the literal value.

## Transactions

Expand Down
40 changes: 7 additions & 33 deletions .github/workflows/benchmark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,28 @@ name: Benchmarking
on:
push:
branches:
- mainnet
- "forks/**"
paths:
- "tests/benchmark/**"
- "packages/testing/src/execution_testing/benchmark/**"
- "packages/testing/src/execution_testing/cli/pytest_commands/plugins/**"
- ".github/workflows/benchmark.yaml"
pull_request:
paths-ignore:
- "benchmarks/**"
paths-ignore: &non_benchmark_paths
- "**.md"
- "LICENSE*"
- ".gitignore"
- ".vscode/**"
- "whitelist.txt"
- "docs/**"
- "docs/**"
- "mkdocs.yml"
pull_request:
branches:
- "benchmarks/**"
paths-ignore: *non_benchmark_paths
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: ${{ github.ref_name != github.event.repository.default_branch }}

jobs:
unit-tests:
name: Benchmark Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: true

- uses: ./.github/actions/setup-uv
with:
enable-cache: "false"

- uses: ./.github/actions/build-evm-base
id: evm-builder
with:
type: benchmark

- name: Run benchmark unit tests
run: just test-tests-bench
env:
EVM_BIN: ${{ steps.evm-builder.outputs.evm-bin }}

sanity-checks:
name: ${{ matrix.name }}
needs: [unit-tests]
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
- master
- mainnet
- "forks/**"
- "benchmarks/**"
paths-ignore:
- "**.md"
- "LICENSE*"
Expand Down Expand Up @@ -163,10 +164,20 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/setup-uv
- uses: ./.github/actions/build-evmone
# On benchmarks/** branches, build the pinned geth so the benchmark
# plugin tests fill against a real client (via EVM_BIN). On every other
# branch this step is skipped, EVM_BIN stays empty, and those fills fall
# through to fill's in-repo EELS t8n.
- uses: ./.github/actions/build-evm-base
id: evm-builder
if: ${{ startsWith(github.ref, 'refs/heads/benchmarks/') || startsWith(github.base_ref, 'benchmarks/') }}
with:
type: benchmark
- name: Run test-tests
run: just test-tests
env:
PYTEST_XDIST_AUTO_NUM_WORKERS: auto
EVM_BIN: ${{ steps.evm-builder.outputs.evm-bin }}

test-tests-pypy:
runs-on: [self-hosted-ghr, size-l-x64]
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ When reviewing PRs that implement or test EIPs:
## When to Use Skills

- Writing or modifying tests → run `/write-test` first
- Cleaning up or future-proofing a `tests/ported_static/` test → run `/enhance-ported-test` first
- Writing or modifying pytester-based plugin tests → run `/pytester` first
- Filling test fixtures → run `/fill-tests` first
- Implementing an EIP or modifying fork code in `src/` → run `/implement-eip` first
Expand All @@ -55,6 +56,7 @@ When reviewing PRs that implement or test EIPs:
## Available Skills

- `/write-test` — test writing patterns, fixtures, markers, bytecode helpers
- `/enhance-ported-test` — ordered methodology to clean up & future-proof `tests/ported_static/` tests
- `/pytester` — pytester execution modes, isolation, output handling for plugin tests
- `/fill-tests` — `fill` CLI reference, flags, debugging, benchmark tests
- `/implement-eip` — fork structure, import rules, adding opcodes/precompiles/tx types
Expand Down
11 changes: 0 additions & 11 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ test-tests *args:
cd packages/testing && uv run pytest \
-n {{ xdist_workers }} \
--basetemp="{{ output_dir }}/test-tests/tmp" \
--ignore=src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py \
"$@" \
src

Expand All @@ -235,16 +234,6 @@ test-tests-pypy *args:
"$@" \
src

# Run benchmark framework unit tests (with Python)
[group('unit tests')]
[group('benchmark tests')]
test-tests-bench *args:
@mkdir -p "{{ output_dir }}/test-tests-bench/tmp"
uv run pytest \
--basetemp="{{ output_dir }}/test-tests-bench/tmp" \
"$@" \
packages/testing/src/execution_testing/cli/pytest_commands/plugins/filler/tests/test_benchmarking.py

# Run CI release script integration tests
[group('unit tests')]
test-ci-scripts *args:
Expand Down
2 changes: 1 addition & 1 deletion docs/getting_started/verifying_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Some CI jobs are slow. Only run the checks relevant to your change.
| Any PR (baseline) | `just static` | Lint, format, mypy, spellcheck, import isolation, workflow lint. |
| Added or modified tests | `just fill tests/path/to/new/tests` | See [Filling Tests](../filling_tests/index.md). |
| Framework changes (`packages/testing/`) | `just test-tests` | Framework unit tests. Mirrors the `test-tests` CI job. |
| Benchmark framework changes | `just test-tests-bench`, `just bench-gas`, `just bench-opcode`, `just bench-opcode-config` | Benchmark unit tests and sanity checks. Mirrors the benchmark CI workflow. |
| Benchmark framework changes | `just test-tests`, `just bench-gas`, `just bench-opcode`, `just bench-opcode-config` | Benchmark plugin unit tests now run within `test-tests`; the `bench-*` recipes fill/verify the suite (geth-backed on `benchmarks/**`). |
| Markdown touched | `just lint-md` | Requires `markdownlint-cli2`; see [Linting Markdown](#linting-markdown). |
| Docs touched | `just docs` or `just docs-fast` | `docs-fast` skips the Test Case Reference section for faster iteration. |

Expand Down
9 changes: 7 additions & 2 deletions docs/writing_tests/fork_methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ def test_some_feature(fork):

```python
def test_transaction_gas(fork, state_test):
gas_cost = fork.gas_costs().GAS_TX_BASE
# Derive the fork's intrinsic gas from the calculator rather than
# summing raw `gas_costs()` constants (see the Gas Parameters warning).
intrinsic_gas = fork.transaction_intrinsic_cost_calculator()()

# Create a transaction with the correct gas parameters for this fork
tx = Transaction(
gas_limit=gas_cost + 10000,
gas_limit=intrinsic_gas + 10000,
# ...
)

Expand Down Expand Up @@ -114,6 +116,9 @@ fork.memory_expansion_gas_calculator() # Returns a callable
fork.transaction_intrinsic_cost_calculator() # Returns a callable
```

!!! warning "Do not reconstruct expected gas from `gas_costs()` constants"
`fork.gas_costs()` exposes the raw schedule for framework internals. When a test needs an *expected* gas amount, derive it from a cost construct that tracks the live schedule (`bytecode.gas_cost(fork)` / `.execution_cost(fork)` / `.state_cost(fork)` / `.refund(fork)`, opcode metadata, the intrinsic/top-frame/data-floor calculators, `fork.call_value_stipend()`) rather than hand-summing constants — hand-built expectations silently break when a fork reprices. See [Opcode Metadata and Gas Calculations](opcode_metadata.md#do-not-hand-reconstruct-gas-from-constants).

### Transaction Types

Methods for determining valid transaction types:
Expand Down
15 changes: 15 additions & 0 deletions docs/writing_tests/opcode_metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ The execution testing package provides capabilities to calculate gas costs and r
- Validating gas cost calculations for specific opcode scenarios
- Future-proofing tests against breaking in upcoming forks that change gas rules

## Do Not Hand-Reconstruct Gas From Constants

Never build an expected gas amount by summing `fork.gas_costs()` constants (`NEW_ACCOUNT`, `CALL_VALUE`, `COLD_STORAGE_WRITE`, `VERY_LOW`, ...). Re-deriving the schedule by hand duplicates the framework's own calculation and silently breaks when a future fork reprices or restructures a cost. Always derive the expectation from a framework construct that tracks the live schedule:

- **The bytecode/opcode under test:** set the relevant metadata (see below) and read `bytecode.gas_cost(fork)` (execution + state), `.execution_cost(fork)`, `.state_cost(fork)`, or `.refund(fork)`. Link the exact opcode to the behavior, e.g. `Op.SELFDESTRUCT(account_new=True).state_cost(fork)`.
- **A single bare opcode/schedule cost** comes from a metadata-only opcode: `Op.BALANCE.with_metadata(address_warm=False).gas_cost(fork)` yields the cold account-access cost with no operand pushes.
- **Transaction-level costs:** `fork.transaction_intrinsic_cost_calculator()`, `fork.transaction_top_frame_state_gas(contract_creation=True)` (the created account's new-account state gas — on recent forks it is charged at the top frame, *not* in the intrinsic, so never subtract it from the intrinsic), `fork.transaction_data_floor_cost_calculator()`, and `fork.call_value_stipend()`.
- **Cross-fork / fork-transition comparisons:** evaluate the *same* bytecode or intrinsic at each fork (`before = fork.fork_at(timestamp=...)`, `after = ...`) and compare the resulting costs — do not compare raw schedule constants.

Additional rules:

- **Do not add "self-check" assertions** that compare a framework-computed value against a `fork.gas_costs()` decomposition of the same fork. They add no coverage over the runtime behavior the test already exercises and only break on repricing.
- **If the framework cannot express a cost, extend the framework** (wire the opcode into its gas/state map, add an accessor) rather than working around it in the test.
- **Exception:** a test whose *subject* is a specific schedule value — for example a regression asserting that an opcode's cost is unchanged across a fork — may compare a runtime measurement (`CodeGasMeasure`) against the fork's declared `fork.gas_costs().OPCODE_*` value. Even then, never hardcode the literal number.

## Opcode Metadata

Many opcodes accept metadata parameters that affect their gas cost calculations. Metadata represents runtime state information that influences gas consumption.
Expand Down
1 change: 1 addition & 0 deletions packages/testing/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies = [
"tenacity>=9.0.0,<10",
"Jinja2>=3,<4",
"ijson>=3.3,<4",
"eth-remerkleable==0.1.31",
]

[project.urls]
Expand Down
Loading