Skip to content

Commit 956e38b

Browse files
committed
fix(tests/static): Resolve discrepancies with pydantic logic refactor
fix: fix when tabs are found in lines with spaces; fix shortnames fix: Resolve issues with label: / raw: parsing Turn off more tests, mostly related to create / `creation` addresses
1 parent 65b7888 commit 956e38b

File tree

82 files changed

+1713
-27691
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1713
-27691
lines changed

failed_tests_output.txt

Lines changed: 0 additions & 26392 deletions
This file was deleted.

scripts/convert_addresses.py

Lines changed: 737 additions & 512 deletions
Large diffs are not rendered by default.

src/ethereum_test_specs/static_state/account.py

Lines changed: 157 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Account structure of ethereum/tests fillers."""
22

3-
from typing import Any, Dict, Mapping
3+
from typing import Any, Dict, List, Mapping, Set, Tuple
44

55
from pydantic import BaseModel
66

7-
from ethereum_test_base_types import EthereumTestRootModel, HexNumber
7+
from ethereum_test_base_types import Bytes, EthereumTestRootModel, HexNumber, Storage
88
from ethereum_test_types import Alloc
99

1010
from .common import (
@@ -78,7 +78,7 @@ def resolve(self, tags: TagDict) -> Dict[str, Any]:
7878
if compiled_code := self.code.compiled(tags):
7979
account_properties["code"] = compiled_code
8080
if self.nonce is not None:
81-
account_properties["nonce"] = self.nonce
81+
account_properties["nonce"] = self.nonce
8282
if self.storage is not None:
8383
if resolved_storage := self.storage.resolve(tags):
8484
account_properties["storage"] = resolved_storage
@@ -90,51 +90,165 @@ class PreInFiller(EthereumTestRootModel):
9090

9191
root: Dict[AddressOrTagInFiller, AccountInFiller]
9292

93-
def setup(self, pre: Alloc, all_dependencies: Dict[str, Tag]) -> TagDict:
94-
"""Resolve the pre-state."""
95-
max_tries = len(self.root)
93+
def _build_dependency_graph(
94+
self,
95+
) -> Tuple[Dict[str, Set[str]], Dict[str, AddressOrTagInFiller]]:
96+
"""Build a dependency graph for all tags."""
97+
dep_graph: Dict[str, Set[str]] = {}
98+
tag_to_address: Dict[str, AddressOrTagInFiller] = {}
99+
100+
# First pass: identify all tags and their dependencies
101+
for address_or_tag, account in self.root.items():
102+
if isinstance(address_or_tag, Tag):
103+
tag_name = address_or_tag.name
104+
tag_to_address[tag_name] = address_or_tag
105+
dep_graph[tag_name] = set()
106+
107+
# Get dependencies from account properties
108+
dependencies = account.tag_dependencies()
109+
for dep_name in dependencies:
110+
if dep_name != tag_name: # Ignore self-references
111+
dep_graph[tag_name].add(dep_name)
112+
113+
return dep_graph, tag_to_address
114+
115+
def _topological_sort(self, dep_graph: Dict[str, Set[str]]) -> List[str]:
116+
"""Perform topological sort on dependency graph."""
117+
# Create a copy to modify
118+
graph = {node: deps.copy() for node, deps in dep_graph.items()}
119+
120+
# Find nodes with no dependencies
121+
no_deps = [node for node, deps in graph.items() if not deps]
122+
sorted_nodes = []
123+
124+
while no_deps:
125+
# Process a node with no dependencies
126+
node = no_deps.pop()
127+
sorted_nodes.append(node)
128+
129+
# Remove this node from other nodes' dependencies
130+
for other_node, deps in graph.items():
131+
if node in deps:
132+
deps.remove(node)
133+
if not deps and other_node not in sorted_nodes:
134+
no_deps.append(other_node)
135+
136+
# Check for cycles
137+
remaining = [node for node in graph if node not in sorted_nodes]
138+
if remaining:
139+
# Handle cycles by processing remaining nodes in any order
140+
# This works because self-references are allowed
141+
sorted_nodes.extend(remaining)
142+
143+
return sorted_nodes
96144

97-
unresolved_accounts_in_pre = dict(self.root)
145+
def setup(self, pre: Alloc, all_dependencies: Dict[str, Tag]) -> TagDict:
146+
"""Resolve the pre-state with improved tag resolution."""
98147
resolved_accounts: TagDict = {}
99148

100-
while max_tries > 0:
101-
# Naive approach to resolve accounts
102-
unresolved_accounts_keys = list(unresolved_accounts_in_pre.keys())
103-
for address_or_tag in unresolved_accounts_keys:
104-
account = unresolved_accounts_in_pre[address_or_tag]
105-
tag_dependencies = account.tag_dependencies()
106-
# check if all tag dependencies are resolved
107-
if all(dependency in resolved_accounts for dependency in tag_dependencies):
108-
account_properties = account.resolve(resolved_accounts)
109-
if isinstance(address_or_tag, Tag):
110-
if isinstance(address_or_tag, ContractTag):
111-
resolved_accounts[address_or_tag.name] = pre.deploy_contract(
112-
**account_properties,
113-
label=address_or_tag.name,
114-
)
115-
elif isinstance(address_or_tag, SenderTag):
116-
if "balance" in account_properties:
117-
account_properties["amount"] = account_properties.pop("balance")
118-
resolved_accounts[address_or_tag.name] = pre.fund_eoa(
119-
**account_properties,
120-
label=address_or_tag.name,
121-
)
122-
else:
123-
raise ValueError(f"Unknown tag type: {address_or_tag}")
124-
else:
125-
raise ValueError(f"Unknown tag type: {address_or_tag}")
126-
del unresolved_accounts_in_pre[address_or_tag]
127-
128-
max_tries -= 1
129-
assert len(unresolved_accounts_in_pre) == 0, (
130-
f"Unresolved accounts: {unresolved_accounts_in_pre}, probably a circular dependency"
131-
)
149+
# Separate tagged and non-tagged accounts
150+
tagged_accounts = {}
151+
non_tagged_accounts = {}
152+
153+
for address_or_tag, account in self.root.items():
154+
if isinstance(address_or_tag, Tag):
155+
tagged_accounts[address_or_tag] = account
156+
else:
157+
non_tagged_accounts[address_or_tag] = account
158+
159+
# Step 1: Process non-tagged accounts but don't compile code yet
160+
# We'll compile code later after all tags are resolved
161+
non_tagged_to_process = []
162+
for address, account in non_tagged_accounts.items():
163+
non_tagged_to_process.append((address, account))
164+
resolved_accounts[address.hex()] = address
165+
166+
# Step 2: Build dependency graph for tagged accounts
167+
dep_graph, tag_to_address = self._build_dependency_graph()
168+
169+
# Step 3: Get topological order
170+
resolution_order = self._topological_sort(dep_graph)
171+
172+
# Step 4: Pre-deploy all contract tags and pre-fund EOAs to get addresses
173+
for tag_name in resolution_order:
174+
if tag_name in tag_to_address:
175+
tag = tag_to_address[tag_name]
176+
if isinstance(tag, ContractTag):
177+
# Deploy with placeholder to get address
178+
deployed_address = pre.deploy_contract(
179+
code=b"", # Temporary placeholder
180+
label=tag_name,
181+
)
182+
resolved_accounts[tag_name] = deployed_address
183+
elif isinstance(tag, SenderTag):
184+
# Create EOA to get address - use amount=1 to ensure account is created
185+
eoa = pre.fund_eoa(amount=1, label=tag_name)
186+
# Store the EOA object for SenderKeyTag resolution
187+
resolved_accounts[tag_name] = eoa
188+
189+
# Step 5: Now resolve all properties with all addresses available
190+
for tag_name in resolution_order:
191+
if tag_name in tag_to_address:
192+
tag = tag_to_address[tag_name]
193+
assert isinstance(tag, (ContractTag, SenderTag)), (
194+
f"Tag {tag_name} is not a contract or sender"
195+
)
196+
account = tagged_accounts[tag]
197+
198+
# All addresses are now available, so resolve properties
199+
account_properties = account.resolve(resolved_accounts)
200+
201+
if isinstance(tag, ContractTag):
202+
# Update the already-deployed contract
203+
deployed_address = resolved_accounts[tag_name]
204+
deployed_account = pre[deployed_address]
205+
206+
if deployed_account is not None:
207+
if "code" in account_properties:
208+
deployed_account.code = Bytes(account_properties["code"])
209+
if "balance" in account_properties:
210+
deployed_account.balance = account_properties["balance"]
211+
if "nonce" in account_properties:
212+
deployed_account.nonce = account_properties["nonce"]
213+
if "storage" in account_properties:
214+
deployed_account.storage = Storage(root=account_properties["storage"])
215+
216+
elif isinstance(tag, SenderTag):
217+
eoa_account = pre[resolved_accounts[tag_name]]
218+
219+
if eoa_account is not None:
220+
if "balance" in account_properties:
221+
eoa_account.balance = account_properties["balance"]
222+
if "nonce" in account_properties:
223+
eoa_account.nonce = account_properties["nonce"]
224+
if "code" in account_properties:
225+
eoa_account.code = Bytes(account_properties["code"])
226+
if "storage" in account_properties:
227+
eoa_account.storage = Storage(root=account_properties["storage"])
228+
229+
# Step 6: Now process non-tagged accounts (including code compilation)
230+
for address, account in non_tagged_to_process:
231+
account_properties = account.resolve(resolved_accounts)
232+
if "balance" in account_properties:
233+
pre.fund_address(address, account_properties["balance"])
234+
235+
existing_account = pre[address]
236+
if existing_account is not None:
237+
if "code" in account_properties:
238+
existing_account.code = Bytes(account_properties["code"])
239+
if "nonce" in account_properties:
240+
existing_account.nonce = account_properties["nonce"]
241+
if "storage" in account_properties:
242+
existing_account.storage = Storage(root=account_properties["storage"])
243+
244+
# Step 7: Handle any extra dependencies not in pre
132245
for extra_dependency in all_dependencies:
133246
if extra_dependency not in resolved_accounts:
134247
if all_dependencies[extra_dependency].type != "eoa":
135248
raise ValueError(f"Contract dependency {extra_dependency} not found in pre")
136-
# Assume the extra EOA is an empty account
137-
resolved_accounts[extra_dependency] = pre.fund_eoa(
138-
amount=0, label=extra_dependency
139-
)
249+
250+
# Create new EOA - this will have a dynamically generated key and address
251+
eoa = pre.fund_eoa(amount=0, label=extra_dependency)
252+
resolved_accounts[extra_dependency] = eoa
253+
140254
return resolved_accounts

src/ethereum_test_specs/static_state/common/common.py

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,29 @@ class CodeInFiller(BaseModel, TagDependentData):
6666
@model_validator(mode="before")
6767
@classmethod
6868
def validate_from_string(cls, code: Any) -> Any:
69-
"""Validate the sender tag from string: <eoa:name:0x...>."""
69+
"""Validate from string, separating label from code source."""
7070
if isinstance(code, str):
7171
label_marker = ":label"
72-
label_index = code.find(label_marker)
72+
# Only look for label at the beginning of the string (possibly after whitespace)
73+
stripped_code = code.lstrip()
7374

7475
# Parse :label into code options
7576
label = None
76-
if label_index != -1:
77+
source = code
78+
79+
# Check if the code starts with :label
80+
if stripped_code.startswith(label_marker):
81+
# Calculate the position in the original string
82+
label_index = code.find(label_marker)
7783
space_index = code.find(" ", label_index + len(label_marker) + 1)
7884
if space_index == -1:
7985
label = code[label_index + len(label_marker) + 1 :]
86+
source = "" # No source after label
8087
else:
8188
label = code[label_index + len(label_marker) + 1 : space_index]
82-
return {"label": label, "source": code}
89+
source = code[space_index + 1 :].strip()
90+
91+
return {"label": label, "source": source}
8392
return code
8493

8594
def model_post_init(self, context):
@@ -101,29 +110,35 @@ def compiled(self, tags: TagDict) -> bytes:
101110
return bytes.fromhex(hex_str)
102111

103112
if not isinstance(raw_code, str):
104-
raise ValueError(f"parse_code(code: str) code is not string: {raw_code}")
113+
raise ValueError(f"code is of type {type(raw_code)} but expected a string: {raw_code}")
105114
if len(raw_code) == 0:
106115
return b""
107116

108117
compiled_code = ""
109118

110-
raw_marker = ":raw 0x"
111-
raw_index = raw_code.find(raw_marker)
112-
abi_marker = ":abi"
113-
abi_index = raw_code.find(abi_marker)
114-
yul_marker = ":yul"
115-
yul_index = raw_code.find(yul_marker)
116-
117119
def replace_tags(raw_code, keep_prefix: bool) -> str:
118120
for tag in self._dependencies.values():
119121
if tag.name not in tags:
120122
raise ValueError(f"Tag {tag} not found in tags")
121123
substitution_address = f"{tag.resolve(tags)}"
122124
if not keep_prefix and substitution_address.startswith("0x"):
123125
substitution_address = substitution_address[2:]
124-
raw_code = re.sub(f"<\\w+:{tag.name}(:0x.+)?>", substitution_address, raw_code)
126+
# Use the original string if available, otherwise construct a pattern
127+
if hasattr(tag, "original_string") and tag.original_string:
128+
raw_code = raw_code.replace(tag.original_string, substitution_address)
129+
else:
130+
raw_code = re.sub(f"<\\w+:{tag.name}(:0x.+)?>", substitution_address, raw_code)
125131
return raw_code
126132

133+
raw_marker = ":raw 0x"
134+
raw_index = raw_code.find(raw_marker)
135+
if raw_index == -1:
136+
raw_index = replace_tags(raw_code, True).find(raw_marker)
137+
abi_marker = ":abi"
138+
abi_index = raw_code.find(abi_marker)
139+
yul_marker = ":yul"
140+
yul_index = raw_code.find(yul_marker)
141+
127142
# Parse :raw or 0x
128143
if raw_index != -1 or raw_code.lstrip().startswith("0x"):
129144
raw_code = replace_tags(raw_code, False)
@@ -190,7 +205,11 @@ def replace_tags(raw_code, keep_prefix: bool) -> str:
190205
return function_signature
191206

192207
# Parse lllc code
193-
elif raw_code.lstrip().startswith("{") or raw_code.lstrip().startswith("(asm"):
208+
elif (
209+
raw_code.lstrip().startswith("{")
210+
or raw_code.lstrip().startswith("(asm")
211+
or raw_code.lstrip().startswith(":raw 0x")
212+
):
194213
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp:
195214
tmp.write(raw_code)
196215
tmp_path = tmp.name
@@ -199,8 +218,9 @@ def replace_tags(raw_code, keep_prefix: bool) -> str:
199218
result = subprocess.run(["lllc", tmp_path], capture_output=True, text=True)
200219

201220
# - using docker:
202-
# If the running machine does not have lllc installed, we can use docker to run lllc,
203-
# but we need to start a container first, and the process is generally slower.
221+
# If the running machine does not have lllc installed, we can use docker to
222+
# run lllc, but we need to start a container first, and the process is generally
223+
# slower.
204224
# from .docker import get_lllc_container_id
205225
# result = subprocess.run(
206226
# ["docker", "exec", get_lllc_container_id(), "lllc", tmp_path[5:]],

0 commit comments

Comments
 (0)