Skip to content

docs: add getting started guide for EEST opcode minilang #1818

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 10 commits into from
Jul 7, 2025
65 changes: 57 additions & 8 deletions docs/writing_tests/writing_a_new_test.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,66 @@ which allows checking for an exact `gas_used` value.

## Writing code for the accounts in the test

Account bytecode can be embedded in the test accounts by adding it to the `code`
field of the `account` object, or the `data` field of the `tx` object if the
bytecode is meant to be treated as init code or call data.
Account bytecode can be "deployed" in a test's pre-state using the `pre` pytest fixture. The @ethereum/execution-spec-tests Python [`Opcodes`][ethereum_test_vm.Opcodes] minilang can be used to help write the bytecode in a readable form.

The code can be in either of the following formats:
### Using the Python Opcode Minilang

- `bytes` object, representing the raw opcodes in binary format.
- `str`, representing an hexadecimal format of the opcodes.
- `Code` compilable object.
EVM bytecode for tests should be written using the Python-based minilang provided by the [`Opcodes`][ethereum_test_vm.Opcodes] class. This allows you to construct bytecode using symbolic opcodes as Python objects.

`Code` objects can be concatenated together by using the `+` operator.
#### Example: Simple Addition Contract

```python
from ethereum_test_vm.opcode import Opcodes

code = (
Opcodes.PUSH1(0x02)
+ Opcodes.PUSH1(0x03)
+ Opcodes.ADD()
+ Opcodes.PUSH1(0x00)
+ Opcodes.SSTORE()
+ Opcodes.STOP()
)

# within a test function, using the "pre" fixture
contract_address = pre.deploy_contract(code=code)
```

You add this contract to the test's pre-state using the `pre` fixture or assign this `code` to the `code` field of an account in your test's `post` state. See the [state test tutorial](./tutorials/state_transition.md) for more help.

For a full list of available opcodes and their usage, see [`Opcodes`][ethereum_test_vm.Opcodes].

#### Higher-Level Constructs

For more complex control flow, you can use constructs like [`Switch`][ethereum_test_tools.code.generators.Switch] and [`Case`][ethereum_test_tools.code.generators.Case] from the `ethereum_test_tools.code.generators` module:

```python
from ethereum_test_tools.code.generators import Switch, Case
from ethereum_test_vm.opcode import Opcodes as Op

code = Switch(
cases=[
Case(condition=Op.EQ(Op.CALLDATALOAD(0), 1), action=Op.PUSH1(0x01) + Op.STOP()),
Case(condition=Op.EQ(Op.CALLDATALOAD(0), 2), action=Op.PUSH1(0x02) + Op.STOP()),
],
default_action=Op.PUSH1(0x00) + Op.STOP(),
)
```

The `ethereum_test_tools.code.generators` module also defines other high-level constructs like [`While`][ethereum_test_tools.code.generators.While] and [`Conditional`][ethereum_test_tools.code.generators.Conditional].

#### Converting Bytecode to Minilang

If you have EVM bytecode (as hex or binary), you can use the [`evm_bytes` CLI tool](../library/cli/evm_bytes.md) to convert it to the EEST Python opcode minilang automatically, for example:

```console
uv run evm_bytes hex-string 0x604260005260206000F3
# ->
# Op.PUSH1[0x42] + Op.PUSH1[0x0] + Op.MSTORE + Op.PUSH1[0x20] + Op.PUSH1[0x0] + Op.RETURN
```

#### Restrictions: No Yul in Python Test Cases

As of [PR #1779](https://github.com/ethereum/execution-spec-tests/pull/1779), the use of Yul source in Python test cases is forbidden. All new tests must use the Python opcode minilang as shown above.

## Verifying the Accounts' Post States

Expand Down
Loading