Skip to content

Commit 07588f0

Browse files
committed
do not compile test fillers source code while loading the models
1 parent 864b0b8 commit 07588f0

File tree

6 files changed

+130
-118
lines changed

6 files changed

+130
-118
lines changed

Diff for: src/ethereum_test_specs/static_state/account.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ class Config:
1919
"""Model Config."""
2020

2121
extra = "forbid"
22+
arbitrary_types_allowed = True

Diff for: src/ethereum_test_specs/static_state/common/common.py

+121-107
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
import re
44
import subprocess
55
import tempfile
6-
from typing import Tuple
6+
from functools import cached_property
7+
from typing import Any
78

89
from eth_abi import encode
910
from eth_utils import function_signature_to_4byte_selector
10-
from pydantic import BaseModel, Field
11+
from pydantic import BaseModel
1112
from pydantic.functional_validators import BeforeValidator
1213
from typing_extensions import Annotated
1314

14-
from ethereum_test_base_types import Address, Bytes, Hash, HexNumber
15+
from ethereum_test_base_types import Address, Hash, HexNumber
1516

1617
from .compile_yul import compile_yul
1718

@@ -30,12 +31,6 @@ def parse_hex_number(i: str | int) -> int:
3031
return int(i, 10)
3132

3233

33-
class CodeOptions(BaseModel):
34-
"""Define options of the code."""
35-
36-
label: str = Field("")
37-
38-
3934
def parse_args_from_string_into_array(stream: str, pos: int, delim: str = " "):
4035
"""Parse YUL options into array."""
4136
args = []
@@ -53,116 +48,135 @@ def parse_args_from_string_into_array(stream: str, pos: int, delim: str = " "):
5348
return args, pos
5449

5550

56-
def parse_code(code: str) -> Tuple[bytes, CodeOptions]:
57-
"""Check if the given string is a valid code."""
58-
# print("parse `" + str(code) + "`")
59-
if isinstance(code, int):
60-
# Users pass code as int (very bad)
61-
hex_str = format(code, "02x")
62-
return (bytes.fromhex(hex_str), CodeOptions())
63-
if not isinstance(code, str):
64-
raise ValueError(f"parse_code(code: str) code is not string: {code}")
65-
if len(code) == 0:
66-
return (bytes.fromhex(""), CodeOptions())
67-
68-
compiled_code = ""
69-
code_options: CodeOptions = CodeOptions()
70-
71-
raw_marker = ":raw 0x"
72-
raw_index = code.find(raw_marker)
73-
abi_marker = ":abi"
74-
abi_index = code.find(abi_marker)
51+
class CodeInFillerSource:
52+
"""Not compiled code source in test filler."""
53+
54+
code_label: str | None
55+
code_raw: Any
56+
57+
def __init__(self, code: Any, label: str | None = None):
58+
"""Instantiate."""
59+
self.code_label = label
60+
self.code_raw = code
61+
62+
@cached_property
63+
def compiled(self) -> bytes:
64+
"""Compile the code from source to bytes."""
65+
if isinstance(self.code_raw, int):
66+
# Users pass code as int (very bad)
67+
hex_str = format(self.code_raw, "02x")
68+
return bytes.fromhex(hex_str)
69+
70+
if not isinstance(self.code_raw, str):
71+
raise ValueError(f"parse_code(code: str) code is not string: {self.code_raw}")
72+
if len(self.code_raw) == 0:
73+
return bytes.fromhex("")
74+
75+
compiled_code = ""
76+
77+
raw_marker = ":raw 0x"
78+
raw_index = self.code_raw.find(raw_marker)
79+
abi_marker = ":abi"
80+
abi_index = self.code_raw.find(abi_marker)
81+
yul_marker = ":yul"
82+
yul_index = self.code_raw.find(yul_marker)
83+
84+
# Prase :raw
85+
if raw_index != -1:
86+
compiled_code = self.code_raw[raw_index + len(raw_marker) :]
87+
88+
# Prase :yul
89+
elif yul_index != -1:
90+
option_start = yul_index + len(yul_marker)
91+
options: list[str] = []
92+
native_yul_options: str = ""
93+
94+
if self.code_raw[option_start:].lstrip().startswith("{"):
95+
# No yul options, proceed to code parsing
96+
source_start = option_start
97+
else:
98+
opt, source_start = parse_args_from_string_into_array(
99+
self.code_raw, option_start + 1
100+
)
101+
for arg in opt:
102+
if arg == "object" or arg == '"C"':
103+
native_yul_options += arg + " "
104+
else:
105+
options.append(arg)
106+
107+
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp:
108+
tmp.write(native_yul_options + self.code_raw[source_start:])
109+
tmp_path = tmp.name
110+
compiled_code = compile_yul(
111+
source_file=tmp_path,
112+
evm_version=options[0] if len(options) >= 1 else None,
113+
optimize=options[1] if len(options) >= 2 else None,
114+
)[2:]
115+
116+
# Prase :abi
117+
elif abi_index != -1:
118+
abi_encoding = self.code_raw[abi_index + len(abi_marker) + 1 :]
119+
tokens = abi_encoding.strip().split()
120+
abi = tokens[0]
121+
function_signature = function_signature_to_4byte_selector(abi)
122+
parameter_str = re.sub(r"^\w+", "", abi).strip()
123+
124+
parameter_types = parameter_str.strip("()").split(",")
125+
if len(tokens) > 1:
126+
function_parameters = encode(
127+
[parameter_str],
128+
[
129+
[
130+
int(t.lower(), 0) & ((1 << 256) - 1) # treat big ints as 256bits
131+
if parameter_types[t_index] == "uint"
132+
else int(t.lower(), 0) > 0 # treat positive values as True
133+
if parameter_types[t_index] == "bool"
134+
else False and ValueError("unhandled parameter_types")
135+
for t_index, t in enumerate(tokens[1:])
136+
]
137+
],
138+
)
139+
return function_signature + function_parameters
140+
return function_signature
141+
142+
# Prase plain code 0x
143+
elif self.code_raw.lstrip().startswith("0x"):
144+
compiled_code = self.code_raw[2:].lower()
145+
146+
# Prase lllc code
147+
elif self.code_raw.lstrip().startswith("{") or self.code_raw.lstrip().startswith("(asm"):
148+
binary_path = "lllc"
149+
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp:
150+
tmp.write(self.code_raw)
151+
tmp_path = tmp.name
152+
result = subprocess.run([binary_path, tmp_path], capture_output=True, text=True)
153+
compiled_code = "".join(result.stdout.splitlines())
154+
else:
155+
raise Exception(f'Error parsing code: "{self.code_raw}"')
156+
157+
try:
158+
return bytes.fromhex(compiled_code)
159+
except ValueError as e:
160+
raise Exception(f'Error parsing compile code: "{self.code_raw}"') from e
161+
162+
163+
def parse_code_label(code) -> CodeInFillerSource:
164+
"""Parse label from code."""
75165
label_marker = ":label"
76166
label_index = code.find(label_marker)
77-
yul_marker = ":yul"
78-
yul_index = code.find(yul_marker)
79167

