diff --git a/src/ethereum/cancun/vm/__init__.py b/src/ethereum/cancun/vm/__init__.py index b4cbf42ea2..bd7e40120f 100644 --- a/src/ethereum/cancun/vm/__init__.py +++ b/src/ethereum/cancun/vm/__init__.py @@ -19,8 +19,9 @@ from ethereum_types.bytes import Bytes, Bytes0, Bytes32 from ethereum_types.numeric import U64, U256, Uint -from ethereum.crypto.hash import Hash32 +from ethereum.crypto.hash import Hash32, keccak256 from ethereum.exceptions import EthereumException +from ethereum.utils.byte import left_pad_zero_bytes from ..blocks import Log from ..fork_types import Address, VersionedHash @@ -150,3 +151,32 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None: ): evm.touched_accounts.add(RIPEMD160_ADDRESS) evm.gas_left += child_evm.gas_left + +def eth_transfer_log(sender: Address, recipient: Address, amount: U256) -> Log: + """ + EIP-7708 style logs for all kinds of ETH transfers + + Parameters + ---------- + sender : + The address of the sender + recipient : + The address of the recipient + amount : + The amount transferred + Returns + ------- + log_entry: + A log entry that can be appended to the current evm context + """ + magic_signature = "MAGIC_00" + topic1 = keccak256(magic_signature.encode()) # Magic Signature + topic2 = Hash32(left_pad_zero_bytes(sender, 32)) + topic3 = Hash32(left_pad_zero_bytes(recipient, 32)) + amount_bytes = amount.to_bytes(32, byteorder='big') + + return Log( + address=sender, + topics=(topic1, topic2, topic3), + data=amount_bytes, + ) diff --git a/src/ethereum/cancun/vm/instructions/system.py b/src/ethereum/cancun/vm/instructions/system.py index da47df38cf..bf45878e05 100644 --- a/src/ethereum/cancun/vm/instructions/system.py +++ b/src/ethereum/cancun/vm/instructions/system.py @@ -37,6 +37,7 @@ Message, incorporate_child_on_error, incorporate_child_on_success, + eth_transfer_log ) from ..exceptions import OutOfGasError, Revert, WriteInStaticContext from ..gas import ( @@ -321,6 +322,7 @@ def generic_call( evm.return_data = child_evm.output push(evm.stack, U256(0)) else: + # child CALL logs would be appended to parent logs incorporate_child_on_success(evm, child_evm) evm.return_data = child_evm.output push(evm.stack, U256(1)) @@ -384,7 +386,7 @@ def call(evm: Evm) -> None: raise WriteInStaticContext evm.memory += b"\x00" * extend_memory.expand_by sender_balance = get_account( - evm.env.state, evm.message.current_target + evm.env.state, evm.message.current_target # current_target is the contract that is calling the call opcode ).balance if sender_balance < value: push(evm.stack, U256(0)) @@ -522,6 +524,11 @@ def selfdestruct(evm: Evm) -> None: originator_balance, ) + # check if amount was non zero, then update evm logs with the new log + if originator_balance > 0: + log_entry = eth_transfer_log(originator, beneficiary, originator_balance) + evm.logs = evm.logs + (log_entry,) + # register account for deletion only if it was created # in the same transaction if originator in evm.env.state.created_accounts: diff --git a/src/ethereum/cancun/vm/interpreter.py b/src/ethereum/cancun/vm/interpreter.py index cfb5f32843..e7011f53aa 100644 --- a/src/ethereum/cancun/vm/interpreter.py +++ b/src/ethereum/cancun/vm/interpreter.py @@ -49,7 +49,7 @@ from ..vm import Message from ..vm.gas import GAS_CODE_DEPOSIT, charge_gas from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS -from . import Environment, Evm +from . import Environment, Evm, eth_transfer_log from .exceptions import ( AddressCollision, ExceptionalHalt, @@ -89,6 +89,7 @@ class MessageCallOutput: error: Optional[EthereumException] +## This is where a general transfer tx would start from def process_message_call( message: Message, env: Environment ) -> MessageCallOutput: @@ -121,6 +122,7 @@ def process_message_call( evm = process_create_message(message, env) else: evm = process_message(message, env) + ## can append to logs here if account_exists_and_is_empty(env.state, Address(message.target)): evm.touched_accounts.add(Address(message.target)) @@ -232,12 +234,18 @@ def process_message(message: Message, env: Environment) -> Evm: touch_account(env.state, message.current_target) + log_entry = () if message.should_transfer_value and message.value != 0: move_ether( env.state, message.caller, message.current_target, message.value ) + ## add log here for transfer CALL (child evm calls) and normal ETH value transfers + log_entry = eth_transfer_log(message.caller, message.current_target, message.value) + + # this ensures value transfer tx logs are before the child evm logs + # both eth value transfers and CALL opcode invocations reach this execution phase + evm = execute_code(message, env, log_entry) - evm = execute_code(message, env) if evm.error: # revert state to the last saved checkpoint # since the message call resulted in an error @@ -247,7 +255,7 @@ def process_message(message: Message, env: Environment) -> Evm: return evm -def execute_code(message: Message, env: Environment) -> Evm: +def execute_code(message: Message, env: Environment, log_entry: Log) -> Evm: """ Executes bytecode present in the `message`. @@ -274,7 +282,7 @@ def execute_code(message: Message, env: Environment) -> Evm: gas_left=message.gas, env=env, valid_jump_destinations=valid_jump_destinations, - logs=(), + logs=(log_entry,) if log_entry != () else (), refund_counter=0, running=True, message=message, @@ -286,6 +294,7 @@ def execute_code(message: Message, env: Environment) -> Evm: accessed_addresses=message.accessed_addresses, accessed_storage_keys=message.accessed_storage_keys, ) + try: if evm.message.code_address in PRE_COMPILED_CONTRACTS: evm_trace(evm, PrecompileStart(evm.message.code_address))