From 304cf07c4a07154a56cc80eb3f0b048f4dc85787 Mon Sep 17 00:00:00 2001 From: fselmo Date: Fri, 22 May 2026 14:02:12 -0600 Subject: [PATCH 1/3] fix(specs): backport ``check_gas`` before state access boundaries from Amsterdam --- src/ethereum/forks/arrow_glacier/vm/gas.py | 17 ++ .../arrow_glacier/vm/instructions/storage.py | 13 +- .../arrow_glacier/vm/instructions/system.py | 133 ++++++--- src/ethereum/forks/berlin/vm/gas.py | 17 ++ .../forks/berlin/vm/instructions/storage.py | 13 +- .../forks/berlin/vm/instructions/system.py | 134 ++++++--- src/ethereum/forks/bpo1/vm/eoa_delegation.py | 30 +- src/ethereum/forks/bpo1/vm/gas.py | 17 ++ .../forks/bpo1/vm/instructions/storage.py | 18 +- .../forks/bpo1/vm/instructions/system.py | 255 +++++++++++------ src/ethereum/forks/bpo2/vm/eoa_delegation.py | 30 +- src/ethereum/forks/bpo2/vm/gas.py | 17 ++ .../forks/bpo2/vm/instructions/storage.py | 19 +- .../forks/bpo2/vm/instructions/system.py | 255 +++++++++++------ src/ethereum/forks/bpo3/vm/eoa_delegation.py | 30 +- src/ethereum/forks/bpo3/vm/gas.py | 17 ++ .../forks/bpo3/vm/instructions/storage.py | 19 +- .../forks/bpo3/vm/instructions/system.py | 255 +++++++++++------ src/ethereum/forks/bpo4/vm/eoa_delegation.py | 30 +- src/ethereum/forks/bpo4/vm/gas.py | 17 ++ .../forks/bpo4/vm/instructions/storage.py | 18 +- .../forks/bpo4/vm/instructions/system.py | 255 +++++++++++------ src/ethereum/forks/bpo5/vm/eoa_delegation.py | 30 +- src/ethereum/forks/bpo5/vm/gas.py | 17 ++ .../forks/bpo5/vm/instructions/storage.py | 18 +- .../forks/bpo5/vm/instructions/system.py | 255 +++++++++++------ src/ethereum/forks/byzantium/vm/gas.py | 17 ++ .../forks/byzantium/vm/instructions/system.py | 15 +- src/ethereum/forks/cancun/vm/gas.py | 17 ++ .../forks/cancun/vm/instructions/storage.py | 18 +- .../forks/cancun/vm/instructions/system.py | 159 +++++++---- src/ethereum/forks/constantinople/vm/gas.py | 17 ++ .../constantinople/vm/instructions/system.py | 15 +- src/ethereum/forks/dao_fork/vm/gas.py | 17 ++ src/ethereum/forks/frontier/vm/gas.py | 17 ++ src/ethereum/forks/gray_glacier/vm/gas.py | 17 ++ .../gray_glacier/vm/instructions/storage.py | 13 +- .../gray_glacier/vm/instructions/system.py | 133 ++++++--- src/ethereum/forks/homestead/vm/gas.py | 17 ++ src/ethereum/forks/istanbul/vm/gas.py | 17 ++ .../forks/istanbul/vm/instructions/storage.py | 11 +- .../forks/istanbul/vm/instructions/system.py | 15 +- src/ethereum/forks/london/vm/gas.py | 17 ++ .../forks/london/vm/instructions/storage.py | 13 +- .../forks/london/vm/instructions/system.py | 133 ++++++--- src/ethereum/forks/muir_glacier/vm/gas.py | 17 ++ .../muir_glacier/vm/instructions/storage.py | 11 +- .../muir_glacier/vm/instructions/system.py | 15 +- src/ethereum/forks/osaka/vm/eoa_delegation.py | 29 +- src/ethereum/forks/osaka/vm/gas.py | 17 ++ .../forks/osaka/vm/instructions/storage.py | 18 +- .../forks/osaka/vm/instructions/system.py | 253 +++++++++++------ src/ethereum/forks/paris/vm/gas.py | 17 ++ .../forks/paris/vm/instructions/storage.py | 13 +- .../forks/paris/vm/instructions/system.py | 131 ++++++--- .../forks/prague/vm/eoa_delegation.py | 30 +- src/ethereum/forks/prague/vm/gas.py | 17 ++ .../forks/prague/vm/instructions/storage.py | 18 +- .../forks/prague/vm/instructions/system.py | 257 ++++++++++++------ src/ethereum/forks/shanghai/vm/gas.py | 17 ++ .../forks/shanghai/vm/instructions/storage.py | 14 +- .../forks/shanghai/vm/instructions/system.py | 154 +++++++---- src/ethereum/forks/spurious_dragon/vm/gas.py | 17 ++ .../forks/tangerine_whistle/vm/gas.py | 17 ++ 64 files changed, 2510 insertions(+), 1159 deletions(-) diff --git a/src/ethereum/forks/arrow_glacier/vm/gas.py b/src/ethereum/forks/arrow_glacier/vm/gas.py index 2842b80b4d2..aac2f75f253 100644 --- a/src/ethereum/forks/arrow_glacier/vm/gas.py +++ b/src/ethereum/forks/arrow_glacier/vm/gas.py @@ -194,6 +194,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/arrow_glacier/vm/instructions/storage.py b/src/ethereum/forks/arrow_glacier/vm/instructions/storage.py index e9d86666668..dc37f9c2893 100644 --- a/src/ethereum/forks/arrow_glacier/vm/instructions/storage.py +++ b/src/ethereum/forks/arrow_glacier/vm/instructions/storage.py @@ -15,10 +15,11 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -64,11 +65,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -118,8 +123,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/arrow_glacier/vm/instructions/system.py b/src/ethereum/forks/arrow_glacier/vm/instructions/system.py index 8d8eeeb8f2c..034aefdf69b 100644 --- a/src/ethereum/forks/arrow_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/arrow_glacier/vm/instructions/system.py @@ -44,6 +44,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, max_message_call_gas, ) from ..memory import memory_read_bytes, memory_write @@ -64,14 +65,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -333,6 +335,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -342,32 +347,45 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): + if value == 0 or is_account_alive(tx_state, to): create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -422,13 +440,25 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + else: + access_gas_cost = GasCosts.WARM_ACCESS transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(code_address) + message_call_gas = calculate_message_call_gas( value, gas, @@ -440,9 +470,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -477,52 +505,55 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account( - evm.message.tx_env.state, beneficiary - ).balance - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance + beneficiary_balance = get_account(tx_state, beneficiary).balance + originator_balance = get_account(tx_state, originator).balance # First Transfer to beneficiary set_account_balance( - evm.message.tx_env.state, + tx_state, beneficiary, beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.tx_env.state, beneficiary): + if account_exists_and_is_empty(tx_state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution @@ -559,11 +590,18 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS message_call_gas = calculate_message_call_gas( U256(0), @@ -622,11 +660,18 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to diff --git a/src/ethereum/forks/berlin/vm/gas.py b/src/ethereum/forks/berlin/vm/gas.py index 672dc2fd026..ab8f2a69fc9 100644 --- a/src/ethereum/forks/berlin/vm/gas.py +++ b/src/ethereum/forks/berlin/vm/gas.py @@ -194,6 +194,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/berlin/vm/instructions/storage.py b/src/ethereum/forks/berlin/vm/instructions/storage.py index e9d86666668..dc37f9c2893 100644 --- a/src/ethereum/forks/berlin/vm/instructions/storage.py +++ b/src/ethereum/forks/berlin/vm/instructions/storage.py @@ -15,10 +15,11 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -64,11 +65,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -118,8 +123,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/berlin/vm/instructions/system.py b/src/ethereum/forks/berlin/vm/instructions/system.py index 42b44718be8..9a84994b577 100644 --- a/src/ethereum/forks/berlin/vm/instructions/system.py +++ b/src/ethereum/forks/berlin/vm/instructions/system.py @@ -44,6 +44,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, max_message_call_gas, ) from ..memory import memory_read_bytes, memory_write @@ -64,14 +65,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -333,6 +335,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -342,32 +347,45 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): + if value == 0 or is_account_alive(tx_state, to): create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -422,13 +440,25 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + else: + access_gas_cost = GasCosts.WARM_ACCESS transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(code_address) + message_call_gas = calculate_message_call_gas( value, gas, @@ -440,9 +470,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -477,21 +505,30 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT @@ -507,33 +544,26 @@ def selfdestruct(evm: Evm) -> None: evm.refund_counter += GasCosts.REFUND_SELF_DESTRUCT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext - originator = evm.message.current_target - beneficiary_balance = get_account( - evm.message.tx_env.state, beneficiary - ).balance - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance + beneficiary_balance = get_account(tx_state, beneficiary).balance + originator_balance = get_account(tx_state, originator).balance # First Transfer to beneficiary set_account_balance( - evm.message.tx_env.state, + tx_state, beneficiary, beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.tx_env.state, beneficiary): + if account_exists_and_is_empty(tx_state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution @@ -570,11 +600,18 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost @@ -629,11 +666,18 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to diff --git a/src/ethereum/forks/bpo1/vm/eoa_delegation.py b/src/ethereum/forks/bpo1/vm/eoa_delegation.py index 1ba552f8dd6..273f9a24136 100644 --- a/src/ethereum/forks/bpo1/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo1/vm/eoa_delegation.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover @@ -120,39 +119,40 @@ def recover_authority(authorization: Authorization) -> Address: return Address(keccak256(public_key)[12:32]) -def access_delegation( +def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Uint]: """ - Get the delegation address, code, and the cost of access from the address. + Get the delegation address and the cost of access from the address. Parameters ---------- evm : `Evm` The execution frame. address : `Address` - The address to get the delegation from. + The address to check for delegation. Returns ------- - delegation : `Tuple[bool, Address, Bytes, Uint]` - The delegation address, code, and access gas cost. + delegation : `Tuple[bool, Address, Uint]` + The delegation address and access gas cost. """ tx_state = evm.message.tx_env.state + code = get_code(tx_state, get_account(tx_state, address).code_hash) + if not is_valid_delegation(code): - return False, address, code, Uint(0) + return False, address, Uint(0) - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + + if delegated_address in evm.accessed_addresses: + delegation_gas_cost = GasCosts.WARM_ACCESS else: - evm.accessed_addresses.add(address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code = get_code(tx_state, get_account(tx_state, address).code_hash) + delegation_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - return True, address, code, access_gas_cost + return True, delegated_address, delegation_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/bpo1/vm/gas.py b/src/ethereum/forks/bpo1/vm/gas.py index 2cd628097bc..a75d4973723 100644 --- a/src/ethereum/forks/bpo1/vm/gas.py +++ b/src/ethereum/forks/bpo1/vm/gas.py @@ -222,6 +222,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/bpo1/vm/instructions/storage.py b/src/ethereum/forks/bpo1/vm/instructions/storage.py index 99dcce877dd..56e1ed28d06 100644 --- a/src/ethereum/forks/bpo1/vm/instructions/storage.py +++ b/src/ethereum/forks/bpo1/vm/instructions/storage.py @@ -21,10 +21,11 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -70,11 +71,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -124,8 +129,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -169,14 +172,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/bpo1/vm/instructions/system.py b/src/ethereum/forks/bpo1/vm/instructions/system.py index d71c776bf3d..a9724430c06 100644 --- a/src/ethereum/forks/bpo1/vm/instructions/system.py +++ b/src/ethereum/forks/bpo1/vm/instructions/system.py @@ -21,6 +21,7 @@ account_has_code_or_nonce, account_has_storage, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -31,7 +32,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import calculate_delegation_cost from .. import ( Evm, Message, @@ -44,6 +45,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +71,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +104,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -357,6 +365,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -366,39 +377,57 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + create_gas_cost = GasCosts.NEW_ACCOUNT + if value == 0 or is_account_alive(tx_state, to): + create_gas_cost = Uint(0) + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): - create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -418,7 +447,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -455,35 +484,54 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost + transfer_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance + if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -503,7 +551,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -520,46 +568,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -596,22 +644,42 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( - U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -631,7 +699,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -665,27 +733,42 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -705,7 +788,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo2/vm/eoa_delegation.py b/src/ethereum/forks/bpo2/vm/eoa_delegation.py index 1ba552f8dd6..273f9a24136 100644 --- a/src/ethereum/forks/bpo2/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo2/vm/eoa_delegation.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover @@ -120,39 +119,40 @@ def recover_authority(authorization: Authorization) -> Address: return Address(keccak256(public_key)[12:32]) -def access_delegation( +def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Uint]: """ - Get the delegation address, code, and the cost of access from the address. + Get the delegation address and the cost of access from the address. Parameters ---------- evm : `Evm` The execution frame. address : `Address` - The address to get the delegation from. + The address to check for delegation. Returns ------- - delegation : `Tuple[bool, Address, Bytes, Uint]` - The delegation address, code, and access gas cost. + delegation : `Tuple[bool, Address, Uint]` + The delegation address and access gas cost. """ tx_state = evm.message.tx_env.state + code = get_code(tx_state, get_account(tx_state, address).code_hash) + if not is_valid_delegation(code): - return False, address, code, Uint(0) + return False, address, Uint(0) - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + + if delegated_address in evm.accessed_addresses: + delegation_gas_cost = GasCosts.WARM_ACCESS else: - evm.accessed_addresses.add(address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code = get_code(tx_state, get_account(tx_state, address).code_hash) + delegation_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - return True, address, code, access_gas_cost + return True, delegated_address, delegation_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/bpo2/vm/gas.py b/src/ethereum/forks/bpo2/vm/gas.py index f2488b8e20a..40af560dc62 100644 --- a/src/ethereum/forks/bpo2/vm/gas.py +++ b/src/ethereum/forks/bpo2/vm/gas.py @@ -222,6 +222,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/bpo2/vm/instructions/storage.py b/src/ethereum/forks/bpo2/vm/instructions/storage.py index 437af01941e..60394a6e143 100644 --- a/src/ethereum/forks/bpo2/vm/instructions/storage.py +++ b/src/ethereum/forks/bpo2/vm/instructions/storage.py @@ -21,8 +21,8 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext -from ..gas import GasCosts, charge_gas +from ..exceptions import WriteInStaticContext +from ..gas import GasCosts, charge_gas, check_gas from ..stack import pop, push @@ -67,11 +67,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -121,8 +125,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -166,14 +168,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/bpo2/vm/instructions/system.py b/src/ethereum/forks/bpo2/vm/instructions/system.py index 713a04af13c..bbf614a7b24 100644 --- a/src/ethereum/forks/bpo2/vm/instructions/system.py +++ b/src/ethereum/forks/bpo2/vm/instructions/system.py @@ -21,6 +21,7 @@ account_has_code_or_nonce, account_has_storage, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -31,7 +32,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import calculate_delegation_cost from .. import ( Evm, Message, @@ -44,6 +45,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +71,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +104,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -356,6 +364,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -365,39 +376,57 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + create_gas_cost = GasCosts.NEW_ACCOUNT + if value == 0 or is_account_alive(tx_state, to): + create_gas_cost = Uint(0) + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): - create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -417,7 +446,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -454,35 +483,54 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost + transfer_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance + if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -502,7 +550,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -519,46 +567,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -595,22 +643,42 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( - U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -630,7 +698,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -664,27 +732,42 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -704,7 +787,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo3/vm/eoa_delegation.py b/src/ethereum/forks/bpo3/vm/eoa_delegation.py index 1ba552f8dd6..273f9a24136 100644 --- a/src/ethereum/forks/bpo3/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo3/vm/eoa_delegation.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover @@ -120,39 +119,40 @@ def recover_authority(authorization: Authorization) -> Address: return Address(keccak256(public_key)[12:32]) -def access_delegation( +def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Uint]: """ - Get the delegation address, code, and the cost of access from the address. + Get the delegation address and the cost of access from the address. Parameters ---------- evm : `Evm` The execution frame. address : `Address` - The address to get the delegation from. + The address to check for delegation. Returns ------- - delegation : `Tuple[bool, Address, Bytes, Uint]` - The delegation address, code, and access gas cost. + delegation : `Tuple[bool, Address, Uint]` + The delegation address and access gas cost. """ tx_state = evm.message.tx_env.state + code = get_code(tx_state, get_account(tx_state, address).code_hash) + if not is_valid_delegation(code): - return False, address, code, Uint(0) + return False, address, Uint(0) - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + + if delegated_address in evm.accessed_addresses: + delegation_gas_cost = GasCosts.WARM_ACCESS else: - evm.accessed_addresses.add(address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code = get_code(tx_state, get_account(tx_state, address).code_hash) + delegation_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - return True, address, code, access_gas_cost + return True, delegated_address, delegation_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/bpo3/vm/gas.py b/src/ethereum/forks/bpo3/vm/gas.py index f2488b8e20a..40af560dc62 100644 --- a/src/ethereum/forks/bpo3/vm/gas.py +++ b/src/ethereum/forks/bpo3/vm/gas.py @@ -222,6 +222,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/bpo3/vm/instructions/storage.py b/src/ethereum/forks/bpo3/vm/instructions/storage.py index 437af01941e..60394a6e143 100644 --- a/src/ethereum/forks/bpo3/vm/instructions/storage.py +++ b/src/ethereum/forks/bpo3/vm/instructions/storage.py @@ -21,8 +21,8 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext -from ..gas import GasCosts, charge_gas +from ..exceptions import WriteInStaticContext +from ..gas import GasCosts, charge_gas, check_gas from ..stack import pop, push @@ -67,11 +67,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -121,8 +125,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -166,14 +168,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/bpo3/vm/instructions/system.py b/src/ethereum/forks/bpo3/vm/instructions/system.py index 713a04af13c..bbf614a7b24 100644 --- a/src/ethereum/forks/bpo3/vm/instructions/system.py +++ b/src/ethereum/forks/bpo3/vm/instructions/system.py @@ -21,6 +21,7 @@ account_has_code_or_nonce, account_has_storage, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -31,7 +32,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import calculate_delegation_cost from .. import ( Evm, Message, @@ -44,6 +45,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +71,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +104,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -356,6 +364,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -365,39 +376,57 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + create_gas_cost = GasCosts.NEW_ACCOUNT + if value == 0 or is_account_alive(tx_state, to): + create_gas_cost = Uint(0) + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): - create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -417,7 +446,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -454,35 +483,54 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost + transfer_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance + if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -502,7 +550,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -519,46 +567,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -595,22 +643,42 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( - U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -630,7 +698,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -664,27 +732,42 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -704,7 +787,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo4/vm/eoa_delegation.py b/src/ethereum/forks/bpo4/vm/eoa_delegation.py index 1ba552f8dd6..273f9a24136 100644 --- a/src/ethereum/forks/bpo4/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo4/vm/eoa_delegation.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover @@ -120,39 +119,40 @@ def recover_authority(authorization: Authorization) -> Address: return Address(keccak256(public_key)[12:32]) -def access_delegation( +def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Uint]: """ - Get the delegation address, code, and the cost of access from the address. + Get the delegation address and the cost of access from the address. Parameters ---------- evm : `Evm` The execution frame. address : `Address` - The address to get the delegation from. + The address to check for delegation. Returns ------- - delegation : `Tuple[bool, Address, Bytes, Uint]` - The delegation address, code, and access gas cost. + delegation : `Tuple[bool, Address, Uint]` + The delegation address and access gas cost. """ tx_state = evm.message.tx_env.state + code = get_code(tx_state, get_account(tx_state, address).code_hash) + if not is_valid_delegation(code): - return False, address, code, Uint(0) + return False, address, Uint(0) - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + + if delegated_address in evm.accessed_addresses: + delegation_gas_cost = GasCosts.WARM_ACCESS else: - evm.accessed_addresses.add(address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code = get_code(tx_state, get_account(tx_state, address).code_hash) + delegation_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - return True, address, code, access_gas_cost + return True, delegated_address, delegation_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/bpo4/vm/gas.py b/src/ethereum/forks/bpo4/vm/gas.py index f2488b8e20a..40af560dc62 100644 --- a/src/ethereum/forks/bpo4/vm/gas.py +++ b/src/ethereum/forks/bpo4/vm/gas.py @@ -222,6 +222,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/bpo4/vm/instructions/storage.py b/src/ethereum/forks/bpo4/vm/instructions/storage.py index 99dcce877dd..56e1ed28d06 100644 --- a/src/ethereum/forks/bpo4/vm/instructions/storage.py +++ b/src/ethereum/forks/bpo4/vm/instructions/storage.py @@ -21,10 +21,11 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -70,11 +71,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -124,8 +129,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -169,14 +172,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/bpo4/vm/instructions/system.py b/src/ethereum/forks/bpo4/vm/instructions/system.py index d71c776bf3d..a9724430c06 100644 --- a/src/ethereum/forks/bpo4/vm/instructions/system.py +++ b/src/ethereum/forks/bpo4/vm/instructions/system.py @@ -21,6 +21,7 @@ account_has_code_or_nonce, account_has_storage, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -31,7 +32,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import calculate_delegation_cost from .. import ( Evm, Message, @@ -44,6 +45,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +71,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +104,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -357,6 +365,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -366,39 +377,57 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + create_gas_cost = GasCosts.NEW_ACCOUNT + if value == 0 or is_account_alive(tx_state, to): + create_gas_cost = Uint(0) + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): - create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -418,7 +447,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -455,35 +484,54 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost + transfer_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance + if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -503,7 +551,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -520,46 +568,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -596,22 +644,42 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( - U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -631,7 +699,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -665,27 +733,42 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -705,7 +788,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/bpo5/vm/eoa_delegation.py b/src/ethereum/forks/bpo5/vm/eoa_delegation.py index 1ba552f8dd6..273f9a24136 100644 --- a/src/ethereum/forks/bpo5/vm/eoa_delegation.py +++ b/src/ethereum/forks/bpo5/vm/eoa_delegation.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover @@ -120,39 +119,40 @@ def recover_authority(authorization: Authorization) -> Address: return Address(keccak256(public_key)[12:32]) -def access_delegation( +def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Uint]: """ - Get the delegation address, code, and the cost of access from the address. + Get the delegation address and the cost of access from the address. Parameters ---------- evm : `Evm` The execution frame. address : `Address` - The address to get the delegation from. + The address to check for delegation. Returns ------- - delegation : `Tuple[bool, Address, Bytes, Uint]` - The delegation address, code, and access gas cost. + delegation : `Tuple[bool, Address, Uint]` + The delegation address and access gas cost. """ tx_state = evm.message.tx_env.state + code = get_code(tx_state, get_account(tx_state, address).code_hash) + if not is_valid_delegation(code): - return False, address, code, Uint(0) + return False, address, Uint(0) - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + + if delegated_address in evm.accessed_addresses: + delegation_gas_cost = GasCosts.WARM_ACCESS else: - evm.accessed_addresses.add(address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code = get_code(tx_state, get_account(tx_state, address).code_hash) + delegation_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - return True, address, code, access_gas_cost + return True, delegated_address, delegation_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/bpo5/vm/gas.py b/src/ethereum/forks/bpo5/vm/gas.py index f2488b8e20a..40af560dc62 100644 --- a/src/ethereum/forks/bpo5/vm/gas.py +++ b/src/ethereum/forks/bpo5/vm/gas.py @@ -222,6 +222,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/bpo5/vm/instructions/storage.py b/src/ethereum/forks/bpo5/vm/instructions/storage.py index 99dcce877dd..56e1ed28d06 100644 --- a/src/ethereum/forks/bpo5/vm/instructions/storage.py +++ b/src/ethereum/forks/bpo5/vm/instructions/storage.py @@ -21,10 +21,11 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -70,11 +71,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -124,8 +129,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -169,14 +172,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/bpo5/vm/instructions/system.py b/src/ethereum/forks/bpo5/vm/instructions/system.py index d71c776bf3d..a9724430c06 100644 --- a/src/ethereum/forks/bpo5/vm/instructions/system.py +++ b/src/ethereum/forks/bpo5/vm/instructions/system.py @@ -21,6 +21,7 @@ account_has_code_or_nonce, account_has_storage, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -31,7 +32,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import calculate_delegation_cost from .. import ( Evm, Message, @@ -44,6 +45,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +71,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +104,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -357,6 +365,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -366,39 +377,57 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + create_gas_cost = GasCosts.NEW_ACCOUNT + if value == 0 or is_account_alive(tx_state, to): + create_gas_cost = Uint(0) + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): - create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -418,7 +447,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -455,35 +484,54 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost + transfer_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance + if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -503,7 +551,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -520,46 +568,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -596,22 +644,42 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( - U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -631,7 +699,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -665,27 +733,42 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -705,7 +788,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/byzantium/vm/gas.py b/src/ethereum/forks/byzantium/vm/gas.py index 0395403db01..aa31644e161 100644 --- a/src/ethereum/forks/byzantium/vm/gas.py +++ b/src/ethereum/forks/byzantium/vm/gas.py @@ -187,6 +187,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/byzantium/vm/instructions/system.py b/src/ethereum/forks/byzantium/vm/instructions/system.py index 1f4dc02194b..d4598196115 100644 --- a/src/ethereum/forks/byzantium/vm/instructions/system.py +++ b/src/ethereum/forks/byzantium/vm/instructions/system.py @@ -59,6 +59,9 @@ def create(evm: Evm) -> None: # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + # STACK endowment = pop(evm.stack) memory_start_position = pop(evm.stack) @@ -73,8 +76,6 @@ def create(evm: Evm) -> None: create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by evm.return_data = b"" @@ -258,6 +259,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -281,8 +285,6 @@ def call(evm: Evm) -> None: GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( evm.message.tx_env.state, evm.message.current_target @@ -389,6 +391,9 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) @@ -415,8 +420,6 @@ def selfdestruct(evm: Evm) -> None: evm.refund_counter += GasCosts.REFUND_SELF_DESTRUCT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext beneficiary_balance = get_account( evm.message.tx_env.state, beneficiary diff --git a/src/ethereum/forks/cancun/vm/gas.py b/src/ethereum/forks/cancun/vm/gas.py index 89ad479051f..202345a468a 100644 --- a/src/ethereum/forks/cancun/vm/gas.py +++ b/src/ethereum/forks/cancun/vm/gas.py @@ -208,6 +208,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/cancun/vm/instructions/storage.py b/src/ethereum/forks/cancun/vm/instructions/storage.py index 99dcce877dd..56e1ed28d06 100644 --- a/src/ethereum/forks/cancun/vm/instructions/storage.py +++ b/src/ethereum/forks/cancun/vm/instructions/storage.py @@ -21,10 +21,11 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -70,11 +71,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -124,8 +129,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -169,14 +172,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/cancun/vm/instructions/system.py b/src/ethereum/forks/cancun/vm/instructions/system.py index 5b880be7285..68de94ba6e2 100644 --- a/src/ethereum/forks/cancun/vm/instructions/system.py +++ b/src/ethereum/forks/cancun/vm/instructions/system.py @@ -44,6 +44,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +70,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +103,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -353,6 +360,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -362,32 +372,45 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): + if value == 0 or is_account_alive(tx_state, to): create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -442,27 +465,39 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + else: + access_gas_cost = GasCosts.WARM_ACCESS transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(code_address) + + extra_gas = access_gas_cost + transfer_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -497,46 +532,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -573,11 +608,18 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost @@ -632,11 +674,18 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to diff --git a/src/ethereum/forks/constantinople/vm/gas.py b/src/ethereum/forks/constantinople/vm/gas.py index 770c5212461..078ff62c14c 100644 --- a/src/ethereum/forks/constantinople/vm/gas.py +++ b/src/ethereum/forks/constantinople/vm/gas.py @@ -191,6 +191,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/constantinople/vm/instructions/system.py b/src/ethereum/forks/constantinople/vm/instructions/system.py index d9faf756729..3d4ee4a8aa0 100644 --- a/src/ethereum/forks/constantinople/vm/instructions/system.py +++ b/src/ethereum/forks/constantinople/vm/instructions/system.py @@ -64,14 +64,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -331,6 +332,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -354,8 +358,6 @@ def call(evm: Evm) -> None: GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( evm.message.tx_env.state, evm.message.current_target @@ -462,6 +464,9 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) @@ -488,8 +493,6 @@ def selfdestruct(evm: Evm) -> None: evm.refund_counter += GasCosts.REFUND_SELF_DESTRUCT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target beneficiary_balance = get_account( diff --git a/src/ethereum/forks/dao_fork/vm/gas.py b/src/ethereum/forks/dao_fork/vm/gas.py index cb663624a36..79954f90f10 100644 --- a/src/ethereum/forks/dao_fork/vm/gas.py +++ b/src/ethereum/forks/dao_fork/vm/gas.py @@ -180,6 +180,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/frontier/vm/gas.py b/src/ethereum/forks/frontier/vm/gas.py index 1974386b1c3..d194a10f07a 100644 --- a/src/ethereum/forks/frontier/vm/gas.py +++ b/src/ethereum/forks/frontier/vm/gas.py @@ -178,6 +178,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/gray_glacier/vm/gas.py b/src/ethereum/forks/gray_glacier/vm/gas.py index 2842b80b4d2..aac2f75f253 100644 --- a/src/ethereum/forks/gray_glacier/vm/gas.py +++ b/src/ethereum/forks/gray_glacier/vm/gas.py @@ -194,6 +194,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/gray_glacier/vm/instructions/storage.py b/src/ethereum/forks/gray_glacier/vm/instructions/storage.py index e9d86666668..dc37f9c2893 100644 --- a/src/ethereum/forks/gray_glacier/vm/instructions/storage.py +++ b/src/ethereum/forks/gray_glacier/vm/instructions/storage.py @@ -15,10 +15,11 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -64,11 +65,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -118,8 +123,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/gray_glacier/vm/instructions/system.py b/src/ethereum/forks/gray_glacier/vm/instructions/system.py index a4c1044a687..08609c63111 100644 --- a/src/ethereum/forks/gray_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/gray_glacier/vm/instructions/system.py @@ -44,6 +44,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, max_message_call_gas, ) from ..memory import memory_read_bytes, memory_write @@ -64,14 +65,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -333,6 +335,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -342,32 +347,45 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): + if value == 0 or is_account_alive(tx_state, to): create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -422,13 +440,25 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + else: + access_gas_cost = GasCosts.WARM_ACCESS transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(code_address) + message_call_gas = calculate_message_call_gas( value, gas, @@ -440,9 +470,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -477,52 +505,55 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account( - evm.message.tx_env.state, beneficiary - ).balance - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance + beneficiary_balance = get_account(tx_state, beneficiary).balance + originator_balance = get_account(tx_state, originator).balance # First Transfer to beneficiary set_account_balance( - evm.message.tx_env.state, + tx_state, beneficiary, beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.tx_env.state, beneficiary): + if account_exists_and_is_empty(tx_state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution @@ -559,11 +590,18 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost @@ -618,11 +656,18 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to diff --git a/src/ethereum/forks/homestead/vm/gas.py b/src/ethereum/forks/homestead/vm/gas.py index cb663624a36..79954f90f10 100644 --- a/src/ethereum/forks/homestead/vm/gas.py +++ b/src/ethereum/forks/homestead/vm/gas.py @@ -180,6 +180,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/istanbul/vm/gas.py b/src/ethereum/forks/istanbul/vm/gas.py index 25ff304ae00..c7ca14c2455 100644 --- a/src/ethereum/forks/istanbul/vm/gas.py +++ b/src/ethereum/forks/istanbul/vm/gas.py @@ -194,6 +194,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/istanbul/vm/instructions/storage.py b/src/ethereum/forks/istanbul/vm/instructions/storage.py index 07ba90a057c..82e4bf9ba46 100644 --- a/src/ethereum/forks/istanbul/vm/instructions/storage.py +++ b/src/ethereum/forks/istanbul/vm/instructions/storage.py @@ -15,10 +15,11 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -60,11 +61,13 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -104,8 +107,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/istanbul/vm/instructions/system.py b/src/ethereum/forks/istanbul/vm/instructions/system.py index cca5876670a..9b401516503 100644 --- a/src/ethereum/forks/istanbul/vm/instructions/system.py +++ b/src/ethereum/forks/istanbul/vm/instructions/system.py @@ -64,14 +64,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -331,6 +332,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -354,8 +358,6 @@ def call(evm: Evm) -> None: GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( evm.message.tx_env.state, evm.message.current_target @@ -462,6 +464,9 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) @@ -488,8 +493,6 @@ def selfdestruct(evm: Evm) -> None: evm.refund_counter += GasCosts.REFUND_SELF_DESTRUCT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target beneficiary_balance = get_account( diff --git a/src/ethereum/forks/london/vm/gas.py b/src/ethereum/forks/london/vm/gas.py index 2842b80b4d2..aac2f75f253 100644 --- a/src/ethereum/forks/london/vm/gas.py +++ b/src/ethereum/forks/london/vm/gas.py @@ -194,6 +194,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/london/vm/instructions/storage.py b/src/ethereum/forks/london/vm/instructions/storage.py index e9d86666668..dc37f9c2893 100644 --- a/src/ethereum/forks/london/vm/instructions/storage.py +++ b/src/ethereum/forks/london/vm/instructions/storage.py @@ -15,10 +15,11 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -64,11 +65,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -118,8 +123,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/london/vm/instructions/system.py b/src/ethereum/forks/london/vm/instructions/system.py index a4c1044a687..08609c63111 100644 --- a/src/ethereum/forks/london/vm/instructions/system.py +++ b/src/ethereum/forks/london/vm/instructions/system.py @@ -44,6 +44,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, max_message_call_gas, ) from ..memory import memory_read_bytes, memory_write @@ -64,14 +65,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -333,6 +335,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -342,32 +347,45 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): + if value == 0 or is_account_alive(tx_state, to): create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -422,13 +440,25 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + else: + access_gas_cost = GasCosts.WARM_ACCESS transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(code_address) + message_call_gas = calculate_message_call_gas( value, gas, @@ -440,9 +470,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -477,52 +505,55 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account( - evm.message.tx_env.state, beneficiary - ).balance - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance + beneficiary_balance = get_account(tx_state, beneficiary).balance + originator_balance = get_account(tx_state, originator).balance # First Transfer to beneficiary set_account_balance( - evm.message.tx_env.state, + tx_state, beneficiary, beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) # mark beneficiary as touched - if account_exists_and_is_empty(evm.message.tx_env.state, beneficiary): + if account_exists_and_is_empty(tx_state, beneficiary): evm.touched_accounts.add(beneficiary) # HALT the execution @@ -559,11 +590,18 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost @@ -618,11 +656,18 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to diff --git a/src/ethereum/forks/muir_glacier/vm/gas.py b/src/ethereum/forks/muir_glacier/vm/gas.py index 25ff304ae00..c7ca14c2455 100644 --- a/src/ethereum/forks/muir_glacier/vm/gas.py +++ b/src/ethereum/forks/muir_glacier/vm/gas.py @@ -194,6 +194,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/muir_glacier/vm/instructions/storage.py b/src/ethereum/forks/muir_glacier/vm/instructions/storage.py index 07ba90a057c..82e4bf9ba46 100644 --- a/src/ethereum/forks/muir_glacier/vm/instructions/storage.py +++ b/src/ethereum/forks/muir_glacier/vm/instructions/storage.py @@ -15,10 +15,11 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -60,11 +61,13 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -104,8 +107,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/muir_glacier/vm/instructions/system.py b/src/ethereum/forks/muir_glacier/vm/instructions/system.py index cca5876670a..9b401516503 100644 --- a/src/ethereum/forks/muir_glacier/vm/instructions/system.py +++ b/src/ethereum/forks/muir_glacier/vm/instructions/system.py @@ -64,14 +64,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -331,6 +332,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -354,8 +358,6 @@ def call(evm: Evm) -> None: GasCosts.OPCODE_CALL_BASE + create_gas_cost + transfer_gas_cost, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( evm.message.tx_env.state, evm.message.current_target @@ -462,6 +464,9 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) @@ -488,8 +493,6 @@ def selfdestruct(evm: Evm) -> None: evm.refund_counter += GasCosts.REFUND_SELF_DESTRUCT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target beneficiary_balance = get_account( diff --git a/src/ethereum/forks/osaka/vm/eoa_delegation.py b/src/ethereum/forks/osaka/vm/eoa_delegation.py index 62193fe0819..273f9a24136 100644 --- a/src/ethereum/forks/osaka/vm/eoa_delegation.py +++ b/src/ethereum/forks/osaka/vm/eoa_delegation.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover @@ -120,40 +119,40 @@ def recover_authority(authorization: Authorization) -> Address: return Address(keccak256(public_key)[12:32]) -def access_delegation( +def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Uint]: """ - Get the delegation address, code, and the cost of access from the address. + Get the delegation address and the cost of access from the address. Parameters ---------- evm : `Evm` The execution frame. address : `Address` - The address to get the delegation from. + The address to check for delegation. Returns ------- - delegation : `Tuple[bool, Address, Bytes, Uint]` - The delegation address, code, and access gas cost. + delegation : `Tuple[bool, Address, Uint]` + The delegation address and access gas cost. """ tx_state = evm.message.tx_env.state code = get_code(tx_state, get_account(tx_state, address).code_hash) + if not is_valid_delegation(code): - return False, address, code, Uint(0) + return False, address, Uint(0) + + delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + if delegated_address in evm.accessed_addresses: + delegation_gas_cost = GasCosts.WARM_ACCESS else: - evm.accessed_addresses.add(address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code = get_code(tx_state, get_account(tx_state, address).code_hash) + delegation_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - return True, address, code, access_gas_cost + return True, delegated_address, delegation_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/osaka/vm/gas.py b/src/ethereum/forks/osaka/vm/gas.py index 1d49b46098c..13ba8b671d5 100644 --- a/src/ethereum/forks/osaka/vm/gas.py +++ b/src/ethereum/forks/osaka/vm/gas.py @@ -222,6 +222,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/osaka/vm/instructions/storage.py b/src/ethereum/forks/osaka/vm/instructions/storage.py index 63fda6bc3f3..f3e279c3895 100644 --- a/src/ethereum/forks/osaka/vm/instructions/storage.py +++ b/src/ethereum/forks/osaka/vm/instructions/storage.py @@ -21,10 +21,11 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -70,11 +71,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -119,8 +124,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -164,14 +167,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/osaka/vm/instructions/system.py b/src/ethereum/forks/osaka/vm/instructions/system.py index b431ce264ef..a9724430c06 100644 --- a/src/ethereum/forks/osaka/vm/instructions/system.py +++ b/src/ethereum/forks/osaka/vm/instructions/system.py @@ -21,6 +21,7 @@ account_has_code_or_nonce, account_has_storage, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -31,7 +32,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import calculate_delegation_cost from .. import ( Evm, Message, @@ -44,6 +45,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +71,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +104,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -357,6 +365,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -366,39 +377,57 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + create_gas_cost = GasCosts.NEW_ACCOUNT + if value == 0 or is_account_alive(tx_state, to): + create_gas_cost = Uint(0) + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): - create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -418,7 +447,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -455,36 +484,54 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost + transfer_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, - evm.message.current_target, - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance + if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -504,7 +551,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -521,47 +568,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, - evm.message.current_target, - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -598,26 +644,42 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -637,7 +699,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -671,27 +733,42 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -711,7 +788,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/paris/vm/gas.py b/src/ethereum/forks/paris/vm/gas.py index b4b9ab1e402..88a9b20dbc6 100644 --- a/src/ethereum/forks/paris/vm/gas.py +++ b/src/ethereum/forks/paris/vm/gas.py @@ -194,6 +194,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/paris/vm/instructions/storage.py b/src/ethereum/forks/paris/vm/instructions/storage.py index e9d86666668..dc37f9c2893 100644 --- a/src/ethereum/forks/paris/vm/instructions/storage.py +++ b/src/ethereum/forks/paris/vm/instructions/storage.py @@ -15,10 +15,11 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -64,11 +65,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -118,8 +123,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/paris/vm/instructions/system.py b/src/ethereum/forks/paris/vm/instructions/system.py index 50eb3136a1d..b0caecb5dc8 100644 --- a/src/ethereum/forks/paris/vm/instructions/system.py +++ b/src/ethereum/forks/paris/vm/instructions/system.py @@ -43,6 +43,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, max_message_call_gas, ) from ..memory import memory_read_bytes, memory_write @@ -63,14 +64,15 @@ def generic_create( # if it's not moved inside this method from ...vm.interpreter import STACK_DEPTH_LIMIT, process_create_message + if evm.message.is_static: + raise WriteInStaticContext + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target @@ -332,6 +334,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -341,32 +346,45 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): + if value == 0 or is_account_alive(tx_state, to): create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -421,13 +439,25 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + else: + access_gas_cost = GasCosts.WARM_ACCESS transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(code_address) + message_call_gas = calculate_message_call_gas( value, gas, @@ -439,9 +469,7 @@ def callcode(evm: Evm) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -476,46 +504,49 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account( - evm.message.tx_env.state, beneficiary - ).balance - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance + beneficiary_balance = get_account(tx_state, beneficiary).balance + originator_balance = get_account(tx_state, originator).balance # First Transfer to beneficiary set_account_balance( - evm.message.tx_env.state, + tx_state, beneficiary, beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) @@ -554,11 +585,18 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost @@ -613,11 +651,18 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to diff --git a/src/ethereum/forks/prague/vm/eoa_delegation.py b/src/ethereum/forks/prague/vm/eoa_delegation.py index 1ba552f8dd6..273f9a24136 100644 --- a/src/ethereum/forks/prague/vm/eoa_delegation.py +++ b/src/ethereum/forks/prague/vm/eoa_delegation.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple from ethereum_rlp import rlp -from ethereum_types.bytes import Bytes from ethereum_types.numeric import U64, U256, Uint from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover @@ -120,39 +119,40 @@ def recover_authority(authorization: Authorization) -> Address: return Address(keccak256(public_key)[12:32]) -def access_delegation( +def calculate_delegation_cost( evm: Evm, address: Address -) -> Tuple[bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Uint]: """ - Get the delegation address, code, and the cost of access from the address. + Get the delegation address and the cost of access from the address. Parameters ---------- evm : `Evm` The execution frame. address : `Address` - The address to get the delegation from. + The address to check for delegation. Returns ------- - delegation : `Tuple[bool, Address, Bytes, Uint]` - The delegation address, code, and access gas cost. + delegation : `Tuple[bool, Address, Uint]` + The delegation address and access gas cost. """ tx_state = evm.message.tx_env.state + code = get_code(tx_state, get_account(tx_state, address).code_hash) + if not is_valid_delegation(code): - return False, address, code, Uint(0) + return False, address, Uint(0) - address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) - if address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + delegated_address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) + + if delegated_address in evm.accessed_addresses: + delegation_gas_cost = GasCosts.WARM_ACCESS else: - evm.accessed_addresses.add(address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code = get_code(tx_state, get_account(tx_state, address).code_hash) + delegation_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - return True, address, code, access_gas_cost + return True, delegated_address, delegation_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/prague/vm/gas.py b/src/ethereum/forks/prague/vm/gas.py index e6cc23df1b8..6cf95bd65e1 100644 --- a/src/ethereum/forks/prague/vm/gas.py +++ b/src/ethereum/forks/prague/vm/gas.py @@ -217,6 +217,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/prague/vm/instructions/storage.py b/src/ethereum/forks/prague/vm/instructions/storage.py index 99dcce877dd..56e1ed28d06 100644 --- a/src/ethereum/forks/prague/vm/instructions/storage.py +++ b/src/ethereum/forks/prague/vm/instructions/storage.py @@ -21,10 +21,11 @@ set_transient_storage, ) from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext +from ..exceptions import WriteInStaticContext from ..gas import ( GasCosts, charge_gas, + check_gas, ) from ..stack import pop, push @@ -70,11 +71,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -124,8 +129,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER @@ -169,14 +172,15 @@ def tstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) # GAS charge_gas(evm, GasCosts.WARM_ACCESS) - if evm.message.is_static: - raise WriteInStaticContext set_transient_storage( evm.message.tx_env.state, evm.message.current_target, diff --git a/src/ethereum/forks/prague/vm/instructions/system.py b/src/ethereum/forks/prague/vm/instructions/system.py index 713a04af13c..9b25e14ad82 100644 --- a/src/ethereum/forks/prague/vm/instructions/system.py +++ b/src/ethereum/forks/prague/vm/instructions/system.py @@ -21,6 +21,7 @@ account_has_code_or_nonce, account_has_storage, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -31,7 +32,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import calculate_delegation_cost from .. import ( Evm, Message, @@ -44,6 +45,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -69,20 +71,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -96,13 +104,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -356,6 +364,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -365,39 +376,57 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + create_gas_cost = GasCosts.NEW_ACCOUNT + if value == 0 or is_account_alive(tx_state, to): + create_gas_cost = Uint(0) + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): - create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -417,7 +446,7 @@ def call(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -426,7 +455,7 @@ def call(evm: Evm) -> None: def callcode(evm: Evm) -> None: """ - Message-call into this account with alternative account’s code. + Message-call into this account with alternative account's code. Parameters ---------- @@ -454,35 +483,54 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost + transfer_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance + if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -502,7 +550,7 @@ def callcode(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -519,46 +567,46 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance - - move_ether( - evm.message.tx_env.state, - originator, - beneficiary, - originator_balance, - ) + originator_balance = get_account(tx_state, originator).balance + + move_ether(tx_state, originator, beneficiary, originator_balance) # register account for deletion only if it was created # in the same transaction - if originator in evm.message.tx_env.state.created_accounts: + if originator in tx_state.created_accounts: # If beneficiary is the same as originator, then # the ether is burnt. - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) evm.accounts_to_delete.add(originator) # HALT the execution @@ -595,22 +643,42 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, code_address) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( - U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost + U256(0), + gas, + Uint(evm.gas_left), + extend_memory.cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -630,7 +698,7 @@ def delegatecall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER @@ -664,27 +732,42 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS - code_address = to + extra_gas = access_gas_cost ( - disable_precompiles, + is_delegated, code_address, - code, - delegated_access_gas_cost, - ) = access_delegation(evm, code_address) - access_gas_cost += delegated_access_gas_cost + delegation_access_cost, + ) = calculate_delegation_cost(evm, to) + + if is_delegated: + # check enough gas for delegation access + extra_gas += delegation_access_cost + check_gas(evm, extra_gas + extend_memory.cost) + if code_address not in evm.accessed_addresses: + evm.accessed_addresses.add(code_address) + + code = get_code(tx_state, get_account(tx_state, code_address).code_hash) message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) @@ -704,7 +787,7 @@ def staticcall(evm: Evm) -> None: memory_output_start_position, memory_output_size, code, - disable_precompiles, + is_delegated, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/shanghai/vm/gas.py b/src/ethereum/forks/shanghai/vm/gas.py index f7b597f700f..693cd7e2524 100644 --- a/src/ethereum/forks/shanghai/vm/gas.py +++ b/src/ethereum/forks/shanghai/vm/gas.py @@ -196,6 +196,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/shanghai/vm/instructions/storage.py b/src/ethereum/forks/shanghai/vm/instructions/storage.py index c3fe02532ad..e3726ec6a39 100644 --- a/src/ethereum/forks/shanghai/vm/instructions/storage.py +++ b/src/ethereum/forks/shanghai/vm/instructions/storage.py @@ -15,8 +15,8 @@ from ...state_tracker import get_storage, get_storage_original, set_storage from .. import Evm -from ..exceptions import OutOfGasError, WriteInStaticContext -from ..gas import GasCosts, charge_gas +from ..exceptions import WriteInStaticContext +from ..gas import GasCosts, charge_gas, check_gas from ..stack import pop, push @@ -61,11 +61,15 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) - if evm.gas_left <= GasCosts.CALL_STIPEND: - raise OutOfGasError + + # check we have at least the stipend gas + check_gas(evm, GasCosts.CALL_STIPEND + Uint(1)) tx_state = evm.message.tx_env.state original_value = get_storage_original( @@ -115,8 +119,6 @@ def sstore(evm: Evm) -> None: ) charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/shanghai/vm/instructions/system.py b/src/ethereum/forks/shanghai/vm/instructions/system.py index b83e201eff2..6195b41452a 100644 --- a/src/ethereum/forks/shanghai/vm/instructions/system.py +++ b/src/ethereum/forks/shanghai/vm/instructions/system.py @@ -43,6 +43,7 @@ calculate_gas_extend_memory, calculate_message_call_gas, charge_gas, + check_gas, init_code_cost, max_message_call_gas, ) @@ -68,20 +69,26 @@ def generic_create( process_create_message, ) + # Check static context first + if evm.message.is_static: + raise WriteInStaticContext + + # Check max init code size early before memory read + if memory_size > U256(MAX_INIT_CODE_SIZE): + raise OutOfGasError + + tx_state = evm.message.tx_env.state + call_data = memory_read_bytes( evm.memory, memory_start_position, memory_size ) - if len(call_data) > MAX_INIT_CODE_SIZE: - raise OutOfGasError create_message_gas = max_message_call_gas(Uint(evm.gas_left)) evm.gas_left -= create_message_gas - if evm.message.is_static: - raise WriteInStaticContext evm.return_data = b"" sender_address = evm.message.current_target - sender = get_account(evm.message.tx_env.state, sender_address) + sender = get_account(tx_state, sender_address) if ( sender.balance < endowment @@ -95,13 +102,13 @@ def generic_create( evm.accessed_addresses.add(contract_address) if account_has_code_or_nonce( - evm.message.tx_env.state, contract_address - ) or account_has_storage(evm.message.tx_env.state, contract_address): - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + tx_state, contract_address + ) or account_has_storage(tx_state, contract_address): + increment_nonce(tx_state, evm.message.current_target) push(evm.stack, U256(0)) return - increment_nonce(evm.message.tx_env.state, evm.message.current_target) + increment_nonce(tx_state, evm.message.current_target) child_message = Message( block_env=evm.message.block_env, @@ -352,6 +359,9 @@ def call(evm: Evm) -> None: memory_output_start_position = pop(evm.stack) memory_output_size = pop(evm.stack) + if evm.message.is_static and value != U256(0): + raise WriteInStaticContext + # GAS extend_memory = calculate_gas_extend_memory( evm.memory, @@ -361,32 +371,45 @@ def call(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + transfer_gas_cost + extend_memory.cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to create_gas_cost = GasCosts.NEW_ACCOUNT - if value == 0 or is_account_alive(evm.message.tx_env.state, to): + if value == 0 or is_account_alive(tx_state, to): create_gas_cost = Uint(0) - transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + extra_gas = access_gas_cost + transfer_gas_cost + create_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + create_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) - if evm.message.is_static and value != U256(0): - raise WriteInStaticContext + + # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -441,27 +464,39 @@ def callcode(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS - else: - evm.accessed_addresses.add(code_address) + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS + else: + access_gas_cost = GasCosts.WARM_ACCESS transfer_gas_cost = Uint(0) if value == 0 else GasCosts.CALL_VALUE + + # check static gas before state access + check_gas( + evm, + access_gas_cost + extend_memory.cost + transfer_gas_cost, + ) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(code_address) + + extra_gas = access_gas_cost + transfer_gas_cost + message_call_gas = calculate_message_call_gas( value, gas, Uint(evm.gas_left), extend_memory.cost, - access_gas_cost + transfer_gas_cost, + extra_gas, ) charge_gas(evm, message_call_gas.cost + extend_memory.cost) # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - sender_balance = get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance + sender_balance = get_account(tx_state, evm.message.current_target).balance if sender_balance < value: push(evm.stack, U256(0)) evm.return_data = b"" @@ -496,46 +531,49 @@ def selfdestruct(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK beneficiary = to_address_masked(pop(evm.stack)) # GAS gas_cost = GasCosts.OPCODE_SELFDESTRUCT_BASE - if beneficiary not in evm.accessed_addresses: - evm.accessed_addresses.add(beneficiary) + + is_cold_access = beneficiary not in evm.accessed_addresses + if is_cold_access: gas_cost += GasCosts.COLD_ACCOUNT_ACCESS + # check access gas cost before state access + check_gas(evm, gas_cost) + + # STATE ACCESS + tx_state = evm.message.tx_env.state + if is_cold_access: + evm.accessed_addresses.add(beneficiary) + if ( - not is_account_alive(evm.message.tx_env.state, beneficiary) - and get_account( - evm.message.tx_env.state, evm.message.current_target - ).balance - != 0 + not is_account_alive(tx_state, beneficiary) + and get_account(tx_state, evm.message.current_target).balance != 0 ): gas_cost += GasCosts.OPCODE_SELFDESTRUCT_NEW_ACCOUNT charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext originator = evm.message.current_target - beneficiary_balance = get_account( - evm.message.tx_env.state, beneficiary - ).balance - originator_balance = get_account( - evm.message.tx_env.state, originator - ).balance + beneficiary_balance = get_account(tx_state, beneficiary).balance + originator_balance = get_account(tx_state, originator).balance # First Transfer to beneficiary set_account_balance( - evm.message.tx_env.state, + tx_state, beneficiary, beneficiary_balance + originator_balance, ) # Next, Zero the balance of the address being deleted (must come after # sending to beneficiary in case the contract named itself as the # beneficiary). - set_account_balance(evm.message.tx_env.state, originator, U256(0)) + set_account_balance(tx_state, originator, U256(0)) # register account for deletion evm.accounts_to_delete.add(originator) @@ -574,11 +612,18 @@ def delegatecall(evm: Evm) -> None: ], ) - if code_address in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = code_address not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(code_address) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS message_call_gas = calculate_message_call_gas( U256(0), gas, Uint(evm.gas_left), extend_memory.cost, access_gas_cost @@ -633,11 +678,18 @@ def staticcall(evm: Evm) -> None: ], ) - if to in evm.accessed_addresses: - access_gas_cost = GasCosts.WARM_ACCESS + is_cold_access = to not in evm.accessed_addresses + if is_cold_access: + access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS else: + access_gas_cost = GasCosts.WARM_ACCESS + + # check static gas before state access + check_gas(evm, access_gas_cost + extend_memory.cost) + + # STATE ACCESS + if is_cold_access: evm.accessed_addresses.add(to) - access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code_address = to diff --git a/src/ethereum/forks/spurious_dragon/vm/gas.py b/src/ethereum/forks/spurious_dragon/vm/gas.py index c4cd3ce528f..4692b7956c6 100644 --- a/src/ethereum/forks/spurious_dragon/vm/gas.py +++ b/src/ethereum/forks/spurious_dragon/vm/gas.py @@ -180,6 +180,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/tangerine_whistle/vm/gas.py b/src/ethereum/forks/tangerine_whistle/vm/gas.py index 93c81fae94d..64715782606 100644 --- a/src/ethereum/forks/tangerine_whistle/vm/gas.py +++ b/src/ethereum/forks/tangerine_whistle/vm/gas.py @@ -180,6 +180,23 @@ class MessageCallGas: sub_call: Uint +def check_gas(evm: Evm, amount: Uint) -> None: + """ + Checks if `amount` gas is available without charging it. + Raises OutOfGasError if insufficient gas. + + Parameters + ---------- + evm : + The current EVM. + amount : + The amount of gas to check. + + """ + if evm.gas_left < amount: + raise OutOfGasError + + def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. From ea386e8ffc74d4c3e76ae5aab6709aca3fcfcd01 Mon Sep 17 00:00:00 2001 From: fselmo Date: Fri, 22 May 2026 14:47:10 -0600 Subject: [PATCH 2/3] chore: align formatting across forks to display cleaner diffs --- src/ethereum/forks/bpo1/vm/instructions/system.py | 3 +-- src/ethereum/forks/bpo4/vm/instructions/system.py | 3 +-- src/ethereum/forks/bpo5/vm/instructions/system.py | 3 +-- src/ethereum/forks/osaka/vm/instructions/system.py | 3 +-- src/ethereum/forks/prague/vm/instructions/system.py | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ethereum/forks/bpo1/vm/instructions/system.py b/src/ethereum/forks/bpo1/vm/instructions/system.py index a9724430c06..bbf614a7b24 100644 --- a/src/ethereum/forks/bpo1/vm/instructions/system.py +++ b/src/ethereum/forks/bpo1/vm/instructions/system.py @@ -165,8 +165,7 @@ def create(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( - evm, - GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas, + evm, GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas ) # OPERATION diff --git a/src/ethereum/forks/bpo4/vm/instructions/system.py b/src/ethereum/forks/bpo4/vm/instructions/system.py index a9724430c06..bbf614a7b24 100644 --- a/src/ethereum/forks/bpo4/vm/instructions/system.py +++ b/src/ethereum/forks/bpo4/vm/instructions/system.py @@ -165,8 +165,7 @@ def create(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( - evm, - GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas, + evm, GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas ) # OPERATION diff --git a/src/ethereum/forks/bpo5/vm/instructions/system.py b/src/ethereum/forks/bpo5/vm/instructions/system.py index a9724430c06..bbf614a7b24 100644 --- a/src/ethereum/forks/bpo5/vm/instructions/system.py +++ b/src/ethereum/forks/bpo5/vm/instructions/system.py @@ -165,8 +165,7 @@ def create(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( - evm, - GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas, + evm, GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas ) # OPERATION diff --git a/src/ethereum/forks/osaka/vm/instructions/system.py b/src/ethereum/forks/osaka/vm/instructions/system.py index a9724430c06..bbf614a7b24 100644 --- a/src/ethereum/forks/osaka/vm/instructions/system.py +++ b/src/ethereum/forks/osaka/vm/instructions/system.py @@ -165,8 +165,7 @@ def create(evm: Evm) -> None: init_code_gas = init_code_cost(Uint(memory_size)) charge_gas( - evm, - GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas, + evm, GasCosts.OPCODE_CREATE_BASE + extend_memory.cost + init_code_gas ) # OPERATION diff --git a/src/ethereum/forks/prague/vm/instructions/system.py b/src/ethereum/forks/prague/vm/instructions/system.py index 9b25e14ad82..bbf614a7b24 100644 --- a/src/ethereum/forks/prague/vm/instructions/system.py +++ b/src/ethereum/forks/prague/vm/instructions/system.py @@ -455,7 +455,7 @@ def call(evm: Evm) -> None: def callcode(evm: Evm) -> None: """ - Message-call into this account with alternative account's code. + Message-call into this account with alternative account’s code. Parameters ---------- From 0a3f40e3569433bce342e8e98967d19f13e4a3f5 Mon Sep 17 00:00:00 2001 From: fselmo Date: Tue, 26 May 2026 11:22:49 -0600 Subject: [PATCH 3/3] fix: Some cleanup from comments on PR #2903 --- .../forks/amsterdam/vm/instructions/log.py | 5 +++-- .../forks/arrow_glacier/vm/instructions/log.py | 5 +++-- .../forks/berlin/vm/instructions/log.py | 5 +++-- src/ethereum/forks/bpo1/vm/instructions/log.py | 5 +++-- src/ethereum/forks/bpo2/vm/instructions/log.py | 5 +++-- src/ethereum/forks/bpo3/vm/instructions/log.py | 5 +++-- src/ethereum/forks/bpo4/vm/instructions/log.py | 5 +++-- src/ethereum/forks/bpo5/vm/instructions/log.py | 5 +++-- src/ethereum/forks/byzantium/vm/gas.py | 17 ----------------- .../forks/byzantium/vm/instructions/log.py | 5 +++-- .../forks/byzantium/vm/instructions/storage.py | 5 +++-- .../forks/cancun/vm/instructions/log.py | 5 +++-- src/ethereum/forks/constantinople/vm/gas.py | 17 ----------------- .../forks/constantinople/vm/instructions/log.py | 5 +++-- .../constantinople/vm/instructions/storage.py | 5 +++-- src/ethereum/forks/dao_fork/vm/gas.py | 17 ----------------- src/ethereum/forks/frontier/vm/gas.py | 17 ----------------- .../forks/gray_glacier/vm/instructions/log.py | 5 +++-- src/ethereum/forks/homestead/vm/gas.py | 17 ----------------- .../forks/istanbul/vm/instructions/log.py | 5 +++-- .../forks/london/vm/instructions/log.py | 5 +++-- .../forks/muir_glacier/vm/instructions/log.py | 5 +++-- src/ethereum/forks/osaka/vm/instructions/log.py | 5 +++-- src/ethereum/forks/paris/vm/instructions/log.py | 5 +++-- .../forks/prague/vm/instructions/log.py | 5 +++-- .../forks/shanghai/vm/instructions/log.py | 5 +++-- src/ethereum/forks/spurious_dragon/vm/gas.py | 17 ----------------- src/ethereum/forks/tangerine_whistle/vm/gas.py | 17 ----------------- 28 files changed, 63 insertions(+), 161 deletions(-) diff --git a/src/ethereum/forks/amsterdam/vm/instructions/log.py b/src/ethereum/forks/amsterdam/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/amsterdam/vm/instructions/log.py +++ b/src/ethereum/forks/amsterdam/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/arrow_glacier/vm/instructions/log.py b/src/ethereum/forks/arrow_glacier/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/arrow_glacier/vm/instructions/log.py +++ b/src/ethereum/forks/arrow_glacier/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/berlin/vm/instructions/log.py b/src/ethereum/forks/berlin/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/berlin/vm/instructions/log.py +++ b/src/ethereum/forks/berlin/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/bpo1/vm/instructions/log.py b/src/ethereum/forks/bpo1/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/bpo1/vm/instructions/log.py +++ b/src/ethereum/forks/bpo1/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/bpo2/vm/instructions/log.py b/src/ethereum/forks/bpo2/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/bpo2/vm/instructions/log.py +++ b/src/ethereum/forks/bpo2/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/bpo3/vm/instructions/log.py b/src/ethereum/forks/bpo3/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/bpo3/vm/instructions/log.py +++ b/src/ethereum/forks/bpo3/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/bpo4/vm/instructions/log.py b/src/ethereum/forks/bpo4/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/bpo4/vm/instructions/log.py +++ b/src/ethereum/forks/bpo4/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/bpo5/vm/instructions/log.py b/src/ethereum/forks/bpo5/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/bpo5/vm/instructions/log.py +++ b/src/ethereum/forks/bpo5/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/byzantium/vm/gas.py b/src/ethereum/forks/byzantium/vm/gas.py index aa31644e161..0395403db01 100644 --- a/src/ethereum/forks/byzantium/vm/gas.py +++ b/src/ethereum/forks/byzantium/vm/gas.py @@ -187,23 +187,6 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: - """ - Checks if `amount` gas is available without charging it. - Raises OutOfGasError if insufficient gas. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas to check. - - """ - if evm.gas_left < amount: - raise OutOfGasError - - def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/byzantium/vm/instructions/log.py b/src/ethereum/forks/byzantium/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/byzantium/vm/instructions/log.py +++ b/src/ethereum/forks/byzantium/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/byzantium/vm/instructions/storage.py b/src/ethereum/forks/byzantium/vm/instructions/storage.py index faf342b34ab..9efcb30323e 100644 --- a/src/ethereum/forks/byzantium/vm/instructions/storage.py +++ b/src/ethereum/forks/byzantium/vm/instructions/storage.py @@ -60,6 +60,9 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) @@ -76,8 +79,6 @@ def sstore(evm: Evm) -> None: evm.refund_counter += GasCosts.REFUND_STORAGE_CLEAR charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/cancun/vm/instructions/log.py b/src/ethereum/forks/cancun/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/cancun/vm/instructions/log.py +++ b/src/ethereum/forks/cancun/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/constantinople/vm/gas.py b/src/ethereum/forks/constantinople/vm/gas.py index 078ff62c14c..770c5212461 100644 --- a/src/ethereum/forks/constantinople/vm/gas.py +++ b/src/ethereum/forks/constantinople/vm/gas.py @@ -191,23 +191,6 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: - """ - Checks if `amount` gas is available without charging it. - Raises OutOfGasError if insufficient gas. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas to check. - - """ - if evm.gas_left < amount: - raise OutOfGasError - - def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/constantinople/vm/instructions/log.py b/src/ethereum/forks/constantinople/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/constantinople/vm/instructions/log.py +++ b/src/ethereum/forks/constantinople/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/constantinople/vm/instructions/storage.py b/src/ethereum/forks/constantinople/vm/instructions/storage.py index faf342b34ab..9efcb30323e 100644 --- a/src/ethereum/forks/constantinople/vm/instructions/storage.py +++ b/src/ethereum/forks/constantinople/vm/instructions/storage.py @@ -60,6 +60,9 @@ def sstore(evm: Evm) -> None: The current EVM frame. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK key = pop(evm.stack).to_be_bytes32() new_value = pop(evm.stack) @@ -76,8 +79,6 @@ def sstore(evm: Evm) -> None: evm.refund_counter += GasCosts.REFUND_STORAGE_CLEAR charge_gas(evm, gas_cost) - if evm.message.is_static: - raise WriteInStaticContext set_storage(tx_state, evm.message.current_target, key, new_value) # PROGRAM COUNTER diff --git a/src/ethereum/forks/dao_fork/vm/gas.py b/src/ethereum/forks/dao_fork/vm/gas.py index 79954f90f10..cb663624a36 100644 --- a/src/ethereum/forks/dao_fork/vm/gas.py +++ b/src/ethereum/forks/dao_fork/vm/gas.py @@ -180,23 +180,6 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: - """ - Checks if `amount` gas is available without charging it. - Raises OutOfGasError if insufficient gas. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas to check. - - """ - if evm.gas_left < amount: - raise OutOfGasError - - def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/frontier/vm/gas.py b/src/ethereum/forks/frontier/vm/gas.py index d194a10f07a..1974386b1c3 100644 --- a/src/ethereum/forks/frontier/vm/gas.py +++ b/src/ethereum/forks/frontier/vm/gas.py @@ -178,23 +178,6 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: - """ - Checks if `amount` gas is available without charging it. - Raises OutOfGasError if insufficient gas. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas to check. - - """ - if evm.gas_left < amount: - raise OutOfGasError - - def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/gray_glacier/vm/instructions/log.py b/src/ethereum/forks/gray_glacier/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/gray_glacier/vm/instructions/log.py +++ b/src/ethereum/forks/gray_glacier/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/homestead/vm/gas.py b/src/ethereum/forks/homestead/vm/gas.py index 79954f90f10..cb663624a36 100644 --- a/src/ethereum/forks/homestead/vm/gas.py +++ b/src/ethereum/forks/homestead/vm/gas.py @@ -180,23 +180,6 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: - """ - Checks if `amount` gas is available without charging it. - Raises OutOfGasError if insufficient gas. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas to check. - - """ - if evm.gas_left < amount: - raise OutOfGasError - - def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/istanbul/vm/instructions/log.py b/src/ethereum/forks/istanbul/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/istanbul/vm/instructions/log.py +++ b/src/ethereum/forks/istanbul/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/london/vm/instructions/log.py b/src/ethereum/forks/london/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/london/vm/instructions/log.py +++ b/src/ethereum/forks/london/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/muir_glacier/vm/instructions/log.py b/src/ethereum/forks/muir_glacier/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/muir_glacier/vm/instructions/log.py +++ b/src/ethereum/forks/muir_glacier/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/osaka/vm/instructions/log.py b/src/ethereum/forks/osaka/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/osaka/vm/instructions/log.py +++ b/src/ethereum/forks/osaka/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/paris/vm/instructions/log.py b/src/ethereum/forks/paris/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/paris/vm/instructions/log.py +++ b/src/ethereum/forks/paris/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/prague/vm/instructions/log.py b/src/ethereum/forks/prague/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/prague/vm/instructions/log.py +++ b/src/ethereum/forks/prague/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/shanghai/vm/instructions/log.py b/src/ethereum/forks/shanghai/vm/instructions/log.py index 695f4de735c..63395875cf7 100644 --- a/src/ethereum/forks/shanghai/vm/instructions/log.py +++ b/src/ethereum/forks/shanghai/vm/instructions/log.py @@ -43,6 +43,9 @@ def log_n(evm: Evm, num_topics: int) -> None: The number of topics to be included in the log entry. """ + if evm.message.is_static: + raise WriteInStaticContext + # STACK memory_start_index = pop(evm.stack) size = pop(evm.stack) @@ -66,8 +69,6 @@ def log_n(evm: Evm, num_topics: int) -> None: # OPERATION evm.memory += b"\x00" * extend_memory.expand_by - if evm.message.is_static: - raise WriteInStaticContext log_entry = Log( address=evm.message.current_target, topics=tuple(topics), diff --git a/src/ethereum/forks/spurious_dragon/vm/gas.py b/src/ethereum/forks/spurious_dragon/vm/gas.py index 4692b7956c6..c4cd3ce528f 100644 --- a/src/ethereum/forks/spurious_dragon/vm/gas.py +++ b/src/ethereum/forks/spurious_dragon/vm/gas.py @@ -180,23 +180,6 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: - """ - Checks if `amount` gas is available without charging it. - Raises OutOfGasError if insufficient gas. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas to check. - - """ - if evm.gas_left < amount: - raise OutOfGasError - - def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`. diff --git a/src/ethereum/forks/tangerine_whistle/vm/gas.py b/src/ethereum/forks/tangerine_whistle/vm/gas.py index 64715782606..93c81fae94d 100644 --- a/src/ethereum/forks/tangerine_whistle/vm/gas.py +++ b/src/ethereum/forks/tangerine_whistle/vm/gas.py @@ -180,23 +180,6 @@ class MessageCallGas: sub_call: Uint -def check_gas(evm: Evm, amount: Uint) -> None: - """ - Checks if `amount` gas is available without charging it. - Raises OutOfGasError if insufficient gas. - - Parameters - ---------- - evm : - The current EVM. - amount : - The amount of gas to check. - - """ - if evm.gas_left < amount: - raise OutOfGasError - - def charge_gas(evm: Evm, amount: Uint) -> None: """ Subtracts `amount` from `evm.gas_left`.