You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This tutorial teaches you to create a state transition execution specification test. These tests verify that a starting pre-state will reach a specified post-state after executing a single transaction.
3
+
This tutorial teaches you to create a state transition execution specification test using the Python Opcodes minilang for writing EVM bytecode. These tests verify that a starting pre-state will reach a specified post-state after executing a single transaction. In this example, we'll create a simple contract using bytecode and then interact with it through a transaction to verify the expected state changes.
4
+
5
+
For an overview of different test types available, see [Types of Tests](../../writing_tests/types_of_tests.md).
4
6
5
7
## Pre-requisites
6
8
7
-
Before proceeding with this tutorial, it is assumed that you have prior knowledge and experience with the following:
9
+
This tutorial will require some prior knowledge and experience with the following:
10
+
11
+
- Repository set-up, see [installation](../../getting_started/installation.md).
12
+
- Ability to run `fill`, see [Getting Started: Filling Tests](../../filling_tests/getting_started.md).
13
+
- Basic familiarity with [Python](https://docs.python.org/3/tutorial/).
14
+
15
+
## Building a State Test
16
+
17
+
The most effective method of learning how to write tests is to study a straightforward example. In this tutorial we will build a simple state test that deploys a contract with bytecode and verifies its execution.
8
18
9
-
- Repository set-up, see [installation](../../getting_started/installation.md).and run an execution specification test as outlined in the .
10
-
- Able to run `fill`, see [Getting Started: Filling Tests](../../filling_tests/getting_started.md).
11
-
- Understand how to read a [static state transition test](https://ethereum-tests.readthedocs.io/en/latest/state-transition-tutorial.html#the-source-code).
12
-
- Know the basics of the [EVM](https://www.evm.codes/).
13
-
- Familiarity with [Python](https://docs.python.org/3/tutorial/).
19
+
### Complete Test Example
14
20
15
-
## Example Test
21
+
We'll examine a simple test that uses the Python Opcodes minilang to write EVM bytecode. This example is based on the CHAINID opcode test from `tests/istanbul/eip1344_chainid/test_chainid.py`.
22
+
23
+
Let's examine each section.
24
+
25
+
```python
26
+
"""State test tutorial demonstrating contract deployment and interaction."""
27
+
```
16
28
17
-
The most effective method of learning how to write tests is to study a straightforward example. In this tutorial we will go over a simple state test that adds two numbers and stores them in the storage.
29
+
In Python, multi-line strings are denoted using `"""`. As a convention, a file's purpose is often described in the opening string of the file.
18
30
19
31
```python
20
-
from ethereum_test_forks import Fork, Frontier, Homestead
21
-
from ethereum_test_tools import (
22
-
Account,
23
-
Alloc,
24
-
Environment,
25
-
StateTestFiller,
26
-
Transaction,
27
-
)
32
+
import pytest
33
+
34
+
from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction
35
+
from ethereum_test_tools.vm.opcode import Opcodes as Op
28
36
```
29
37
30
-
In this snippet the required constants, types and helper functions are imported from `ethereum_test_tools` and `ethereum_test_forks`. We will go over these as we come across them.
38
+
In this snippet the required constants, types and helper functions are imported from `ethereum_test_tools`. The `Opcodes` class (aliased as `Op`) provides the Python minilang for writing EVM bytecode. We will go over these as we come across them.
31
39
32
40
```python
33
-
@pytest.mark.valid_from("Homestead")
41
+
@pytest.mark.valid_from("Istanbul")
34
42
```
35
43
36
44
In Python this kind of definition is called a [*decorator*](https://docs.python.org/3/search.html?q=decorator).
37
45
It modifies the action of the function after it.
38
-
In this case, the decorator is a custom [pytest fixture](https://docs.pytest.org/en/latest/explanation/fixtures.html) defined by the execution-specs-test framework that specifies that the test is valid for the [Homestead fork](https://ethereum.org/en/history/#homestead) and all forks after it. The framework will then fill this test case for all forks in the fork range specified by the command-line arguments.
46
+
In this case, the decorator is a custom [pytest mark](https://docs.pytest.org/en/latest/how-to/mark.html) defined by the execution-specs-test framework that specifies that the test is valid for the [Istanbul fork](https://ethereum.org/en/history/#istanbul) and all forks after it. The framework will then fill this test case for all forks in the fork range specified by the command-line arguments.
47
+
48
+
For more information about test markers and fork validity, see [Test Markers](../../writing_tests/test_markers.md).
39
49
40
50
!!! info "Filling the test"
41
51
To fill this test for all the specified forks, we can specify pytest's `-k` flag that [filters test cases by keyword expression](https://docs.pytest.org/en/latest/how-to/usage.html#specifying-tests-selecting-tests):
42
52
43
53
```console
44
-
fill -k test_example
54
+
fill -k test_state_test_example
45
55
```
46
56
47
57
and to fill it for a specific fork range, we can provide the `--from` and `--until` command-line arguments:
48
58
49
59
```console
50
-
fill -k test_example --from London --until Paris
60
+
fill -k test_state_test_example --from London --until Paris
"""Test state transition using Opcodes minilang bytecode."""
58
66
```
59
67
60
68
This is the format of a [Python function](https://docs.python.org/3/tutorial/controlflow.html#defining-functions).
61
69
It starts with `def <function name>(<parameters>):`, and then has indented code for the function.
62
70
The function definition ends when there is a line that is no longer indented. As with files, by convention functions start with a string that explains what the function does.
63
71
64
-
!!! note "The `state_test` function argument"
65
-
This test defines a state test and, as such, *must* include the `state_test` in its function arguments. This is a callable object (actually a wrapper class to the `StateTest`); we will see how it is called later.
72
+
The function parameters (`state_test` and `pre`) are [pytest fixtures](https://docs.pytest.org/en/latest/explanation/fixtures.html) provided by the execution-spec-tests framework. Pytest fixtures are a powerful dependency injection mechanism that automatically provide objects to your test functions.
66
73
67
-
!!! note "The `pre` function argument"
68
-
For all types of tests, it is highly encouraged that we define the `pre` allocation as a function argument, which will be populated with the pre-state requirements during the execution of the test function (see below).
74
+
**The `state_test` fixture** is a callable that you *must* include in *state test* function arguments. When called at the end of your test function with the environment, pre-state, transaction, and expected post-state, it generates the actual test fixtures. This callable is a wrapper around the `StateTest` class.
75
+
76
+
**The `pre` fixture** provides an `Alloc` object that manages the pre-state allocation for your test. It offers methods like `fund_eoa()` and `deploy_contract()` that automatically generate unique addresses and add accounts to the blockchain state that will exist before your transaction executes. The fixture handles address generation and ensures no conflicts occur.
69
77
70
78
```python
71
-
env = Environment()
79
+
env = Environment(number=1)
72
80
```
73
81
74
-
This line specifies that `env` is an [`Environment`](https://github.com/ethereum/execution-spec-tests/blob/8b4504aaf6ae0b69c3e847a6c051e64fcefa4db0/src/ethereum_test_tools/common/types.py#L711) object, and that we just use the default parameters.
75
-
If necessary we can modify the environment to have different block gas limits, block numbers, etc.
76
-
In most tests the defaults are good enough.
77
-
78
-
For more information, [see the static test documentation](../../running_tests/test_formats/state_test.md).
82
+
This line specifies that `env` is an [`Environment`](https://github.com/ethereum/execution-spec-tests/blob/8b4504aaf6ae0b69c3e847a6c051e64fcefa4db0/src/ethereum_test_tools/common/types.py#L711) object. In this example, we only override the block `number` to 1, leaving all other values at their defaults. It's recommended to use default values whenever possible and only specify custom values when required for your specific test scenario.
79
83
80
-
### Pre State
84
+
####Pre State
81
85
82
-
For every test we need to define the pre-state requirements, so we are certain of what is on the "blockchain" before the transaction is executed.
83
-
It can be used as a [dictionary](https://docs.python.org/3/tutorial/datastructures.html#dictionaries), which is the Python term for an associative array, but the appropriate way to populate it is by using the methods `fund_eoa`, `deploy_contract` or `fund_address` from the `Alloc` object.
86
+
For every test we need to define the pre-state requirements, so we are certain of what is on the "blockchain" before the transaction is executed. The `pre` fixture provides an `Alloc` object with methods to create accounts that are automatically added to the pre-state.
84
87
85
88
In this example we are using the `deploy_contract` method to deploy a contract to some address available in the pre-state.
Specifically we deploy a contract with EVM bytecode that adds two numbers and stores the result in storage.
96
+
Specifically we deploy a contract written with Opcodes minilang code that stores the value `0x03` at storage slot `0x00`. The code consists of:
101
97
102
-
```python
103
-
balance=0x0BA1A9CE0BA1A9CE,
104
-
```
98
+
-`PUSH1(0x03)`: Push the value 3 onto the stack.
99
+
-`PUSH1(0x00)`: Push the storage key 0 onto the stack.
100
+
-`SSTORE`: Store the value at the specified key.
101
+
-`STOP`: End execution.
105
102
106
-
This field is the balance: the amount of Wei that the account has. It usually doesn't matter what its value is in the case of state test contracts.
103
+
As the return value of the `deploy_contract` method, we get the address where the contract was deployed. This address is stored in the `contract_address` variable, which will later be used as the target of our transaction.
107
104
108
-
```python
109
-
contract_address = pre.deploy_contract(
110
-
```
105
+
You can also specify additional parameters for the contract if needed:
111
106
112
-
As return value of the `deploy_contract` method we get the address where the contract was deployed and put it in the `contract_address` variable, which will later be used in the transaction.
107
+
-`balance` parameter to set the contract's initial balance (though often not necessary for state test contracts)
108
+
-`storage` parameter to set initial storage values (though in this example we don't need initial storage since our contract will set it through the `SSTORE` opcode)
113
109
114
-
```python
115
-
storage={
116
-
0x00: 0x03,
117
-
},
118
-
```
110
+
You can combine opcodes using the `+` operator to create more complex bytecode sequences.
119
111
120
-
We could also specify a starting storage for the contract, which is done by adding a `storage` parameter to the `deploy_contract` method.
112
+
Generally for execution spec tests the `SSTORE` instruction acts as a high-level assertion method to check pre to post-state changes. The test filler achieves this by verifying that the correct value is held within post-state storage, hence we can validate that the bytecode has run successfully.
121
113
122
-
Generally for execution spec tests the `sstore` instruction acts as a high-level assertion method to check pre to post-state changes. The test filler achieves this by verifying that the correct value is held within post-state storage, hence we can validate that the code has run successfully.
114
+
Next, we need to create an account that will send the transaction to our contract:
123
115
124
116
```python
125
-
sender= pre.fund_eoa(amount=0x0BA1A9CE0BA1A9CE)
117
+
sender = pre.fund_eoa()
126
118
```
127
119
128
-
In this line we specify that we require a single externally owned account (EOA) with a balance of `0x0BA1A9CE0BA1A9CE`Wei.
120
+
This line creates a single externally owned account (EOA) with a default balance. You can specify a custom amount with `amount=0x0BA1A9CE0BA1A9CE`if needed.
129
121
130
122
The returned object, which includes a private key, an address, and a nonce, is stored in the `sender` variable and will later be used as the sender of the transaction.
131
123
132
124
#### Transactions
133
125
134
126
```python
135
127
tx = Transaction(
136
-
ty=0x0,
137
-
chain_id=0x01,
128
+
ty=0x2,
138
129
sender=sender,
139
130
to=contract_address,
140
-
gas_limit=500000,
141
-
gas_price=10,
142
-
protected=Falseif fork in [Frontier, Homestead] elseTrue,
131
+
gas_limit=100_000,
143
132
)
144
133
```
145
134
146
-
With the pre-state built, we can add a description for the [`Transaction`](https://github.com/ethereum/execution-spec-tests/blob/8b4504aaf6ae0b69c3e847a6c051e64fcefa4db0/src/ethereum_test_tools/common/types.py#L887).
147
-
148
-
```python
149
-
sender=sender,
150
-
```
151
-
152
-
We use the sender variable from the pre-state to specify the sender of the transaction, which already has the necessary information to sign the transaction, and also contains the correct `nonce`for the transaction.
135
+
With the pre-state built, we can now create the transaction that will call our contract. Let's examine the key components of this [`Transaction`](../../../src/ethereum_test_tools/common/types.py):
153
136
154
-
The `nonce`is a protection mechanism to prevent replay attacks, andthe current rules of Ethereum require that the nonce of a transaction isequal to the number of transactions sent from the sender's address, starting from zero. This means that the first transaction sent from an address must have a nonce of zero, the second transaction must have a nonce of one, and so on.
137
+
-**`sender=sender`**: We use the EOA we created earlier, which already has the necessary information to sign the transaction and contains the correct `nonce`. The `nonce`is a protection mechanism to prevent replay attacks - it must equal the number of transactions sent from the sender's address, starting from zero. The framework automatically manages nonce incrementing for us.
155
138
156
-
The `nonce` field of the `sender` variable is automatically incremented for us by the `Transaction`object when the transaction is signed, so ifwe were to create another transaction withthe same sender, the nonce would be incremented by one yet another time.
139
+
-**`to=contract_address`**: This specifies the address of the contract we want to call, which is the contract we deployed earlier.
157
140
158
-
```python
159
-
to=contract_address,
160
-
```
141
+
-**`gas_limit=100_000`**: This sets a high enough gas limit to ensure our simple contract execution doesn't run out of gas.
161
142
162
-
The `to` field specifies the address of the contract we want to call and, in this case, it is the address of the contract we deployed earlier.
143
+
-**`ty=0x2`**: This specifies the transaction type (EIP-1559).
163
144
164
-
For more information, [see the static test documentation](../../running_tests/test_formats/state_test.md)
145
+
For more information, [see the static test documentation](../../running_tests/test_formats/state_test.md).
165
146
166
147
#### Post State
167
148
149
+
Now we need to define what we expect the blockchain state to look like after our transaction executes:
150
+
168
151
```python
169
152
post = {
170
153
contract_address: Account(
@@ -177,16 +160,25 @@ For more information, [see the static test documentation](../../running_tests/te
177
160
178
161
This is the post-state which is equivalent to [`expect`](https://ethereum-tests.readthedocs.io/en/latest/test_filler/state_filler.html#expect) in static tests, but without the indexes. It is similar to the pre-state, except that we do not need to specify everything, only those accounts and fields we wish to test.
179
162
180
-
In this case, we look at the storage of the contract we called and add to it what we expect to see. In this example storage cell `0x00` should be `0x03`asin the pre-state we essentially stored the result of the instruction `add(1, 2)`.
163
+
In this case, we look at the storage of the contract we called and add to it what we expect to see. In this example storage cell `0x00` should be `0x03` as we stored this value using the `SSTORE` opcode in our contract bytecode.
181
164
182
-
#### State Test
165
+
#### Running the State Test
166
+
167
+
Finally, we execute the test by calling the state test wrapper with all our defined components:
183
168
184
169
```python
185
170
state_test(env=env, pre=pre, post=post, tx=tx)
186
171
```
187
172
188
-
This line calls the wrapper to the `StateTest`object that provides all the objects required (for example, the fork parameter) in order to fill the test, generate the test fixtures and write them to file (by default, `./fixtures/<blockchain,state>_tests/example/test_example.json`).
173
+
This line calls the wrapper to the `StateTest` object that provides all the objects required in order to fill the test, generate the test fixtures and write them to file (by default, `./fixtures/<blockchain,state>_tests/example/state_test_example/test_state_test_example.json`).
174
+
175
+
Note that even though we defined a `StateTest`, the `fill` command will also generate other derivative test fixtures: `BlockchainTest`, `BlockchainTestEngine`, and `BlockchainTestEngineX`. For more information about test types and when to use each, see [Test Types: Prefer StateTest for Single Transactions](../../writing_tests/types_of_tests/#prefer-state_test-for-single-transactions).
189
176
190
177
## Conclusion
191
178
192
-
At this point you should be able to state transition tests within a single block.
179
+
At this point you should be able to write state transition tests within a single block.
180
+
181
+
## Next Steps
182
+
183
+
- Learn about [Adding a New Test](../../writing_tests/adding_a_new_test.md) to understand test organization and structure.
184
+
- Explore [Fork Methods](../../writing_tests/fork_methods.md) for writing tests that adapt to different Ethereum forks.
0 commit comments