diff --git a/eth/vm/forks/constantinople/opcodes.py b/eth/vm/forks/constantinople/opcodes.py index 0181256bad..a3a5392f81 100644 --- a/eth/vm/forks/constantinople/opcodes.py +++ b/eth/vm/forks/constantinople/opcodes.py @@ -1,8 +1,40 @@ import copy +from cytoolz import ( + merge +) +from eth import ( + constants +) +from eth.vm import ( + mnemonics, + opcode_values, +) from eth.vm.forks.byzantium.opcodes import ( BYZANTIUM_OPCODES ) +from eth.vm.logic import ( + arithmetic +) +from eth.vm.opcode import ( + as_opcode +) + +UPDATED_OPCODES = { + opcode_values.SHL: as_opcode( + logic_fn=arithmetic.shl, + mnemonic=mnemonics.SHL, + gas_cost=constants.GAS_VERYLOW, + ), + opcode_values.SHR: as_opcode( + logic_fn=arithmetic.shr, + mnemonic=mnemonics.SHR, + gas_cost=constants.GAS_VERYLOW, + ), +} -CONSTANTINOPLE_OPCODES = copy.deepcopy(BYZANTIUM_OPCODES) +CONSTANTINOPLE_OPCODES = merge( + copy.deepcopy(BYZANTIUM_OPCODES), + UPDATED_OPCODES, +) diff --git a/eth/vm/logic/arithmetic.py b/eth/vm/logic/arithmetic.py index 50ce09366a..a667ba4eae 100644 --- a/eth/vm/logic/arithmetic.py +++ b/eth/vm/logic/arithmetic.py @@ -177,3 +177,31 @@ def signextend(computation): result = value computation.stack_push(result) + + +def shl(computation): + """ + Bitwise left shift + """ + shift_length, value = computation.stack_pop(num_items=2, type_hint=constants.UINT256) + + if shift_length >= 256: + result = 0 + else: + result = (value << shift_length) & constants.UINT_256_MAX + + computation.stack_push(result) + + +def shr(computation): + """ + Bitwise right shift + """ + shift_length, value = computation.stack_pop(num_items=2, type_hint=constants.UINT256) + + if shift_length >= 256: + result = 0 + else: + result = (value >> shift_length) & constants.UINT_256_MAX + + computation.stack_push(result) diff --git a/eth/vm/mnemonics.py b/eth/vm/mnemonics.py index 40e498788d..a830f37f1d 100644 --- a/eth/vm/mnemonics.py +++ b/eth/vm/mnemonics.py @@ -13,6 +13,8 @@ MULMOD = 'MULMOD' EXP = 'EXP' SIGNEXTEND = 'SIGNEXTEND' +SHL = 'SHL' +SHR = 'SHR' # # Comparisons # diff --git a/eth/vm/opcode_values.py b/eth/vm/opcode_values.py index 1fb198106b..264fe2dbe7 100644 --- a/eth/vm/opcode_values.py +++ b/eth/vm/opcode_values.py @@ -29,6 +29,8 @@ XOR = 0x18 NOT = 0x19 BYTE = 0x1a +SHL = 0x1b +SHR = 0x1c # diff --git a/tests/core/opcodes/test_opcodes.py b/tests/core/opcodes/test_opcodes.py new file mode 100644 index 0000000000..897ace3cee --- /dev/null +++ b/tests/core/opcodes/test_opcodes.py @@ -0,0 +1,267 @@ +import pytest + +from eth_utils import ( + decode_hex, + encode_hex, + to_canonical_address, + int_to_big_endian, +) + +from eth import ( + constants +) +from eth.utils.padding import ( + pad32 +) +from eth.vm import ( + opcode_values +) +from eth.vm.forks import ( + ConstantinopleVM, + ByzantiumVM, + SpuriousDragonVM, + TangerineWhistleVM, + HomesteadVM, + FrontierVM, +) + +from eth.vm.message import ( + Message, +) + + +NORMALIZED_ADDRESS_A = "0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" +NORMALIZED_ADDRESS_B = "0xcd1722f3947def4cf144679da39c4c32bdc35681" +CANONICAL_ADDRESS_A = to_canonical_address("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6") +CANONICAL_ADDRESS_B = to_canonical_address("0xcd1722f3947def4cf144679da39c4c32bdc35681") + + +def prepare_computation(vm_class): + + message = Message( + to=CANONICAL_ADDRESS_A, + sender=CANONICAL_ADDRESS_B, + value=100, + data=b'', + code=b'', + gas=800, + ) + + tx_context = vm_class._state_class.transaction_context_class( + gas_price=1, + origin=CANONICAL_ADDRESS_B, + ) + + computation = vm_class._state_class.computation_class( + state=None, + message=message, + transaction_context=tx_context, + ) + return computation + + +@pytest.mark.parametrize( + 'vm_class, val1, val2, expected', + ( + (ByzantiumVM, 2, 4, 6,), + (SpuriousDragonVM, 2, 4, 6,), + (TangerineWhistleVM, 2, 4, 6,), + (HomesteadVM, 2, 4, 6,), + (FrontierVM, 2, 4, 6,), + ) +) +def test_add(vm_class, val1, val2, expected): + computation = prepare_computation(vm_class) + computation.stack_push(val1) + computation.stack_push(val2) + computation.opcodes[opcode_values.ADD](computation) + + result = computation.stack_pop(type_hint=constants.UINT256) + + assert result == expected + + +@pytest.mark.parametrize( + 'vm_class, val1, val2, expected', + ( + (ByzantiumVM, 2, 2, 4,), + (SpuriousDragonVM, 2, 2, 4,), + (TangerineWhistleVM, 2, 2, 4,), + (HomesteadVM, 2, 2, 4,), + (FrontierVM, 2, 2, 4,), + ) +) +def test_mul(vm_class, val1, val2, expected): + computation = prepare_computation(vm_class) + computation.stack_push(val1) + computation.stack_push(val2) + computation.opcodes[opcode_values.MUL](computation) + + result = computation.stack_pop(type_hint=constants.UINT256) + + assert result == expected + + +@pytest.mark.parametrize( + # Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left + 'vm_class, val1, val2, expected', + ( + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x00', + '0x0000000000000000000000000000000000000000000000000000000000000001', + ), + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x01', + '0x0000000000000000000000000000000000000000000000000000000000000002', + ), + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0xff', + '0x8000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0100', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0101', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0x00', + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0x01', + '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0xff', + '0x8000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0x0100', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x01', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0x01', + '0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe', + ), + ) +) +def test_shl(vm_class, val1, val2, expected): + computation = prepare_computation(vm_class) + computation.stack_push(decode_hex(val1)) + computation.stack_push(decode_hex(val2)) + computation.opcodes[opcode_values.SHL](computation) + + result = computation.stack_pop(type_hint=constants.UINT256) + + assert encode_hex(pad32(int_to_big_endian(result))) == expected + + +@pytest.mark.parametrize( + # Cases: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right + 'vm_class, val1, val2, expected', + ( + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x00', + '0x0000000000000000000000000000000000000000000000000000000000000001', + ), + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x01', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x8000000000000000000000000000000000000000000000000000000000000000', + '0x01', + '0x4000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x8000000000000000000000000000000000000000000000000000000000000000', + '0xff', + '0x0000000000000000000000000000000000000000000000000000000000000001', + ), + ( + ConstantinopleVM, + '0x8000000000000000000000000000000000000000000000000000000000000000', + '0x0100', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x8000000000000000000000000000000000000000000000000000000000000000', + '0x0101', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0x00', + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0x01', + '0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0xff', + '0x0000000000000000000000000000000000000000000000000000000000000001', + ), + ( + ConstantinopleVM, + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + '0x0100', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ( + ConstantinopleVM, + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x01', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ), + ) +) +def test_shr(vm_class, val1, val2, expected): + computation = prepare_computation(vm_class) + computation.stack_push(decode_hex(val1)) + computation.stack_push(decode_hex(val2)) + computation.opcodes[opcode_values.SHR](computation) + + result = computation.stack_pop(type_hint=constants.UINT256) + assert encode_hex(pad32(int_to_big_endian(result))) == expected