Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 63 additions & 6 deletions src/ethereum/osaka/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
InvalidSignatureError,
NonceOverflowError,
)
from ethereum.utils.byte import count_bytes

from .exceptions import (
InitCodeTooLargeError,
Expand Down Expand Up @@ -63,6 +64,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)


Expand Down Expand Up @@ -568,6 +585,36 @@ 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
"""
total_zero_bytes = 0
total_nonzero_bytes = 0

for access in access_list:
# Count bytes in address (20 bytes)
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:
zero_bytes, nonzero_bytes = count_bytes(slot)
total_zero_bytes += zero_bytes
total_nonzero_bytes += nonzero_bytes

return total_zero_bytes, total_nonzero_bytes


def calculate_intrinsic_cost(tx: Transaction) -> Tuple[Uint, Uint]:
"""
Calculates the gas that is charged before execution is started.
Expand All @@ -586,7 +633,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
Expand All @@ -596,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
Expand All @@ -615,6 +662,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,
(
Expand All @@ -624,11 +672,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):
Expand All @@ -640,6 +696,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,
Expand Down
26 changes: 26 additions & 0 deletions src/ethereum/utils/byte.py
Original file line number Diff line number Diff line change
Expand Up @@ -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