diff --git a/src/ethereum/osaka/utils/address.py b/src/ethereum/osaka/utils/address.py index 1872dcf317..1e93656fb7 100644 --- a/src/ethereum/osaka/utils/address.py +++ b/src/ethereum/osaka/utils/address.py @@ -22,6 +22,9 @@ from ethereum.utils.byte import left_pad_zero_bytes from ..fork_types import Address +from ..vm.exceptions import InvalidParameter + +MAX_ADDRESS = Address(b"\xff" * 20) def to_address(data: Union[Uint, U256]) -> Address: @@ -41,6 +44,31 @@ def to_address(data: Union[Uint, U256]) -> Address: return Address(data.to_be_bytes32()[-20:]) +def to_address_unmasked(data: U256) -> Address: + """ + Convert a U256 value to a valid address (20 bytes), + raising an error if the input is too large to fit. + + Parameters + ---------- + data : + The string to be converted to bytes. + + Raises + ------ + InvalidParameter + If `data` is larger than `MAX_ADDRESS`. + + Returns + ------- + address : `Address` + The obtained address. + """ + if data > U256.from_be_bytes(MAX_ADDRESS): + raise InvalidParameter("Address is too large") + return to_address(data) + + def compute_contract_address(address: Address, nonce: Uint) -> Address: """ Computes address of the new account that needs to be created. diff --git a/src/ethereum/osaka/vm/instructions/__init__.py b/src/ethereum/osaka/vm/instructions/__init__.py index b220581c72..01ba88b2bf 100644 --- a/src/ethereum/osaka/vm/instructions/__init__.py +++ b/src/ethereum/osaka/vm/instructions/__init__.py @@ -210,6 +210,7 @@ class Ops(enum.Enum): DELEGATECALL = 0xF4 CREATE2 = 0xF5 STATICCALL = 0xFA + PAY = 0xFC REVERT = 0xFD SELFDESTRUCT = 0xFF @@ -361,6 +362,7 @@ class Ops(enum.Enum): Ops.DELEGATECALL: system_instructions.delegatecall, Ops.SELFDESTRUCT: system_instructions.selfdestruct, Ops.STATICCALL: system_instructions.staticcall, + Ops.PAY: system_instructions.pay, Ops.REVERT: system_instructions.revert, Ops.CREATE2: system_instructions.create2, } diff --git a/src/ethereum/osaka/vm/instructions/system.py b/src/ethereum/osaka/vm/instructions/system.py index ea9be98391..208aa78f08 100644 --- a/src/ethereum/osaka/vm/instructions/system.py +++ b/src/ethereum/osaka/vm/instructions/system.py @@ -31,6 +31,7 @@ compute_contract_address, compute_create2_contract_address, to_address, + to_address_unmasked, ) from ...vm.eoa_delegation import access_delegation from .. import ( @@ -742,3 +743,50 @@ def revert(evm: Evm) -> None: # PROGRAM COUNTER pass + + +def pay(evm: Evm) -> None: + """ + Transfer ether to an account without executing its code. + + Parameters + ---------- + evm : + The current EVM frame. + """ + # STACK + to = to_address_unmasked(pop(evm.stack)) + value = pop(evm.stack) + + # GAS + if to in evm.accessed_addresses: + access_gas_cost = GAS_WARM_ACCESS + else: + evm.accessed_addresses.add(to) + access_gas_cost = GAS_COLD_ACCOUNT_ACCESS + + create_gas_cost = GAS_NEW_ACCOUNT + if value == 0 or is_account_alive(evm.message.block_env.state, to): + create_gas_cost = Uint(0) + + transfer_gas_cost = Uint(0) if value == U256(0) else GAS_CALL_VALUE + + charge_gas(evm, access_gas_cost + create_gas_cost + transfer_gas_cost) + + # OPERATION + if evm.message.is_static: + raise WriteInStaticContext("Cannot PAY in static context") + + try: + move_ether( + evm.message.block_env.state, + evm.message.current_target, + to, + value, + ) + push(evm.stack, U256(1)) + except AssertionError: + push(evm.stack, U256(0)) + + # PROGRAM COUNTER + evm.pc += Uint(1)