diff --git a/src/ethereum/forks/amsterdam/fork.py b/src/ethereum/forks/amsterdam/fork.py index 5632b81759d..c77bf9a4a34 100644 --- a/src/ethereum/forks/amsterdam/fork.py +++ b/src/ethereum/forks/amsterdam/fork.py @@ -98,10 +98,13 @@ from .vm.gas import ( BLOB_SCHEDULE_MAX, GAS_PER_BLOB, + STATE_BYTES_PER_NEW_ACCOUNT, + STATE_BYTES_PER_STORAGE_SET, calculate_blob_gas_price, calculate_data_fee, calculate_excess_blob_gas, calculate_total_blob_gas, + state_gas_per_byte, ) from .vm.interpreter import MessageCallOutput, process_message_call @@ -485,6 +488,8 @@ def check_transaction( block_output: vm.BlockOutput, tx: Transaction, tx_state: TransactionState, + intrinsic_regular_gas: Uint, + intrinsic_state_gas: Uint, ) -> Tuple[Address, Uint, Tuple[VersionedHash, ...], U64]: """ Check if the transaction is includable in the block. @@ -499,6 +504,10 @@ def check_transaction( The transaction. tx_state : The transaction state tracker. + intrinsic_regular_gas : + The transaction's intrinsic regular gas. + intrinsic_state_gas : + The transaction's intrinsic state gas. Returns ------- @@ -546,16 +555,24 @@ def check_transaction( is empty. """ - # Regular gas is capped at TX_MAX_GAS_LIMIT per EIP-7825. - # State gas is not checked per-tx; block-end validation enforces - # max(block_regular_gas_used, block_state_gas_used) <= gas_limit. + # Both regular and state gas are validated before execution. regular_gas_available = ( block_env.block_gas_limit - block_output.block_gas_used ) + state_gas_available = ( + block_env.block_gas_limit - block_output.block_state_gas_used + ) blob_gas_available = MAX_BLOB_GAS_PER_BLOCK - block_output.blob_gas_used - if min(TX_MAX_GAS_LIMIT, tx.gas) > regular_gas_available: - raise GasUsedExceedsLimitError("regular gas used exceeds limit") + regular_gas_contribution = min( + TX_MAX_GAS_LIMIT, tx.gas - intrinsic_state_gas + ) + if regular_gas_contribution > regular_gas_available: + raise GasUsedExceedsLimitError("regular gas exceeds limit") + + state_gas_contribution = tx.gas - intrinsic_regular_gas + if state_gas_contribution > state_gas_available: + raise GasUsedExceedsLimitError("state gas exceeds limit") tx_blob_gas_used = calculate_total_blob_gas(tx) if tx_blob_gas_used > blob_gas_available: @@ -988,6 +1005,8 @@ def process_transaction( block_output=block_output, tx=tx, tx_state=tx_state, + intrinsic_regular_gas=intrinsic.regular, + intrinsic_state_gas=intrinsic.state, ) sender_account = get_account(tx_state, sender) @@ -1054,6 +1073,30 @@ def process_transaction( tx_output.state_gas_left += tx_output.state_gas_used tx_output.state_gas_used = Uint(0) + # Refund state gas for accounts created and destroyed in the + # same tx (EIP-6780). Covers account, storage, and code. + cost_per_state_byte = state_gas_per_byte(block_env.block_gas_limit) + for address in tx_output.accounts_to_delete: + if address in tx_state.created_accounts: + selfdestruct_refund = ( + STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte + ) + storage = tx_state.storage_writes.get(address, {}) + created_slots = sum(1 for v in storage.values() if v != 0) + selfdestruct_refund += ( + Uint(created_slots) + * STATE_BYTES_PER_STORAGE_SET + * cost_per_state_byte + ) + account = get_account(tx_state, address) + code = get_code(tx_state, account.code_hash) + selfdestruct_refund += Uint(len(code)) * cost_per_state_byte + selfdestruct_refund = min( + selfdestruct_refund, tx_output.state_gas_used + ) + tx_output.state_gas_left += selfdestruct_refund + tx_output.state_gas_used -= selfdestruct_refund + tx_gas_used_before_refund = ( tx.gas - tx_output.gas_left - tx_output.state_gas_left ) diff --git a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py index 56cd3ea55f5..7de070c149f 100644 --- a/src/ethereum/forks/amsterdam/vm/eoa_delegation.py +++ b/src/ethereum/forks/amsterdam/vm/eoa_delegation.py @@ -163,8 +163,8 @@ def set_delegation(message: Message) -> None: """ Set the delegation code for the authorities in the message. - For existing accounts, adjusts intrinsic_state_gas downward since - no account creation is needed (only delegation code write). + For existing accounts, refunds the account-creation component of + state gas to the reservoir (no mutation of intrinsic_state_gas). Parameters ---------- @@ -199,11 +199,10 @@ def set_delegation(message: Message) -> None: continue # For existing accounts, no account creation needed. - # Refund the account creation state gas to the reservoir - # and adjust intrinsic accounting to avoid double-counting. + # Refund the account creation state gas to the reservoir. + # intrinsic_state_gas is immutable after validation. if account_exists(tx_state, authority): refund = STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte - message.tx_env.intrinsic_state_gas -= refund message.state_gas_reservoir += refund if auth.address == NULL_ADDRESS: