6
6
import os
7
7
import sys
8
8
from dataclasses import asdict , dataclass
9
- from typing import Dict , List , Union
9
+ from typing import Dict , List
10
10
11
11
import requests
12
12
13
- from ethereum_test_tools import Account , Address , Transaction
13
+ from ethereum_test_tools import Account , Address , Transaction , common
14
14
15
15
16
16
def main (): # noqa: D103
@@ -45,74 +45,131 @@ def make_test(args): # noqa: D103
45
45
print ("Perform debug_trace_call" )
46
46
state = request .debug_trace_call (tr )
47
47
48
+ print ("Perform eth_get_block_by_number" )
49
+ block = request .eth_get_block_by_number (tr .block_number )
50
+
48
51
print ("Generate py test >> " + output_file )
49
- test = make_test_template (tr , state )
52
+ constructor = TestConstructor (PYTEST_TEMPLATE )
53
+ test = constructor .make_test_template (block , tr , state )
50
54
with open (output_file , "w" ) as file :
51
55
file .write (test )
52
56
53
- print (test )
57
+ print ("Finished" )
54
58
55
59
56
- def make_test_template (
57
- tr : "RequestManager.RemoteTransaction" , state : Dict [Address , Account ]
58
- ) -> str :
60
+ class TestConstructor :
59
61
"""
60
- Prepare the .py file template
62
+ Construct .py file from test template, by replacing keywords with test data
61
63
"""
62
- test = PYTEST_TEMPLATE
63
- test = test .replace (
64
- "$HEADLINE_COMMENT" ,
65
- "gentest autogenerated test with debug_traceCall of tx.hash " + tr .tr_hash ,
66
- )
67
- test = test .replace ("$TEST_NAME" , "test_transaction_" + tr .tr_hash [2 :])
68
- test = test .replace (
69
- "$TEST_COMMENT" , "gentest autogenerated test for tx.hash " + tr .tr_hash [2 :]
70
- )
71
64
72
- # Print a nice .py storage pre
73
- pad = " "
74
- state_str = ""
75
- for address , account in state .items ():
76
- if isinstance (account , dict ):
77
- account_obj = Account (** account )
78
- state_str += ' "' + str (address ) + '": Account(\n '
79
- state_str += pad + "balance=" + str (account_obj .balance ) + ",\n "
80
- if address == tr .transaction .sender :
81
- state_str += pad + "nonce=" + str (tr .transaction .nonce ) + ",\n "
82
- else :
83
- state_str += pad + "nonce=" + str (account_obj .nonce ) + ",\n "
65
+ test_template : str
84
66
85
- if account_obj .code is None :
86
- state_str += pad + 'code="0x",\n '
87
- else :
88
- state_str += pad + 'code="' + str (account_obj .code ) + '",\n '
89
- state_str += pad + "storage={\n "
90
-
91
- if account_obj .storage is not None :
92
- for record , value in account_obj .storage .items ():
93
- state_str += pad + ' "' + str (record ) + '" : "' + str (value ) + '",\n '
94
-
95
- state_str += pad + "}\n "
96
- state_str += " ),\n "
97
- test = test .replace ("$PRE" , state_str )
98
-
99
- # Print legacy transaction in .py
100
- tr_str = ""
101
- tr_str += pad + "ty=" + str (tr .transaction .ty ) + ",\n "
102
- tr_str += pad + "chain_id=" + str (tr .transaction .chain_id ) + ",\n "
103
- tr_str += pad + "nonce=" + str (tr .transaction .nonce ) + ",\n "
104
- tr_str += pad + 'to="' + str (tr .transaction .to ) + '",\n '
105
- tr_str += pad + "gas_price=" + str (tr .transaction .gas_price ) + ",\n "
106
- tr_str += pad + "protected=False,\n "
107
- tr_str += pad + 'data="' + str (tr .transaction .data ) + '",\n '
108
- tr_str += pad + "gas_limit=" + str (tr .transaction .gas_limit ) + ",\n "
109
- tr_str += pad + "value=" + str (tr .transaction .value ) + ",\n "
110
- tr_str += pad + "v=" + str (tr .transaction .v ) + ",\n "
111
- tr_str += pad + "r=" + str (tr .transaction .r ) + ",\n "
112
- tr_str += pad + "s=" + str (tr .transaction .s ) + ",\n "
113
-
114
- test = test .replace ("$TR" , tr_str )
115
- return test
67
+ def __init__ (self , test_template : str ):
68
+ """
69
+ Initialize with template
70
+ """
71
+ self .test_template = test_template
72
+
73
+ def _make_test_comments (self , test : str , tr_hash : str ) -> str :
74
+ test = test .replace (
75
+ "$HEADLINE_COMMENT" ,
76
+ "gentest autogenerated test with debug_traceCall of tx.hash " + tr_hash ,
77
+ )
78
+ test = test .replace ("$TEST_NAME" , "test_transaction_" + tr_hash [2 :])
79
+ test = test .replace (
80
+ "$TEST_COMMENT" , "gentest autogenerated test for tx.hash " + tr_hash [2 :]
81
+ )
82
+ return test
83
+
84
+ def _make_test_environment (self , test : str , bl : "RequestManager.RemoteBlock" ) -> str :
85
+ env_str = ""
86
+ pad = " "
87
+ for field , value in asdict (bl ).items ():
88
+ env_str += (
89
+ f'{ pad } { field } ="{ value } ",\n ' if field == "coinbase" else f"{ pad } { field } ={ value } ,\n "
90
+ )
91
+ test = test .replace ("$ENV" , env_str )
92
+ return test
93
+
94
+ def _make_pre_state (
95
+ self , test : str , tr : "RequestManager.RemoteTransaction" , state : Dict [Address , Account ]
96
+ ) -> str :
97
+ # Print a nice .py storage pre
98
+ pad = " "
99
+ state_str = ""
100
+ for address , account in state .items ():
101
+ if isinstance (account , dict ):
102
+ account_obj = Account (** account )
103
+ state_str += f' "{ address } ": Account(\n '
104
+ state_str += f"{ pad } balance={ account_obj .balance } ,\n "
105
+ if address == tr .transaction .sender :
106
+ state_str += f"{ pad } nonce={ tr .transaction .nonce } ,\n "
107
+ else :
108
+ state_str += f"{ pad } nonce={ account_obj .nonce } ,\n "
109
+
110
+ if account_obj .code is None :
111
+ state_str += f'{ pad } code="0x",\n '
112
+ else :
113
+ state_str += f'{ pad } code="{ account_obj .code } ",\n '
114
+ state_str += pad + "storage={\n "
115
+
116
+ if account_obj .storage is not None :
117
+ for record , value in account_obj .storage .items ():
118
+ pad_record = common .ZeroPaddedHexNumber (record )
119
+ pad_value = common .ZeroPaddedHexNumber (value )
120
+ state_str += f'{ pad } "{ pad_record } " : "{ pad_value } ",\n '
121
+
122
+ state_str += pad + "}\n "
123
+ state_str += " ),\n "
124
+ return test .replace ("$PRE" , state_str )
125
+
126
+ def _make_transaction (self , test : str , tr : "RequestManager.RemoteTransaction" ) -> str :
127
+ """
128
+ Print legacy transaction in .py
129
+ """
130
+ pad = " "
131
+ tr_str = ""
132
+ quoted_fields_array = ["data" , "to" ]
133
+ hex_fields_array = ["v" , "r" , "s" ]
134
+ legacy_fields_array = [
135
+ "ty" ,
136
+ "chain_id" ,
137
+ "nonce" ,
138
+ "gas_price" ,
139
+ "protected" ,
140
+ "gas_limit" ,
141
+ "value" ,
142
+ ]
143
+ for field , value in asdict (tr .transaction ).items ():
144
+ if value is None :
145
+ continue
146
+
147
+ if field in legacy_fields_array :
148
+ tr_str += f"{ pad } { field } ={ value } ,\n "
149
+
150
+ if field in quoted_fields_array :
151
+ tr_str += f'{ pad } { field } ="{ value } ",\n '
152
+
153
+ if field in hex_fields_array :
154
+ tr_str += f"{ pad } { field } ={ hex (value )} ,\n "
155
+
156
+ return test .replace ("$TR" , tr_str )
157
+
158
+ def make_test_template (
159
+ self ,
160
+ bl : "RequestManager.RemoteBlock" ,
161
+ tr : "RequestManager.RemoteTransaction" ,
162
+ state : Dict [Address , Account ],
163
+ ) -> str :
164
+ """
165
+ Prepare the .py file template
166
+ """
167
+ test = self .test_template
168
+ test = self ._make_test_comments (test , tr .tr_hash )
169
+ test = self ._make_test_environment (test , bl )
170
+ test = self ._make_pre_state (test , tr , state )
171
+ test = self ._make_transaction (test , tr )
172
+ return test
116
173
117
174
118
175
class PyspecConfig :
@@ -139,15 +196,7 @@ def __init__(self, config_path: str):
139
196
"""
140
197
with open (config_path , "r" ) as file :
141
198
data = json .load (file )
142
- self .remote_nodes = [self ._json_to_remote_node (node ) for node in data ["remote_nodes" ]]
143
-
144
- def _json_to_remote_node (self , d ):
145
- return PyspecConfig .RemoteNode (
146
- name = d ["name" ],
147
- node_url = d ["node_url" ],
148
- client_id = d ["client_id" ],
149
- secret = d ["secret" ],
150
- )
199
+ self .remote_nodes = [PyspecConfig .RemoteNode (** node ) for node in data ["remote_nodes" ]]
151
200
152
201
153
202
class RequestManager :
@@ -165,6 +214,18 @@ class RemoteTransaction:
165
214
tr_hash : str
166
215
transaction : Transaction
167
216
217
+ @dataclass
218
+ class RemoteBlock :
219
+ """
220
+ Remote block header information structure
221
+ """
222
+
223
+ coinbase : str
224
+ difficulty : str
225
+ gas_limit : str
226
+ number : str
227
+ timestamp : str
228
+
168
229
node_url : str
169
230
headers : dict [str , str ]
170
231
@@ -179,6 +240,19 @@ def __init__(self, node_config: PyspecConfig.RemoteNode):
179
240
"Content-Type" : "application/json" ,
180
241
}
181
242
243
+ def _make_request (self , data ) -> requests .Response :
244
+ error_str = "An error occurred while making remote request: "
245
+ try :
246
+ response = requests .post (self .node_url , headers = self .headers , data = json .dumps (data ))
247
+ if response .status_code >= 200 and response .status_code < 300 :
248
+ return response
249
+ else :
250
+ print (error_str + response .text )
251
+ raise requests .exceptions .HTTPError
252
+ except requests .exceptions .RequestException as e :
253
+ print (error_str , e )
254
+ raise e
255
+
182
256
def eth_get_transaction_by_hash (self , transaction_hash : str ) -> RemoteTransaction :
183
257
"""
184
258
Get transaction data.
@@ -189,7 +263,8 @@ def eth_get_transaction_by_hash(self, transaction_hash: str) -> RemoteTransactio
189
263
"params" : [f"{ transaction_hash } " ],
190
264
"id" : 1 ,
191
265
}
192
- response = requests .post (self .node_url , headers = self .headers , data = json .dumps (data ))
266
+
267
+ response = self ._make_request (data )
193
268
res = response .json ().get ("result" , None )
194
269
195
270
return RequestManager .RemoteTransaction (
@@ -207,10 +282,11 @@ def eth_get_transaction_by_hash(self, transaction_hash: str) -> RemoteTransactio
207
282
v = int (res ["v" ], 16 ),
208
283
r = int (res ["r" ], 16 ),
209
284
s = int (res ["s" ], 16 ),
285
+ protected = True if int (res ["v" ], 16 ) > 30 else False ,
210
286
),
211
287
)
212
288
213
- def eth_get_block_by_number (self , block_number : str ):
289
+ def eth_get_block_by_number (self , block_number : str ) -> RemoteBlock :
214
290
"""
215
291
Get block by number
216
292
"""
@@ -220,8 +296,16 @@ def eth_get_block_by_number(self, block_number: str):
220
296
"params" : [f"{ block_number } " , False ],
221
297
"id" : 1 ,
222
298
}
223
- response = requests .post (self .node_url , headers = self .headers , data = json .dumps (data ))
224
- return response .json ().get ("result" , None )
299
+ response = self ._make_request (data )
300
+ res = response .json ().get ("result" , None )
301
+
302
+ return RequestManager .RemoteBlock (
303
+ coinbase = res ["miner" ],
304
+ number = res ["number" ],
305
+ difficulty = res ["difficulty" ],
306
+ gas_limit = res ["gasLimit" ],
307
+ timestamp = res ["timestamp" ],
308
+ )
225
309
226
310
def debug_trace_call (self , tr : RemoteTransaction ) -> Dict [Address , Account ]:
227
311
"""
@@ -242,7 +326,7 @@ def debug_trace_call(self, tr: RemoteTransaction) -> Dict[Address, Account]:
242
326
"id" : 1 ,
243
327
}
244
328
245
- response = requests . post ( self .node_url , headers = self . headers , data = json . dumps (data ) )
329
+ response = self ._make_request (data )
246
330
return response .json ().get ("result" , None )
247
331
248
332
@@ -262,19 +346,14 @@ def debug_trace_call(self, tr: RemoteTransaction) -> Dict[Address, Account]:
262
346
Transaction,
263
347
)
264
348
265
- REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6780.md "
266
- REFERENCE_SPEC_VERSION = "2f8299df31bb8173618901a03a8366a3183479b0 "
349
+ REFERENCE_SPEC_GIT_PATH = "N/A "
350
+ REFERENCE_SPEC_VERSION = "N/A "
267
351
268
352
269
353
@pytest.fixture
270
354
def env(): # noqa: D103
271
355
return Environment(
272
- coinbase="0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
273
- difficulty=3402669764409613,
274
- #gas_limit=12469470,
275
- gas_limit=0x016345785d8a0000,
276
- number=11114732,
277
- timestamp="0x5f933d46",
356
+ $ENV
278
357
)
279
358
280
359
0 commit comments