Skip to content

Commit 514a68a

Browse files
committed
Implement EIP-7692
1 parent b3b2a57 commit 514a68a

19 files changed

+3378
-30
lines changed

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ packages =
119119
ethereum/osaka
120120
ethereum/osaka/utils
121121
ethereum/osaka/vm
122+
ethereum/osaka/vm/eof
122123
ethereum/osaka/vm/instructions
123124
ethereum/osaka/vm/precompiled_contracts
124125
ethereum/osaka/vm/precompiled_contracts/bls12_381

src/ethereum/osaka/fork.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
from .utils.message import prepare_message
6868
from .vm import Message
6969
from .vm.eoa_delegation import is_valid_delegation
70+
from .vm.exceptions import InvalidEof
7071
from .vm.gas import (
7172
calculate_blob_gas_price,
7273
calculate_data_fee,
@@ -578,6 +579,7 @@ def process_system_transaction(
578579
accessed_addresses=set(),
579580
accessed_storage_keys=set(),
580581
parent_evm=None,
582+
eof=None,
581583
)
582584

583585
system_tx_output = process_message_call(system_tx_message)
@@ -796,9 +798,14 @@ def process_transaction(
796798
traces=[],
797799
)
798800

799-
message = prepare_message(block_env, tx_env, tx)
800-
801-
tx_output = process_message_call(message)
801+
try:
802+
message = prepare_message(block_env, tx_env, tx)
803+
except InvalidEof as error:
804+
tx_output = MessageCallOutput(
805+
gas, U256(0), tuple(), set(), set(), error, b""
806+
)
807+
else:
808+
tx_output = process_message_call(message)
802809

803810
# For EIP-7623 we first calculate the execution_gas_used, which includes
804811
# the execution gas refund.

src/ethereum/osaka/utils/address.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
from ..fork_types import Address
2525

26+
MAX_ADDRESS_U256 = U256.from_be_bytes(b"\xff" * 20)
27+
2628

2729
def to_address(data: Union[Uint, U256]) -> Address:
2830
"""
@@ -41,9 +43,35 @@ def to_address(data: Union[Uint, U256]) -> Address:
4143
return Address(data.to_be_bytes32()[-20:])
4244

4345

46+
def to_address_without_mask(data: U256) -> Address:
47+
"""
48+
Convert a Uint or U256 value to a valid address (20 bytes).
49+
Raises a `ValueError` if the data is larger than `MAX_ADDRESS_U256
50+
51+
Parameters
52+
----------
53+
data :
54+
The string to be converted to bytes.
55+
56+
Raises
57+
------
58+
ValueError
59+
If `data` is larger than `MAX_ADDRESS_U256`.
60+
61+
Returns
62+
-------
63+
address : `Address`
64+
The obtained address.
65+
"""
66+
if data > MAX_ADDRESS_U256:
67+
raise ValueError("Address is too large")
68+
return Address(data.to_be_bytes32()[-20:])
69+
70+
4471
def compute_contract_address(address: Address, nonce: Uint) -> Address:
4572
"""
46-
Computes address of the new account that needs to be created.
73+
Computes address of the new account that needs to be created based
74+
on the account nonce.
4775
4876
Parameters
4977
----------

src/ethereum/osaka/utils/message.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
from ..transactions import Transaction
2121
from ..vm import BlockEnvironment, Message, TransactionEnvironment
2222
from ..vm.eoa_delegation import get_delegated_code_address
23+
from ..vm.eof import ContainerContext, Eof, EofVersion, get_eof_version
24+
from ..vm.eof.utils import metadata_from_container
25+
from ..vm.eof.validation import parse_create_tx_call_data
2326
from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS
2427
from .address import compute_contract_address
2528

@@ -56,8 +59,13 @@ def prepare_message(
5659
tx_env.origin,
5760
get_account(block_env.state, tx_env.origin).nonce - Uint(1),
5861
)
59-
msg_data = Bytes(b"")
60-
code = tx.data
62+
if get_eof_version(tx.data) == EofVersion.LEGACY:
63+
msg_data = Bytes(b"")
64+
code = tx.data
65+
eof = None
66+
else:
67+
eof, msg_data = parse_create_tx_call_data(tx.data)
68+
code = eof.container
6169
code_address = None
6270
elif isinstance(tx.to, Address):
6371
current_target = tx.to
@@ -70,6 +78,20 @@ def prepare_message(
7078
code = get_account(block_env.state, delegated_address).code
7179

7280
code_address = tx.to
81+
82+
if get_eof_version(code) == EofVersion.LEGACY:
83+
eof = None
84+
else:
85+
metadata = metadata_from_container(
86+
code,
87+
validate=False,
88+
context=ContainerContext.RUNTIME,
89+
)
90+
eof = Eof(
91+
version=get_eof_version(code),
92+
container=code,
93+
metadata=metadata,
94+
)
7395
else:
7496
raise AssertionError("Target must be address or empty bytes")
7597

@@ -92,4 +114,5 @@ def prepare_message(
92114
accessed_addresses=accessed_addresses,
93115
accessed_storage_keys=set(tx_env.access_list_storage_keys),
94116
parent_evm=None,
117+
eof=eof,
95118
)

src/ethereum/osaka/vm/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"""
1515

1616
from dataclasses import dataclass, field
17-
from typing import List, Optional, Set, Tuple, Union
17+
from typing import TYPE_CHECKING, List, Optional, Set, Tuple, Union
1818

1919
from ethereum_types.bytes import Bytes, Bytes0, Bytes32
2020
from ethereum_types.numeric import U64, U256, Uint
@@ -29,9 +29,16 @@
2929
from ..trie import Trie
3030
from .precompiled_contracts import RIPEMD160_ADDRESS
3131

32+
if TYPE_CHECKING:
33+
from .eof import Eof, ReturnStackItem
34+
35+
3236
__all__ = ("Environment", "Evm", "Message")
3337

3438

39+
MAX_CODE_SIZE = 0x6000
40+
41+
3542
@dataclass
3643
class BlockEnvironment:
3744
"""
@@ -132,6 +139,7 @@ class Message:
132139
accessed_addresses: Set[Address]
133140
accessed_storage_keys: Set[Tuple[Address, Bytes32]]
134141
parent_evm: Optional["Evm"]
142+
eof: Optional["Eof"]
135143

136144

137145
@dataclass
@@ -155,6 +163,10 @@ class Evm:
155163
error: Optional[EthereumException]
156164
accessed_addresses: Set[Address]
157165
accessed_storage_keys: Set[Tuple[Address, Bytes32]]
166+
eof: Optional["Eof"]
167+
current_section_index: Uint
168+
return_stack: List["ReturnStackItem"]
169+
deploy_container: Optional[Bytes]
158170

159171

160172
def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None:

src/ethereum/osaka/vm/eof/__init__.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""
2+
Ethereum Object Format (EOF)
3+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
5+
.. contents:: Table of Contents
6+
:backlinks: none
7+
:local:
8+
9+
Introduction
10+
------------
11+
12+
Implementation of the Ethereum Object Format (EOF) specification.
13+
"""
14+
15+
import enum
16+
from dataclasses import dataclass
17+
from typing import Dict, List, Optional, Set
18+
19+
from ethereum_types.bytes import Bytes
20+
from ethereum_types.numeric import Uint
21+
22+
from ..exceptions import InvalidEof
23+
24+
EOF_MAGIC = b"\xEF\x00"
25+
EOF_MAGIC_LENGTH = len(EOF_MAGIC)
26+
27+
28+
class EofVersion(enum.Enum):
29+
"""
30+
Enumeration of the different kinds of EOF containers.
31+
Legacy code is assigned zero.
32+
"""
33+
34+
LEGACY = 0
35+
EOF1 = 1
36+
37+
38+
class ContainerContext(enum.Enum):
39+
"""
40+
The context of the container. Create transaction
41+
data / init / account code / sub-container.
42+
A sub-container can either be an EOFCREATE target (init)
43+
or a RETURNCONTRACT target.
44+
"""
45+
46+
CREATE_TX_DATA = 0
47+
INIT = 1
48+
RUNTIME = 2
49+
RETURNCONTRACT_TARGET = 3
50+
51+
52+
@dataclass
53+
class EofMetadata:
54+
"""
55+
Dataclass to hold the metadata information of the
56+
EOF container.
57+
"""
58+
59+
context: ContainerContext
60+
type_size: Uint
61+
num_code_sections: Uint
62+
code_sizes: List[Uint]
63+
num_container_sections: Uint
64+
container_sizes: List[Uint]
65+
data_size: Uint
66+
body_start_index: Uint
67+
type_section_contents: List[bytes]
68+
code_section_contents: List[bytes]
69+
container_section_contents: List[bytes]
70+
data_section_contents: bytes
71+
72+
73+
@dataclass
74+
class Eof:
75+
"""
76+
Dataclass to hold the EOF container information.
77+
"""
78+
79+
version: EofVersion
80+
container: Bytes
81+
metadata: EofMetadata
82+
83+
84+
@dataclass
85+
class ReturnStackItem:
86+
"""
87+
Stack item for the return stack.
88+
"""
89+
90+
code_section_index: Uint
91+
offset: Uint
92+
93+
94+
@dataclass
95+
class OperandStackHeight:
96+
"""
97+
Stack height bounds of an instruction.
98+
"""
99+
100+
min: int
101+
max: int
102+
103+
104+
@dataclass
105+
class InstructionMetadata:
106+
"""
107+
Metadata of an instruction in the code section.
108+
"""
109+
110+
from ..instructions import Ops
111+
112+
opcode: Ops
113+
pc_post_instruction: Uint
114+
relative_offsets: List[int]
115+
target_index: Optional[Uint]
116+
container_index: Optional[Uint]
117+
stack_height: Optional[OperandStackHeight]
118+
119+
120+
SectionMetadata = Dict[Uint, InstructionMetadata]
121+
122+
123+
@dataclass
124+
class Validator:
125+
"""
126+
Validator for the Ethereum Object Format (EOF) container.
127+
"""
128+
129+
from ..instructions import Ops
130+
131+
eof: Eof
132+
sections: Dict[Uint, SectionMetadata]
133+
current_index: Uint
134+
current_code: bytes
135+
current_pc: Uint
136+
is_current_section_returning: bool
137+
has_return_contract: bool
138+
has_stop: bool
139+
has_return: bool
140+
reached_code_sections: List[Set[Uint]]
141+
referenced_subcontainers: Dict[Ops, List[Uint]]
142+
current_stack_height: Optional[OperandStackHeight]
143+
144+
145+
def get_eof_version(code: bytes) -> EofVersion:
146+
"""
147+
Get the Eof container's version.
148+
149+
Parameters
150+
----------
151+
code : bytes
152+
The code to check.
153+
154+
Returns
155+
-------
156+
Eof
157+
Eof Version of the container.
158+
"""
159+
if not code.startswith(EOF_MAGIC):
160+
return EofVersion.LEGACY
161+
162+
if code[EOF_MAGIC_LENGTH] == 1:
163+
return EofVersion.EOF1
164+
else:
165+
raise InvalidEof("Invalid EOF version")

0 commit comments

Comments
 (0)