Skip to content

Commit f9b75b4

Browse files
committed
feat(tests): add benchmark for the worst initcode jumpdest analysis
1 parent bceb08f commit f9b75b4

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

tests/zkevm/test_worst_bytecode.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pytest
1111

1212
from ethereum_test_forks import Fork
13+
from ethereum_test_specs import StateTestFiller
1314
from ethereum_test_tools import (
1415
Account,
1516
Alloc,
@@ -204,3 +205,92 @@ def test_worst_bytecode_single_opcode(
204205
],
205206
exclude_full_post_state_in_output=True,
206207
)
208+
209+
210+
@pytest.mark.valid_from("Osaka")
211+
@pytest.mark.parametrize("initcode_size", [0xC000, 512 * 1024])
212+
@pytest.mark.parametrize(
213+
"pattern",
214+
[
215+
Op.STOP,
216+
Op.JUMPDEST,
217+
Op.PUSH1[Op.JUMPDEST],
218+
Op.PUSH2[Op.JUMPDEST + Op.JUMPDEST],
219+
Op.PUSH1[Op.JUMPDEST] + Op.JUMPDEST,
220+
Op.PUSH2[Op.JUMPDEST + Op.JUMPDEST] + Op.JUMPDEST,
221+
],
222+
ids=lambda x: x.hex(),
223+
)
224+
def test_worst_initcode_jumpdest_analysis(
225+
state_test: StateTestFiller,
226+
fork: Fork,
227+
pre: Alloc,
228+
initcode_size: int,
229+
pattern: Bytecode,
230+
):
231+
"""Test the jumpdest analysis performance of the initcode."""
232+
# Expand the initcode pattern to the transaction data so it can be used in CALLDATACOPY
233+
# in the main contract. TODO: tune the tx_data_len param.
234+
tx_data_len = 1024
235+
tx_data = pattern * (tx_data_len // len(pattern))
236+
tx_data += (tx_data_len - len(tx_data)) * bytes(Op.JUMPDEST)
237+
assert len(tx_data) == tx_data_len
238+
assert initcode_size % len(tx_data) == 0
239+
240+
# Prepare the initcode in memory.
241+
code_prepare_initcode = sum(
242+
(
243+
Op.CALLDATACOPY(dest_offset=i * len(tx_data), offset=0, size=Op.CALLDATASIZE)
244+
for i in range(initcode_size // len(tx_data))
245+
),
246+
Bytecode(),
247+
)
248+
249+
# At the start of the initcode execution, jump to the last opcode.
250+
# This forces EVM to do the full jumpdest analysis.
251+
initcode_prefix = Op.JUMP(initcode_size - 1)
252+
code_prepare_initcode += Op.MSTORE(
253+
0, Op.PUSH32[initcode_prefix + Op.STOP * (32 - len(initcode_prefix))]
254+
)
255+
256+
# Make sure the last opcode in the initcode is JUMPDEST.
257+
code_prepare_initcode += Op.MSTORE(initcode_size - 32, Op.PUSH1[Op.JUMPDEST])
258+
259+
# We are using the Paris fork where there is no initcode size limit.
260+
# This allows us to test the increased initcode limits.
261+
# However, Paris doesn't have PUSH0 so simulate it with PUSH1.
262+
push0 = Op.PUSH0 if Op.PUSH0 in fork.valid_opcodes() else Op.PUSH1[0]
263+
264+
code_invoke_create = (
265+
Op.PUSH1[len(initcode_prefix)]
266+
+ Op.MSTORE
267+
+ Op.CREATE(value=push0, offset=push0, size=Op.MSIZE)
268+
)
269+
270+
initial_random = push0
271+
code_prefix = code_prepare_initcode + initial_random
272+
code_loop_header = Op.JUMPDEST
273+
code_loop_footer = Op.JUMP(len(code_prefix))
274+
code_loop_body_len = (
275+
MAX_CONTRACT_SIZE - len(code_prefix) - len(code_loop_header) - len(code_loop_footer)
276+
)
277+
278+
code_loop_body = (code_loop_body_len // len(code_invoke_create)) * bytes(code_invoke_create)
279+
code = code_prefix + code_loop_header + code_loop_body + code_loop_footer
280+
assert (MAX_CONTRACT_SIZE - len(code_invoke_create)) < len(code) <= MAX_CONTRACT_SIZE
281+
282+
env = Environment()
283+
284+
tx = Transaction(
285+
to=pre.deploy_contract(code=code),
286+
data=tx_data,
287+
gas_limit=env.gas_limit,
288+
sender=pre.fund_eoa(),
289+
)
290+
291+
state_test(
292+
env=env,
293+
pre=pre,
294+
post={},
295+
tx=tx,
296+
)

0 commit comments

Comments
 (0)