Skip to content

Commit 4411f3c

Browse files
committed
Implement EIP-7873
1 parent 795fc3b commit 4411f3c

File tree

6 files changed

+346
-88
lines changed

6 files changed

+346
-88
lines changed

src/ethereum/osaka/blocks.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .transactions import (
2222
AccessListTransaction,
2323
BlobTransaction,
24+
EofInitCodeTransaction,
2425
FeeMarketTransaction,
2526
LegacyTransaction,
2627
SetCodeTransaction,
@@ -121,6 +122,8 @@ def encode_receipt(tx: Transaction, receipt: Receipt) -> Union[Bytes, Receipt]:
121122
return b"\x03" + rlp.encode(receipt)
122123
elif isinstance(tx, SetCodeTransaction):
123124
return b"\x04" + rlp.encode(receipt)
125+
elif isinstance(tx, EofInitCodeTransaction):
126+
return b"\x05" + rlp.encode(receipt)
124127
else:
125128
return receipt
126129

@@ -130,7 +133,7 @@ def decode_receipt(receipt: Union[Bytes, Receipt]) -> Receipt:
130133
Decodes a receipt.
131134
"""
132135
if isinstance(receipt, Bytes):
133-
assert receipt[0] in (1, 2, 3, 4)
136+
assert receipt[0] in (1, 2, 3, 4, 5)
134137
return rlp.decode_to(Receipt, receipt[1:])
135138
else:
136139
return receipt

src/ethereum/osaka/fork.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from .transactions import (
5353
AccessListTransaction,
5454
BlobTransaction,
55+
EofInitCodeTransaction,
5556
FeeMarketTransaction,
5657
LegacyTransaction,
5758
SetCodeTransaction,
@@ -428,7 +429,13 @@ def check_transaction(
428429
sender_account = get_account(block_env.state, sender_address)
429430

430431
if isinstance(
431-
tx, (FeeMarketTransaction, BlobTransaction, SetCodeTransaction)
432+
tx,
433+
(
434+
FeeMarketTransaction,
435+
BlobTransaction,
436+
SetCodeTransaction,
437+
EofInitCodeTransaction,
438+
),
432439
):
433440
if tx.max_fee_per_gas < tx.max_priority_fee_per_gas:
434441
raise InvalidBlock
@@ -465,7 +472,9 @@ def check_transaction(
465472
else:
466473
blob_versioned_hashes = ()
467474

468-
if isinstance(tx, (BlobTransaction, SetCodeTransaction)):
475+
if isinstance(
476+
tx, (BlobTransaction, SetCodeTransaction, EofInitCodeTransaction)
477+
):
469478
if not isinstance(tx.to, Address):
470479
raise InvalidBlock
471480

@@ -557,6 +566,7 @@ def process_system_transaction(
557566
transient_storage=TransientStorage(),
558567
blob_versioned_hashes=(),
559568
authorizations=(),
569+
init_codes=None,
560570
index_in_block=None,
561571
tx_hash=None,
562572
traces=[],
@@ -731,7 +741,7 @@ def process_transaction(
731741
encode_transaction(tx),
732742
)
733743

734-
intrinsic_gas, calldata_floor_gas_cost = validate_transaction(tx)
744+
intrinsic_gas, floor_gas_cost = validate_transaction(tx)
735745

736746
(
737747
sender,
@@ -773,6 +783,7 @@ def process_transaction(
773783
FeeMarketTransaction,
774784
BlobTransaction,
775785
SetCodeTransaction,
786+
EofInitCodeTransaction,
776787
),
777788
):
778789
for address, keys in tx.access_list:
@@ -784,6 +795,11 @@ def process_transaction(
784795
if isinstance(tx, SetCodeTransaction):
785796
authorizations = tx.authorizations
786797

798+
if isinstance(tx, EofInitCodeTransaction):
799+
init_codes = tx.init_codes
800+
else:
801+
init_codes = None
802+
787803
tx_env = vm.TransactionEnvironment(
788804
origin=sender,
789805
gas_price=effective_gas_price,
@@ -793,6 +809,7 @@ def process_transaction(
793809
transient_storage=TransientStorage(),
794810
blob_versioned_hashes=blob_versioned_hashes,
795811
authorizations=authorizations,
812+
init_codes=init_codes,
796813
index_in_block=index,
797814
tx_hash=get_transaction_hash(encode_transaction(tx)),
798815
traces=[],
@@ -817,7 +834,7 @@ def process_transaction(
817834

818835
# Transactions with less execution_gas_used than the floor pay at the
819836
# floor cost.
820-
tx_gas_used = max(execution_gas_used, calldata_floor_gas_cost)
837+
tx_gas_used = max(execution_gas_used, floor_gas_cost)
821838

822839
tx_output.gas_left = tx.gas - tx_gas_used
823840
gas_refund_amount = tx_output.gas_left * effective_gas_price

src/ethereum/osaka/transactions.py

Lines changed: 115 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
from .fork_types import Address, Authorization, VersionedHash
2020

2121
TX_BASE_COST = Uint(21000)
22-
FLOOR_CALLDATA_COST = Uint(10)
23-
STANDARD_CALLDATA_TOKEN_COST = Uint(4)
22+
TOTAL_COST_FLOOR_PER_TOKEN = Uint(10)
23+
STANDARD_TOKEN_COST = Uint(4)
2424
TX_CREATE_COST = Uint(32000)
2525
TX_ACCESS_LIST_ADDRESS_COST = Uint(2400)
2626
TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900)
2727

28+
MAX_INIT_CODE_COUNT = 256
29+
2830

2931
@slotted_freezable
3032
@dataclass
@@ -130,12 +132,35 @@ class SetCodeTransaction:
130132
s: U256
131133

132134

135+
@slotted_freezable
136+
@dataclass
137+
class EofInitCodeTransaction:
138+
"""
139+
The transaction type added in EIP-7873.
140+
"""
141+
142+
chain_id: U64
143+
nonce: U256
144+
max_priority_fee_per_gas: Uint
145+
max_fee_per_gas: Uint
146+
gas: Uint
147+
to: Union[Bytes0, Address]
148+
value: U256
149+
data: Bytes
150+
access_list: Tuple[Tuple[Address, Tuple[Bytes32, ...]], ...]
151+
init_codes: Tuple[Bytes, ...]
152+
y_parity: U256
153+
r: U256
154+
s: U256
155+
156+
133157
Transaction = Union[
134158
LegacyTransaction,
135159
AccessListTransaction,
136160
FeeMarketTransaction,
137161
BlobTransaction,
138162
SetCodeTransaction,
163+
EofInitCodeTransaction,
139164
]
140165

141166

@@ -153,6 +178,8 @@ def encode_transaction(tx: Transaction) -> Union[LegacyTransaction, Bytes]:
153178
return b"\x03" + rlp.encode(tx)
154179
elif isinstance(tx, SetCodeTransaction):
155180
return b"\x04" + rlp.encode(tx)
181+
elif isinstance(tx, EofInitCodeTransaction):
182+
return b"\x05" + rlp.encode(tx)
156183
else:
157184
raise Exception(f"Unable to encode transaction of type {type(tx)}")
158185

@@ -170,6 +197,8 @@ def decode_transaction(tx: Union[LegacyTransaction, Bytes]) -> Transaction:
170197
return rlp.decode_to(BlobTransaction, tx[1:])
171198
elif tx[0] == 4:
172199
return rlp.decode_to(SetCodeTransaction, tx[1:])
200+
elif tx[0] == 5:
201+
return rlp.decode_to(EofInitCodeTransaction, tx[1:])
173202
else:
174203
raise TransactionTypeError(tx[0])
175204
else:
@@ -211,6 +240,12 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]:
211240
"""
212241
from .vm.interpreter import MAX_CODE_SIZE
213242

243+
if isinstance(tx, EofInitCodeTransaction):
244+
if len(tx.init_codes) == 0:
245+
raise InvalidTransaction("Type 5 tx with no init codes")
246+
if len(tx.init_codes) > MAX_INIT_CODE_COUNT:
247+
raise InvalidTransaction("Type 5 tx with too many init codes")
248+
214249
intrinsic_gas, calldata_floor_gas_cost = calculate_intrinsic_cost(tx)
215250
if max(intrinsic_gas, calldata_floor_gas_cost) > tx.gas:
216251
raise InvalidTransaction("Insufficient gas")
@@ -222,6 +257,28 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]:
222257
return intrinsic_gas, calldata_floor_gas_cost
223258

