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

new(tests): EOF - EIP-6206: JUMPF Tests #540

Merged
merged 17 commits into from
May 23, 2024
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
4 changes: 4 additions & 0 deletions src/ethereum_test_tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
BaseTest,
BlockchainTest,
BlockchainTestFiller,
EOFStateTest,
EOFStateTestFiller,
EOFTest,
EOFTestFiller,
FixtureCollector,
Expand Down Expand Up @@ -78,6 +80,8 @@
"EngineAPIError",
"Environment",
"EOFException",
"EOFStateTest",
"EOFStateTestFiller",
"EOFTest",
"EOFTestFiller",
"FixtureCollector",
Expand Down
10 changes: 10 additions & 0 deletions src/ethereum_test_tools/exceptions/evmone_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class EvmoneExceptionMapper:
EOFException.MISSING_HEADERS_TERMINATOR, "err: section_headers_not_terminated"
),
ExceptionMessage(EOFException.INVALID_VERSION, "err: eof_version_unknown"),
ExceptionMessage(
EOFException.INVALID_NON_RETURNING_FLAG, "err: invalid_non_returning_flag"
),
ExceptionMessage(EOFException.INVALID_MAGIC, "err: invalid_prefix"),
ExceptionMessage(
EOFException.INVALID_FIRST_SECTION_TYPE, "err: invalid_first_section_type"
Expand All @@ -53,6 +56,13 @@ class EvmoneExceptionMapper:
ExceptionMessage(
EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT, "err: max_stack_height_above_limit"
),
ExceptionMessage(
EOFException.STACK_HIGHER_THAN_OUTPUTS, "err: stack_higher_than_outputs_required"
),
ExceptionMessage(
EOFException.JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS,
"err: jumpf_destination_incompatible_outputs",
),
ExceptionMessage(EOFException.INVALID_MAX_STACK_HEIGHT, "err: invalid_max_stack_height"),
ExceptionMessage(EOFException.INVALID_DATALOADN_INDEX, "err: invalid_dataloadn_index"),
)
Expand Down
13 changes: 13 additions & 0 deletions src/ethereum_test_tools/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ class EOFException(ExceptionBase):
"""
EOF container version bytes mismatch
"""
INVALID_NON_RETURNING_FLAG = auto()
"""
EOF container's section has non-returning flag set incorrectly
"""
INVALID_RJUMP_DESTINATION = auto()
"""
Code has RJUMP instruction with invalid parameters
Expand Down Expand Up @@ -311,6 +315,15 @@ class EOFException(ExceptionBase):
"""
EOF container's specified max stack height is above the limit
"""
STACK_HIGHER_THAN_OUTPUTS = auto()
"""
EOF container section stack height is higher than the outputs
when returning
"""
JUMPF_DESTINATION_INCOMPATIBLE_OUTPUTS = auto()
"""
EOF container section JUMPF's to a destination section with incompatible outputs
"""
INVALID_MAX_STACK_HEIGHT = auto()
"""
EOF container section's specified max stack height does not match the actual stack height
Expand Down
20 changes: 18 additions & 2 deletions src/ethereum_test_tools/spec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@

from .base.base_test import BaseFixture, BaseTest, TestSpec
from .blockchain.blockchain_test import BlockchainTest, BlockchainTestFiller, BlockchainTestSpec
from .eof.eof_test import EOFTest, EOFTestFiller, EOFTestSpec
from .eof.eof_test import (
EOFStateTest,
EOFStateTestFiller,
EOFStateTestSpec,
EOFTest,
EOFTestFiller,
EOFTestSpec,
)
from .fixture_collector import FixtureCollector, TestInfo
from .state.state_test import StateTest, StateTestFiller, StateTestOnly, StateTestSpec

SPEC_TYPES: List[Type[BaseTest]] = [BlockchainTest, StateTest, StateTestOnly, EOFTest]
SPEC_TYPES: List[Type[BaseTest]] = [
BlockchainTest,
StateTest,
StateTestOnly,
EOFTest,
EOFStateTest,
]

