Skip to content

feat(checklists): add EIPChecklist enum to specify EIP checklist items #1718

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Users can select any of the artifacts depending on their testing needs for their
- 🐞 Fix bug in ported-from plugin and coverage script that made PRs fail with modified tests that contained no ported tests ([#1661](https://github.com/ethereum/execution-spec-tests/pull/1661)).
- 🔀 Refactor the `click`-based CLI interface used for pytest-based commands (`fill`, `execute`, `consume`) to make them more extensible ([#1654](https://github.com/ethereum/execution-spec-tests/pull/1654)).
- 🔀 Split `src/ethereum_test_types/types.py` into several files to improve code organization ([#1665](https://github.com/ethereum/execution-spec-tests/pull/1665)).
- ✨ Added automatic checklist generation for every EIP inside of the `tests` folder. The checklist is appended to each EIP in the documentation in the "Test Case Reference" section ([#1679](https://github.com/ethereum/execution-spec-tests/pull/1679)).
- ✨ Added automatic checklist generation for every EIP inside of the `tests` folder. The checklist is appended to each EIP in the documentation in the "Test Case Reference" section ([#1679](https://github.com/ethereum/execution-spec-tests/pull/1679), [#1718](https://github.com/ethereum/execution-spec-tests/pull/1718)).

### 🧪 Test Cases

Expand Down
452 changes: 226 additions & 226 deletions docs/writing_tests/checklist_templates/eip_testing_checklist_template.md

Large diffs are not rendered by default.

100 changes: 78 additions & 22 deletions docs/writing_tests/eip_checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,44 @@ The EIP checklist feature helps track test coverage for EIP implementations by a

When implementing tests for an EIP, you can mark specific tests as covering checklist items from the [EIP testing checklist template](../writing_tests/checklist_templates/eip_testing_checklist_template.md). The framework will then generate a filled checklist showing which items have been implemented.

## Using the `pytest.mark.eip_checklist` Marker
## Marking Tests as implementing EIP Checklist Items

To mark a test as implementing a specific checklist item:
To mark a test as implementing a specific checklist item, use the structured `EIPChecklist` class:

### The `EIPChecklist` Class

```python
import pytest
from ethereum_test_tools import StateTestFiller
from ethereum_test_checklists import EIPChecklist

@pytest.mark.eip_checklist("new_transaction_type/test/intrinsic_validity/gas_limit/exact")
@EIPChecklist.TransactionType.Test.IntrinsicValidity.GasLimit.Exact()
def test_exact_intrinsic_gas(state_test: StateTestFiller):
"""Test transaction with exact intrinsic gas limit."""
# Test implementation
pass

# You can also use the marker without parentheses
@EIPChecklist.TransactionType.Test.IntrinsicValidity.GasLimit.Insufficient
def test_insufficient_intrinsic_gas(state_test: StateTestFiller):
"""Test transaction with insufficient intrinsic gas limit."""
# Test implementation
pass
```

The `EIPChecklist` class provides type safety and IDE autocompletion, making it easier to find and reference checklist items correctly.

### Marker Parameters

- **First positional parameter** (required): The checklist item ID from the template
- **First positional parameter** (required): The checklist item ID (`EIPChecklist` reference)
- **`eip` keyword parameter** (optional): List of additional EIPs covered by the test

Example with multiple EIPs covered by the same test:

```python
@pytest.mark.eip_checklist("new_transaction_type/test/signature/invalid/v/0", eip=[7702, 2930])
@EIPChecklist.TransactionType.Test.Signature.Invalid.V.Two(
eip=[7702, 2930]
)
def test_invalid_signature(state_test: StateTestFiller):
"""Test invalid signature that affects multiple EIPs."""
pass
Expand All @@ -40,8 +54,7 @@ def test_invalid_signature(state_test: StateTestFiller):
You can use partial IDs that will match all checklist items starting with that prefix:

```python
# This will mark all items under "new_transaction_type/test/signature/invalid/" as covered
@pytest.mark.eip_checklist("new_transaction_type/test/signature/invalid/")
@EIPChecklist.TransactionType.Test.Signature.Invalid()
def test_all_invalid_signatures(state_test: StateTestFiller):
"""Test covering all invalid signature scenarios."""
pass
Expand Down Expand Up @@ -91,17 +104,44 @@ For checklist items that are not applicable to a specific EIP, create a file nam

```text
# tests/prague/eip7702_set_code_tx/eip_checklist_not_applicable.txt
new_system_contract = EIP-7702 does not introduce a system contract
new_precompile = EIP-7702 does not introduce a precompile
system_contract = EIP-7702 does not introduce a system contract
precompile = EIP-7702 does not introduce a precompile
```

Format: `checklist_item_id = reason`

Both files support partial ID matching, so you can mark entire sections as not applicable:

## MyPy Type Checking Support

The `EIPChecklist` classes are made callable through a companion `.pyi` stub file that provides proper type hints for mypy. This allows you to use both decorator patterns without type checking errors:

```python
# Both of these work with proper mypy support
@EIPChecklist.Opcode.Test.StackComplexOperations() # With parentheses
@EIPChecklist.Opcode.Test.StackComplexOperations # Without parentheses
```

### Regenerating Type Stubs

If you modify the `EIPChecklist` class structure in `src/ethereum_test_checklists/eip_checklist.py`, you need to regenerate the type stub file:

```bash
# Generate the stub file (for maintainers):
uv run generate_checklist_stubs

# Preview what would be generated without writing the file
uv run generate_checklist_stubs --dry-run

# Generate to a custom location
uv run generate_checklist_stubs --output path/to/custom/stubs.pyi
```

The generated stub file (`eip_checklist.pyi`) should be committed to the repository to ensure proper type checking for all developers.

```text
# Mark all system contract items as not applicable
new_system_contract/ = EIP does not introduce system contracts
system_contract/ = EIP does not introduce system contracts
```

## Output Format
Expand Down Expand Up @@ -135,28 +175,30 @@ Example output snippet:
| `general/code_coverage/eels` | Run produced tests against EELS... | ✅ | Covered by EELS test suite |
| `general/code_coverage/test_coverage` | Run coverage on the test code itself... | ✅ | `tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_txs` |

## New Transaction Type
## Transaction Type

| ID | Description | Status | Tests |
| -- | ----------- | ------ | ----- |
| `new_transaction_type/test/intrinsic_validity/gas_limit/exact` | Provide the exact intrinsic gas... | ✅ | `tests/prague/eip7702_set_code_tx/test_checklist_example.py::test_exact_intrinsic_gas` |
| `new_transaction_type/test/intrinsic_validity/gas_limit/insufficient` | Provide the exact intrinsic gas minus one... | | |
| `transaction_type/test/intrinsic_validity/gas_limit/exact` | Provide the exact intrinsic gas... | ✅ | `tests/prague/eip7702_set_code_tx/test_checklist_example.py::test_exact_intrinsic_gas` |
| `transaction_type/test/intrinsic_validity/gas_limit/insufficient` | Provide the exact intrinsic gas minus one... | | |

## New System Contract
## System Contract

| ID | Description | Status | Tests |
| -- | ----------- | ------ | ----- |
| `new_system_contract/test/deployment/missing` | Verify block execution behavior... | N/A | EIP-7702 does not introduce a system contract |
| `system_contract/test/deployment/missing` | Verify block execution behavior... | N/A | EIP-7702 does not introduce a system contract |
```

## Best Practices

1. **Start with the checklist**: Review the checklist template before writing tests to ensure comprehensive coverage
2. **Use descriptive test names**: The test name will appear in the checklist, so make it clear what the test covers
3. **Mark items as you go**: Add `eip_checklist` markers while writing tests, not as an afterthought
4. **Document external coverage**: If items are covered by external tools/tests, document this in `eip_checklist_external_coverage.txt`
5. **Be explicit about N/A items**: Document why items are not applicable in `eip_checklist_not_applicable.txt`
6. **Use partial IDs wisely**: When a test covers multiple related items, use partial IDs to mark them all
2. **Use the `EIPChecklist` class**: Use `EIPChecklist.Opcode.Test.GasUsage.Normal` for type safety and IDE autocompletion
3. **Use descriptive test names**: The test name will appear in the checklist, so make it clear what the test covers
4. **Mark items as you go**: Add `eip_checklist` markers while writing tests, not as an afterthought
5. **Document external coverage**: If items are covered by external tools/tests, document this in `eip_checklist_external_coverage.txt`
6. **Be explicit about N/A items**: Document why items are not applicable in `eip_checklist_not_applicable.txt`
7. **Use partial IDs wisely**: When a test covers multiple related items, use partial IDs to mark them all
8. **Verify IDs before using**: Use `str(EIPChecklist.Section.Subsection)` to verify the exact string ID when needed

## Workflow Example

Expand All @@ -174,7 +216,9 @@ Example output snippet:
2. **Mark tests as you implement them**:

```python
@pytest.mark.eip_checklist("new_opcode/test/gas_usage/normal")
from ethereum_test_checklists import EIPChecklist

@EIPChecklist.Opcode.Test.GasUsage.Normal()
def test_opcode_gas_consumption(state_test: StateTestFiller):
"""Test normal gas consumption of the new opcode."""
pass
Expand All @@ -187,11 +231,23 @@ Example output snippet:
general/code_coverage/eels = Covered by ethereum/execution-specs PR #1234
```

You can verify the correct ID using:

```python
# str(EIPChecklist.General.CodeCoverage.Eels) = "general/code_coverage/eels"
```

4. **Mark non-applicable items**:

```text
# eip_checklist_not_applicable.txt
new_precompile/ = EIP-9999 introduces an opcode, not a precompile
precompile/ = EIP-9999 introduces an opcode, not a precompile
```

You can verify the correct ID using:

```python
# str(EIPChecklist.Precompile) = "precompile"
```

5. **Generate and review checklist**:
Expand Down
6 changes: 3 additions & 3 deletions docs/writing_tests/exception_tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ Exception tests are a special type of test which verify that an invalid transact

To test for an exception, the test can use either of the following types from `ethereum_test_exceptions` library:

1. [`TransactionException`](../running_tests/test_formats/exceptions.md#transactionexception): To be added to the `error` field of the `Transaction` object, and to the `exception` field of the `Block` object that includes the transaction; this exception type is used when a transaction is invalid, and therefore when included in a block, the block is expected to be invalid too. This is different from valid transactions where an exception during EVM execution is expected (e.g. a revert, or out-of-gas), which can be included in valid blocks.
1. [`TransactionException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.TransactionException): To be added to the `error` field of the `Transaction` object, and to the `exception` field of the `Block` object that includes the transaction; this exception type is used when a transaction is invalid, and therefore when included in a block, the block is expected to be invalid too. This is different from valid transactions where an exception during EVM execution is expected (e.g. a revert, or out-of-gas), which can be included in valid blocks.

For an example, see [`eip3860_initcode.test_initcode.test_contract_creating_tx`](../tests/shanghai/eip3860_initcode/test_initcode/test_contract_creating_tx.md) which raises `TransactionException.INITCODE_SIZE_EXCEEDED` in the case that the initcode size exceeds the maximum allowed size.

2. [`BlockException`](../running_tests/test_formats/exceptions.md#blockexception): To be added to the `exception` field of the `Block` object; this exception type is used when a block is expected to be invalid, but the exception is related to a block property, e.g. an invalid value of the block header.
2. [`BlockException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.BlockException): To be added to the `exception` field of the `Block` object; this exception type is used when a block is expected to be invalid, but the exception is related to a block property, e.g. an invalid value of the block header.

For an example, see [`eip4844_blobs.test_excess_blob_gas.test_invalid_static_excess_blob_gas`](../tests/cancun/eip4844_blobs/test_excess_blob_gas/test_invalid_static_excess_blob_gas.md) which raises `BlockException.INCORRECT_EXCESS_BLOB_GAS` in the case that the `excessBlobGas` remains unchanged
but the parent blobs included are not `TARGET_BLOBS_PER_BLOCK`.
Expand All @@ -19,7 +19,7 @@ Although exceptions can be combined with the `|` operator to indicate that a tes

## Adding a new exception

If a test requires a new exception, because none of the existing ones is suitable for the test, a new exception can be added to either [`TransactionException`](../running_tests/test_formats/exceptions.md#transactionexception) or [`BlockException`](../running_tests/test_formats/exceptions.md#blockexception) classes.
If a test requires a new exception, because none of the existing ones is suitable for the test, a new exception can be added to either [`TransactionException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.TransactionException) or [`BlockException`](../library/ethereum_test_exceptions.md#ethereum_test_exceptions.BlockException) classes.

The new exception should be added as a new enum value, and the docstring of the attribute should be a string that describes the exception.

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ check_eip_versions = "cli.pytest_commands.check_eip_versions:check_eip_versions"
consume = "cli.pytest_commands.consume:consume"
protec = "cli.pytest_commands.consume:consume"
checklist = "cli.pytest_commands.checklist:checklist"
generate_checklist_stubs = "cli.generate_checklist_stubs:generate_checklist_stubs"
genindex = "cli.gen_index:generate_fixtures_index_cli"
gentest = "cli.gentest:generate"
eofwrap = "cli.eofwrap:eof_wrap"
Expand Down
Loading
Loading