@@ -104,3 +104,207 @@ def test_worst_keccak(
104
104
post = {},
105
105
blocks = [Block (txs = [tx ])],
106
106
)
107
+
108
+
109
+ @pytest .mark .zkevm
110
+ @pytest .mark .valid_from ("Cancun" )
111
+ @pytest .mark .parametrize (
112
+ "gas_limit" ,
113
+ [36_000_000 ],
114
+ )
115
+ def run_jump_analysis_test (
116
+ blockchain_test : BlockchainTestFiller ,
117
+ pre : Alloc ,
118
+ fork : Fork ,
119
+ gas_limit : int ,
120
+ opcodes_pattern ,
121
+ iteration_gas_cost : int ,
122
+ description : str = "Opcode test" ,
123
+ ):
124
+ """
125
+ Generate test for worst-case compute scenario for a given opcode pattern.
126
+ """
127
+ env = Environment (gas_limit = gas_limit )
128
+
129
+ # Intrinsic gas cost is paid once
130
+ intrinsic_gas_calculator = fork .transaction_intrinsic_cost_calculator ()
131
+ available_gas = gas_limit - intrinsic_gas_calculator ()
132
+
133
+ # Each iteration adds the bytes of the opcode pattern
134
+ bytes_per_iter = len (opcodes_pattern )
135
+
136
+ # Calculate max iterations based on code size and gas constraints
137
+ max_iters_by_size = MAX_CODE_SIZE // bytes_per_iter
138
+ max_iters_by_gas = (
139
+ available_gas // iteration_gas_cost if iteration_gas_cost > 0 else float ("inf" )
140
+ )
141
+ num_iters = min (max_iters_by_size , max_iters_by_gas )
142
+
143
+ # Create code with repeated pattern by concatenating the pattern num_iters times
144
+ code = opcodes_pattern * num_iters
145
+
146
+ # Calculate utilization percentages
147
+ code_size_pct = (len (code ) / MAX_CODE_SIZE ) * 100
148
+ gas_used = num_iters * iteration_gas_cost
149
+ gas_pct = (gas_used / available_gas ) * 100 if available_gas > 0 else None
150
+
151
+ # Only write to the summary file if this is the first time we've seen this description
152
+ # This prevents duplication because tests are run for multiple blockchain test implementations
153
+ if description not in LOGGED_DESCRIPTIONS :
154
+ with open (SUMMARY_FILE , "a" ) as f :
155
+ # Format bytecode and gas info with aligned parentheses
156
+ bytecode_values = f"{ len (code )} /{ MAX_CODE_SIZE } "
157
+ gas_values = f"{ gas_used } /{ available_gas } "
158
+
159
+ # Align the parentheses by padding the values section
160
+ f .write (f"{ description :<30} { bytecode_values :>15} ({ code_size_pct :6.2f} %) { gas_values :>20} ({ gas_pct :6.2f} %) { bytes_per_iter :>4} { num_iters :>10} \n " )
161
+
162
+ LOGGED_DESCRIPTIONS .add (description )
163
+
164
+ if len (code ) > MAX_CODE_SIZE :
165
+ # Must never happen, but keep it as a sanity check.
166
+ raise ValueError (f"Code size { len (code )} exceeds maximum code size { MAX_CODE_SIZE } " )
167
+
168
+ code_address = pre .deploy_contract (code = code )
169
+
170
+ tx = Transaction (
171
+ to = code_address ,
172
+ gas_limit = gas_limit ,
173
+ gas_price = 10 ,
174
+ sender = pre .fund_eoa (),
175
+ data = [],
176
+ value = 0 ,
177
+ )
178
+
179
+ return blockchain_test (
180
+ env = env ,
181
+ pre = pre ,
182
+ post = {},
183
+ blocks = [Block (txs = [tx ])],
184
+ )
185
+
186
+
187
+ @pytest .mark .zkevm
188
+ @pytest .mark .valid_from ("Cancun" )
189
+ @pytest .mark .parametrize (
190
+ "gas_limit" ,
191
+ [
192
+ 36_000_000 ,
193
+ ],
194
+ )
195
+ def test_jumpdest_only (
196
+ blockchain_test : BlockchainTestFiller ,
197
+ pre : Alloc ,
198
+ fork : Fork ,
199
+ gas_limit : int ,
200
+ ):
201
+ """Test with only JUMPDEST opcodes"""
202
+ gsc = fork .gas_costs ()
203
+ iteration_gas_cost = gsc .G_JUMPDEST
204
+
205
+ # Use Op.JUMPDEST instead of raw bytes
206
+ opcodes_pattern = Op .JUMPDEST
207
+
208
+ run_jump_analysis_test (
209
+ blockchain_test = blockchain_test ,
210
+ pre = pre ,
211
+ fork = fork ,
212
+ gas_limit = gas_limit ,
213
+ opcodes_pattern = opcodes_pattern ,
214
+ iteration_gas_cost = iteration_gas_cost ,
215
+ description = "JUMPDEST only" ,
216
+ )
217
+
218
+
219
+ @pytest .mark .zkevm
220
+ @pytest .mark .valid_from ("Cancun" )
221
+ @pytest .mark .parametrize (
222
+ "gas_limit" ,
223
+ [
224
+ 36_000_000 ,
225
+ ],
226
+ )
227
+ def test_jumpdest_jump (
228
+ blockchain_test : BlockchainTestFiller ,
229
+ pre : Alloc ,
230
+ fork : Fork ,
231
+ gas_limit : int ,
232
+ ):
233
+ """Test with JUMPDEST and JUMP opcodes"""
234
+ gsc = fork .gas_costs ()
235
+ iteration_gas_cost = gsc .G_JUMPDEST + gsc .G_MID
236
+
237
+ # Use Op.JUMPDEST + Op.JUMP instead of raw bytes
238
+ opcodes_pattern = Op .JUMPDEST + Op .JUMP
239
+
240
+ run_jump_analysis_test (
241
+ blockchain_test = blockchain_test ,
242
+ pre = pre ,
243
+ fork = fork ,
244
+ gas_limit = gas_limit ,
245
+ opcodes_pattern = opcodes_pattern ,
246
+ iteration_gas_cost = iteration_gas_cost ,
247
+ description = "JUMPDEST + JUMP" ,
248
+ )
249
+
250
+
251
+ @pytest .mark .zkevm
252
+ @pytest .mark .valid_from ("Cancun" )
253
+ @pytest .mark .parametrize (
254
+ "gas_limit" ,
255
+ [
256
+ 36_000_000 ,
257
+ ],
258
+ )
259
+ def test_jumpdest_push0_pop_jump (
260
+ blockchain_test : BlockchainTestFiller ,
261
+ pre : Alloc ,
262
+ fork : Fork ,
263
+ gas_limit : int ,
264
+ ):
265
+ """Test with JUMPDEST, PUSH0, POP, and JUMP opcodes"""
266
+ gsc = fork .gas_costs ()
267
+ iteration_gas_cost = gsc .G_JUMPDEST + gsc .G_VERY_LOW + gsc .G_BASE + gsc .G_MID
268
+
269
+ # Use Op.JUMPDEST + Op.PUSH0 + Op.POP + Op.JUMP instead of raw bytes
270
+ opcodes_pattern = Op .JUMPDEST + Op .PUSH0 + Op .POP + Op .JUMP
271
+
272
+ run_jump_analysis_test (
273
+ blockchain_test = blockchain_test ,
274
+ pre = pre ,
275
+ fork = fork ,
276
+ gas_limit = gas_limit ,
277
+ opcodes_pattern = opcodes_pattern ,
278
+ iteration_gas_cost = iteration_gas_cost ,
279
+ description = "JUMPDEST + PUSH0 + POP + JUMP" ,
280
+ )
281
+
282
+
283
+ @pytest .mark .zkevm
284
+ @pytest .mark .valid_from ("Cancun" )
285
+ @pytest .mark .parametrize (
286
+ "gas_limit" ,
287
+ [36_000_000 ],
288
+ )
289
+ def test_push0_pop_no_jump (
290
+ blockchain_test : BlockchainTestFiller ,
291
+ pre : Alloc ,
292
+ fork : Fork ,
293
+ gas_limit : int ,
294
+ ):
295
+ """Test with just PUSH0 and POP opcodes"""
296
+ gsc = fork .gas_costs ()
297
+ iteration_gas_cost = gsc .G_VERY_LOW + gsc .G_BASE
298
+
299
+ # Use Op.PUSH0 + Op.POP instead of raw bytes
300
+ opcodes_pattern = Op .PUSH0 + Op .POP
301
+
302
+ run_jump_analysis_test (
303
+ blockchain_test = blockchain_test ,
304
+ pre = pre ,
305
+ fork = fork ,
306
+ gas_limit = gas_limit ,
307
+ opcodes_pattern = opcodes_pattern ,
308
+ iteration_gas_cost = iteration_gas_cost ,
309
+ description = "PUSH0 + POP" ,
310
+ )
0 commit comments