55Tests that benchmark EVMs in the worst-case memory opcodes.
66"""
77
8+ from enum import auto
9+
810import pytest
911
1012from ethereum_test_base_types .base_types import Bytes
13+ from ethereum_test_benchmark .benchmark_code_generator import JumpLoopGenerator
1114from ethereum_test_forks import Fork
1215from ethereum_test_tools import (
1316 Alloc ,
17+ BenchmarkTestFiller ,
1418 Bytecode ,
15- StateTestFiller ,
1619 Transaction ,
1720)
1821from ethereum_test_tools .vm .opcode import Opcodes as Op
1922
20- from .helpers import code_loop_precompile_call
21-
2223REFERENCE_SPEC_GIT_PATH = "TODO"
2324REFERENCE_SPEC_VERSION = "TODO"
2425
2526
2627class CallDataOrigin :
2728 """Enum for calldata origins."""
2829
29- TRANSACTION = 1
30- CALL = 2
30+ TRANSACTION = auto ()
31+ CALL = auto ()
3132
3233
3334@pytest .mark .parametrize (
@@ -61,7 +62,7 @@ class CallDataOrigin:
6162 ],
6263)
6364def test_worst_calldatacopy (
64- state_test : StateTestFiller ,
65+ benchmark_test : BenchmarkTestFiller ,
6566 pre : Alloc ,
6667 fork : Fork ,
6768 origin : CallDataOrigin ,
@@ -87,13 +88,15 @@ def test_worst_calldatacopy(
8788 #
8889 # If `non_zero_data` is True, we leverage CALLDATASIZE for the copy length. Otherwise, since we
8990 # don't send zero data explicitly via calldata, PUSH the target size and use DUP1 to copy it.
90- prefix = Bytecode () if non_zero_data or size == 0 else Op .PUSH3 (size )
91+ setup = Bytecode () if non_zero_data or size == 0 else Op .PUSH3 (size )
9192 src_dst = 0 if fixed_src_dst else Op .MOD (Op .GAS , 7 )
9293 attack_block = Op .CALLDATACOPY (
9394 src_dst , src_dst , Op .CALLDATASIZE if non_zero_data or size == 0 else Op .DUP1
9495 )
95- code = code_loop_precompile_call (prefix , attack_block , fork )
96- code_address = pre .deploy_contract (code = code )
96+
97+ code_address = JumpLoopGenerator (setup = setup , attack_block = attack_block ).deploy_contracts (
98+ pre , fork
99+ )
97100
98101 tx_target = code_address
99102
@@ -102,15 +105,17 @@ def test_worst_calldatacopy(
102105 if origin == CallDataOrigin .CALL :
103106 # If `non_zero_data` is False we leverage just using zeroed memory. Otherwise, we
104107 # copy the calldata received from the transaction.
105- prefix = (
108+ setup = (
106109 Op .CALLDATACOPY (Op .PUSH0 , Op .PUSH0 , Op .CALLDATASIZE ) if non_zero_data else Bytecode ()
107110 ) + Op .JUMPDEST
108111 arg_size = Op .CALLDATASIZE if non_zero_data else size
109- code = prefix + Op .STATICCALL (
112+ attack_block = Op .STATICCALL (
110113 address = code_address , args_offset = Op .PUSH0 , args_size = arg_size
111114 )
112- code += Op .JUMP (len (prefix ) - 1 )
113- tx_target = pre .deploy_contract (code = code )
115+
116+ tx_target = JumpLoopGenerator (setup = setup , attack_block = attack_block ).deploy_contracts (
117+ pre , fork
118+ )
114119
115120 tx = Transaction (
116121 to = tx_target ,
@@ -119,7 +124,7 @@ def test_worst_calldatacopy(
119124 sender = pre .fund_eoa (),
120125 )
121126
122- state_test (
127+ benchmark_test (
123128 pre = pre ,
124129 post = {},
125130 tx = tx ,
@@ -144,38 +149,40 @@ def test_worst_calldatacopy(
144149 ],
145150)
146151def test_worst_codecopy (
147- state_test : StateTestFiller ,
152+ benchmark_test : BenchmarkTestFiller ,
148153 pre : Alloc ,
149154 fork : Fork ,
150155 max_code_size_ratio : float ,
151156 fixed_src_dst : bool ,
152- gas_benchmark_value : int ,
157+ dq ,
153158):
154159 """Test running a block filled with CODECOPY executions."""
155160 max_code_size = fork .max_code_size ()
156161
157162 size = int (max_code_size * max_code_size_ratio )
158163
159- code_prefix = Op .PUSH32 (size )
164+ setup = Op .PUSH32 (size )
160165 src_dst = 0 if fixed_src_dst else Op .MOD (Op .GAS , 7 )
161166 attack_block = Op .CODECOPY (src_dst , src_dst , Op .DUP1 ) # DUP1 copies size.
162- code = code_loop_precompile_call (code_prefix , attack_block , fork )
167+
168+ code = JumpLoopGenerator (setup = setup , attack_block = attack_block ).generate_repeated_code (
169+ attack_block , Bytecode (), Bytecode (), fork
170+ )
163171
164172 # The code generated above is not guaranteed to be of max_code_size, so we pad it since
165173 # a test parameter targets CODECOPYing a contract with max code size. Padded bytecode values
166174 # are not relevant.
167- code = code + Op .INVALID * (max_code_size - len (code ))
175+ code += Op .INVALID * (max_code_size - len (code ))
168176 assert len (code ) == max_code_size , (
169177 f"Code size { len (code )} is not equal to max code size { max_code_size } ."
170178 )
171179
172180 tx = Transaction (
173181 to = pre .deploy_contract (code = code ),
174- gas_limit = gas_benchmark_value ,
175182 sender = pre .fund_eoa (),
176183 )
177184
178- state_test (
185+ benchmark_test (
179186 pre = pre ,
180187 post = {},
181188 tx = tx ,
@@ -199,16 +206,12 @@ def test_worst_codecopy(
199206 ],
200207)
201208def test_worst_returndatacopy (
202- state_test : StateTestFiller ,
209+ benchmark_test : BenchmarkTestFiller ,
203210 pre : Alloc ,
204- fork : Fork ,
205211 size : int ,
206212 fixed_dst : bool ,
207- gas_benchmark_value : int ,
208213):
209214 """Test running a block filled with RETURNDATACOPY executions."""
210- max_code_size = fork .max_code_size ()
211-
212215 # Create the contract that will RETURN the data that will be used for RETURNDATACOPY.
213216 # Random-ish data is injected at different points in memory to avoid making the content
214217 # predictable. If `size` is 0, this helper contract won't be used.
@@ -220,13 +223,13 @@ def test_worst_returndatacopy(
220223 )
221224 helper_contract = pre .deploy_contract (code = code )
222225
223- # We create the contract that will be doing the RETURNDATACOPY multiple times.
224226 returndata_gen = Op .STATICCALL (address = helper_contract ) if size > 0 else Bytecode ()
225227 dst = 0 if fixed_dst else Op .MOD (Op .GAS , 7 )
226- attack_iter = Op .RETURNDATACOPY (dst , Op .PUSH0 , Op .RETURNDATASIZE )
227228
228- jumpdest = Op .JUMPDEST
229- jump_back = Op .JUMP (len (returndata_gen ))
229+ # We create the contract that will be doing the RETURNDATACOPY multiple times.
230+ returndata_gen = Op .STATICCALL (address = helper_contract ) if size > 0 else Bytecode ()
231+ attack_block = Op .RETURNDATACOPY (dst , Op .PUSH0 , Op .RETURNDATASIZE )
232+
230233 # The attack loop is constructed as:
231234 # ```
232235 # JUMPDEST(#)
@@ -238,30 +241,13 @@ def test_worst_returndatacopy(
238241 # ```
239242 # The goal is that once per (big) loop iteration, the helper contract is called to
240243 # generate fresh returndata to continue calling RETURNDATACOPY.
241- max_iters_loop = (
242- max_code_size - 2 * len (returndata_gen ) - len (jumpdest ) - len (jump_back )
243- ) // len (attack_iter )
244- code = (
245- returndata_gen
246- + jumpdest
247- + sum ([attack_iter ] * max_iters_loop )
248- + returndata_gen
249- + jump_back
250- )
251- assert len (code ) <= max_code_size , (
252- f"Code size { len (code )} is not equal to max code size { max_code_size } ."
253- )
254-
255- tx = Transaction (
256- to = pre .deploy_contract (code = code ),
257- gas_limit = gas_benchmark_value ,
258- sender = pre .fund_eoa (),
259- )
260244
261- state_test (
245+ benchmark_test (
262246 pre = pre ,
263247 post = {},
264- tx = tx ,
248+ code_generator = JumpLoopGenerator (
249+ setup = returndata_gen , attack_block = attack_block , cleanup = returndata_gen
250+ ),
265251 )
266252
267253
@@ -282,42 +268,24 @@ def test_worst_returndatacopy(
282268 ],
283269)
284270def test_worst_mcopy (
285- state_test : StateTestFiller ,
271+ benchmark_test : BenchmarkTestFiller ,
286272 pre : Alloc ,
287- fork : Fork ,
288273 size : int ,
289274 fixed_src_dst : bool ,
290- gas_benchmark_value : int ,
291275):
292276 """Test running a block filled with MCOPY executions."""
293- max_code_size = fork .max_code_size ()
277+ src_dst = 0 if fixed_src_dst else Op .MOD (Op .GAS , 7 )
278+ attack_block = Op .MCOPY (src_dst , src_dst , size )
294279
295280 mem_touch = (
296281 Op .MSTORE8 (0 , Op .GAS ) + Op .MSTORE8 (size // 2 , Op .GAS ) + Op .MSTORE8 (size - 1 , Op .GAS )
297282 if size > 0
298283 else Bytecode ()
299284 )
300- src_dst = 0 if fixed_src_dst else Op .MOD (Op .GAS , 7 )
301- attack_block = Op .MCOPY (src_dst , src_dst , size )
302-
303- jumpdest = Op .JUMPDEST
304- jump_back = Op .JUMP (len (mem_touch ))
305- max_iters_loop = (max_code_size - 2 * len (mem_touch ) - len (jumpdest ) - len (jump_back )) // len (
306- attack_block
307- )
308- code = mem_touch + jumpdest + sum ([attack_block ] * max_iters_loop ) + mem_touch + jump_back
309- assert len (code ) <= max_code_size , (
310- f"Code size { len (code )} is not equal to max code size { max_code_size } ."
311- )
312-
313- tx = Transaction (
314- to = pre .deploy_contract (code = code ),
315- gas_limit = gas_benchmark_value ,
316- sender = pre .fund_eoa (),
317- )
318-
319- state_test (
285+ benchmark_test (
320286 pre = pre ,
321287 post = {},
322- tx = tx ,
288+ code_generator = JumpLoopGenerator (
289+ setup = mem_touch , attack_block = attack_block , cleanup = mem_touch
290+ ),
323291 )
0 commit comments