diff --git a/eth/vm/forks/constantinople/constants.py b/eth/vm/forks/constantinople/constants.py new file mode 100644 index 0000000000..09b76e041a --- /dev/null +++ b/eth/vm/forks/constantinople/constants.py @@ -0,0 +1 @@ +GAS_EXTCODEHASH_EIP1052 = 400 diff --git a/eth/vm/forks/constantinople/opcodes.py b/eth/vm/forks/constantinople/opcodes.py index a63821c0a1..039f279ccd 100644 --- a/eth/vm/forks/constantinople/opcodes.py +++ b/eth/vm/forks/constantinople/opcodes.py @@ -13,8 +13,12 @@ from eth.vm.forks.byzantium.opcodes import ( BYZANTIUM_OPCODES ) +from eth.vm.forks.constantinople.constants import ( + GAS_EXTCODEHASH_EIP1052 +) from eth.vm.logic import ( - arithmetic + arithmetic, + context, ) from eth.vm.opcode import ( as_opcode @@ -37,6 +41,11 @@ mnemonic=mnemonics.SAR, gas_cost=constants.GAS_VERYLOW, ), + opcode_values.EXTCODEHASH: as_opcode( + logic_fn=context.extcodehash, + mnemonic=mnemonics.EXTCODEHASH, + gas_cost=GAS_EXTCODEHASH_EIP1052, + ), } CONSTANTINOPLE_OPCODES = merge( diff --git a/eth/vm/logic/context.py b/eth/vm/logic/context.py index 9c6324cd5b..1b14886ba2 100644 --- a/eth/vm/logic/context.py +++ b/eth/vm/logic/context.py @@ -138,6 +138,20 @@ def extcodecopy(computation): computation.memory_write(mem_start_position, size, padded_code_bytes) +def extcodehash(computation): + """ + Return the code hash for a given address. + EIP: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1052.md + """ + account = force_bytes_to_address(computation.stack_pop(type_hint=constants.BYTES)) + account_db = computation.state.account_db + + if not account_db.account_exists(account): + computation.stack_push(constants.NULL_BYTE) + else: + computation.stack_push(account_db.get_code_hash(account)) + + def returndatasize(computation): size = len(computation.return_data) computation.stack_push(size) diff --git a/eth/vm/mnemonics.py b/eth/vm/mnemonics.py index 09611a4126..e3bb17b728 100644 --- a/eth/vm/mnemonics.py +++ b/eth/vm/mnemonics.py @@ -50,6 +50,7 @@ GASPRICE = 'GASPRICE' EXTCODESIZE = 'EXTCODESIZE' EXTCODECOPY = 'EXTCODECOPY' +EXTCODEHASH = 'EXTCODEHASH' RETURNDATASIZE = 'RETURNDATASIZE' RETURNDATACOPY = 'RETURNDATACOPY' # diff --git a/eth/vm/opcode_values.py b/eth/vm/opcode_values.py index baac7f4024..33f8fe135c 100644 --- a/eth/vm/opcode_values.py +++ b/eth/vm/opcode_values.py @@ -56,6 +56,7 @@ GASPRICE = 0x3a EXTCODESIZE = 0x3b EXTCODECOPY = 0x3c +EXTCODEHASH = 0x3f RETURNDATASIZE = 0x3d RETURNDATACOPY = 0x3e diff --git a/tests/core/opcodes/test_opcodes.py b/tests/core/opcodes/test_opcodes.py index 5ecd0f0e21..6e9ca5ba18 100644 --- a/tests/core/opcodes/test_opcodes.py +++ b/tests/core/opcodes/test_opcodes.py @@ -6,10 +6,18 @@ to_canonical_address, int_to_big_endian, ) - from eth import ( constants ) +from eth.db.backends.memory import ( + MemoryDB +) +from eth.db.chain import ( + ChainDB +) +from eth.rlp.headers import ( + BlockHeader, +) from eth.utils.padding import ( pad32 ) @@ -24,7 +32,6 @@ HomesteadVM, FrontierVM, ) - from eth.vm.message import ( Message, ) @@ -32,8 +39,16 @@ NORMALIZED_ADDRESS_A = "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" NORMALIZED_ADDRESS_B = "0xcd1722f3947def4cf144679da39c4c32bdc35681" +ADDRESS_WITH_CODE = ("0xddd722f3947def4cf144679da39c4c32bdc35681", b'pseudocode') +EMPTY_ADDRESS_IN_STATE = NORMALIZED_ADDRESS_A +ADDRESS_NOT_IN_STATE = NORMALIZED_ADDRESS_B CANONICAL_ADDRESS_A = to_canonical_address("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6") CANONICAL_ADDRESS_B = to_canonical_address("0xcd1722f3947def4cf144679da39c4c32bdc35681") +GENESIS_HEADER = BlockHeader( + difficulty=constants.GENESIS_DIFFICULTY, + block_number=constants.GENESIS_BLOCK_NUMBER, + gas_limit=constants.GENESIS_GAS_LIMIT, +) def prepare_computation(vm_class): @@ -52,11 +67,17 @@ def prepare_computation(vm_class): origin=CANONICAL_ADDRESS_B, ) + vm = vm_class(GENESIS_HEADER, ChainDB(MemoryDB())) + computation = vm_class._state_class.computation_class( - state=None, + state=vm.state, message=message, transaction_context=tx_context, ) + + computation.state.account_db.touch_account(decode_hex(EMPTY_ADDRESS_IN_STATE)) + computation.state.account_db.set_code(decode_hex(ADDRESS_WITH_CODE[0]), ADDRESS_WITH_CODE[1]) + return computation @@ -378,3 +399,34 @@ def test_sar(vm_class, val1, val2, expected): result = computation.stack_pop(type_hint=constants.UINT256) assert encode_hex(pad32(int_to_big_endian(result))) == expected + + +@pytest.mark.parametrize( + 'vm_class, address, expected', + ( + ( + ConstantinopleVM, + ADDRESS_NOT_IN_STATE, + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + EMPTY_ADDRESS_IN_STATE, + '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', + ), + ( + ConstantinopleVM, + ADDRESS_WITH_CODE[0], + # equivalent to encode_hex(keccak(ADDRESS_WITH_CODE[1])), + '0xb6f5188e2984211a0de167a56a92d85bee084d7a469d97a59e1e2b573dbb4301' + ), + ) +) +def test_extcodehash(vm_class, address, expected): + computation = prepare_computation(vm_class) + + computation.stack_push(decode_hex(address)) + computation.opcodes[opcode_values.EXTCODEHASH](computation) + + result = computation.stack_pop(type_hint=constants.BYTES) + assert encode_hex(pad32(result)) == expected