224259

260+
def calculate_tokens_in_data(data: Bytes) -> Uint:
261+
"""
262+
Calculate the tokens in a certain data.
263+
264+
Parameters
265+
----------
266+
data :
267+
Data in which tokens are to be calculated.
268+
269+
Returns
270+
-------
271+
tokens_in_data :
272+
Tokens in the data.
273+
"""
274+
zero_bytes = 0
275+
for byte in data:
276+
if byte == 0:
277+
zero_bytes += 1
278+
279+
return Uint(zero_bytes + (len(data) - zero_bytes) * 4)
280+
281+
225282
def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
226283
"""
227284
Calculates the gas that is charged before execution is started.
@@ -250,19 +307,27 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
250307
"""
251308
from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST
252309
from .vm.gas import init_code_cost
310+
from .vm.interpreter import MAX_CODE_SIZE
253311

254-
zero_bytes = 0
255-
for byte in tx.data:
256-
if byte == 0:
257-
zero_bytes += 1
312+
tokens_in_tx = Uint(0)
313+
314+
tokens_in_tx += calculate_tokens_in_data(tx.data)
315+
316+
if isinstance(tx, EofInitCodeTransaction):
317+
for init_code in tx.init_codes:
318+
if len(init_code) == 0:
319+
raise InvalidTransaction(
320+
"Type 5 tx with zero-length init code"
321+
)
322+
if len(init_code) > 2 * MAX_CODE_SIZE:
323+
raise InvalidTransaction("Type 5 tx with too large init code")
324+
325+
tokens_in_tx += calculate_tokens_in_data(init_code)
258326

