From 0d03773c7b40a6fab30c2aab73467297b5ccabf8 Mon Sep 17 00:00:00 2001 From: Dimitry Kh Date: Mon, 16 Sep 2024 11:10:19 +0200 Subject: [PATCH] feat(hint): post state key hint --- .../composite_types.py | 49 +++++++++++++++---- src/ethereum_test_specs/state.py | 6 ++- src/ethereum_test_types/types.py | 6 +-- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/ethereum_test_base_types/composite_types.py b/src/ethereum_test_base_types/composite_types.py index 7627c5b107..9307a33e93 100644 --- a/src/ethereum_test_base_types/composite_types.py +++ b/src/ethereum_test_base_types/composite_types.py @@ -2,7 +2,7 @@ Base composite types for Ethereum test cases. """ from dataclasses import dataclass -from typing import Any, ClassVar, Dict, SupportsBytes, Type, TypeAlias +from typing import Any, ClassVar, Dict, Optional, SupportsBytes, Type, TypeAlias from pydantic import Field, PrivateAttr, RootModel, TypeAdapter @@ -92,22 +92,37 @@ class KeyValueMismatch(Exception): key: int want: int got: int - - def __init__(self, address: Address, key: int, want: int, got: int, *args): + hint: Optional[Dict[int, str]] = None + + def __init__( + self, + address: Address, + key: int, + want: int, + got: int, + hint: Optional[Dict[int, str]] = None, + *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""" label_str = "" if self.address.label is not None: label_str = f" ({self.address.label})" + + key = Hash(self.key) + if self.hint is not None: + key = self.hint[self.key] + return ( f"incorrect value in address {self.address}{label_str} for " - + f"key {Hash(self.key)}:" + + f"key {key}:" + f" want {HexNumber(self.want)} (dec:{int(self.want)})," + f" got {HexNumber(self.got)} (dec:{int(self.got)})" ) @@ -233,7 +248,12 @@ def must_contain(self, address: Address, other: "Storage"): address=address, key=key, want=self[key], got=other[key] ) - def must_be_equal(self, address: Address, other: "Storage | None"): + def must_be_equal( + self, + address: Address, + other: "Storage | None", + post_hint: Optional[Dict[int, str]] = None, + ): """ Succeeds only if "self" is equal to "other" storage. """ @@ -243,17 +263,21 @@ 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=post_hint ) # 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=post_hint + ) 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=post_hint + ) def canary(self) -> "Storage": """ @@ -374,7 +398,12 @@ def __str__(self): + f"want {self.want}, got {self.got}" ) - def check_alloc(self: "Account", address: Address, account: "Account"): + def check_alloc( + self: "Account", + address: Address, + account: "Account", + post_hint: Optional[Dict[int, str]] = None, + ): """ Checks the returned alloc against an expected account in post state. Raises exception on failure. @@ -404,7 +433,7 @@ def check_alloc(self: "Account", address: Address, account: "Account"): ) if "storage" in self.model_fields_set: - self.storage.must_be_equal(address=address, other=account.storage) + self.storage.must_be_equal(address=address, other=account.storage, post_hint=post_hint) def __bool__(self: "Account") -> bool: """ diff --git a/src/ethereum_test_specs/state.py b/src/ethereum_test_specs/state.py index 84f30dda74..b2e0aa4137 100644 --- a/src/ethereum_test_specs/state.py +++ b/src/ethereum_test_specs/state.py @@ -40,6 +40,7 @@ class StateTest(BaseTest): pre: Alloc post: Alloc tx: Transaction + post_hint: Optional[Dict[int, str]] = None engine_api_error_code: Optional[EngineAPIError] = None blockchain_test_header_verify: Optional[Header] = None blockchain_test_rlp_modifier: Optional[Header] = None @@ -117,6 +118,7 @@ def make_state_test_fixture( t8n: TransitionTool, fork: Fork, eips: Optional[List[int]] = None, + post_hint: Optional[Dict[int, str]] = None, ) -> Fixture: """ Create a fixture from the state test definition. @@ -146,7 +148,7 @@ def make_state_test_fixture( ) try: - self.post.verify_post_alloc(transition_tool_output.alloc) + self.post.verify_post_alloc(transition_tool_output.alloc, post_hint) except Exception as e: print_traces(t8n.get_traces()) raise e @@ -183,7 +185,7 @@ def generate( request=request, t8n=t8n, fork=fork, fixture_format=fixture_format, eips=eips ) elif fixture_format == StateFixture: - return self.make_state_test_fixture(t8n, fork, eips) + return self.make_state_test_fixture(t8n, fork, eips, self.post_hint) raise Exception(f"Unknown fixture format: {fixture_format}") diff --git a/src/ethereum_test_types/types.py b/src/ethereum_test_types/types.py index d06a0883f5..529724b3b3 100644 --- a/src/ethereum_test_types/types.py +++ b/src/ethereum_test_types/types.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from functools import cached_property -from typing import Any, ClassVar, Dict, Generic, List, Literal, Sequence, Tuple +from typing import Any, ClassVar, Dict, Generic, List, Literal, Optional, Sequence, Tuple from coincurve.keys import PrivateKey, PublicKey from ethereum import rlp as eth_rlp @@ -268,7 +268,7 @@ def state_root(self) -> bytes: ) return state_root(state) - def verify_post_alloc(self, got_alloc: "Alloc"): + def verify_post_alloc(self, got_alloc: "Alloc", post_hint: Optional[Dict[int, str]] = None): """ Verify that the allocation matches the expected post in the test. Raises exception on unexpected values. @@ -284,7 +284,7 @@ def verify_post_alloc(self, got_alloc: "Alloc"): got_account = got_alloc.root[address] assert isinstance(got_account, Account) assert isinstance(account, Account) - account.check_alloc(address, got_account) + account.check_alloc(address, got_account, post_hint) else: raise Alloc.MissingAccount(address)