Skip to content

Commit

Permalink
new(tests): basic EOF execution tests (#912)
Browse files Browse the repository at this point in the history
- Add basic EOF execution tests migrated from ethereum/tests:
  EIPTests/StateTests/stEOF/stEIP3540/EOF1_Execution.json
- Extend execution test of legacy invoking `EXTCODE*` instruction on EOF
  with the case from ethereum/tests.
  • Loading branch information
chfast authored and winsvega committed Oct 25, 2024
1 parent ac615f6 commit 28d11c9
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 23 deletions.
2 changes: 2 additions & 0 deletions converted-ethereum-tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ EOFTests/efValidation/unreachable_code_sections_.json
EOFTests/efValidation/EOF1_returncontract_invalid_.json
EOFTests/efValidation/EOF1_returncontract_valid_.json

EIPTests/StateTests/stEOF/stEIP3540/EOF1_Execution.json

([#440](https://github.com/ethereum/execution-spec-tests/pull/440))
GeneralStateTests/Cancun/stEIP1153-transientStorage/01_tloadBeginningTxn.json
GeneralStateTests/Cancun/stEIP1153-transientStorage/02_tloadAfterTstore.json
Expand Down
39 changes: 32 additions & 7 deletions src/ethereum_test_base_types/composite_types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Base composite types for Ethereum test cases.
"""

from dataclasses import dataclass
from typing import Any, ClassVar, Dict, SupportsBytes, Type, TypeAlias

Expand All @@ -24,6 +25,7 @@ class Storage(RootModel[Dict[StorageKeyValueType, StorageKeyValueType]]):
root: Dict[StorageKeyValueType, StorageKeyValueType] = Field(default_factory=dict)

_current_slot: int = PrivateAttr(0)
_hint_map: Dict[StorageKeyValueType, str] = {}

StorageDictType: ClassVar[TypeAlias] = Dict[
str | int | bytes | SupportsBytes, str | int | bytes | SupportsBytes
Expand Down Expand Up @@ -92,13 +94,15 @@ class KeyValueMismatch(Exception):
key: int
want: int
got: int
hint: str

def __init__(self, address: Address, key: int, want: int, got: int, *args):
def __init__(self, address: Address, key: int, want: int, got: int, hint: str, *args):
super().__init__(args)
self.address = address
self.key = key
self.want = want
self.got = got
self.hint = hint

def __str__(self):
"""Print exception string"""
Expand All @@ -107,7 +111,7 @@ def __str__(self):
label_str = f" ({self.address.label})"
return (
f"incorrect value in address {self.address}{label_str} for "
+ f"key {Hash(self.key)}:"
+ f"key {Hash(self.key)} ({self.hint}):"
+ f" want {HexNumber(self.want)} (dec:{int(self.want)}),"
+ f" got {HexNumber(self.got)} (dec:{int(self.got)})"
)
Expand Down Expand Up @@ -182,7 +186,7 @@ def items(self):
return self.root.items()

def store_next(
self, value: StorageKeyValueTypeConvertible | StorageKeyValueType | bool
self, value: StorageKeyValueTypeConvertible | StorageKeyValueType | bool, hint: str = ""
) -> StorageKeyValueType:
"""
Stores a value in the storage and returns the key where the value is stored.
Expand All @@ -192,6 +196,7 @@ def store_next(
"""
slot = StorageKeyValueTypeAdapter.validate_python(self._current_slot)
self._current_slot += 1
self._hint_map[slot] = hint
self[slot] = StorageKeyValueTypeAdapter.validate_python(value)
return slot

Expand Down Expand Up @@ -230,7 +235,11 @@ def must_contain(self, address: Address, other: "Storage"):
raise Storage.MissingKey(key=key)
elif self[key] != other[key]:
raise Storage.KeyValueMismatch(
address=address, key=key, want=self[key], got=other[key]
address=address,
key=key,
want=self[key],
got=other[key],
hint=self._hint_map[key],
)

def must_be_equal(self, address: Address, other: "Storage | None"):
Expand All @@ -243,17 +252,33 @@ def must_be_equal(self, address: Address, other: "Storage | None"):
for key in self.keys() & other.keys():
if self[key] != other[key]:
raise Storage.KeyValueMismatch(
address=address, key=key, want=self[key], got=other[key]
address=address,
key=key,
want=self[key],
got=other[key],
hint=self._hint_map[key],
)

# Test keys contained in either one of the storage objects
for key in self.keys() ^ other.keys():
if key in self:
if self[key] != 0:
raise Storage.KeyValueMismatch(address=address, key=key, want=self[key], got=0)
raise Storage.KeyValueMismatch(
address=address,
key=key,
want=self[key],
got=0,
hint=self._hint_map[key],
)

elif other[key] != 0:
raise Storage.KeyValueMismatch(address=address, key=key, want=0, got=other[key])
raise Storage.KeyValueMismatch(
address=address,
key=key,
want=0,
got=other[key],
hint=self._hint_map[key],
)

def canary(self) -> "Storage":
"""
Expand Down
78 changes: 78 additions & 0 deletions tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_execution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Execution of basic EOF containers.
"""

import pytest

from ethereum_test_base_types import Storage
from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction
from ethereum_test_tools.eof.v1 import Container, Section
from ethereum_test_tools.vm.opcode import Opcodes as Op

from .. import EOF_FORK_NAME

REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md"
REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183"

pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)

EXPECTED_STORAGE = (bytes.fromhex("EF"), bytes.fromhex("BADDCAFE"))
"""Expected storage (key => value) to be produced by the EOF containers"""


@pytest.mark.parametrize(
"container",
(
Container(
name="store_from_push",
sections=[Section.Code(Op.SSTORE(*EXPECTED_STORAGE) + Op.STOP)],
),
Container(
name="store_with_data",
sections=[
Section.Code(Op.SSTORE(Op.DATALOADN[0], Op.DATALOADN[32]) + Op.STOP),
Section.Data(
EXPECTED_STORAGE[0].rjust(32, b"\x00") + EXPECTED_STORAGE[1].rjust(32, b"\x00")
),
],
),
),
ids=lambda x: x.name,
)
def test_eof_execution(
state_test: StateTestFiller,
pre: Alloc,
container: Container,
):
"""
Test simple contracts that are expected to succeed on call.
"""
env = Environment()

storage = Storage()
sender = pre.fund_eoa()
container_address = pre.deploy_contract(container)
caller_contract = Op.SSTORE(
storage.store_next(1), Op.CALL(Op.GAS, container_address, 0, 0, 0, 0, 0)
)
caller_address = pre.deploy_contract(caller_contract)

tx = Transaction(
to=caller_address,
gas_limit=1_000_000,
gas_price=10,
protected=False,
sender=sender,
)

post = {
caller_address: Account(storage=storage),
container_address: Account(storage=dict([EXPECTED_STORAGE])),
}

state_test(
env=env,
pre=pre,
post=post,
tx=tx,
)
20 changes: 8 additions & 12 deletions tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ethereum_test_tools import Account, Alloc, Environment
from ethereum_test_tools import Opcodes as Op
from ethereum_test_tools import StateTestFiller, Storage, Transaction, keccak256
from ethereum_test_tools.eof.v1 import Container, Section
from ethereum_test_tools.eof.v1 import Container

from .. import EOF_FORK_NAME

Expand All @@ -23,15 +23,8 @@ def test_legacy_calls_eof_sstore(
):
"""Test EXTCODE* opcodes calling EOF and legacy contracts"""
env = Environment()
address_eof_contract = pre.deploy_contract(
Container(
sections=[
Section.Code(
code=Op.RJUMP[0] + Op.STOP,
)
]
)
)
eof_code = Container.Code(Op.RJUMP[0] + Op.STOP)
address_eof_contract = pre.deploy_contract(eof_code)
legacy_code = Op.PUSH1(2) + Op.JUMPDEST + Op.STOP
address_legacy_contract = pre.deploy_contract(legacy_code)

Expand All @@ -40,7 +33,7 @@ def test_legacy_calls_eof_sstore(
Op.SSTORE(storage_test.store_next(4), Op.EXTCODESIZE(address_legacy_contract))
+ Op.EXTCODECOPY(address_legacy_contract, 0, 0, Op.EXTCODESIZE(address_legacy_contract))
+ Op.SSTORE(
storage_test.store_next(bytes(legacy_code) + (b"\0" * (32 - len(legacy_code)))),
storage_test.store_next(bytes(legacy_code).ljust(32, b"\0")),
Op.MLOAD(0),
)
+ Op.SSTORE(
Expand All @@ -49,7 +42,10 @@ def test_legacy_calls_eof_sstore(
)
+ Op.SSTORE(storage_test.store_next(2), Op.EXTCODESIZE(address_eof_contract))
+ Op.EXTCODECOPY(address_eof_contract, 0x20, 0, 6)
+ Op.SSTORE(storage_test.store_next(b"\xef" + (b"\0" * 31)), Op.MLOAD(0x20))
+ Op.SSTORE(storage_test.store_next(b"\xef".ljust(32, b"\0")), Op.MLOAD(0x20))
+ Op.MSTORE(0x40, b"\xcc" * 32) # clobber memory slot
+ Op.EXTCODECOPY(address_eof_contract, 0x40, len(eof_code) - 4, 8) # out-of-bounds "read"
+ Op.SSTORE(storage_test.store_next(b"\xcc" * 24), Op.MLOAD(0x40))
+ Op.SSTORE(
storage_test.store_next(keccak256(b"\xef\x00")),
Op.EXTCODEHASH(address_eof_contract),
Expand Down
8 changes: 4 additions & 4 deletions tests/osaka/eip7692_eof_v1/eof_tracker.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@

### Execution

- [ ] Execution of EOF contracts (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_ExecutionFiller.yml)
- [ ] Legacy executing EXTCODESIZE of EOF contract (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_ExecutionFiller.yml)
- [ ] Legacy executing EXTCODEHASH of EOF contract (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_ExecutionFiller.yml)
- [ ] Legacy executing EXTCODECOPY of EOF contract (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_ExecutionFiller.yml)
- [x] Execution of EOF contracts ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_execution.py::test_eof_execution`](./eip3540_eof_v1/test_execution/test_eof_execution.md))
- [x] Legacy executing EXTCODESIZE of EOF contract ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py::test_legacy_calls_eof_sstore`](./eip3540_eof_v1/test_extcode/test_legacy_calls_eof_sstore.md))
- [x] Legacy executing EXTCODEHASH of EOF contract ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py::test_legacy_calls_eof_sstore`](./eip3540_eof_v1/test_extcode/test_legacy_calls_eof_sstore.md))
- [x] Legacy executing EXTCODECOPY of EOF contract ([`tests/osaka/eip7692_eof_v1/eip3540_eof_v1/test_extcode.py::test_legacy_calls_eof_sstore`](./eip3540_eof_v1/test_extcode/test_legacy_calls_eof_sstore.md))
- [ ] `*CALLs` from legacy contracts to EOF contracts (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_CallsFiller.yml)
- [ ] `EXT*CALLs` from EOF to legacy contracts (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_CallsFiller.yml)
- [ ] EXTDELEGATECALL from EOF to EOF contract (ethereum/tests: src/EIPTestsFiller/StateTests/stEOF/stEIP3540/EOF1_CallsFiller.yml)
Expand Down

0 comments on commit 28d11c9

Please sign in to comment.