Skip to content

Commit 57fb187

Browse files
refactor(benchmark): update worst memory scenario
1 parent 1b1d1c5 commit 57fb187

File tree

1 file changed

+43
-76
lines changed

1 file changed

+43
-76
lines changed

tests/benchmark/test_worst_memory.py

Lines changed: 43 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,30 @@
55
Tests that benchmark EVMs in the worst-case memory opcodes.
66
"""
77

8+
from enum import auto
9+
810
import pytest
911

1012
from ethereum_test_base_types.base_types import Bytes
13+
from ethereum_test_benchmark.benchmark_code_generator import JumpLoopGenerator
1114
from ethereum_test_forks import Fork
1215
from ethereum_test_tools import (
1316
Alloc,
17+
BenchmarkTestFiller,
1418
Bytecode,
15-
StateTestFiller,
1619
Transaction,
1720
)
1821
from ethereum_test_vm import Opcodes as Op
1922

20-
from .helpers import code_loop_precompile_call
21-
2223
REFERENCE_SPEC_GIT_PATH = "TODO"
2324
REFERENCE_SPEC_VERSION = "TODO"
2425

2526

2627
class 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
)
6364
def 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,39 @@ def test_worst_calldatacopy(
144149
],
145150
)
146151
def 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,
153157
):
154158
"""Test running a block filled with CODECOPY executions."""
155159
max_code_size = fork.max_code_size()
156160

157161
size = int(max_code_size * max_code_size_ratio)
158162

159-
code_prefix = Op.PUSH32(size)
163+
setup = Op.PUSH32(size)
160164
src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7)
161165
attack_block = Op.CODECOPY(src_dst, src_dst, Op.DUP1) # DUP1 copies size.
162-
code = code_loop_precompile_call(code_prefix, attack_block, fork)
166+
167+
code = JumpLoopGenerator(setup=setup, attack_block=attack_block).generate_repeated_code(
168+
attack_block, Bytecode(), Bytecode(), fork
169+
)
163170

164171
# The code generated above is not guaranteed to be of max_code_size, so we pad it since
165172
# a test parameter targets CODECOPYing a contract with max code size. Padded bytecode values
166173
# are not relevant.
167-
code = code + Op.INVALID * (max_code_size - len(code))
174+
code += Op.INVALID * (max_code_size - len(code))
168175
assert len(code) == max_code_size, (
169176
f"Code size {len(code)} is not equal to max code size {max_code_size}."
170177
)
171178

172179
tx = Transaction(
173180
to=pre.deploy_contract(code=code),
174-
gas_limit=gas_benchmark_value,
175181
sender=pre.fund_eoa(),
176182
)
177183

178-
state_test(
184+
benchmark_test(
179185
pre=pre,
180186
post={},
181187
tx=tx,
@@ -199,16 +205,12 @@ def test_worst_codecopy(
199205
],
200206
)
201207
def test_worst_returndatacopy(
202-
state_test: StateTestFiller,
208+
benchmark_test: BenchmarkTestFiller,
203209
pre: Alloc,
204-
fork: Fork,
205210
size: int,
206211
fixed_dst: bool,
207-
gas_benchmark_value: int,
208212
):
209213
"""Test running a block filled with RETURNDATACOPY executions."""
210-
max_code_size = fork.max_code_size()
211-
212214
# Create the contract that will RETURN the data that will be used for RETURNDATACOPY.
213215
# Random-ish data is injected at different points in memory to avoid making the content
214216
# predictable. If `size` is 0, this helper contract won't be used.
@@ -220,13 +222,13 @@ def test_worst_returndatacopy(
220222
)
221223
helper_contract = pre.deploy_contract(code=code)
222224

223-
# We create the contract that will be doing the RETURNDATACOPY multiple times.
224225
returndata_gen = Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode()
225226
dst = 0 if fixed_dst else Op.MOD(Op.GAS, 7)
226-
attack_iter = Op.RETURNDATACOPY(dst, Op.PUSH0, Op.RETURNDATASIZE)
227227

228-
jumpdest = Op.JUMPDEST
229-
jump_back = Op.JUMP(len(returndata_gen))
228+
# We create the contract that will be doing the RETURNDATACOPY multiple times.
229+
returndata_gen = Op.STATICCALL(address=helper_contract) if size > 0 else Bytecode()
230+
attack_block = Op.RETURNDATACOPY(dst, Op.PUSH0, Op.RETURNDATASIZE)
231+
230232
# The attack loop is constructed as:
231233
# ```
232234
# JUMPDEST(#)
@@ -238,30 +240,13 @@ def test_worst_returndatacopy(
238240
# ```
239241
# The goal is that once per (big) loop iteration, the helper contract is called to
240242
# 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-
)
260243

261-
state_test(
244+
benchmark_test(
262245
pre=pre,
263246
post={},
264-
tx=tx,
247+
code_generator=JumpLoopGenerator(
248+
setup=returndata_gen, attack_block=attack_block, cleanup=returndata_gen
249+
),
265250
)
266251

267252

@@ -282,42 +267,24 @@ def test_worst_returndatacopy(
282267
],
283268
)
284269
def test_worst_mcopy(
285-
state_test: StateTestFiller,
270+
benchmark_test: BenchmarkTestFiller,
286271
pre: Alloc,
287-
fork: Fork,
288272
size: int,
289273
fixed_src_dst: bool,
290-
gas_benchmark_value: int,
291274
):
292275
"""Test running a block filled with MCOPY executions."""
293-
max_code_size = fork.max_code_size()
276+
src_dst = 0 if fixed_src_dst else Op.MOD(Op.GAS, 7)
277+
attack_block = Op.MCOPY(src_dst, src_dst, size)
294278

295279
mem_touch = (
296280
Op.MSTORE8(0, Op.GAS) + Op.MSTORE8(size // 2, Op.GAS) + Op.MSTORE8(size - 1, Op.GAS)
297281
if size > 0
298282
else Bytecode()
299283
)
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(
284+
benchmark_test(
320285
pre=pre,
321286
post={},
322-
tx=tx,
287+
code_generator=JumpLoopGenerator(
288+
setup=mem_touch, attack_block=attack_block, cleanup=mem_touch
289+
),
323290
)

0 commit comments

Comments
 (0)