Skip to content

Commit 65e4eee

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

File tree

6 files changed

+129
-118
lines changed

6 files changed

+129
-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

+120-107
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
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
1111
from pydantic.functional_validators import BeforeValidator
1212
from typing_extensions import Annotated
1313

14-
from ethereum_test_base_types import Address, Bytes, Hash, HexNumber
14+
from ethereum_test_base_types import Address, Hash, HexNumber
1515

1616
from .compile_yul import compile_yul
1717

@@ -30,12 +30,6 @@ def parse_hex_number(i: str | int) -> int:
3030
return int(i, 10)
3131

3232

33-
class CodeOptions(BaseModel):
34-
"""Define options of the code."""
35-
36-
label: str = Field("")
37-
38-
3933
def parse_args_from_string_into_array(stream: str, pos: int, delim: str = " "):
4034
"""Parse YUL options into array."""
4135
args = []
@@ -53,116 +47,135 @@ def parse_args_from_string_into_array(stream: str, pos: int, delim: str = " "):
5347
return args, pos
5448

5549

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

80167
# Parse :label into code options
168+
label = None
81169
if label_index != -1:
82170
space_index = code.find(" ", label_index + len(label_marker) + 1)
83171
if space_index == -1:
84172
label = code[label_index + len(label_marker) + 1 :]
85173
else:
86174
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
175+
return CodeInFillerSource(code, label)
163176

164177

165178
AddressInFiller = Annotated[Address, BeforeValidator(lambda a: Address(a, left_padding=True))]
166179
ValueInFiller = Annotated[HexNumber, BeforeValidator(parse_hex_number)]
167-
CodeInFiller = Annotated[Tuple[Bytes, CodeOptions], BeforeValidator(parse_code)]
180+
CodeInFiller = Annotated[CodeInFillerSource, BeforeValidator(parse_code_label)]
168181
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)