80168
# Parse :label into code options
169+
label = None
81170
if label_index != -1:
82171
space_index = code.find(" ", label_index + len(label_marker) + 1)
83172
if space_index == -1:
84173
label = code[label_index + len(label_marker) + 1 :]
85174
else:
86175
label = code[label_index + len(label_marker) + 1 : space_index]
87-
code_options.label = label
88-
89-
# Prase :raw
90-
if raw_index != -1:
91-
compiled_code = code[raw_index + len(raw_marker) :]
92-
93-
elif yul_index != -1:
94-
option_start = yul_index + len(yul_marker)
95-
options: list[str] = []
96-
native_yul_options: str = ""
97-
98-
if code[option_start:].lstrip().startswith("{"):
99-
# No yul options, proceed to code parsing
100-
source_start = option_start
101-
else:
102-
opt, source_start = parse_args_from_string_into_array(code, option_start + 1)
103-
for arg in opt:
104-
if arg == "object" or arg == '"C"':
105-
native_yul_options += arg + " "
106-
else:
107-
options.append(arg)
108-
109-
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp:
110-
tmp.write(native_yul_options + code[source_start:])
111-
tmp_path = tmp.name
112-
compiled_code = compile_yul(
113-
source_file=tmp_path,
114-
evm_version=options[0] if len(options) >= 1 else None,
115-
optimize=options[1] if len(options) >= 2 else None,
116-
)[2:]
117-
118-
# Prase :abi
119-
elif abi_index != -1:
120-
abi_encoding = code[abi_index + len(abi_marker) + 1 :]
121-
tokens = abi_encoding.strip().split()
122-
abi = tokens[0]
123-
function_signature = function_signature_to_4byte_selector(abi)
124-
parameter_str = re.sub(r"^\w+", "", abi).strip()
125-
126-
parameter_types = parameter_str.strip("()").split(",")
127-
if len(tokens) > 1:
128-
function_parameters = encode(
129-
[parameter_str],
130-
[
131-
[
132-
int(t.lower(), 0) & ((1 << 256) - 1) # treat big ints as 256bits
133-
if parameter_types[t_index] == "uint"
134-
else int(t.lower(), 0) > 0 # treat positive values as True
135-
if parameter_types[t_index] == "bool"
136-
else False and ValueError("unhandled parameter_types")
137-
for t_index, t in enumerate(tokens[1:])
138-
]
139-
],
140-
)
141-
return (function_signature + function_parameters, code_options)
142-
return (function_signature, code_options)
143-
144-
# Prase plain code 0x
145-
elif code.lstrip().startswith("0x"):
146-
compiled_code = code[2:].lower()
147-
148-
# Prase lllc code
149-
elif code.lstrip().startswith("{") or code.lstrip().startswith("(asm"):
150-
binary_path = "lllc"
151-
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as tmp:
152-
tmp.write(code)
153-
tmp_path = tmp.name
154-
result = subprocess.run([binary_path, tmp_path], capture_output=True, text=True)
155-
compiled_code = "".join(result.stdout.splitlines())
156-
else:
157-
raise Exception(f'Error parsing code: "{code}"')
158-
159-
try:
160-
return (bytes.fromhex(compiled_code), code_options)
161-
except ValueError as e:
162-
raise Exception(f'Error parsing compile code: "{code}"') from e
176+
return CodeInFillerSource(code, label)
163177

