Skip to content

Commit f96e7b5

Browse files
winsvegamarioevz
andauthored
new(tests): EOF - EIP-3540: test all opcodes in valid code section (#634)
* test all opcodes in valid eof code section * make UndefinedOpcodes global * do not produce unreachable code on halting instructions if opcode is halting in eof, let it act as returning opcode to check that no exception happens * types fix after rebase * changelog * feat(fw): implement hash for bytecode * fix(tests): remove ._name_ --------- Co-authored-by: Mario Vega <[email protected]>
1 parent a3743c8 commit f96e7b5

File tree

7 files changed

+320
-18
lines changed

7 files changed

+320
-18
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Test fixtures for use by clients are available for each release on the [Github r
2121
- ✨ Add tests for [EIP-4200: EOF - Static relative jumps](https://eips.ethereum.org/EIPS/eip-4200) ([#581](https://github.com/ethereum/execution-spec-tests/pull/581)).
2222
- ✨ Add tests for [EIP-7069: EOF - Revamped CALL instructions](https://eips.ethereum.org/EIPS/eip-7069) ([#595](https://github.com/ethereum/execution-spec-tests/pull/595)).
2323
- 🐞 Fix typos in self-destruct collision test from erroneous pytest parametrization ([#608](https://github.com/ethereum/execution-spec-tests/pull/608)).
24+
- ✨ Add tests for [EIP-3540: EOF - EVM Object Format v1](https://eips.ethereum.org/EIPS/eip-3540) ([#634](https://github.com/ethereum/execution-spec-tests/pull/634)).
2425

2526
### 🛠️ Framework
2627

src/ethereum_test_tools/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
TestInfo,
6161
)
6262
from .spec.blockchain.types import Block, Header
63-
from .vm import Bytecode, Macro, Macros, Opcode, OpcodeCallArg, Opcodes
63+
from .vm import Bytecode, Macro, Macros, Opcode, OpcodeCallArg, Opcodes, UndefinedOpcodes
6464

6565
__all__ = (
6666
"SPEC_TYPES",
@@ -96,6 +96,7 @@
9696
"Opcode",
9797
"OpcodeCallArg",
9898
"Opcodes",
99+
"UndefinedOpcodes",
99100
"ReferenceSpec",
100101
"ReferenceSpecTypes",
101102
"Removable",

src/ethereum_test_tools/vm/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Ethereum Virtual Machine related definitions and utilities.
33
"""
44

5-
from .opcode import Bytecode, Macro, Macros, Opcode, OpcodeCallArg, Opcodes
5+
from .opcode import Bytecode, Macro, Macros, Opcode, OpcodeCallArg, Opcodes, UndefinedOpcodes
66

77
__all__ = (
88
"Bytecode",
@@ -11,4 +11,5 @@
1111
"Macros",
1212
"OpcodeCallArg",
1313
"Opcodes",
14+
"UndefinedOpcodes",
1415
)

src/ethereum_test_tools/vm/opcode.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ def __eq__(self, other):
138138
return bytes(self) == bytes(other)
139139
raise NotImplementedError(f"Unsupported type for comparison f{type(other)}")
140140

141+
def __hash__(self):
142+
"""
143+
Return the hash of the bytecode representation.
144+
"""
145+
return hash(
146+
(
147+
bytes(self),
148+
self.popped_stack_items,
149+
self.pushed_stack_items,
150+
self.max_stack_height,
151+
self.min_stack_height,
152+
)
153+
)
154+
141155
def __add__(self, other: "Bytecode | int | None") -> "Bytecode":
142156
"""
143157
Concatenate the bytecode representation with another bytecode object.
@@ -5802,3 +5816,98 @@ class Macros(Macro, Enum):
58025816
----
58035817
SHA3(0, 100000000000)
58045818
"""
5819+
5820+
5821+
class UndefinedOpcodes(Opcode, Enum):
5822+
"""
5823+
Enum containing all unknown opcodes (88 at the moment).
5824+
"""
5825+
5826+
OPCODE_0C = Opcode(0x0C)
5827+
OPCODE_0D = Opcode(0x0D)
5828+
OPCODE_0E = Opcode(0x0E)
5829+
OPCODE_0F = Opcode(0x0F)
5830+
OPCODE_1E = Opcode(0x1E)
5831+
OPCODE_1F = Opcode(0x1F)
5832+
OPCODE_21 = Opcode(0x21)
5833+
OPCODE_22 = Opcode(0x22)
5834+
OPCODE_23 = Opcode(0x23)
5835+
OPCODE_24 = Opcode(0x24)
5836+
OPCODE_25 = Opcode(0x25)
5837+
OPCODE_26 = Opcode(0x26)
5838+
OPCODE_27 = Opcode(0x27)
5839+
OPCODE_28 = Opcode(0x28)
5840+
OPCODE_29 = Opcode(0x29)
5841+
OPCODE_2A = Opcode(0x2A)
5842+
OPCODE_2B = Opcode(0x2B)
5843+
OPCODE_2C = Opcode(0x2C)
5844+
OPCODE_2D = Opcode(0x2D)
5845+
OPCODE_2E = Opcode(0x2E)
5846+
OPCODE_2F = Opcode(0x2F)
5847+
OPCODE_4B = Opcode(0x4B)
5848+
OPCODE_4C = Opcode(0x4C)
5849+
OPCODE_4D = Opcode(0x4D)
5850+
OPCODE_4E = Opcode(0x4E)
5851+
OPCODE_4F = Opcode(0x4F)
5852+
OPCODE_A5 = Opcode(0xA5)
5853+
OPCODE_A6 = Opcode(0xA6)
5854+
OPCODE_A7 = Opcode(0xA7)
5855+
OPCODE_A8 = Opcode(0xA8)
5856+
OPCODE_A9 = Opcode(0xA9)
5857+
OPCODE_AA = Opcode(0xAA)
5858+
OPCODE_AB = Opcode(0xAB)
5859+
OPCODE_AC = Opcode(0xAC)
5860+
OPCODE_AD = Opcode(0xAD)
5861+
OPCODE_AE = Opcode(0xAE)
5862+
OPCODE_AF = Opcode(0xAF)
5863+
OPCODE_B0 = Opcode(0xB0)
5864+
OPCODE_B1 = Opcode(0xB1)
5865+
OPCODE_B2 = Opcode(0xB2)
5866+
OPCODE_B3 = Opcode(0xB3)
5867+
OPCODE_B4 = Opcode(0xB4)
5868+
OPCODE_B5 = Opcode(0xB5)
5869+
OPCODE_B6 = Opcode(0xB6)
5870+
OPCODE_B7 = Opcode(0xB7)
5871+
OPCODE_B8 = Opcode(0xB8)
5872+
OPCODE_B9 = Opcode(0xB9)
5873+
OPCODE_BA = Opcode(0xBA)
5874+
OPCODE_BB = Opcode(0xBB)
5875+
OPCODE_BC = Opcode(0xBC)
5876+
OPCODE_BD = Opcode(0xBD)
5877+
OPCODE_BE = Opcode(0xBE)
5878+
OPCODE_BF = Opcode(0xBF)
5879+
OPCODE_C0 = Opcode(0xC0)
5880+
OPCODE_C1 = Opcode(0xC1)
5881+
OPCODE_C2 = Opcode(0xC2)
5882+
OPCODE_C3 = Opcode(0xC3)
5883+
OPCODE_C4 = Opcode(0xC4)
5884+
OPCODE_C5 = Opcode(0xC5)
5885+
OPCODE_C6 = Opcode(0xC6)
5886+
OPCODE_C7 = Opcode(0xC7)
5887+
OPCODE_C8 = Opcode(0xC8)
5888+
OPCODE_C9 = Opcode(0xC9)
5889+
OPCODE_CA = Opcode(0xCA)
5890+
OPCODE_CB = Opcode(0xCB)
5891+
OPCODE_CC = Opcode(0xCC)
5892+
OPCODE_CD = Opcode(0xCD)
5893+
OPCODE_CE = Opcode(0xCE)
5894+
OPCODE_CF = Opcode(0xCF)
5895+
OPCODE_D4 = Opcode(0xD4)
5896+
OPCODE_D5 = Opcode(0xD5)
5897+
OPCODE_D6 = Opcode(0xD6)
5898+
OPCODE_D7 = Opcode(0xD7)
5899+
OPCODE_D8 = Opcode(0xD8)
5900+
OPCODE_D9 = Opcode(0xD9)
5901+
OPCODE_DA = Opcode(0xDA)
5902+
OPCODE_DB = Opcode(0xDB)
5903+
OPCODE_DC = Opcode(0xDC)
5904+
OPCODE_DD = Opcode(0xDD)
5905+
OPCODE_DE = Opcode(0xDE)
5906+
OPCODE_DF = Opcode(0xDF)
5907+
OPCODE_E9 = Opcode(0xE9)
5908+
OPCODE_EA = Opcode(0xEA)
5909+
OPCODE_EB = Opcode(0xEB)
5910+
OPCODE_ED = Opcode(0xED)
5911+
OPCODE_EF = Opcode(0xEF)
5912+
OPCODE_F6 = Opcode(0xF6)
5913+
OPCODE_FC = Opcode(0xFC)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""
2+
EOF Container: check how every opcode behaves in the middle of the valid eof container code
3+
"""
4+
5+
import pytest
6+
7+
from ethereum_test_tools import Bytecode, EOFTestFiller, Opcode
8+
from ethereum_test_tools import Opcodes as Op
9+
from ethereum_test_tools import UndefinedOpcodes
10+
from ethereum_test_tools.eof.v1 import Container, EOFException, Section
11+
12+
from .. import EOF_FORK_NAME
13+
14+
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-3540.md"
15+
REFERENCE_SPEC_VERSION = "8dcb0a8c1c0102c87224308028632cc986a61183"
16+
17+
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)
18+
19+
# Invalid Opcodes will produce EOFException.UNDEFINED_INSTRUCTION when used in EOFContainer
20+
invalid_eof_opcodes = {
21+
Op.CODESIZE,
22+
Op.SELFDESTRUCT,
23+
Op.CREATE2,
24+
Op.CODECOPY,
25+
Op.EXTCODESIZE,
26+
Op.EXTCODECOPY,
27+
Op.EXTCODEHASH,
28+
Op.JUMP,
29+
Op.JUMPI,
30+
Op.PC,
31+
Op.GAS,
32+
Op.CREATE,
33+
Op.CALL,
34+
Op.CALLCODE,
35+
Op.DELEGATECALL,
36+
Op.STATICCALL,
37+
}
38+
39+
# Halting the execution opcodes can be placed without STOP instruction at the end
40+
halting_opcodes = {
41+
Op.STOP,
42+
Op.JUMPF,
43+
Op.RETURNCONTRACT,
44+
Op.RETURN,
45+
Op.REVERT,
46+
Op.INVALID,
47+
}
48+
49+
# Special eof opcodes that require [] operator
50+
eof_opcodes = {
51+
Op.DATALOADN,
52+
Op.RJUMPV,
53+
Op.CALLF,
54+
Op.RETF,
55+
Op.JUMPF,
56+
Op.EOFCREATE,
57+
Op.RETURNCONTRACT,
58+
Op.EXCHANGE,
59+
}
60+
61+
62+
def expect_exception(opcode: Opcode) -> EOFException | None:
63+
"""
64+
Returns exception that eof container reports when having this opcode in the middle of the code
65+
"""
66+
if opcode in invalid_eof_opcodes or opcode in list(UndefinedOpcodes):
67+
return EOFException.UNDEFINED_INSTRUCTION
68+
69+
# RETF not allowed in first section
70+
if opcode == Op.RETF:
71+
return EOFException.INVALID_NON_RETURNING_FLAG
72+
return None
73+
74+
75+
def make_opcode_valid_bytes(opcode: Opcode) -> Opcode | Bytecode:
76+
"""
77+
Construct a valid stack and bytes for the opcode
78+
"""
79+
code: Opcode | Bytecode
80+
if opcode.data_portion_length == 0 and opcode.data_portion_formatter is None:
81+
code = opcode
82+
elif opcode == Op.CALLF:
83+
code = opcode[1]
84+
else:
85+
code = opcode[0]
86+
if opcode not in halting_opcodes:
87+
return code + Op.STOP
88+
return code
89+
90+
91+
def eof_opcode_stack(opcode: Opcode) -> int:
92+
"""
93+
Eof opcode has special stack influence
94+
"""
95+
if opcode in eof_opcodes:
96+
if opcode == Op.CALLF or opcode == Op.JUMPF or opcode == Op.EXCHANGE:
97+
return 0
98+
return 1
99+
return 0
100+
101+
102+
@pytest.mark.parametrize("opcode", list(Op) + list(UndefinedOpcodes))
103+
def test_all_opcodes_in_container(eof_test: EOFTestFiller, opcode: Opcode):
104+
"""
105+
Test all opcodes inside valid container
106+
257 because 0x5B is duplicated
107+
"""
108+
section_call = []
109+
if opcode == Op.CALLF:
110+
section_call = [
111+
Section.Code(
112+
code=Op.RETF,
113+
code_inputs=0,
114+
code_outputs=0,
115+
max_stack_height=0,
116+
)
117+
]
118+
119+
eof_code = Container(
120+
sections=[
121+
Section.Code(
122+
code=Op.PUSH1(00) * 20 + make_opcode_valid_bytes(opcode),
123+
max_stack_height=max(
124+
20,
125+
20
126+
+ opcode.pushed_stack_items
127+
- opcode.popped_stack_items
128+
+ eof_opcode_stack(opcode),
129+
),
130+
),
131+
Section.Container(
132+
container=Container(
133+
sections=[
134+
Section.Code(code=Op.STOP),
135+
]
136+
)
137+
),
138+
]
139+
+ section_call
140+
+ [
141+
Section.Data("1122334455667788" * 4),
142+
],
143+
)
144+
145+
eof_test(
146+
data=eof_code,
147+
expect_exception=expect_exception(opcode),
148+
)

tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_example_valid_invalid.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66

7-
from ethereum_test_tools import EOFTestFiller, Opcode
7+
from ethereum_test_tools import EOFTestFiller
88
from ethereum_test_tools import Opcodes as Op
99
from ethereum_test_tools.eof.v1 import Bytes, Container, EOFException, Section
1010

@@ -34,21 +34,6 @@
3434
None,
3535
id="simple_eof_1_deploy",
3636
),
37-
pytest.param(
38-
# Check that EOF1 with an illegal opcode fails
39-
Container(
40-
name="EOF1I0008",
41-
sections=[
42-
Section.Code(
43-
code=Op.ADDRESS + Opcode(0xEF) + Op.STOP,
44-
),
45-
Section.Data("0x0bad60A7"),
46-
],
47-
),
48-
"ef00010100040200010003040004000080000130ef000bad60A7",
49-
EOFException.UNDEFINED_INSTRUCTION,
50-
id="illegal_opcode_fail",
51-
),
5237
pytest.param(
5338
# Check that valid EOF1 can include 0xFE, the designated invalid opcode
5439
Container(

0 commit comments

Comments
 (0)