Skip to content

Commit e442d03

Browse files
committed
new(tests): add benchmarks with mass binop opcodes
This adds the benchmarks for EVM execution focused on binary instructions (takes two values, produces one). The benchmark has simple structure: the contract code is the infinite loop will up to the code size limit with the parametrized binop applied to two initial values balanced with DUP2 instruction.
1 parent 0a11037 commit e442d03

File tree

1 file changed

+171
-1
lines changed

1 file changed

+171
-1
lines changed

tests/zkevm/test_worst_compute.py

+171-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@
1010
import pytest
1111

1212
from ethereum_test_forks import Fork
13-
from ethereum_test_tools import Alloc, Block, BlockchainTestFiller, Environment, Transaction
13+
from ethereum_test_tools import (
14+
Alloc,
15+
Block,
16+
BlockchainTestFiller,
17+
Environment,
18+
Transaction,
19+
)
1420
from ethereum_test_tools.vm.opcode import Opcodes as Op
1521

1622
REFERENCE_SPEC_GIT_PATH = "TODO"
@@ -170,3 +176,167 @@ def test_worst_modexp(
170176
post={},
171177
blocks=[Block(txs=[tx])],
172178
)
179+
180+
181+
@pytest.mark.valid_from("Cancun")
182+
@pytest.mark.parametrize(
183+
"gas_limit",
184+
[
185+
Environment().gas_limit,
186+
],
187+
)
188+
@pytest.mark.parametrize(
189+
"op",
190+
[
191+
(
192+
Op.ADD,
193+
(
194+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
195+
0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001,
196+
),
197+
),
198+
(
199+
Op.MUL,
200+
(
201+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
202+
0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001,
203+
),
204+
),
205+
(
206+
Op.SUB,
207+
# This has the cycle of 2, after two SUBs values are back to initials.
208+
(
209+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
210+
0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001,
211+
),
212+
),
213+
(
214+
Op.DIV,
215+
# This has the cycle of 2:
216+
# v[0] = a // b
217+
# v[1] = a // v[0] = a // (a // b) = b
218+
# v[2] = a // b
219+
(
220+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
221+
# We want the first divisor to be slightly bigger than 2**128:
222+
# this is the worst case for the division algorithm.
223+
0x100000000000000000000000000000033,
224+
),
225+
),
226+
(
227+
Op.SDIV,
228+
# Same as DIV, but the numerator made positive, and the divisor made negative.
229+
(
230+
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
231+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD,
232+
),
233+
),
234+
# MOD does not work in this scenario because the divisor quickly becomes 0 forever.
235+
# SMOD does not work in this scenario because the divisor quickly becomes 0 forever.
236+
(
237+
Op.EXP,
238+
# This keeps the values unchanged, pow(2**256-1, 2**256-1, 2**256) == 2**256-1.
239+
(
240+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
241+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
242+
),
243+
),
244+
(
245+
# Not great because we always sign-extend the 4 bytes.
246+
Op.SIGNEXTEND,
247+
(
248+
3,
249+
0xFFDADADA, # Negative to have more work.
250+
),
251+
),
252+
(Op.LT, (0, 1)), # Keeps getting result 1.
253+
(Op.GT, (0, 1)), # Keeps getting result 0.
254+
(
255+
Op.SLT, # Keeps getting result 1.
256+
(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 1),
257+
),
258+
(
259+
Op.SGT, # Keeps getting result 0.
260+
(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 1),
261+
),
262+
(
263+
Op.EQ,
264+
# The worst case is if the arguments are equal (no early return),
265+
# so let's keep it comparing ones.
266+
(1, 1),
267+
),
268+
(
269+
Op.AND,
270+
(
271+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
272+
0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001,
273+
),
274+
),
275+
(
276+
Op.OR,
277+
(
278+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
279+
0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001,
280+
),
281+
),
282+
(
283+
Op.XOR,
284+
(
285+
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
286+
0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001,
287+
),
288+
),
289+
(
290+
Op.BYTE,
291+
(31, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F),
292+
),
293+
(
294+
Op.SHL,
295+
(1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F),
296+
),
297+
(
298+
Op.SHR,
299+
(1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F),
300+
),
301+
(
302+
Op.SAR,
303+
(1, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F),
304+
),
305+
],
306+
ids=lambda param_tuple: str(param_tuple[0]),
307+
)
308+
def test_worst_binop_simple(
309+
blockchain_test: BlockchainTestFiller,
310+
pre: Alloc,
311+
gas_limit: int,
312+
op: tuple[Op, tuple[int, int]],
313+
):
314+
"""
315+
Test running a block with as many binary instructions (takes two args, produces one value)
316+
as possible. The execution starts with two initial values on the stack, and the stack is
317+
balanced by the DUP2 instruction.
318+
"""
319+
env = Environment(gas_limit=gas_limit)
320+
321+
tx_data = b"".join(arg.to_bytes(32, byteorder="big") for arg in op[1])
322+
323+
code_prefix = Op.JUMPDEST + Op.CALLDATALOAD(0) + Op.CALLDATALOAD(32)
324+
code_suffix = Op.POP + Op.POP + Op.PUSH0 + Op.JUMP
325+
code_body_len = MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)
326+
code_body = (Op.DUP2 + op[0]) * (code_body_len // 2)
327+
code = code_prefix + code_body + code_suffix
328+
assert len(code) == MAX_CODE_SIZE - 1
329+
330+
tx = Transaction(
331+
to=pre.deploy_contract(code=code),
332+
data=tx_data,
333+
gas_limit=gas_limit,
334+
sender=pre.fund_eoa(),
335+
)
336+
337+
blockchain_test(
338+
env=env,
339+
pre=pre,
340+
post={},
341+
blocks=[Block(txs=[tx])],
342+
)

0 commit comments

Comments
 (0)