__all__ = (
"SPEC_TYPES",
Expand All @@ -19,6 +32,9 @@
"BlockchainTest",
"BlockchainTestFiller",
"BlockchainTestSpec",
"EOFStateTest",
"EOFStateTestFiller",
"EOFStateTestSpec",
"EOFTest",
"EOFTestFiller",
"EOFTestSpec",
Expand Down
2 changes: 0 additions & 2 deletions src/ethereum_test_tools/spec/base/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ def pytest_parameter_name(cls) -> str:

By default, it returns the underscore separated name of the class.
"""
if cls.__name__ == "EOFTest":
return "eof_test"
return reduce(lambda x, y: x + ("_" if y.isupper() else "") + y, cls.__name__).lower()

def get_next_transition_tool_output_path(self) -> str:
Expand Down
121 changes: 118 additions & 3 deletions src/ethereum_test_tools/spec/eof/eof_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@
from pathlib import Path
from shutil import which
from subprocess import CompletedProcess
from typing import Callable, ClassVar, Generator, List, Optional, Type
from typing import Any, Callable, ClassVar, Generator, List, Optional, Type

import pytest
from pydantic import Field, model_validator

from ethereum_test_forks import Fork
from evm_transition_tool import FixtureFormats
from evm_transition_tool import FixtureFormats, TransitionTool

from ...common import Account, Address, Alloc, Environment, Transaction
from ...common.base_types import Bytes
from ...common.constants import TestAddress
from ...eof.v1 import Container
from ...exceptions import EOFException, EvmoneExceptionMapper
from ..base.base_test import BaseFixture, BaseTest
from ..state.state_test import StateTest
from .types import Fixture, Result


Expand Down Expand Up @@ -133,10 +140,36 @@ class EOFTest(BaseTest):
expect_exception: EOFException | None = None

supported_fixture_formats: ClassVar[List[FixtureFormats]] = [
# TODO: Potentially generate a state test and blockchain test too.
FixtureFormats.EOF_TEST,
]

@model_validator(mode="before")
@classmethod
def check_container_exception(cls, data: Any) -> Any:
"""
Check if the container exception matches the expected exception.
"""
if isinstance(data, dict):
container = data.get("data")
expect_exception = data.get("expect_exception")
if container is not None and isinstance(container, Container):
if container.validity_error is not None:
if expect_exception is not None:
assert container.validity_error == expect_exception, (
f"Container validity error {container.validity_error} "
f"does not match expected exception {expect_exception}."
)
if expect_exception is None:
data["expect_exception"] = container.validity_error
return data

@classmethod
def pytest_parameter_name(cls) -> str:
"""
Workaround for pytest parameter name.
"""
return "eof_test"

def make_eof_test_fixture(
self,
*,
Expand Down Expand Up @@ -208,6 +241,7 @@ def verify_result(self, result: CompletedProcess, expected_result: Result, code:
def generate(
self,
*,
t8n: TransitionTool,
fork: Fork,
eips: Optional[List[int]] = None,
fixture_format: FixtureFormats,
Expand All @@ -224,3 +258,84 @@ def generate(

EOFTestSpec = Callable[[str], Generator[EOFTest, None, None]]
EOFTestFiller = Type[EOFTest]


class EOFStateTest(EOFTest):
"""
Filler type that tests EOF containers and also generates a state/blockchain test.
"""

tx_gas_limit: int = 10_000_000
tx_data: Bytes = Bytes(b"")
env: Environment = Field(default_factory=Environment)
container_post: Account = Field(default_factory=Account)

supported_fixture_formats: ClassVar[List[FixtureFormats]] = [
FixtureFormats.EOF_TEST,
FixtureFormats.STATE_TEST,
FixtureFormats.BLOCKCHAIN_TEST,
FixtureFormats.BLOCKCHAIN_TEST_HIVE,
]

@classmethod
def pytest_parameter_name(cls) -> str:
"""
Workaround for pytest parameter name.
"""
return "eof_state_test"

def generate_state_test(self) -> StateTest:
"""
Generate the StateTest filler.
"""
pre = Alloc()
container_address = Address(0x100)
pre[container_address] = Account(code=self.data, nonce=1)
pre[TestAddress] = Account(balance=1_000_000_000_000_000_000_000, nonce=0)
tx = Transaction(
nonce=0,
to=container_address,
gas_limit=self.tx_gas_limit,
gas_price=10,
protected=False,
data=self.tx_data,
)
post = Alloc()
post[container_address] = self.container_post
return StateTest(
pre=pre,
tx=tx,
env=self.env,
post=post,
)

def generate(
self,
*,
t8n: TransitionTool,
fork: Fork,
eips: Optional[List[int]] = None,
fixture_format: FixtureFormats,
**_,
) -> BaseFixture:
"""
Generate the BlockchainTest fixture.
"""
if fixture_format == FixtureFormats.EOF_TEST:
return self.make_eof_test_fixture(fork=fork, eips=eips)
elif fixture_format in (
FixtureFormats.STATE_TEST,
FixtureFormats.BLOCKCHAIN_TEST,
FixtureFormats.BLOCKCHAIN_TEST_HIVE,
):
if self.expect_exception is not None:
pytest.skip("State tests can't be generated for invalid EOF code yet.")
return self.generate_state_test().generate(
t8n=t8n, fork=fork, fixture_format=fixture_format, eips=eips
)

raise Exception(f"Unknown fixture format: {fixture_format}")


EOFStateTestSpec = Callable[[str], Generator[EOFStateTest, None, None]]
EOFStateTestFiller = Type[EOFStateTest]
3 changes: 3 additions & 0 deletions tests/prague/eip7692_eof_v1/eip6206_jumpf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
EOF tests for EIP-6206 JUMPF
"""
13 changes: 13 additions & 0 deletions tests/prague/eip7692_eof_v1/eip6206_jumpf/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
EOF JumpF tests helpers
"""
import itertools

"""Storage addresses for common testing fields"""
_slot = itertools.count()
next(_slot) # don't use slot 0
slot_code_worked = next(_slot)
slot_last_slot = next(_slot)

"""Storage values for common testing fields"""
value_code_worked = 0x2015
5 changes: 5 additions & 0 deletions tests/prague/eip7692_eof_v1/eip6206_jumpf/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
EOF V1 Constants used throughout all tests
"""

EOF_FORK_NAME = "Prague"
Loading