From 9d33e1bc3faf8877ba6285ad415a2cc48ede64bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= Date: Sun, 24 Aug 2025 08:58:57 +0200 Subject: [PATCH 1/2] Add EIP-7981 Implementation --- src/ethereum/osaka/transactions.py | 65 +++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/ethereum/osaka/transactions.py b/src/ethereum/osaka/transactions.py index 90b1deecd7..b320c22fb7 100644 --- a/src/ethereum/osaka/transactions.py +++ b/src/ethereum/osaka/transactions.py @@ -63,6 +63,22 @@ Gas cost for including a storage key in the access list of a transaction. """ +TX_ACCESS_LIST_NONZERO_BYTE_COST = Uint(40) +""" +Gas cost per non-zero byte in access list addresses and storage keys as per +[EIP-7981]. + +[EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 +""" + +TX_ACCESS_LIST_ZERO_BYTE_COST = Uint(10) +""" +Gas cost per zero byte in access list addresses and storage keys as per +[EIP-7981]. + +[EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 +""" + TX_MAX_GAS_LIMIT = Uint(16_777_216) @@ -568,6 +584,40 @@ def validate_transaction(tx: Transaction) -> Tuple[Uint, Uint]: return intrinsic_gas, calldata_floor_gas_cost +def count_access_list_bytes(access_list: Tuple[Access, ...]) -> Tuple[int, int]: + """ + Count zero and non-zero bytes in access list addresses and storage keys. + + This function implements the byte counting mechanism from [EIP-7981], which + counts the actual zero and non-zero bytes in the raw address (20 bytes) and + storage key (32 bytes) data within the access list. + + Returns a tuple of (zero_bytes, nonzero_bytes). + + [EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 + """ + zero_bytes = 0 + nonzero_bytes = 0 + + for access in access_list: + # Count bytes in address (20 bytes) + for byte in access.account: + if byte == 0: + zero_bytes += 1 + else: + nonzero_bytes += 1 + + # Count bytes in storage keys (32 bytes each) + for slot in access.slots: + for byte in slot: + if byte == 0: + zero_bytes += 1 + else: + nonzero_bytes += 1 + + return zero_bytes, nonzero_bytes + + def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: """ Calculates the gas that is charged before execution is started. @@ -586,7 +636,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: 2. Cost for data (zero and non-zero bytes) 3. Cost for contract creation (if applicable) 4. Cost for access list entries (if applicable) - 5. Cost for authorizations (if applicable) + 5. Cost for access list data bytes per [EIP-7981] (if applicable) + 6. Cost for authorizations (if applicable) + + [EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 This function takes a transaction as a parameter and returns the intrinsic @@ -615,6 +668,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: create_cost = Uint(0) access_list_cost = Uint(0) + access_list_data_cost = Uint(0) if isinstance( tx, ( @@ -624,11 +678,19 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: SetCodeTransaction, ), ): + # Standard EIP-2930 access list functionality costs for access in tx.access_list: access_list_cost += TX_ACCESS_LIST_ADDRESS_COST access_list_cost += ( ulen(access.slots) * TX_ACCESS_LIST_STORAGE_KEY_COST ) + + # EIP-7981: Additional data cost for access list bytes + zero_bytes, nonzero_bytes = count_access_list_bytes(tx.access_list) + access_list_data_cost = ( + Uint(zero_bytes) * TX_ACCESS_LIST_ZERO_BYTE_COST + + Uint(nonzero_bytes) * TX_ACCESS_LIST_NONZERO_BYTE_COST + ) auth_cost = Uint(0) if isinstance(tx, SetCodeTransaction): @@ -640,6 +702,7 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: + data_cost + create_cost + access_list_cost + + access_list_data_cost + auth_cost ), calldata_floor_gas_cost, From a47af2c6f3bc6c1c126da241e7fbb39376256f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= Date: Mon, 1 Sep 2025 11:05:44 +0200 Subject: [PATCH 2/2] create byte count function --- src/ethereum/osaka/transactions.py | 30 ++++++++++++------------------ src/ethereum/utils/byte.py | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/ethereum/osaka/transactions.py b/src/ethereum/osaka/transactions.py index b320c22fb7..41bb05dfe1 100644 --- a/src/ethereum/osaka/transactions.py +++ b/src/ethereum/osaka/transactions.py @@ -18,6 +18,7 @@ InvalidSignatureError, NonceOverflowError, ) +from ethereum.utils.byte import count_bytes from .exceptions import ( InitCodeTooLargeError, @@ -596,26 +597,22 @@ def count_access_list_bytes(access_list: Tuple[Access, ...]) -> Tuple[int, int]: [EIP-7981]: https://eips.ethereum.org/EIPS/eip-7981 """ - zero_bytes = 0 - nonzero_bytes = 0 + total_zero_bytes = 0 + total_nonzero_bytes = 0 for access in access_list: # Count bytes in address (20 bytes) - for byte in access.account: - if byte == 0: - zero_bytes += 1 - else: - nonzero_bytes += 1 + zero_bytes, nonzero_bytes = count_bytes(access.account) + total_zero_bytes += zero_bytes + total_nonzero_bytes += nonzero_bytes # Count bytes in storage keys (32 bytes each) for slot in access.slots: - for byte in slot: - if byte == 0: - zero_bytes += 1 - else: - nonzero_bytes += 1 + zero_bytes, nonzero_bytes = count_bytes(slot) + total_zero_bytes += zero_bytes + total_nonzero_bytes += nonzero_bytes - return zero_bytes, nonzero_bytes + return total_zero_bytes, total_nonzero_bytes def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: @@ -649,12 +646,9 @@ def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]: from .vm.eoa_delegation import PER_EMPTY_ACCOUNT_COST from .vm.gas import init_code_cost - zero_bytes = 0 - for byte in tx.data: - if byte == 0: - zero_bytes += 1 + zero_bytes, nonzero_bytes = count_bytes(tx.data) - tokens_in_calldata = Uint(zero_bytes + (len(tx.data) - zero_bytes) * 4) + tokens_in_calldata = Uint(zero_bytes + nonzero_bytes * 4) # EIP-7623 floor price (note: no EVM costs) calldata_floor_gas_cost = ( tokens_in_calldata * FLOOR_CALLDATA_COST + TX_BASE_COST diff --git a/src/ethereum/utils/byte.py b/src/ethereum/utils/byte.py index 30039be686..c1e846866b 100644 --- a/src/ethereum/utils/byte.py +++ b/src/ethereum/utils/byte.py @@ -55,3 +55,29 @@ def right_pad_zero_bytes( right padded byte string of given `size`. """ return value.ljust(int(size), b"\x00") + + +def count_bytes(data: Bytes) -> tuple[int, int]: + """ + Count the number of zero and non-zero bytes in the given data. + + Parameters + ---------- + data : + The byte string to count bytes in. + + Returns + ------- + (zero_bytes, nonzero_bytes) : tuple[int, int] + A tuple containing the count of zero bytes and non-zero bytes. + """ + zero_bytes = 0 + nonzero_bytes = 0 + + for byte in data: + if byte == 0: + zero_bytes += 1 + else: + nonzero_bytes += 1 + + return zero_bytes, nonzero_bytes