From c66f04301e31f55879c88259812a8fc4e5555fb6 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:04:49 +0000 Subject: [PATCH 1/2] fix(monad): ban creates by the frame's recipient, not the code address A frame whose address holds a 7702 delegation cannot create, whichever code it runs, as the client checks the recipient at create time. Co-Authored-By: Claude --- src/ethereum/forks/monad_eight/fork.py | 1 - .../forks/monad_eight/utils/message.py | 1 - src/ethereum/forks/monad_eight/vm/__init__.py | 1 - .../forks/monad_eight/vm/eoa_delegation.py | 12 +++++------ .../monad_eight/vm/instructions/system.py | 20 +++++++------------ .../forks/monad_eight/vm/interpreter.py | 1 - src/ethereum/forks/monad_next/fork.py | 1 - .../forks/monad_next/utils/message.py | 1 - src/ethereum/forks/monad_next/vm/__init__.py | 1 - .../forks/monad_next/vm/eoa_delegation.py | 12 +++++------ .../monad_next/vm/instructions/system.py | 20 +++++++------------ .../forks/monad_next/vm/interpreter.py | 1 - src/ethereum/forks/monad_nine/fork.py | 1 - .../forks/monad_nine/utils/message.py | 1 - src/ethereum/forks/monad_nine/vm/__init__.py | 1 - .../forks/monad_nine/vm/eoa_delegation.py | 12 +++++------ .../monad_nine/vm/instructions/system.py | 20 +++++++------------ .../forks/monad_nine/vm/interpreter.py | 1 - src/ethereum/forks/monad_ten/fork.py | 1 - src/ethereum/forks/monad_ten/utils/message.py | 1 - src/ethereum/forks/monad_ten/vm/__init__.py | 1 - .../forks/monad_ten/vm/eoa_delegation.py | 12 +++++------ .../forks/monad_ten/vm/instructions/system.py | 20 +++++++------------ .../forks/monad_ten/vm/interpreter.py | 1 - 24 files changed, 52 insertions(+), 92 deletions(-) diff --git a/src/ethereum/forks/monad_eight/fork.py b/src/ethereum/forks/monad_eight/fork.py index 150b0fcf79b..210954c1b80 100644 --- a/src/ethereum/forks/monad_eight/fork.py +++ b/src/ethereum/forks/monad_eight/fork.py @@ -710,7 +710,6 @@ def process_unchecked_system_transaction( accessed_storage_keys=set(), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) system_tx_output = process_message_call(system_tx_message) diff --git a/src/ethereum/forks/monad_eight/utils/message.py b/src/ethereum/forks/monad_eight/utils/message.py index 7b36863a32c..7ace9c15f4a 100644 --- a/src/ethereum/forks/monad_eight/utils/message.py +++ b/src/ethereum/forks/monad_eight/utils/message.py @@ -90,5 +90,4 @@ def prepare_message( accessed_storage_keys=set(tx_env.access_list_storage_keys), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) diff --git a/src/ethereum/forks/monad_eight/vm/__init__.py b/src/ethereum/forks/monad_eight/vm/__init__.py index 8041768baa7..f18ed379cb5 100644 --- a/src/ethereum/forks/monad_eight/vm/__init__.py +++ b/src/ethereum/forks/monad_eight/vm/__init__.py @@ -138,7 +138,6 @@ class Message: accessed_storage_keys: Set[Tuple[Address, Bytes32]] disable_precompiles: bool parent_evm: Optional["Evm"] - disable_create_opcodes: bool @final diff --git a/src/ethereum/forks/monad_eight/vm/eoa_delegation.py b/src/ethereum/forks/monad_eight/vm/eoa_delegation.py index 0ba1242c487..cf3c6b678cf 100644 --- a/src/ethereum/forks/monad_eight/vm/eoa_delegation.py +++ b/src/ethereum/forks/monad_eight/vm/eoa_delegation.py @@ -122,7 +122,7 @@ def recover_authority(authorization: Authorization) -> Address: def access_delegation( evm: Evm, address: Address -) -> Tuple[bool, bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Bytes, Uint]: """ Get the delegation address, code, and the cost of access from the address. @@ -135,15 +135,15 @@ def access_delegation( Returns ------- - delegation : `Tuple[bool, bool, Address, Bytes, Uint]` - The disable precompiles, disable creates, delegation address, code, and - access gas cost. + delegation : `Tuple[bool, Address, Bytes, Uint]` + The disable precompiles, delegation address, code, 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, False, address, code, Uint(0) + return False, address, code, Uint(0) address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) if address in evm.accessed_addresses: @@ -153,7 +153,7 @@ def access_delegation( access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code = get_code(tx_state, get_account(tx_state, address).code_hash) - return True, True, address, code, access_gas_cost + return True, address, code, access_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/monad_eight/vm/instructions/system.py b/src/ethereum/forks/monad_eight/vm/instructions/system.py index bbc03513a63..86076c0417e 100644 --- a/src/ethereum/forks/monad_eight/vm/instructions/system.py +++ b/src/ethereum/forks/monad_eight/vm/instructions/system.py @@ -20,6 +20,7 @@ from ...state_tracker import ( account_has_code_or_nonce, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -30,7 +31,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import access_delegation, is_valid_delegation from .. import ( Evm, Message, @@ -83,7 +84,11 @@ def generic_create( evm.gas_left -= create_message_gas if evm.message.is_static: raise WriteInStaticContext - if evm.message.disable_create_opcodes: + tx_state = evm.message.tx_env.state + recipient_code = get_code( + tx_state, get_account(tx_state, evm.message.current_target).code_hash + ) + if is_valid_delegation(recipient_code): raise CreateIn7702Context evm.return_data = b"" @@ -126,7 +131,6 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=False, parent_evm=evm, - disable_create_opcodes=False, ) child_evm = process_create_message(child_message) @@ -288,7 +292,6 @@ def generic_call( memory_output_size: U256, code: Bytes, disable_precompiles: bool, - disable_create_opcodes: bool, ) -> None: """ Perform the core logic of the `CALL*` family of opcodes. @@ -324,7 +327,6 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=disable_precompiles, parent_evm=evm, - disable_create_opcodes=disable_create_opcodes, ) child_evm = process_message(child_message) @@ -382,7 +384,6 @@ def call(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -427,7 +428,6 @@ def call(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -472,7 +472,6 @@ def callcode(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -514,7 +513,6 @@ def callcode(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -615,7 +613,6 @@ def delegatecall(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -648,7 +645,6 @@ def delegatecall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -691,7 +687,6 @@ def staticcall(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -724,7 +719,6 @@ def staticcall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/monad_eight/vm/interpreter.py b/src/ethereum/forks/monad_eight/vm/interpreter.py index 07d86617301..7d4f568f3a2 100644 --- a/src/ethereum/forks/monad_eight/vm/interpreter.py +++ b/src/ethereum/forks/monad_eight/vm/interpreter.py @@ -147,7 +147,6 @@ def process_message_call(message: Message) -> MessageCallOutput: get_account(tx_state, delegated_address).code_hash, ) message.code_address = delegated_address - message.disable_create_opcodes = True evm = process_message(message) diff --git a/src/ethereum/forks/monad_next/fork.py b/src/ethereum/forks/monad_next/fork.py index ed7a048ead7..1a9c251a54e 100644 --- a/src/ethereum/forks/monad_next/fork.py +++ b/src/ethereum/forks/monad_next/fork.py @@ -727,7 +727,6 @@ def process_unchecked_system_transaction( accessed_storage_keys=set(), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) system_tx_output = process_message_call(system_tx_message) diff --git a/src/ethereum/forks/monad_next/utils/message.py b/src/ethereum/forks/monad_next/utils/message.py index 7cad195ead2..c421f740327 100644 --- a/src/ethereum/forks/monad_next/utils/message.py +++ b/src/ethereum/forks/monad_next/utils/message.py @@ -90,5 +90,4 @@ def prepare_message( accessed_storage_keys=set(tx_env.access_list_storage_keys), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) diff --git a/src/ethereum/forks/monad_next/vm/__init__.py b/src/ethereum/forks/monad_next/vm/__init__.py index 15c53bc75b6..5cff6a44f12 100644 --- a/src/ethereum/forks/monad_next/vm/__init__.py +++ b/src/ethereum/forks/monad_next/vm/__init__.py @@ -143,7 +143,6 @@ class Message: accessed_storage_keys: Set[Tuple[Address, Bytes32]] disable_precompiles: bool parent_evm: Optional["Evm"] - disable_create_opcodes: bool @final diff --git a/src/ethereum/forks/monad_next/vm/eoa_delegation.py b/src/ethereum/forks/monad_next/vm/eoa_delegation.py index 0ba1242c487..cf3c6b678cf 100644 --- a/src/ethereum/forks/monad_next/vm/eoa_delegation.py +++ b/src/ethereum/forks/monad_next/vm/eoa_delegation.py @@ -122,7 +122,7 @@ def recover_authority(authorization: Authorization) -> Address: def access_delegation( evm: Evm, address: Address -) -> Tuple[bool, bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Bytes, Uint]: """ Get the delegation address, code, and the cost of access from the address. @@ -135,15 +135,15 @@ def access_delegation( Returns ------- - delegation : `Tuple[bool, bool, Address, Bytes, Uint]` - The disable precompiles, disable creates, delegation address, code, and - access gas cost. + delegation : `Tuple[bool, Address, Bytes, Uint]` + The disable precompiles, delegation address, code, 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, False, address, code, Uint(0) + return False, address, code, Uint(0) address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) if address in evm.accessed_addresses: @@ -153,7 +153,7 @@ def access_delegation( access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code = get_code(tx_state, get_account(tx_state, address).code_hash) - return True, True, address, code, access_gas_cost + return True, address, code, access_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/monad_next/vm/instructions/system.py b/src/ethereum/forks/monad_next/vm/instructions/system.py index 2b0743f4302..38abf706b03 100644 --- a/src/ethereum/forks/monad_next/vm/instructions/system.py +++ b/src/ethereum/forks/monad_next/vm/instructions/system.py @@ -20,6 +20,7 @@ from ...state_tracker import ( account_has_code_or_nonce, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -30,7 +31,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import access_delegation, is_valid_delegation from .. import ( Evm, Message, @@ -84,7 +85,11 @@ def generic_create( evm.gas_left -= create_message_gas if evm.message.is_static: raise WriteInStaticContext - if evm.message.disable_create_opcodes: + tx_state = evm.message.tx_env.state + recipient_code = get_code( + tx_state, get_account(tx_state, evm.message.current_target).code_hash + ) + if is_valid_delegation(recipient_code): raise CreateIn7702Context evm.return_data = b"" @@ -127,7 +132,6 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=False, parent_evm=evm, - disable_create_opcodes=False, ) child_evm = process_create_message(child_message) @@ -292,7 +296,6 @@ def generic_call( memory_output_size: U256, code: Bytes, disable_precompiles: bool, - disable_create_opcodes: bool, ) -> None: """ Perform the core logic of the `CALL*` family of opcodes. @@ -328,7 +331,6 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=disable_precompiles, parent_evm=evm, - disable_create_opcodes=disable_create_opcodes, ) child_evm = process_message(child_message) @@ -386,7 +388,6 @@ def call(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -439,7 +440,6 @@ def call(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -484,7 +484,6 @@ def callcode(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -528,7 +527,6 @@ def callcode(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -629,7 +627,6 @@ def delegatecall(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -664,7 +661,6 @@ def delegatecall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -707,7 +703,6 @@ def staticcall(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -742,7 +737,6 @@ def staticcall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/monad_next/vm/interpreter.py b/src/ethereum/forks/monad_next/vm/interpreter.py index df7ee011d08..1814011826b 100644 --- a/src/ethereum/forks/monad_next/vm/interpreter.py +++ b/src/ethereum/forks/monad_next/vm/interpreter.py @@ -237,7 +237,6 @@ def process_message_call(message: Message) -> MessageCallOutput: get_account(tx_state, delegated_address).code_hash, ) message.code_address = delegated_address - message.disable_create_opcodes = True evm = process_message(message) diff --git a/src/ethereum/forks/monad_nine/fork.py b/src/ethereum/forks/monad_nine/fork.py index e11fd0e2046..ebf97201ec7 100644 --- a/src/ethereum/forks/monad_nine/fork.py +++ b/src/ethereum/forks/monad_nine/fork.py @@ -727,7 +727,6 @@ def process_unchecked_system_transaction( accessed_storage_keys=set(), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) system_tx_output = process_message_call(system_tx_message) diff --git a/src/ethereum/forks/monad_nine/utils/message.py b/src/ethereum/forks/monad_nine/utils/message.py index 38683a7ad14..52b402aece8 100644 --- a/src/ethereum/forks/monad_nine/utils/message.py +++ b/src/ethereum/forks/monad_nine/utils/message.py @@ -90,5 +90,4 @@ def prepare_message( accessed_storage_keys=set(tx_env.access_list_storage_keys), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) diff --git a/src/ethereum/forks/monad_nine/vm/__init__.py b/src/ethereum/forks/monad_nine/vm/__init__.py index bac9c951854..679f04ed628 100644 --- a/src/ethereum/forks/monad_nine/vm/__init__.py +++ b/src/ethereum/forks/monad_nine/vm/__init__.py @@ -143,7 +143,6 @@ class Message: accessed_storage_keys: Set[Tuple[Address, Bytes32]] disable_precompiles: bool parent_evm: Optional["Evm"] - disable_create_opcodes: bool @final diff --git a/src/ethereum/forks/monad_nine/vm/eoa_delegation.py b/src/ethereum/forks/monad_nine/vm/eoa_delegation.py index 0ba1242c487..cf3c6b678cf 100644 --- a/src/ethereum/forks/monad_nine/vm/eoa_delegation.py +++ b/src/ethereum/forks/monad_nine/vm/eoa_delegation.py @@ -122,7 +122,7 @@ def recover_authority(authorization: Authorization) -> Address: def access_delegation( evm: Evm, address: Address -) -> Tuple[bool, bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Bytes, Uint]: """ Get the delegation address, code, and the cost of access from the address. @@ -135,15 +135,15 @@ def access_delegation( Returns ------- - delegation : `Tuple[bool, bool, Address, Bytes, Uint]` - The disable precompiles, disable creates, delegation address, code, and - access gas cost. + delegation : `Tuple[bool, Address, Bytes, Uint]` + The disable precompiles, delegation address, code, 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, False, address, code, Uint(0) + return False, address, code, Uint(0) address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) if address in evm.accessed_addresses: @@ -153,7 +153,7 @@ def access_delegation( access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code = get_code(tx_state, get_account(tx_state, address).code_hash) - return True, True, address, code, access_gas_cost + return True, address, code, access_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/monad_nine/vm/instructions/system.py b/src/ethereum/forks/monad_nine/vm/instructions/system.py index 2b0743f4302..38abf706b03 100644 --- a/src/ethereum/forks/monad_nine/vm/instructions/system.py +++ b/src/ethereum/forks/monad_nine/vm/instructions/system.py @@ -20,6 +20,7 @@ from ...state_tracker import ( account_has_code_or_nonce, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -30,7 +31,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import access_delegation, is_valid_delegation from .. import ( Evm, Message, @@ -84,7 +85,11 @@ def generic_create( evm.gas_left -= create_message_gas if evm.message.is_static: raise WriteInStaticContext - if evm.message.disable_create_opcodes: + tx_state = evm.message.tx_env.state + recipient_code = get_code( + tx_state, get_account(tx_state, evm.message.current_target).code_hash + ) + if is_valid_delegation(recipient_code): raise CreateIn7702Context evm.return_data = b"" @@ -127,7 +132,6 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=False, parent_evm=evm, - disable_create_opcodes=False, ) child_evm = process_create_message(child_message) @@ -292,7 +296,6 @@ def generic_call( memory_output_size: U256, code: Bytes, disable_precompiles: bool, - disable_create_opcodes: bool, ) -> None: """ Perform the core logic of the `CALL*` family of opcodes. @@ -328,7 +331,6 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=disable_precompiles, parent_evm=evm, - disable_create_opcodes=disable_create_opcodes, ) child_evm = process_message(child_message) @@ -386,7 +388,6 @@ def call(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -439,7 +440,6 @@ def call(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -484,7 +484,6 @@ def callcode(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -528,7 +527,6 @@ def callcode(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -629,7 +627,6 @@ def delegatecall(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -664,7 +661,6 @@ def delegatecall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -707,7 +703,6 @@ def staticcall(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -742,7 +737,6 @@ def staticcall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/monad_nine/vm/interpreter.py b/src/ethereum/forks/monad_nine/vm/interpreter.py index 512fb6a24f4..25d15898340 100644 --- a/src/ethereum/forks/monad_nine/vm/interpreter.py +++ b/src/ethereum/forks/monad_nine/vm/interpreter.py @@ -237,7 +237,6 @@ def process_message_call(message: Message) -> MessageCallOutput: get_account(tx_state, delegated_address).code_hash, ) message.code_address = delegated_address - message.disable_create_opcodes = True evm = process_message(message) diff --git a/src/ethereum/forks/monad_ten/fork.py b/src/ethereum/forks/monad_ten/fork.py index ed7a048ead7..1a9c251a54e 100644 --- a/src/ethereum/forks/monad_ten/fork.py +++ b/src/ethereum/forks/monad_ten/fork.py @@ -727,7 +727,6 @@ def process_unchecked_system_transaction( accessed_storage_keys=set(), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) system_tx_output = process_message_call(system_tx_message) diff --git a/src/ethereum/forks/monad_ten/utils/message.py b/src/ethereum/forks/monad_ten/utils/message.py index 4ccede9406d..5de56d23d51 100644 --- a/src/ethereum/forks/monad_ten/utils/message.py +++ b/src/ethereum/forks/monad_ten/utils/message.py @@ -90,5 +90,4 @@ def prepare_message( accessed_storage_keys=set(tx_env.access_list_storage_keys), disable_precompiles=False, parent_evm=None, - disable_create_opcodes=False, ) diff --git a/src/ethereum/forks/monad_ten/vm/__init__.py b/src/ethereum/forks/monad_ten/vm/__init__.py index 15c53bc75b6..5cff6a44f12 100644 --- a/src/ethereum/forks/monad_ten/vm/__init__.py +++ b/src/ethereum/forks/monad_ten/vm/__init__.py @@ -143,7 +143,6 @@ class Message: accessed_storage_keys: Set[Tuple[Address, Bytes32]] disable_precompiles: bool parent_evm: Optional["Evm"] - disable_create_opcodes: bool @final diff --git a/src/ethereum/forks/monad_ten/vm/eoa_delegation.py b/src/ethereum/forks/monad_ten/vm/eoa_delegation.py index 0ba1242c487..cf3c6b678cf 100644 --- a/src/ethereum/forks/monad_ten/vm/eoa_delegation.py +++ b/src/ethereum/forks/monad_ten/vm/eoa_delegation.py @@ -122,7 +122,7 @@ def recover_authority(authorization: Authorization) -> Address: def access_delegation( evm: Evm, address: Address -) -> Tuple[bool, bool, Address, Bytes, Uint]: +) -> Tuple[bool, Address, Bytes, Uint]: """ Get the delegation address, code, and the cost of access from the address. @@ -135,15 +135,15 @@ def access_delegation( Returns ------- - delegation : `Tuple[bool, bool, Address, Bytes, Uint]` - The disable precompiles, disable creates, delegation address, code, and - access gas cost. + delegation : `Tuple[bool, Address, Bytes, Uint]` + The disable precompiles, delegation address, code, 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, False, address, code, Uint(0) + return False, address, code, Uint(0) address = Address(code[EOA_DELEGATION_MARKER_LENGTH:]) if address in evm.accessed_addresses: @@ -153,7 +153,7 @@ def access_delegation( access_gas_cost = GasCosts.COLD_ACCOUNT_ACCESS code = get_code(tx_state, get_account(tx_state, address).code_hash) - return True, True, address, code, access_gas_cost + return True, address, code, access_gas_cost def set_delegation(message: Message) -> U256: diff --git a/src/ethereum/forks/monad_ten/vm/instructions/system.py b/src/ethereum/forks/monad_ten/vm/instructions/system.py index 2b0743f4302..38abf706b03 100644 --- a/src/ethereum/forks/monad_ten/vm/instructions/system.py +++ b/src/ethereum/forks/monad_ten/vm/instructions/system.py @@ -20,6 +20,7 @@ from ...state_tracker import ( account_has_code_or_nonce, get_account, + get_code, increment_nonce, is_account_alive, move_ether, @@ -30,7 +31,7 @@ compute_create2_contract_address, to_address_masked, ) -from ...vm.eoa_delegation import access_delegation +from ...vm.eoa_delegation import access_delegation, is_valid_delegation from .. import ( Evm, Message, @@ -84,7 +85,11 @@ def generic_create( evm.gas_left -= create_message_gas if evm.message.is_static: raise WriteInStaticContext - if evm.message.disable_create_opcodes: + tx_state = evm.message.tx_env.state + recipient_code = get_code( + tx_state, get_account(tx_state, evm.message.current_target).code_hash + ) + if is_valid_delegation(recipient_code): raise CreateIn7702Context evm.return_data = b"" @@ -127,7 +132,6 @@ def generic_create( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=False, parent_evm=evm, - disable_create_opcodes=False, ) child_evm = process_create_message(child_message) @@ -292,7 +296,6 @@ def generic_call( memory_output_size: U256, code: Bytes, disable_precompiles: bool, - disable_create_opcodes: bool, ) -> None: """ Perform the core logic of the `CALL*` family of opcodes. @@ -328,7 +331,6 @@ def generic_call( accessed_storage_keys=evm.accessed_storage_keys.copy(), disable_precompiles=disable_precompiles, parent_evm=evm, - disable_create_opcodes=disable_create_opcodes, ) child_evm = process_message(child_message) @@ -386,7 +388,6 @@ def call(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -439,7 +440,6 @@ def call(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -484,7 +484,6 @@ def callcode(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -528,7 +527,6 @@ def callcode(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -629,7 +627,6 @@ def delegatecall(evm: Evm) -> None: ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -664,7 +661,6 @@ def delegatecall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER @@ -707,7 +703,6 @@ def staticcall(evm: Evm) -> None: code_address = to ( disable_precompiles, - disable_create_opcodes, code_address, code, delegated_access_gas_cost, @@ -742,7 +737,6 @@ def staticcall(evm: Evm) -> None: memory_output_size, code, disable_precompiles, - disable_create_opcodes, ) # PROGRAM COUNTER diff --git a/src/ethereum/forks/monad_ten/vm/interpreter.py b/src/ethereum/forks/monad_ten/vm/interpreter.py index e339ded2c7b..4b989373b55 100644 --- a/src/ethereum/forks/monad_ten/vm/interpreter.py +++ b/src/ethereum/forks/monad_ten/vm/interpreter.py @@ -237,7 +237,6 @@ def process_message_call(message: Message) -> MessageCallOutput: get_account(tx_state, delegated_address).code_hash, ) message.code_address = delegated_address - message.disable_create_opcodes = True evm = process_message(message) From 774a298e32502facb3719f8075f49ee7724f1e83 Mon Sep 17 00:00:00 2001 From: pdobacz <5735525+pdobacz@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:04:49 +0000 Subject: [PATCH 2/2] test(monad): cover the create ban across the call shapes A delegated EOA's creating code and a factory reached from a delegated frame, each entered by every call opcode, on every Monad fork. Co-Authored-By: Claude --- .../monad_eight/delegated_creates/__init__.py | 1 + .../monad_eight/delegated_creates/helpers.py | 26 +++ .../delegated_creates/test_create_ban.py | 155 ++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 tests/monad_eight/delegated_creates/__init__.py create mode 100644 tests/monad_eight/delegated_creates/helpers.py create mode 100644 tests/monad_eight/delegated_creates/test_create_ban.py diff --git a/tests/monad_eight/delegated_creates/__init__.py b/tests/monad_eight/delegated_creates/__init__.py new file mode 100644 index 00000000000..c1bd1092348 --- /dev/null +++ b/tests/monad_eight/delegated_creates/__init__.py @@ -0,0 +1 @@ +"""Tests for the create opcodes around a 7702-delegated EOA.""" diff --git a/tests/monad_eight/delegated_creates/helpers.py b/tests/monad_eight/delegated_creates/helpers.py new file mode 100644 index 00000000000..05b2b627dd4 --- /dev/null +++ b/tests/monad_eight/delegated_creates/helpers.py @@ -0,0 +1,26 @@ +"""Bytecode for the delegated create tests.""" + +from execution_testing import Address, Bytecode, Op + +slot_created = 0x1 +slot_call_result = 0x2 + + +def factory_code(create_opcode: Op) -> Bytecode: + """Deploy the calldata as initcode and record the created address.""" + return Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.SSTORE( + slot_created, create_opcode(value=0, offset=0, size=Op.CALLDATASIZE) + ) + + +def forwarding_code(call_opcode: Op, address: Address) -> Bytecode: + """Forward the calldata to `address` and record the call result.""" + return Op.CALLDATACOPY(0, 0, Op.CALLDATASIZE) + Op.SSTORE( + slot_call_result, + call_opcode( + gas=Op.GAS, + address=address, + args_offset=0, + args_size=Op.CALLDATASIZE, + ), + ) diff --git a/tests/monad_eight/delegated_creates/test_create_ban.py b/tests/monad_eight/delegated_creates/test_create_ban.py new file mode 100644 index 00000000000..097c02596bc --- /dev/null +++ b/tests/monad_eight/delegated_creates/test_create_ban.py @@ -0,0 +1,155 @@ +""" +Tests for the create opcodes around a 7702-delegated EOA. + +Monad forbids the create opcodes in a frame running as a 7702-delegated +EOA, whichever code the frame executes. +""" + +import pytest +from execution_testing import ( + Account, + Address, + Alloc, + Initcode, + Op, + StateTestFiller, + Storage, + Transaction, + compute_create_address, +) + +from .helpers import ( + factory_code, + forwarding_code, + slot_call_result, + slot_created, +) + +pytestmark = pytest.mark.valid_from("MONAD_EIGHT") + + +@pytest.mark.with_all_create_opcodes +def test_create_in_delegated_code( + state_test: StateTestFiller, + pre: Alloc, + create_opcode: Op, +) -> None: + """A create opcode run as a delegated EOA's code halts the frame.""" + initcode = Initcode(deploy_code=Op.STOP) + delegate = pre.deploy_contract(factory_code(create_opcode)) + delegated = pre.fund_eoa(delegation=delegate) + + tx = Transaction(to=delegated, data=initcode, sender=pre.fund_eoa()) + + state_test( + pre=pre, + tx=tx, + post={ + delegated: Account(nonce=delegated.nonce, storage={}), + compute_create_address( + address=delegated, + nonce=delegated.nonce, + initcode=initcode, + opcode=create_opcode, + ): Account.NONEXISTENT, + }, + ) + + +@pytest.mark.with_all_call_opcodes +@pytest.mark.with_all_create_opcodes +def test_contract_calls_delegated_factory( + state_test: StateTestFiller, + pre: Alloc, + call_opcode: Op, + create_opcode: Op, +) -> None: + """ + A contract reaching a delegated EOA's creating code deploys only when + that code runs at the contract's own address. + """ + initcode = Initcode(deploy_code=Op.STOP) + delegate = pre.deploy_contract(factory_code(create_opcode)) + delegated = pre.fund_eoa(delegation=delegate) + deploys = call_opcode in (Op.DELEGATECALL, Op.CALLCODE) + storage = Storage() + storage[slot_call_result] = 1 if deploys else 0 + caller = pre.deploy_contract( + forwarding_code(call_opcode, delegated), storage=storage.canary() + ) + + tx = Transaction(to=caller, data=initcode, sender=pre.fund_eoa()) + + post: dict[Address, Account | None] = { + delegated: Account(nonce=delegated.nonce, storage={}), + } + if deploys: + created = compute_create_address( + address=caller, nonce=1, initcode=initcode, opcode=create_opcode + ) + post[caller] = Account( + nonce=2, storage={slot_call_result: 1, slot_created: created} + ) + post[created] = Account(code=Op.STOP) + else: + post[caller] = Account(nonce=1, storage={slot_call_result: 0}) + post[ + compute_create_address( + address=delegated, + nonce=delegated.nonce, + initcode=initcode, + opcode=create_opcode, + ) + ] = Account.NONEXISTENT + + state_test(pre=pre, tx=tx, post=post) + + +@pytest.mark.with_all_call_opcodes +@pytest.mark.with_all_create_opcodes +def test_delegated_frame_calls_factory( + state_test: StateTestFiller, + pre: Alloc, + call_opcode: Op, + create_opcode: Op, +) -> None: + """ + A delegated frame reaching a factory contract deploys only when the + factory runs at its own address. + """ + initcode = Initcode(deploy_code=Op.STOP) + factory = pre.deploy_contract(factory_code(create_opcode)) + delegate = pre.deploy_contract(forwarding_code(call_opcode, factory)) + deploys = call_opcode == Op.CALL + storage = Storage() + storage[slot_call_result] = 1 if deploys else 0 + delegated = pre.fund_eoa(delegation=delegate, storage=storage.canary()) + + tx = Transaction(to=delegated, data=initcode, sender=pre.fund_eoa()) + + post: dict[Address, Account | None] + if deploys: + created = compute_create_address( + address=factory, nonce=1, initcode=initcode, opcode=create_opcode + ) + post = { + delegated: Account(storage={slot_call_result: 1}), + factory: Account(nonce=2, storage={slot_created: created}), + created: Account(code=Op.STOP), + } + else: + # STATICCALL halts the factory on the static context instead. + post = { + delegated: Account( + nonce=delegated.nonce, storage={slot_call_result: 0} + ), + factory: Account(nonce=1, storage={}), + compute_create_address( + address=delegated, + nonce=delegated.nonce, + initcode=initcode, + opcode=create_opcode, + ): Account.NONEXISTENT, + } + + state_test(pre=pre, tx=tx, post=post)