Skip to content

Commit 4fc2d6b

Browse files
committed
feat(tests): add benchmark for the worst initcode jumpdest analysis
1 parent ee9b84d commit 4fc2d6b

File tree

1 file changed

+94
-0
lines changed

1 file changed

+94
-0
lines changed

tests/zkevm/test_worst_bytecode.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Bytecode,
1919
Environment,
2020
Hash,
21+
StateTestFiller,
2122
Transaction,
2223
While,
2324
compute_create2_address,
@@ -221,3 +222,96 @@ def test_worst_bytecode_single_opcode(
221222
],
222223
exclude_full_post_state_in_output=True,
223224
)
225+
226+
227+
@pytest.mark.valid_from("Cancun")
228+
@pytest.mark.parametrize(
229+
"pattern",
230+
[
231+
Op.STOP,
232+
Op.JUMPDEST,
233+
Op.PUSH1[bytes(Op.JUMPDEST)],
234+
Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)],
235+
Op.PUSH1[bytes(Op.JUMPDEST)] + Op.JUMPDEST,
236+
Op.PUSH2[bytes(Op.JUMPDEST + Op.JUMPDEST)] + Op.JUMPDEST,
237+
],
238+
ids=lambda x: x.hex(),
239+
)
240+
def test_worst_initcode_jumpdest_analysis(
241+
state_test: StateTestFiller,
242+
pre: Alloc,
243+
fork: Fork,
244+
pattern: Bytecode,
245+
):
246+
"""
247+
Test the jumpdest analysis performance of the initcode.
248+
249+
This benchmark places a very long initcode in the memory and then invoke CREATE instructions
250+
with this initcode up to the block gas limit. The initcode itself has minimal execution time
251+
but forces the EVM to perform the full jumpdest analysis on the parametrized byte pattern.
252+
The initicode is modified by mixing-in the returned create address between CREATE invocations
253+
to prevent caching.
254+
"""
255+
max_code_size = fork.max_code_size()
256+
initcode_size = fork.max_initcode_size()
257+
258+
# Expand the initcode pattern to the transaction data so it can be used in CALLDATACOPY
259+
# in the main contract. TODO: tune the tx_data_len param.
260+
tx_data_len = 1024
261+
tx_data = pattern * (tx_data_len // len(pattern))
262+
tx_data += (tx_data_len - len(tx_data)) * bytes(Op.JUMPDEST)
263+
assert len(tx_data) == tx_data_len
264+
assert initcode_size % len(tx_data) == 0
265+
266+
# Prepare the initcode in memory.
267+
code_prepare_initcode = sum(
268+
(
269+
Op.CALLDATACOPY(dest_offset=i * len(tx_data), offset=0, size=Op.CALLDATASIZE)
270+
for i in range(initcode_size // len(tx_data))
271+
),
272+
Bytecode(),
273+
)
274+
275+
# At the start of the initcode execution, jump to the last opcode.
276+
# This forces EVM to do the full jumpdest analysis.
277+
initcode_prefix = Op.JUMP(initcode_size - 1)
278+
code_prepare_initcode += Op.MSTORE(
279+
0, Op.PUSH32[bytes(initcode_prefix).ljust(32, bytes(Op.JUMPDEST))]
280+
)
281+
282+
# Make sure the last opcode in the initcode is JUMPDEST.
283+
code_prepare_initcode += Op.MSTORE(initcode_size - 32, Op.PUSH32[bytes(Op.JUMPDEST) * 32])
284+
285+
code_invoke_create = (
286+
Op.PUSH1[len(initcode_prefix)]
287+
+ Op.MSTORE
288+
+ Op.CREATE(value=Op.PUSH0, offset=Op.PUSH0, size=Op.MSIZE)
289+
)
290+
291+
initial_random = Op.PUSH0
292+
code_prefix = code_prepare_initcode + initial_random
293+
code_loop_header = Op.JUMPDEST
294+
code_loop_footer = Op.JUMP(len(code_prefix))
295+
code_loop_body_len = (
296+
max_code_size - len(code_prefix) - len(code_loop_header) - len(code_loop_footer)
297+
)
298+
299+
code_loop_body = (code_loop_body_len // len(code_invoke_create)) * bytes(code_invoke_create)
300+
code = code_prefix + code_loop_header + code_loop_body + code_loop_footer
301+
assert (max_code_size - len(code_invoke_create)) < len(code) <= max_code_size
302+
303+
env = Environment()
304+
305+
tx = Transaction(
306+
to=pre.deploy_contract(code=code),
307+
data=tx_data,
308+
gas_limit=env.gas_limit,
309+
sender=pre.fund_eoa(),
310+
)
311+
312+
state_test(
313+
env=env,
314+
pre=pre,
315+
post={},
316+
tx=tx,
317+
)

0 commit comments

Comments
 (0)