Skip to content

Commit d5a69e9

Browse files
jsignjochem-brouwer
authored andcommitted
zkevm: add 0-param opcodes coverage (ethereum#1698)
* zkevm: add generic zero-param opcodes Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * zkevm: add CALLVALUE coverage Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * add comment Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * zkevm: add RETURNDATASIZE coverage Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * zkevm: add MSIZE coverage & fixes Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * fixes Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * change params Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * Update tests/zkevm/test_worst_compute.py Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com> * feedback Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * add comment Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * generalize returndata test Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * fix Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * Update tests/zkevm/test_worst_compute.py Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com> * separate RETURNDATASIZE tests Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> * nit Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> --------- Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com> Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com>
1 parent 96906a1 commit d5a69e9

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed

tests/zkevm/test_worst_compute.py

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import math
99
import random
10+
from enum import Enum, auto
1011
from typing import cast
1112

1213
import pytest
@@ -57,6 +58,274 @@ def make_dup(index: int) -> Opcode:
5758
return Opcode(0x80 + index, pushed_stack_items=1, min_stack_height=index + 1)
5859

5960

61+
@pytest.mark.valid_from("Cancun")
62+
@pytest.mark.parametrize(
63+
"opcode",
64+
[
65+
Op.ADDRESS,
66+
Op.ORIGIN,
67+
Op.CALLER,
68+
Op.CODESIZE,
69+
Op.GASPRICE,
70+
Op.COINBASE,
71+
Op.TIMESTAMP,
72+
Op.NUMBER,
73+
Op.PREVRANDAO,
74+
Op.GASLIMIT,
75+
Op.CHAINID,
76+
Op.BASEFEE,
77+
Op.BLOBBASEFEE,
78+
Op.GAS,
79+
# Note that other 0-param opcodes are covered in separate tests.
80+
],
81+
)
82+
def test_worst_zero_param(
83+
state_test: StateTestFiller,
84+
pre: Alloc,
85+
opcode: Op,
86+
):
87+
"""Test running a block with as many zero-parameter opcodes as possible."""
88+
env = Environment()
89+
90+
code_prefix = Op.JUMPDEST
91+
iter_loop = Op.POP(opcode)
92+
code_suffix = Op.PUSH0 + Op.JUMP
93+
code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop)
94+
code = code_prefix + iter_loop * code_iter_len + code_suffix
95+
assert len(code) <= MAX_CODE_SIZE
96+
97+
tx = Transaction(
98+
to=pre.deploy_contract(code=bytes(code)),
99+
gas_limit=env.gas_limit,
100+
sender=pre.fund_eoa(),
101+
)
102+
103+
state_test(
104+
env=env,
105+
pre=pre,
106+
post={},
107+
tx=tx,
108+
)
109+
110+
111+
@pytest.mark.valid_from("Cancun")
112+
@pytest.mark.parametrize("calldata_length", [0, 1_000, 10_000])
113+
def test_worst_calldatasize(
114+
state_test: StateTestFiller,
115+
pre: Alloc,
116+
calldata_length: int,
117+
):
118+
"""Test running a block with as many CALLDATASIZE as possible."""
119+
env = Environment()
120+
121+
code_prefix = Op.JUMPDEST
122+
iter_loop = Op.POP(Op.CALLDATASIZE)
123+
code_suffix = Op.PUSH0 + Op.JUMP
124+
code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop)
125+
code = code_prefix + iter_loop * code_iter_len + code_suffix
126+
assert len(code) <= MAX_CODE_SIZE
127+
128+
tx = Transaction(
129+
to=pre.deploy_contract(code=bytes(code)),
130+
gas_limit=env.gas_limit,
131+
sender=pre.fund_eoa(),
132+
data=b"\x00" * calldata_length,
133+
)
134+
135+
state_test(
136+
env=env,
137+
pre=pre,
138+
post={},
139+
tx=tx,
140+
)
141+
142+
143+
@pytest.mark.valid_from("Cancun")
144+
@pytest.mark.parametrize("non_zero_value", [True, False])
145+
@pytest.mark.parametrize("from_origin", [True, False])
146+
def test_worst_callvalue(
147+
state_test: StateTestFiller,
148+
pre: Alloc,
149+
non_zero_value: bool,
150+
from_origin: bool,
151+
):
152+
"""
153+
Test running a block with as many CALLVALUE opcodes as possible.
154+
155+
The `non_zero_value` parameter controls whether opcode must return non-zero value.
156+
The `from_origin` parameter controls whether the call frame is the immediate from the
157+
transaction or a previous CALL.
158+
"""
159+
env = Environment()
160+
161+
code_prefix = Op.JUMPDEST
162+
iter_loop = Op.POP(Op.CALLVALUE)
163+
code_suffix = Op.PUSH0 + Op.JUMP
164+
code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop)
165+
code = code_prefix + iter_loop * code_iter_len + code_suffix
166+
assert len(code) <= MAX_CODE_SIZE
167+
code_address = pre.deploy_contract(code=bytes(code))
168+
169+
tx_to = (
170+
code_address
171+
if from_origin
172+
else pre.deploy_contract(
173+
code=Op.CALL(address=code_address, value=1 if non_zero_value else 0), balance=10
174+
)
175+
)
176+
177+
tx = Transaction(
178+
to=tx_to,
179+
gas_limit=env.gas_limit,
180+
value=1 if non_zero_value and from_origin else 0,
181+
sender=pre.fund_eoa(),
182+
)
183+
184+
state_test(
185+
env=env,
186+
pre=pre,
187+
post={},
188+
tx=tx,
189+
)
190+
191+
192+
class ReturnDataStyle(Enum):
193+
"""Helper enum to specify return data is returned to the caller."""
194+
195+
RETURN = auto()
196+
REVERT = auto()
197+
IDENTITY = auto()
198+
199+
200+
@pytest.mark.valid_from("Cancun")
201+
@pytest.mark.parametrize(
202+
"return_data_style",
203+
[
204+
ReturnDataStyle.RETURN,
205+
ReturnDataStyle.REVERT,
206+
ReturnDataStyle.IDENTITY,
207+
],
208+
)
209+
@pytest.mark.parametrize("returned_size", [1, 0])
210+
def test_worst_returndatasize_nonzero(
211+
state_test: StateTestFiller,
212+
pre: Alloc,
213+
returned_size: int,
214+
return_data_style: ReturnDataStyle,
215+
):
216+
"""
217+
Test running a block which execute as many RETURNDATASIZE opcodes which return a non-zero
218+
buffer as possible.
219+
220+
The `returned_size` parameter indicates the size of the returned data buffer.
221+
The `return_data_style` indicates how returned data is produced for the opcode caller.
222+
"""
223+
env = Environment()
224+
225+
dummy_contract_call = Bytecode()
226+
if return_data_style != ReturnDataStyle.IDENTITY:
227+
dummy_contract_call = Op.STATICCALL(
228+
address=pre.deploy_contract(
229+
code=Op.REVERT(0, returned_size)
230+
if return_data_style == ReturnDataStyle.REVERT
231+
else Op.RETURN(0, returned_size)
232+
)
233+
)
234+
else:
235+
dummy_contract_call = Op.MSTORE8(0, 1) + Op.STATICCALL(
236+
address=0x04, # Identity precompile
237+
args_size=returned_size,
238+
)
239+
240+
code_prefix = dummy_contract_call + Op.JUMPDEST
241+
iter_loop = Op.POP(Op.RETURNDATASIZE)
242+
code_suffix = Op.JUMP(len(code_prefix) - 1)
243+
code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop)
244+
code = code_prefix + iter_loop * code_iter_len + code_suffix
245+
assert len(code) <= MAX_CODE_SIZE
246+
247+
tx = Transaction(
248+
to=pre.deploy_contract(code=bytes(code)),
249+
gas_limit=env.gas_limit,
250+
sender=pre.fund_eoa(),
251+
)
252+
253+
state_test(
254+
env=env,
255+
pre=pre,
256+
post={},
257+
tx=tx,
258+
)
259+
260+
261+
@pytest.mark.valid_from("Cancun")
262+
def test_worst_returndatasize_zero(
263+
state_test: StateTestFiller,
264+
pre: Alloc,
265+
):
266+
"""Test running a block with as many RETURNDATASIZE opcodes as possible with a zero buffer."""
267+
env = Environment()
268+
269+
dummy_contract_call = Bytecode()
270+
271+
code_prefix = dummy_contract_call + Op.JUMPDEST
272+
iter_loop = Op.POP(Op.RETURNDATASIZE)
273+
code_suffix = Op.JUMP(len(code_prefix) - 1)
274+
code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop)
275+
code = code_prefix + iter_loop * code_iter_len + code_suffix
276+
assert len(code) <= MAX_CODE_SIZE
277+
278+
tx = Transaction(
279+
to=pre.deploy_contract(code=bytes(code)),
280+
gas_limit=env.gas_limit,
281+
sender=pre.fund_eoa(),
282+
)
283+
284+
state_test(
285+
env=env,
286+
pre=pre,
287+
post={},
288+
tx=tx,
289+
)
290+
291+
292+
@pytest.mark.valid_from("Cancun")
293+
@pytest.mark.parametrize("mem_size", [0, 1, 1_000, 100_000, 1_000_000])
294+
def test_worst_msize(
295+
state_test: StateTestFiller,
296+
pre: Alloc,
297+
mem_size: int,
298+
):
299+
"""
300+
Test running a block with as many MSIZE opcodes as possible.
301+
302+
The `mem_size` parameter indicates by how much the memory is expanded.
303+
"""
304+
env = Environment()
305+
306+
# We use CALLVALUE for the parameter since is 1 gas cheaper than PUSHX.
307+
code_prefix = Op.MLOAD(Op.CALLVALUE) + Op.JUMPDEST
308+
iter_loop = Op.POP(Op.MSIZE)
309+
code_suffix = Op.JUMP(len(code_prefix) - 1)
310+
code_iter_len = (MAX_CODE_SIZE - len(code_prefix) - len(code_suffix)) // len(iter_loop)
311+
code = code_prefix + iter_loop * code_iter_len + code_suffix
312+
assert len(code) <= MAX_CODE_SIZE
313+
314+
tx = Transaction(
315+
to=pre.deploy_contract(code=bytes(code)),
316+
gas_limit=env.gas_limit,
317+
sender=pre.fund_eoa(),
318+
value=mem_size,
319+
)
320+
321+
state_test(
322+
env=env,
323+
pre=pre,
324+
post={},
325+
tx=tx,
326+
)
327+
328+
60329
@pytest.mark.valid_from("Cancun")
61330
def test_worst_keccak(
62331
state_test: StateTestFiller,

0 commit comments

Comments
 (0)