Skip to content

Commit ee05f04

Browse files
authored
docs(forks): add a page about the available fork methods (#1630)
1 parent 660bb22 commit ee05f04

File tree

4 files changed

+297
-1
lines changed

4 files changed

+297
-1
lines changed

docs/navigation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* [Verifying Changes Locally](writing_tests/verifying_changes.md)
2020
* [Code Standards](writing_tests/code_standards.md)
2121
* [Exception Tests](writing_tests/exception_tests.md)
22+
* [Using and Extending Fork Methods](writing_tests/fork_methods.md)
2223
* [Referencing an EIP Spec Version](writing_tests/reference_specification.md)
2324
* [Testing Checklist Templates](writing_tests/checklist_templates/index.md)
2425
* [EIP Execution Layer Testing Checklist Template](writing_tests/checklist_templates/eip_testing_checklist_template.md)

docs/writing_tests/fork_methods.md

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
# Using and Extending Fork Methods
2+
3+
This document describes the Fork class in the Ethereum execution spec tests framework, which provides a standardized way to define properties of Ethereum forks. Understanding how to use and extend these fork methods is essential for writing flexible tests that can automatically adapt to different forks.
4+
5+
## Overview
6+
7+
The `BaseFork` class is an abstract base class that defines the interface for all Ethereum forks. Each implemented fork (like Frontier, Homestead, etc.) extends this class and implements its abstract methods to provide fork-specific behavior.
8+
9+
The fork system allows:
10+
11+
1. Defining fork-specific behaviors and parameters
12+
2. Comparing forks chronologically (`Paris < Shanghai`)
13+
3. Supporting automatic fork transitions
14+
4. Writing tests that automatically adapt to different forks
15+
16+
## Using Fork Methods in Tests
17+
18+
Fork methods are powerful tools that allow your tests to adapt to different Ethereum forks automatically. Here are common patterns for using them:
19+
20+
### 1. Check Fork Support for Features
21+
22+
```python
23+
def test_some_feature(fork):
24+
if fork.supports_blobs(block_number=0, timestamp=0):
25+
# Test blob-related functionality
26+
...
27+
else:
28+
# Test alternative or skip
29+
pytest.skip("Fork does not support blobs")
30+
```
31+
32+
### 2. Get Fork-Specific Parameters
33+
34+
```python
35+
def test_transaction_gas(fork, state_test):
36+
gas_cost = fork.gas_costs(block_number=0, timestamp=0).G_TRANSACTION
37+
38+
# Create a transaction with the correct gas parameters for this fork
39+
tx = Transaction(
40+
gas_limit=gas_cost + 10000,
41+
# ...
42+
)
43+
44+
state_test(
45+
env=Environment(),
46+
pre=pre,
47+
tx=tx,
48+
# ...
49+
)
50+
```
51+
52+
### 3. Determine Valid Transaction Types
53+
54+
```python
55+
def test_transaction_types(fork, state_test):
56+
for tx_type in fork.tx_types(block_number=0, timestamp=0):
57+
# Test each transaction type supported by this fork
58+
# ...
59+
```
60+
61+
### 4. Determine Valid Opcodes
62+
63+
```python
64+
def test_opcodes(fork, state_test):
65+
# Create bytecode using only opcodes valid for this fork
66+
valid_opcodes = fork.valid_opcodes()
67+
68+
# Use these opcodes to create test bytecode
69+
# ...
70+
```
71+
72+
### 5. Test Fork Transitions
73+
74+
```python
75+
def test_fork_transition(transition_fork, blockchain_test):
76+
# The transition_fork is a special fork type that changes behavior
77+
# based on block number or timestamp
78+
fork_before = transition_fork.fork_at(block_number=4, timestamp=0)
79+
fork_after = transition_fork.fork_at(block_number=5, timestamp=0)
80+
81+
# Test behavior before and after transition
82+
# ...
83+
```
84+
85+
## Important Fork Methods
86+
87+
### Header Information
88+
89+
These methods determine what fields are required in block headers for a given fork:
90+
91+
```python
92+
fork.header_base_fee_required(block_number=0, timestamp=0) # Added in London
93+
fork.header_prev_randao_required(block_number=0, timestamp=0) # Added in Paris
94+
fork.header_withdrawals_required(block_number=0, timestamp=0) # Added in Shanghai
95+
fork.header_excess_blob_gas_required(block_number=0, timestamp=0) # Added in Cancun
96+
fork.header_blob_gas_used_required(block_number=0, timestamp=0) # Added in Cancun
97+
fork.header_beacon_root_required(block_number=0, timestamp=0) # Added in Cancun
98+
fork.header_requests_required(block_number=0, timestamp=0) # Added in Prague
99+
```
100+
101+
### Gas Parameters
102+
103+
Methods for determining gas costs and calculations:
104+
105+
```python
106+
fork.gas_costs(block_number=0, timestamp=0) # Returns a GasCosts dataclass
107+
fork.memory_expansion_gas_calculator(block_number=0, timestamp=0) # Returns a callable
108+
fork.transaction_intrinsic_cost_calculator(block_number=0, timestamp=0) # Returns a callable
109+
```
110+
111+
### Transaction Types
112+
113+
Methods for determining valid transaction types:
114+
115+
```python
116+
fork.tx_types(block_number=0, timestamp=0) # Returns list of supported transaction types
117+
fork.contract_creating_tx_types(block_number=0, timestamp=0) # Returns list of tx types that can create contracts
118+
fork.precompiles(block_number=0, timestamp=0) # Returns list of precompile addresses
119+
fork.system_contracts(block_number=0, timestamp=0) # Returns list of system contract addresses
120+
```
121+
122+
### EVM Features
123+
124+
Methods for determining EVM features and valid opcodes:
125+
126+
```python
127+
fork.evm_code_types(block_number=0, timestamp=0) # Returns list of supported code types (e.g., Legacy, EOF)
128+
fork.valid_opcodes() # Returns list of valid opcodes for this fork
129+
fork.call_opcodes(block_number=0, timestamp=0) # Returns list of call opcodes with their code types
130+
fork.create_opcodes(block_number=0, timestamp=0) # Returns list of create opcodes with their code types
131+
```
132+
133+
### Blob-related Methods (Cancun+)
134+
135+
Methods for blob transaction support:
136+
137+
```python
138+
fork.supports_blobs(block_number=0, timestamp=0) # Returns whether blobs are supported
139+
fork.blob_gas_price_calculator(block_number=0, timestamp=0) # Returns a callable
140+
fork.excess_blob_gas_calculator(block_number=0, timestamp=0) # Returns a callable
141+
fork.min_base_fee_per_blob_gas(block_number=0, timestamp=0) # Returns minimum base fee per blob gas
142+
fork.blob_gas_per_blob(block_number=0, timestamp=0) # Returns blob gas per blob
143+
fork.target_blobs_per_block(block_number=0, timestamp=0) # Returns target blobs per block
144+
fork.max_blobs_per_block(block_number=0, timestamp=0) # Returns max blobs per block
145+
```
146+
147+
### Meta Information
148+
149+
Methods for fork identification and comparison:
150+
151+
```python
152+
fork.name() # Returns the name of the fork
153+
fork.transition_tool_name(block_number=0, timestamp=0) # Returns name for transition tools
154+
fork.solc_name() # Returns name for the solc compiler
155+
fork.solc_min_version() # Returns minimum solc version supporting this fork
156+
fork.blockchain_test_network_name() # Returns network name for blockchain tests
157+
fork.is_deployed() # Returns whether the fork is deployed to mainnet
158+
```
159+
160+
## Fork Transitions
161+
162+
The framework supports creating transition forks that change behavior at specific block numbers or timestamps:
163+
164+
```python
165+
@transition_fork(to_fork=Shanghai, at_timestamp=15_000)
166+
class ParisToShanghaiAtTime15k(Paris):
167+
"""Paris to Shanghai transition at Timestamp 15k."""
168+
pass
169+
```
170+
171+
With transition forks, you can test how behavior changes across fork boundaries:
172+
173+
```python
174+
# Behavior changes at block 5
175+
fork = BerlinToLondonAt5
176+
assert not fork.header_base_fee_required(block_number=4) # Berlin doesn't require base fee
177+
assert fork.header_base_fee_required(block_number=5) # London requires base fee
178+
```
179+
180+
## Adding New Fork Methods
181+
182+
When adding new fork methods, follow these guidelines:
183+
184+
1. **Abstract Method Definition**: Add the new abstract method to `BaseFork` in `base_fork.py`
185+
2. **Consistent Parameter Pattern**: Use `block_number` and `timestamp` parameters with default values
186+
3. **Method Documentation**: Add docstrings explaining the purpose and behavior
187+
4. **Implementation in Subsequent Forks**: Implement the method in every subsequent fork class **only** if the fork updates the value from previous forks.
188+
189+
Example of adding a new method:
190+
191+
```python
192+
@classmethod
193+
@abstractmethod
194+
def supports_new_feature(cls, block_number: int = 0, timestamp: int = 0) -> bool:
195+
"""Return whether the given fork supports the new feature."""
196+
pass
197+
```
198+
199+
Implementation in a fork class:
200+
201+
```python
202+
@classmethod
203+
def supports_new_feature(cls, block_number: int = 0, timestamp: int = 0) -> bool:
204+
"""Return whether the given fork supports the new feature."""
205+
return False # Frontier doesn't support this feature
206+
```
207+
208+
Implementation in a newer fork class:
209+
210+
```python
211+
@classmethod
212+
def supports_new_feature(cls, block_number: int = 0, timestamp: int = 0) -> bool:
213+
"""Return whether the given fork supports the new feature."""
214+
return True # This fork does support the feature
215+
```
216+
217+
## When to Add a New Fork Method
218+
219+
Add a new fork method when:
220+
221+
1. **A New EIP Introduces a Feature**: Add methods describing the new feature's behavior
222+
2. **Tests Need to Behave Differently**: When tests need to adapt to different fork behaviors
223+
3. **Common Fork Information is Needed**: When multiple tests need the same fork-specific information
224+
4. **Intrinsic Fork Properties Change**: When gas costs, opcodes, or other intrinsic properties change
225+
226+
Do not add a new fork method when:
227+
228+
1. The information is only needed for one specific test
229+
2. The information is not directly related to fork behavior
230+
3. The information can be calculated using existing methods
231+
232+
## Best Practices
233+
234+
1. **Use Existing Methods**: Check if there's already a method that provides the information you need
235+
2. **Name Methods Clearly**: Method names should clearly describe what they return
236+
3. **Document Behavior**: Include clear docstrings explaining the method's purpose and return value
237+
4. **Avoid Hard-coding**: Use fork methods in tests instead of hard-coding fork-specific behavior
238+
5. **Test Transitions**: Ensure your method works correctly with transition forks
239+
240+
## Example: Complete Test Using Fork Methods
241+
242+
Here's an example of a test that fully utilizes fork methods to adapt its behavior:
243+
244+
```python
245+
def test_transaction_with_fork_adaptability(fork, state_test):
246+
# Prepare pre-state
247+
pre = Alloc()
248+
sender = pre.fund_eoa()
249+
250+
# Define transaction based on fork capabilities
251+
tx_params = {
252+
"gas_limit": 1_000_000,
253+
"sender": sender,
254+
}
255+
256+
# Add appropriate transaction type based on fork
257+
tx_types = fork.tx_types(block_number=0, timestamp=0)
258+
if 3 in tx_types and fork.supports_blobs(block_number=0, timestamp=0):
259+
# EIP-4844 blob transaction (type 3)
260+
tx_params["blob_versioned_hashes"] = [Hash.generate_zero_hashes(1)[0]]
261+
elif 2 in tx_types:
262+
# EIP-1559 transaction (type 2)
263+
tx_params["max_fee_per_gas"] = 10
264+
tx_params["max_priority_fee_per_gas"] = 1
265+
elif 1 in tx_types:
266+
# EIP-2930 transaction (type 1)
267+
tx_params["access_list"] = []
268+
269+
# Create and run the test
270+
tx = Transaction(**tx_params)
271+
272+
state_test(
273+
env=Environment(),
274+
pre=pre,
275+
tx=tx,
276+
post={
277+
sender: Account(nonce=1),
278+
},
279+
)
280+
```
281+
282+
## Conclusion
283+
284+
The Fork class is a powerful abstraction that allows tests to adapt to different Ethereum forks. By using fork methods consistently, you can write tests that automatically handle fork-specific behavior, making your tests more maintainable and future-proof.
285+
286+
When adding new fork methods, keep them focused, well-documented, and implement them across all forks. This will ensure that all tests can benefit from the information and that transitions between forks are handled correctly.

docs/writing_tests/index.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,11 @@ For help deciding which test format to select, see [Types of Tests](./types_of_t
1919
- [tests.berlin.eip2930_access_list.test_acl.test_access_list](../tests/berlin/eip2930_access_list/test_acl/test_access_list.md).
2020
- [tests.istanbul.eip1344_chainid.test_chainid.test_chainid](../tests/istanbul/eip1344_chainid/test_chainid/test_chainid.md).
2121

22-
Please check that your code adheres to the repo's [Coding Standards](./code_standards.md) and read the other pages in this section for more background and an explanation of how to implement state transition and blockchain tests.
22+
## Key Resources
23+
24+
- [Coding Standards](./code_standards.md) - Code style and standards for this repository
25+
- [Adding a New Test](./adding_a_new_test.md) - Step-by-step guide to adding new tests
26+
- [Writing a New Test](./writing_a_new_test.md) - Detailed guide on writing different test types
27+
- [Using and Extending Fork Methods](./fork_methods.md) - How to use fork methods to write fork-adaptive tests
28+
29+
Please check that your code adheres to the repo's coding standards and read the other pages in this section for more background and an explanation of how to implement state transition and blockchain tests.

whitelist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ base64
3131
basefee
3232
basename
3333
bb
34+
BerlinToLondonAt
3435
besu
3536
pyspecs
3637
big0
@@ -145,6 +146,7 @@ EngineAPI
145146
enum
146147
env
147148
envvar
149+
eoa
148150
EOA
149151
EOAs
150152
eof

0 commit comments

Comments
 (0)