3
3
import re
4
4
import subprocess
5
5
import tempfile
6
- from typing import Tuple
6
+ from functools import cached_property
7
+ from typing import Any
7
8
8
9
from eth_abi import encode
9
10
from eth_utils import function_signature_to_4byte_selector
10
- from pydantic import BaseModel , Field
11
11
from pydantic .functional_validators import BeforeValidator
12
12
from typing_extensions import Annotated
13
13
14
- from ethereum_test_base_types import Address , Bytes , Hash , HexNumber
14
+ from ethereum_test_base_types import Address , Hash , HexNumber
15
15
16
16
from .compile_yul import compile_yul
17
17
@@ -30,12 +30,6 @@ def parse_hex_number(i: str | int) -> int:
30
30
return int (i , 10 )
31
31
32
32
33
- class CodeOptions (BaseModel ):
34
- """Define options of the code."""
35
-
36
- label : str = Field ("" )
37
-
38
-
39
33
def parse_args_from_string_into_array (stream : str , pos : int , delim : str = " " ):
40
34
"""Parse YUL options into array."""
41
35
args = []
@@ -53,116 +47,135 @@ def parse_args_from_string_into_array(stream: str, pos: int, delim: str = " "):
53
47
return args , pos
54
48
55
49
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."""
75
164
label_marker = ":label"
76
165
label_index = code .find (label_marker )
77
- yul_marker = ":yul"
78
- yul_index = code .find (yul_marker )
79
166
80
167
# Parse :label into code options
168
+ label = None
81
169
if label_index != - 1 :
82
170
space_index = code .find (" " , label_index + len (label_marker ) + 1 )
83
171
if space_index == - 1 :
84
172
label = code [label_index + len (label_marker ) + 1 :]
85
173
else :
86
174
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 )
163
176
164
177
165
178
AddressInFiller = Annotated [Address , BeforeValidator (lambda a : Address (a , left_padding = True ))]
166
179
ValueInFiller = Annotated [HexNumber , BeforeValidator (parse_hex_number )]
167
- CodeInFiller = Annotated [Tuple [ Bytes , CodeOptions ], BeforeValidator (parse_code )]
180
+ CodeInFiller = Annotated [CodeInFillerSource , BeforeValidator (parse_code_label )]
168
181
Hash32InFiller = Annotated [Hash , BeforeValidator (lambda h : Hash (h , left_padding = True ))]
0 commit comments