Skip to content
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

feat(tests) Precompile Checks #1120

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
133 changes: 133 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
// If the VS Code "Run and Debug" button, respecively launch selector are not visible, see this answer:
// https://stackoverflow.com/a/74245823
//
"version": "0.2.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the settings got exported?

"configurations": [
{
"name": "Launch fill --until choose fork",
"type": "python",
"request": "launch",
"module": "pytest",
"args": [
"-c",
"pytest.ini",
"--until",
"${input:fork}",
"--evm-bin",
"${input:evmBinary}",
"-v"
],
"cwd": "${workspaceFolder}"
},
{
"name": "Launch fill -k choose test path",
"type": "python",
"request": "launch",
"module": "pytest",
"args": [
"-c",
"pytest.ini",
"--until",
"Cancun",
"--evm-bin",
"${input:evmBinary}",
"-v",
"-k",
"${input:testPathOrId}"
],
"cwd": "${workspaceFolder}"
},
{
"name": "Launch fill --until Cancun",
"type": "python",
"request": "launch",
"module": "pytest",
"args": [
"-c",
"pytest.ini",
"--until",
"Cancun",
"--evm-bin",
"${input:evmBinary}",
"-v"
],
"cwd": "${workspaceFolder}"
},
{
"name": "Launch fill --until Prague",
"type": "python",
"request": "launch",
"module": "pytest",
"args": [
"-c",
"pytest.ini",
"--until",
"Prague",
"--evm-bin",
"${input:evmBinary}",
"-v"
],
"cwd": "${workspaceFolder}"
}
],
"inputs": [
{
"type": "pickString",
"id": "evmBinary",
"description": "Which evm binary to you want to run?",
"options": [
{
"label": "First evm binary in PATH",
"value": "evm",
},
{
"label": "Geth, mario's repo",
"value": "~/code/github/marioevz/go-ethereum/build/bin/evm",
},
{
"label": "Geth, danceratopz's repo",
"value": "~/code/github/danceratopz/go-ethereum/build/bin/evm",
},
{
"label": "evmone",
"value": "~/code/github/ethereum/evmone/build/bin/evmone-t8n",
},
{
"label": "besu",
"value": "~/code/github/danceratopz/besu/ethereum/evmtool/build/install/evmtool/bin/evm",
}
],
"default": "evm"
},
{
"type": "pickString",
"id": "fork",
"description": "Which fork do you want to use?",
"options": [
"Frontier",
"Homestead",
"Byzantium",
"Constantinople",
"ConstantinopleFix",
"Istanbul",
"Berlin",
"London",
"Paris",
"Shanghai",
"Cancun",
"Prague",
],
"default": "Cancun"
},
{
"type": "promptString",
"id": "testPathOrId",
"description": "Enter a test path string or id to provide to pytest -k",
"default": "test_"
}
]
}
9 changes: 9 additions & 0 deletions .vscode/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"_comment": [
"Dynamic python testing settings for the vscode testing extension."
],
"python.testing.promptToConfigure": false,
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": ["-c", "pytest.ini"]
}
114 changes: 114 additions & 0 deletions tests/frontier/precompiles/test_precompiles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Tests supported precompiled contracts."""

from typing import Iterator, Tuple

import pytest

from ethereum_test_forks import Fork
from ethereum_test_tools import (
Account,
Alloc,
Environment,
StateTestFiller,
Transaction,
)
from ethereum_test_tools.code.generators import Conditional
from ethereum_test_tools.vm.opcode import Opcodes as Op


def precompile_addresses(fork: Fork) -> Iterator[Tuple[str, bool]]:
"""
Yield the addresses of precompiled contracts and their support status for a given fork.

Args:
fork (Fork): The fork instance containing precompiled contract information.

Yields:
Iterator[Tuple[str, bool]]: A tuple containing the address in hexadecimal format and a
boolean indicating whether the address is a supported precompile.

"""
supported_precompiles = fork.precompiles()

for address in range(1, len(supported_precompiles) + 2):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why +2
and what if precompiles are ranges [1..to 11], [100 to 112]

yield (hex(address), address in supported_precompiles)


@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize_by_fork("address,precompile_exists", precompile_addresses)
def test_precompiles(
state_test: StateTestFiller, address: str, precompile_exists: bool, pre: Alloc
):
"""
Tests the behavior of precompiled contracts in the Ethereum state test.

Args:
state_test (StateTestFiller): The state test filler object used to run the test.
address (str): The address of the precompiled contract to test.
precompile_exists (bool): A flag indicating whether the precompiled contract exists at the
given address.
pre (Alloc): The allocation object used to deploy the contract and set up the initial
state.

This test deploys a contract that performs two CALL operations to the specified address and a
fixed address (0x10000), measuring the gas used for each call. It then stores the difference
in gas usage in storage slot 0. The test verifies the expected storage value based on
whether the precompiled contract exists at the given address.

"""
env = Environment()

args_offset = 0x1000
Copy link
Contributor

@winsvega winsvega Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you didn't want to copy, but then you can actually use default values of this args leaving it empty.
and it is always good when reading the code If it is visible what is the offset without scrolling up to the defenition (IMHO), then the call would fit in one line.

args_size = 0x20
output_offset = 0x2000
output_size = 0x20

gas_test = 0x00
gas_10000 = 0x20

account = pre.deploy_contract(
Op.MSTORE(gas_test, Op.GAS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marioevz it works Op.GAS as well as Op.GAS() ?

+ Op.CALL(
address=address,
args_offset=args_offset,
args_size=args_size,
output_offset=output_offset,
output_size=output_size,
)
+ Op.MSTORE(gas_test, Op.SUB(Op.GAS, Op.MLOAD(gas_test)))
+ Op.MSTORE(gas_10000, Op.GAS)
+ Op.CALL(
address=0x10000,
args_offset=args_offset,
args_size=args_size,
output_offset=output_offset,
output_size=output_size,
)
+ Op.MSTORE(gas_10000, Op.SUB(Op.GAS, Op.MLOAD(gas_10000)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you get negative value here

+ Op.SSTORE(
0,
Op.LT(
Conditional(
condition=Op.GT(Op.MLOAD(gas_test), Op.MLOAD(gas_10000)),
if_true=Op.SUB(Op.MLOAD(gas_test), Op.MLOAD(gas_10000)),
if_false=Op.SUB(Op.MLOAD(gas_10000), Op.MLOAD(gas_test)),
),
0x1A4,
),
)
+ Op.STOP,
storage={0: 0xDEADBEEF},
)

tx = Transaction(
to=account,
sender=pre.fund_eoa(),
gas_limit=1_000_000,
protected=True,
)

# A high gas cost will result from calling a precompile
# Expect 0x00 when a precompile exists at the address, 0x01 otherwise
post = {account: Account(storage={0: "0x00" if precompile_exists else "0x01"})}

state_test(env=env, pre=pre, post=post, tx=tx)