259-
tokens_in_calldata = Uint(zero_bytes + (len(tx.data) - zero_bytes) * 4)
260327
# EIP-7623 floor price (note: no EVM costs)
261-
calldata_floor_gas_cost = (
262-
tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST
263-
)
328+
floor_gas_cost = tokens_in_tx * TOTAL_COST_FLOOR_PER_TOKEN + TX_BASE_COST
264329

265-
data_cost = tokens_in_calldata * STANDARD_CALLDATA_TOKEN_COST
330+
data_cost = tokens_in_tx * STANDARD_TOKEN_COST
266331

267332
if tx.to == Bytes0(b""):
268333
create_cost = TX_CREATE_COST + init_code_cost(ulen(tx.data))
@@ -277,6 +342,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
277342
FeeMarketTransaction,
278343
BlobTransaction,
279344
SetCodeTransaction,
345+
EofInitCodeTransaction,
280346
),
281347
):
282348
for _address, keys in tx.access_list:
@@ -295,7 +361,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
295361
+ access_list_cost
296362
+ auth_cost
297363
),
298-
calldata_floor_gas_cost,
364+
floor_gas_cost,
299365
)
300366

301367

@@ -365,6 +431,10 @@ def recover_sender(chain_id: U64, tx: Transaction) -> Address:
365431
public_key = secp256k1_recover(
366432
r, s, tx.y_parity, signing_hash_7702(tx)
367433
)
434+
elif isinstance(tx, EofInitCodeTransaction):
435+
public_key = secp256k1_recover(
436+
r, s, tx.y_parity, signing_hash_7873(tx)
437+
)
368438

369439
return Address(keccak256(public_key)[12:32])
370440

@@ -560,6 +630,39 @@ def signing_hash_7702(tx: SetCodeTransaction) -> Hash32:
560630
)
561631

562632

633+
def signing_hash_7873(tx: EofInitCodeTransaction) -> Hash32:
634+
"""
635+
Compute the hash of a transaction used in a EIP-7873 signature.
636+
637+
Parameters
638+
----------
639+
tx :
640+
Transaction of interest.
641+
642+
Returns
643+
-------
644+
hash : `ethereum.crypto.hash.Hash32`
645+
Hash of the transaction.
646+
"""
647+
return keccak256(
648+
b"\x05"
649+
+ rlp.encode(
650+
(
651+
tx.chain_id,
652+
tx.nonce,
653+
tx.max_priority_fee_per_gas,
654+
tx.max_fee_per_gas,
655+
tx.gas,
656+
tx.to,
657+
tx.value,
658+
tx.data,
659+
tx.access_list,
660+
tx.init_codes,
661+
)
662+
)
663+
)
664+
665+
563666
def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32:
564667
"""
565668
Parameters

src/ethereum/osaka/utils/address.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,30 @@ def compute_create2_contract_address(
119119
padded_address = left_pad_zero_bytes(canonical_address, 20)
120120

121121
return Address(padded_address)
122+
123+
124+
def compute_eof_tx_create_contract_address(
125+
address: Address, salt: Bytes32
126+
) -> Address:
127+
"""
128+
Computes address of the new account that needs to be created, in the
129+
EOF1 TXCREATE Opcode.
130+
131+
Parameters
132+
----------
133+
address :
134+
The address of the account that wants to create the new account.
135+
salt :
136+
Address generation salt.
137+
138+
Returns
139+
-------
140+
address: `ethereum.osaka.fork_types.Address`
141+
The computed address of the new account.
142+
"""
143+
preimage = b"\xff" + address + salt
144+
computed_address = keccak256(preimage)
145+
canonical_address = computed_address[-20:]
146+
padded_address = left_pad_zero_bytes(canonical_address, 20)
147+
148+
return Address(padded_address)

src/ethereum/osaka/vm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
The abstract computer which runs the code stored in an
1313
`.fork_types.Account`.
1414
"""
15-
1615
from dataclasses import dataclass, field
1716
from typing import TYPE_CHECKING, List, Optional, Set, Tuple, Union
1817

@@ -112,6 +111,7 @@ class TransactionEnvironment:
112111
transient_storage: TransientStorage
113112
blob_versioned_hashes: Tuple[VersionedHash, ...]
114113
authorizations: Tuple[Authorization, ...]
114+
init_codes: Optional[Tuple[Bytes, ...]]
115115
index_in_block: Optional[Uint]
116116
tx_hash: Optional[Hash32]
117117
traces: List[dict]

0 commit comments

Comments
 (0)