164178

165179
AddressInFiller = Annotated[Address, BeforeValidator(lambda a: Address(a, left_padding=True))]
166180
ValueInFiller = Annotated[HexNumber, BeforeValidator(parse_hex_number)]
167-
CodeInFiller = Annotated[Tuple[Bytes, CodeOptions], BeforeValidator(parse_code)]
181+
CodeInFiller = Annotated[CodeInFillerSource, BeforeValidator(parse_code_label)]
168182
Hash32InFiller = Annotated[Hash, BeforeValidator(lambda h: Hash(h, left_padding=True))]

Diff for: src/ethereum_test_specs/static_state/expect_section.py

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Config:
3838
"""Model Config."""
3939

4040
extra = "forbid"
41+
arbitrary_types_allowed = True # For CodeInFiller
4142

4243

4344
class CMP(Enum):

Diff for: src/ethereum_test_specs/static_state/general_transaction.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class Config:
1919
"""Model Config."""
2020

2121
extra = "forbid"
22+
arbitrary_types_allowed = True # For CodeInFiller
2223

2324
@field_validator("access_list", mode="before")
2425
def convert_keys_to_hash(cls, access_list): # noqa: N805

Diff for: src/ethereum_test_specs/static_state/state_static.py

+5-9
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,10 @@ def _get_pre(self) -> Alloc:
110110
for key, value in account.storage.items():
111111
storage[key] = value
112112

113-
acc_code, acc_code_opt = account.code
114113
pre[account_address] = Account(
115114
balance=account.balance,
116115
nonce=account.nonce,
117-
code=acc_code,
116+
code=account.code.compiled,
118117
storage=storage,
119118
)
120119
return pre
@@ -129,13 +128,11 @@ def _make_vector(
129128
) -> Tuple[Alloc, Transaction]:
130129
"""Compose test vector from test data."""
131130
general_tr = self.transaction
132-
data = general_tr.data[d]
133-
134-
data_code, options = data.data
131+
data_box = general_tr.data[d]
135132

136133
tr: Transaction = Transaction(
137-
data=data_code,
138-
access_list=data.access_list,
134+
data=data_box.data.compiled,
135+
access_list=data_box.access_list,
139136
gas_limit=HexNumber(general_tr.gas_limit[g]),
140137
value=HexNumber(general_tr.value[v]),
141138
gas_price=general_tr.gas_price,
@@ -165,8 +162,7 @@ def _make_vector(
165162
storage.set_expect_any(key)
166163
account_kwargs["storage"] = storage
167164
if account.code is not None:
168-
code_bytes, code_options = account.code
169-
account_kwargs["code"] = code_bytes
165+
account_kwargs["code"] = account.code.compiled
170166
if account.balance is not None:
171167
account_kwargs["balance"] = account.balance
172168
if account.nonce is not None:

Diff for: src/ethereum_test_specs/static_state/state_test_filler.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ def parse_string_indexes(indexes: str) -> List[int]:
4646
indexes = indexes.replace(":label ", "")
4747
tx_matches: List[int] = []
4848
for idx, d in enumerate(self.transaction.data):
49-
_, code_opt = d.data
50-
if indexes == code_opt.label:
49+
if indexes == d.data.code_label:
5150
tx_matches.append(idx)
5251
return tx_matches
5352
else:

0 commit comments

Comments
 (0)