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