Skip to content

Commit db13a63

Browse files
shemnonmarioevz
andauthored
new(tests): EOF: Validate EOF only opcodes are invalid in legacy (#768)
* Ensure gas_test uses EOF Wrap the baseline and subject in EOF container code. Signed-off-by: Danno Ferrin <[email protected]> * Test all EOF opcodes in legacy Test a code block containing the new EOF opcodes and checks they fail before they complete. Signed-off-by: Danno Ferrin <[email protected]> * review changes Signed-off-by: Danno Ferrin <[email protected]> * Apply suggestions from code review --------- Signed-off-by: Danno Ferrin <[email protected]> Co-authored-by: Mario Vega <[email protected]>
1 parent 7c30878 commit db13a63

File tree

3 files changed

+294
-3
lines changed

3 files changed

+294
-3
lines changed

tests/prague/eip7692_eof_v1/eip3540_eof_v1/opcodes.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,33 @@
88

99
V1_EOF_OPCODES: List[Op] = [
1010
# new eof ops
11+
# EIP-663 Swap and Dup
12+
Op.DUPN,
13+
Op.SWAPN,
14+
Op.EXCHANGE,
15+
# EIP-4200 Relative Jumps
1116
Op.RJUMP,
1217
Op.RJUMPI,
1318
Op.RJUMPV,
19+
# EIP-4750 functions
1420
Op.CALLF,
1521
Op.RETF,
16-
# Op.JUMPF,
22+
# EIP-6209 JUMPF Instruction
23+
Op.JUMPF,
24+
# EIP-7069 Revamped EOF Call
25+
Op.EXTCALL,
26+
Op.EXTDELEGATECALL,
27+
Op.EXTSTATICCALL,
28+
Op.RETURNDATALOAD,
29+
# EIP-7480 EOF Data Section Access
30+
Op.DATALOAD,
31+
Op.DATALOADN,
32+
Op.DATASIZE,
33+
Op.DATACOPY,
34+
# EIP-7620 EOF Create and Return Contract operation
35+
Op.EOFCREATE,
36+
Op.RETURNCONTRACT,
37+
# Non-deprecated Legacy Opcodes
1738
Op.STOP,
1839
Op.ADD,
1940
Op.MUL,
@@ -174,11 +195,30 @@
174195
"""
175196

176197
V1_EOF_ONLY_OPCODES = [
198+
Op.DUPN,
199+
Op.SWAPN,
200+
Op.EXCHANGE,
201+
# EIP-4200 Relative Jumps
177202
Op.RJUMP,
178203
Op.RJUMPI,
179204
Op.RJUMPV,
205+
# EIP-4750 functions
180206
Op.CALLF,
181207
Op.RETF,
208+
# EIP-6209 JUMPF Instruction
209+
Op.JUMPF,
210+
# EIP-7069 Revamped EOF Call
211+
Op.EXTCALL,
212+
Op.EXTDELEGATECALL,
213+
Op.EXTSTATICCALL,
214+
# EIP-7480 EOF Data Section Access
215+
Op.DATALOAD,
216+
Op.DATALOADN,
217+
Op.DATASIZE,
218+
Op.DATACOPY,
219+
# EIP-7620 EOF Create and Return Contract operation
220+
Op.EOFCREATE,
221+
Op.RETURNCONTRACT,
182222
]
183223
"""
184224
List of valid EOF V1 opcodes that are disabled in legacy bytecode.
@@ -190,6 +230,7 @@
190230
Op.REVERT,
191231
Op.INVALID,
192232
Op.RETF,
233+
Op.JUMPF,
193234
]
194235

195236
INVALID_TERMINATING_OPCODES = [op for op in V1_EOF_OPCODES if op not in VALID_TERMINATING_OPCODES]
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
"""
2+
Tests all EOF-only opcodes in legacy contracts and expects failure.
3+
"""
4+
import pytest
5+
6+
from ethereum_test_base_types import Account
7+
from ethereum_test_specs import StateTestFiller
8+
from ethereum_test_tools import Initcode
9+
from ethereum_test_tools import Opcodes as Op
10+
from ethereum_test_tools.eof.v1 import Container, Section
11+
from ethereum_test_types import Alloc, Environment, Transaction
12+
from ethereum_test_vm import Opcodes
13+
14+
from .. import EOF_FORK_NAME
15+
16+
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-7692.md"
17+
REFERENCE_SPEC_VERSION = "f0e7661ee0d16e612e0931ec88b4c9f208e9d513"
18+
19+
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)
20+
21+
slot_code_executed = b"EXEC"
22+
slot_code_worked = b"WORK"
23+
slot_create_address = b"ADDR"
24+
25+
value_code_executed = b"exec"
26+
value_code_worked = b"work"
27+
value_non_execution_canary = b"brid"
28+
value_create_failed = 0
29+
30+
eof_opcode_blocks = [
31+
pytest.param(Op.PUSH0 + Op.DUPN[0], id="DUPN"),
32+
pytest.param(Op.PUSH0 + Op.PUSH0 + Op.SWAPN[0], id="SWAPN"),
33+
pytest.param(Op.PUSH0 + Op.PUSH0 + Op.PUSH0 + Op.EXCHANGE[2, 3], id="EXCHANGE"),
34+
pytest.param(Op.RJUMP[0], id="RJUMP"),
35+
pytest.param(Op.PUSH0 + Op.RJUMPI[0], id="RJUMPI"),
36+
pytest.param(Op.PUSH0 + Op.RJUMPV[0, 0], id="RJUMPI"),
37+
pytest.param(Op.CALLF[1], id="CALLF"),
38+
pytest.param(Op.RETF, id="RETF"),
39+
pytest.param(Op.JUMPF[0], id="JUMPF"),
40+
pytest.param(Op.PUSH0 + Op.PUSH0 + Op.PUSH0 + Op.PUSH1(2) + Op.EXTCALL, id="EXTCALL"),
41+
pytest.param(
42+
Op.PUSH0 + Op.PUSH0 + Op.PUSH0 + Op.PUSH1(2) + Op.EXTDELEGATECALL, id="EXTDELEGATECALL"
43+
),
44+
pytest.param(
45+
Op.PUSH0 + Op.PUSH0 + Op.PUSH0 + Op.PUSH1(2) + Op.EXTSTATICCALL, id="EXTSTATICCALL"
46+
),
47+
pytest.param(Op.DATALOAD(0), id="DATALOAD"),
48+
pytest.param(Op.DATALOADN[0], id="DATALOADN"),
49+
pytest.param(Op.DATASIZE, id="DATASIZE"),
50+
pytest.param(Op.DATACOPY(0, 0, 32), id="DATACOPY"),
51+
pytest.param(Op.EOFCREATE[0](0, 0, 0, 0), id="EOFCREATE"),
52+
pytest.param(Op.RETURNCONTRACT[0], id="RETURNCONTRACT"),
53+
]
54+
55+
56+
@pytest.mark.parametrize(
57+
"code",
58+
eof_opcode_blocks,
59+
)
60+
def test_opcodes_in_legacy(state_test: StateTestFiller, pre: Alloc, code: Opcodes):
61+
"""
62+
Test all EOF only opcodes in legacy contracts and expects failure.
63+
"""
64+
env = Environment()
65+
66+
address_test_contract = pre.deploy_contract(
67+
code=code + Op.SSTORE(slot_code_executed, value_code_executed),
68+
storage={slot_code_executed: value_non_execution_canary},
69+
)
70+
71+
post = {
72+
# assert the canary is not over-written. If it was written then the EOF opcode was valid
73+
address_test_contract: Account(storage={slot_code_executed: value_non_execution_canary}),
74+
}
75+
76+
sender = pre.fund_eoa()
77+
78+
tx = Transaction(
79+
sender=sender,
80+
to=address_test_contract,
81+
gas_limit=5_000_000,
82+
gas_price=10,
83+
protected=False,
84+
data="",
85+
)
86+
87+
state_test(
88+
env=env,
89+
pre=pre,
90+
post=post,
91+
tx=tx,
92+
)
93+
94+
95+
@pytest.mark.parametrize(
96+
"code",
97+
eof_opcode_blocks,
98+
)
99+
def test_opcodes_in_create_tx(state_test: StateTestFiller, pre: Alloc, code: Opcodes):
100+
"""
101+
Test all EOF only opcodes in legacy contracts and expects failure.
102+
"""
103+
env = Environment()
104+
105+
sender = pre.fund_eoa()
106+
107+
tx = Transaction(
108+
sender=sender,
109+
to=None,
110+
gas_limit=5_000_000,
111+
gas_price=10,
112+
protected=False,
113+
data=code,
114+
)
115+
116+
post = {
117+
# Should revert in initcode, which results in no contract created
118+
tx.created_contract: Account.NONEXISTENT
119+
}
120+
121+
state_test(
122+
env=env,
123+
pre=pre,
124+
post=post,
125+
tx=tx,
126+
)
127+
128+
129+
@pytest.mark.parametrize(
130+
"legacy_create_opcode",
131+
[
132+
pytest.param(Op.CREATE, id="CREATE"),
133+
pytest.param(Op.CREATE2, id="CREATE2"),
134+
],
135+
)
136+
@pytest.mark.parametrize(
137+
"code",
138+
eof_opcode_blocks,
139+
)
140+
def test_opcodes_in_create_operation(
141+
state_test: StateTestFiller,
142+
pre: Alloc,
143+
code: Opcodes,
144+
legacy_create_opcode: Opcodes,
145+
):
146+
"""
147+
Test all EOF only opcodes in legacy contracts and expects failure.
148+
"""
149+
env = Environment()
150+
151+
init_code = Initcode(initcode_prefix=code, deploy_code=Op.RETURN(0, 0))
152+
factory_code = (
153+
Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE)
154+
+ Op.SSTORE(slot_create_address, legacy_create_opcode(size=Op.CALLDATASIZE))
155+
+ Op.SSTORE(slot_code_worked, value_code_worked)
156+
)
157+
158+
sender = pre.fund_eoa()
159+
contract_address = pre.deploy_contract(code=factory_code)
160+
161+
post = {
162+
contract_address: Account(
163+
storage={slot_create_address: value_create_failed, slot_code_worked: value_code_worked}
164+
)
165+
}
166+
tx = Transaction(
167+
to=contract_address,
168+
gas_limit=10_000_000,
169+
gas_price=10,
170+
protected=False,
171+
data=init_code,
172+
sender=sender,
173+
)
174+
175+
state_test(env=env, pre=pre, post=post, tx=tx)
176+
177+
178+
@pytest.mark.parametrize(
179+
("ext_call_opcode"),
180+
[
181+
pytest.param(Op.EXTCALL, id="EXTCALL"),
182+
pytest.param(Op.EXTDELEGATECALL, id="EXTDELEGATECALL"),
183+
pytest.param(Op.EXTSTATICCALL, id="EXTSTATICCALL"),
184+
],
185+
)
186+
@pytest.mark.parametrize(
187+
"code",
188+
eof_opcode_blocks,
189+
)
190+
def test_opcodes_in_eof_calling_legacy(
191+
state_test: StateTestFiller,
192+
pre: Alloc,
193+
code: Opcodes,
194+
ext_call_opcode: Op,
195+
):
196+
"""
197+
Test all EOF only opcodes in legacy contracts and expects failure.
198+
"""
199+
env = Environment()
200+
201+
address_test_contract = pre.deploy_contract(
202+
code=code + Op.SSTORE(slot_code_executed, value_code_executed),
203+
storage={slot_code_executed: value_non_execution_canary},
204+
)
205+
206+
address_entry_contract = pre.deploy_contract(
207+
code=Container(
208+
sections=[
209+
Section.Code(
210+
ext_call_opcode(address=address_test_contract)
211+
+ Op.SSTORE(slot_code_worked, value_code_worked)
212+
+ Op.STOP
213+
)
214+
]
215+
),
216+
storage={slot_code_executed: value_non_execution_canary},
217+
)
218+
219+
post = {
220+
# assert the canary is not over-written. If it was written then the EOF opcode was valid
221+
address_test_contract: Account(storage={slot_code_executed: value_non_execution_canary}),
222+
address_entry_contract: Account(
223+
storage={
224+
slot_code_executed: value_non_execution_canary,
225+
slot_code_worked: value_code_worked,
226+
}
227+
),
228+
}
229+
230+
sender = pre.fund_eoa()
231+
232+
tx = Transaction(
233+
sender=sender,
234+
to=address_entry_contract,
235+
gas_limit=5_000_000,
236+
gas_price=10,
237+
protected=False,
238+
data="",
239+
)
240+
241+
state_test(
242+
env=env,
243+
pre=pre,
244+
post=post,
245+
tx=tx,
246+
)

tests/prague/eip7692_eof_v1/eip7069_extcall/test_gas.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ def gas_test(
5050

5151
sender = pre.fund_eoa(10**18)
5252

53-
address_baseline = pre.deploy_contract(setup_code + tear_down_code)
54-
address_subject = pre.deploy_contract(setup_code + subject_code + tear_down_code)
53+
address_baseline = pre.deploy_contract(
54+
Container(sections=[Section.Code(setup_code + tear_down_code)])
55+
)
56+
address_subject = pre.deploy_contract(
57+
Container(sections=[Section.Code(setup_code + subject_code + tear_down_code)])
58+
)
5559
address_legacy_harness = pre.deploy_contract(
5660
code=(
5761
# warm subject and baseline without executing

0 commit comments

Comments
 (0)