Skip to content

Commit 6b017c7

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

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
@@ -204,3 +204,93 @@ def test_worst_bytecode_single_opcode(
204204
],
205205
exclude_full_post_state_in_output=True,
206206
)
207+
208+
209+
@pytest.mark.valid_from("Paris")
210+
@pytest.mark.parametrize("initcode_size", [0xC000])
211+
@pytest.mark.parametrize(
212+
"pattern",
213+
[
214+
Op.STOP,
215+
Op.JUMPDEST,
216+
Op.PUSH1[Op.JUMPDEST],
217+
Op.PUSH2[Op.JUMPDEST + Op.JUMPDEST],
218+
Op.PUSH1[Op.JUMPDEST] + Op.JUMPDEST,
219+
Op.PUSH2[Op.JUMPDEST + Op.JUMPDEST] + Op.JUMPDEST,
220+
],
221+
ids=lambda x: x.hex(),
222+
)
223+
def test_worst_initcode_jumpdest_analysis(
224+
blockchain_test: BlockchainTestFiller,
225+
fork: Fork,
226+
pre: Alloc,
227+
initcode_size: int,
228+
pattern: Bytecode,
229+
):
230+
"""Test the jumpdest analysis performance of the initcode."""
231+
# Expand the initcode pattern to the transaction data so it can be used in CALLDATACOPY
232+
# in the main contract. TODO: tune the tx_data_len param.
233+
tx_data_len = 1024
234+
tx_data = pattern * (tx_data_len // len(pattern))
235+
tx_data += (tx_data_len - len(tx_data)) * bytes(Op.JUMPDEST)
236+
assert len(tx_data) == tx_data_len
237+
assert initcode_size % len(tx_data) == 0
238+
239+
# Prepare the initcode in memory.
240+
code_prepare_initcode = sum(
241+
(
242+
Op.CALLDATACOPY(dest_offset=i * len(tx_data), offset=0, size=Op.CALLDATASIZE)
243+
for i in range(initcode_size // len(tx_data))
244+
),
245+
Bytecode(),
246+
)
247+
248+
# At the start of the initcode execution, jump to the last opcode.
249+
# This forces EVM to do the full jumpdest analysis.
250+
initcode_prefix = Op.JUMP(initcode_size - 1)
251+
assert len(initcode_prefix) <= 4
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+
blockchain_test(
292+
env=env,
293+
pre=pre,
294+
post={},
295+
blocks=[Block(txs=[tx])],
296+
)

0 commit comments

Comments
 (0)