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

fix(tests): EOF - EIP-3540: Organize code_validation.py tests #668

Merged
merged 2 commits into from
Jul 9, 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
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Test fixtures for use by clients are available for each release on the [Github r
- ✨ Add tests for [EIP-4200: EOF - Static relative jumps](https://eips.ethereum.org/EIPS/eip-4200) ([#581](https://github.com/ethereum/execution-spec-tests/pull/581), [#666](https://github.com/ethereum/execution-spec-tests/pull/666)).
- ✨ Add tests for [EIP-7069: EOF - Revamped CALL instructions](https://eips.ethereum.org/EIPS/eip-7069) ([#595](https://github.com/ethereum/execution-spec-tests/pull/595)).
- 🐞 Fix typos in self-destruct collision test from erroneous pytest parametrization ([#608](https://github.com/ethereum/execution-spec-tests/pull/608)).
- ✨ Add tests for [EIP-3540: EOF - EVM Object Format v1](https://eips.ethereum.org/EIPS/eip-3540) ([#634](https://github.com/ethereum/execution-spec-tests/pull/634)).
- ✨ Add tests for [EIP-3540: EOF - EVM Object Format v1](https://eips.ethereum.org/EIPS/eip-3540) ([#634](https://github.com/ethereum/execution-spec-tests/pull/634), [#668](https://github.com/ethereum/execution-spec-tests/pull/668)).
- 🔀 Update EIP-7002 tests to match spec changes in [ethereum/execution-apis#549](https://github.com/ethereum/execution-apis/pull/549) ([#600](https://github.com/ethereum/execution-spec-tests/pull/600))
- ✨ Convert a few eip1153 tests from ethereum/tests repo into .py ([#440](https://github.com/ethereum/execution-spec-tests/pull/440)).
- ✨ Add tests for [EIP-7480: EOF - Data section access instructions](https://eips.ethereum.org/EIPS/eip-7480) ([#518](https://github.com/ethereum/execution-spec-tests/pull/518), [#664](https://github.com/ethereum/execution-spec-tests/pull/664)).
Expand Down
12 changes: 12 additions & 0 deletions src/ethereum_test_tools/tests/test_vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ def test_opcodes_repr():
assert f"{Op.DELEGATECALL}" == "DELEGATECALL"
assert f"{Om.OOG}" == "OOG"
assert str(Op.ADD) == "ADD"
assert f"{Op.DUPN[1]}" == "DUPN[0x01]"
assert f"{Op.DATALOADN[1]}" == "DATALOADN[0x0001]"


def test_macros():
Expand Down Expand Up @@ -356,3 +358,13 @@ def test_bytecode_properties(
assert bytecode.pushed_stack_items == expected_pushed_items, "Pushed stack items mismatch"
assert bytecode.max_stack_height == expected_max_stack_height, "Max stack height mismatch"
assert bytecode.min_stack_height == expected_min_stack_height, "Min stack height mismatch"


def test_opcode_comparison():
"""
Test that the opcodes are comparable.
"""
assert Op.STOP < Op.ADD
assert Op.ADD == Op.ADD
assert Op.ADD != Op.STOP
assert Op.ADD > Op.STOP
116 changes: 96 additions & 20 deletions src/ethereum_test_tools/vm/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,19 @@ def _get_int_size(n: int) -> int:

class Bytecode:
"""
Base class for Macro and Opcode, inherits from bytes.
Base class to represent EVM bytecode.

This class is designed to represent a base structure for individual evm opcodes
and opcode macros.
Stack calculations are automatically done after an addition operation between two bytecode
objects. The stack height is not guaranteed to be correct, so the user must take this into
consideration.

Parameters
----------
- popped_stack_items: number of items the bytecode pops from the stack
- pushed_stack_items: number of items the bytecode pushes to the stack
- min_stack_height: minimum stack height required by the bytecode
- max_stack_height: maximum stack height reached by the bytecode
- unchecked_stack: whether the bytecode should ignore stack checks when being called
"""

_name_: str = ""
Expand Down Expand Up @@ -312,15 +321,19 @@ class Opcode(Bytecode):

Parameters
----------
- popped_stack_items: number of items the opcode pops from the stack
- pushed_stack_items: number of items the opcode pushes to the stack
- min_stack_height: minimum stack height required by the opcode
- data_portion_length: number of bytes after the opcode in the bytecode
that represent data
- data_portion_formatter: function to format the data portion of the opcode, if any
- stack_properties_modifier: function to modify the stack properties of the opcode after the
data portion has been processed
- kwargs: list of keyword arguments that can be passed to the opcode, in the order they are
meant to be placed in the stack
- kwargs_defaults: default values for the keyword arguments if any, otherwise 0
"""

data_portion_length: int
data_portion_formatter: Optional[Callable[[Any], bytes]]
stack_properties_modifier: Optional[Callable[[Any], tuple[int, int, int, int]]]
marioevz marked this conversation as resolved.
Show resolved Hide resolved
kwargs: List[str] | None
kwargs_defaults: KW_ARGS_DEFAULTS_TYPE

Expand All @@ -334,6 +347,7 @@ def __new__(
min_stack_height: int | None = None,
data_portion_length: int = 0,
data_portion_formatter=None,
stack_properties_modifier=None,
unchecked_stack=False,
kwargs: List[str] | None = None,
kwargs_defaults: KW_ARGS_DEFAULTS_TYPE = {},
Expand Down Expand Up @@ -366,6 +380,7 @@ def __new__(
)
obj.data_portion_length = data_portion_length
obj.data_portion_formatter = data_portion_formatter
obj.stack_properties_modifier = stack_properties_modifier
obj.unchecked_stack = unchecked_stack
obj.kwargs = kwargs
obj.kwargs_defaults = kwargs_defaults
Expand Down Expand Up @@ -409,24 +424,36 @@ def __getitem__(self, *args: "int | bytes | str | FixedSizeBytes | Iterable[int]
)
else:
raise TypeError("Opcode data portion must be either an int or bytes/hex string")
popped_stack_items = self.popped_stack_items
pushed_stack_items = self.pushed_stack_items
min_stack_height = self.min_stack_height
max_stack_height = self.max_stack_height
assert (
self.popped_stack_items is not None
and self.pushed_stack_items is not None
and self.min_stack_height is not None
popped_stack_items is not None
and pushed_stack_items is not None
and min_stack_height is not None
)
if self.stack_properties_modifier is not None:
(
popped_stack_items,
pushed_stack_items,
min_stack_height,
max_stack_height,
) = self.stack_properties_modifier(data_portion)

new_opcode = Opcode(
bytes(self) + data_portion,
popped_stack_items=self.popped_stack_items,
pushed_stack_items=self.pushed_stack_items,
min_stack_height=self.min_stack_height,
max_stack_height=self.max_stack_height,
popped_stack_items=popped_stack_items,
pushed_stack_items=pushed_stack_items,
min_stack_height=min_stack_height,
max_stack_height=max_stack_height,
data_portion_length=0,
data_portion_formatter=None,
unchecked_stack=self.unchecked_stack,
kwargs=self.kwargs,
kwargs_defaults=self.kwargs_defaults,
)
new_opcode._name_ = self._name_
new_opcode._name_ = f"{self._name_}[0x{data_portion.hex()}]"
return new_opcode

def __call__(
Expand Down Expand Up @@ -483,12 +510,30 @@ def __call__(

return super().__call__(*args, unchecked=unchecked)

def __lt__(self, other: "Opcode") -> bool:
"""
Compares two opcodes by their integer value.
"""
return self.int() < other.int()

def __gt__(self, other: "Opcode") -> bool:
"""
Compares two opcodes by their integer value.
"""
return self.int() > other.int()

def int(self) -> int:
"""
Returns the integer representation of the opcode.
"""
return int.from_bytes(self, byteorder="big")

def has_data_portion(self) -> bool:
"""
Returns whether the opcode has a data portion.
"""
return self.data_portion_length > 0 or self.data_portion_formatter is not None


class Macro(Bytecode):
"""
Expand Down Expand Up @@ -570,6 +615,28 @@ def _exchange_encoder(*args: int) -> bytes:
return int.to_bytes(imm, 1, "big")


def _swapn_stack_properties_modifier(data: bytes) -> tuple[int, int, int, int]:
imm = int.from_bytes(data, "big")
n = imm + 1
min_stack_height = n + 1
return 0, 0, min_stack_height, min_stack_height


def _dupn_stack_properties_modifier(data: bytes) -> tuple[int, int, int, int]:
imm = int.from_bytes(data, "big")
n = imm + 1
min_stack_height = n
return 0, 1, min_stack_height, min_stack_height + 1


def _exchange_stack_properties_modifier(data: bytes) -> tuple[int, int, int, int]:
imm = int.from_bytes(data, "big")
n = (imm >> 4) + 1
m = (imm & 0x0F) + 1
min_stack_height = n + m + 1
return 0, 0, min_stack_height, min_stack_height


class Opcodes(Opcode, Enum):
"""
Enum containing all known opcodes.
Expand Down Expand Up @@ -5146,7 +5213,12 @@ class Opcodes(Opcode, Enum):

"""

DUPN = Opcode(0xE6, pushed_stack_items=1, data_portion_length=1)
DUPN = Opcode(
0xE6,
pushed_stack_items=1,
data_portion_length=1,
stack_properties_modifier=_dupn_stack_properties_modifier,
)
"""
!!! Note: This opcode is under development

Expand Down Expand Up @@ -5178,7 +5250,9 @@ class Opcodes(Opcode, Enum):

"""

SWAPN = Opcode(0xE7, data_portion_length=1)
SWAPN = Opcode(
0xE7, data_portion_length=1, stack_properties_modifier=_swapn_stack_properties_modifier
)
"""
!!! Note: This opcode is under development

Expand Down Expand Up @@ -5210,7 +5284,11 @@ class Opcodes(Opcode, Enum):

"""

EXCHANGE = Opcode(0xE8, data_portion_formatter=_exchange_encoder)
EXCHANGE = Opcode(
0xE8,
data_portion_formatter=_exchange_encoder,
stack_properties_modifier=_exchange_stack_properties_modifier,
)
"""
!!! Note: This opcode is under development

Expand Down Expand Up @@ -5275,9 +5353,7 @@ class Opcodes(Opcode, Enum):

"""

RETURNCONTRACT = Opcode(
0xEE, popped_stack_items=2, pushed_stack_items=1, data_portion_length=1
)
RETURNCONTRACT = Opcode(0xEE, popped_stack_items=2, data_portion_length=1)
"""
!!! Note: This opcode is under development

Expand Down
Loading