From f3c45c153e1e7f0438457047b7b23c9e09d91a4e Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 20 Jan 2020 19:25:24 +0000 Subject: [PATCH] Support Istanbul fork (#1292) * Add new opcodes * Add new precompile * Add tests * Refactor the code * Run only on python 3.6 * Remove support of python3.5 from setup.py text * Change the TODO comment * Add a logging message * Fix py-evm version Co-authored-by: JoranHonig --- mythril/laser/ethereum/call.py | 1 + mythril/laser/ethereum/instruction_data.py | 8 ++-- mythril/laser/ethereum/instructions.py | 21 +++++++++++ mythril/laser/ethereum/natives.py | 22 ++++++++++- mythril/laser/ethereum/state/environment.py | 3 ++ mythril/support/opcodes.py | 2 + requirements.txt | 1 + setup.py | 4 +- tests/laser/Precompiles/test_blake2.py | 42 +++++++++++++++++++++ tox.ini | 2 +- 10 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 tests/laser/Precompiles/test_blake2.py diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 715c8bd4..72116107 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -265,6 +265,7 @@ def native_call( global_state.mstate.min_gas_used += native_gas_min global_state.mstate.max_gas_used += native_gas_max global_state.mstate.mem_extend(mem_out_start, mem_out_sz) + try: data = natives.native_contracts(call_address_int, call_data) except natives.NativeContractException: diff --git a/mythril/laser/ethereum/instruction_data.py b/mythril/laser/ethereum/instruction_data.py index cf936a16..b4bdb675 100644 --- a/mythril/laser/ethereum/instruction_data.py +++ b/mythril/laser/ethereum/instruction_data.py @@ -48,7 +48,7 @@ OPCODES = { STACK: BIN_OPERATOR_TUPLE, }, "ADDRESS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, - "BALANCE": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, + "BALANCE": {GAS: (700, 700), STACK: UN_OPERATOR_TUPLE}, "ORIGIN": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, "CALLER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, "CALLVALUE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, @@ -69,7 +69,7 @@ OPCODES = { GAS: (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 STACK: (4, 0), }, - "EXTCODEHASH": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, + "EXTCODEHASH": {GAS: (700, 700), STACK: UN_OPERATOR_TUPLE}, "RETURNDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, "RETURNDATACOPY": {GAS: (3, 3), STACK: (3, 0)}, "BLOCKHASH": {GAS: (20, 20), STACK: UN_OPERATOR_TUPLE}, @@ -78,13 +78,15 @@ OPCODES = { "NUMBER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, "DIFFICULTY": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, "GASLIMIT": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, + "CHAINID": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, + "SELFBALANCE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, "POP": {GAS: (2, 2), STACK: (1, 0)}, # assume 1KB memory r/w cost as upper bound "MLOAD": {GAS: (3, 96), STACK: UN_OPERATOR_TUPLE}, "MSTORE": {GAS: (3, 98), STACK: (2, 0)}, "MSTORE8": {GAS: (3, 98), STACK: (2, 0)}, # assume 64 byte r/w cost as upper bound - "SLOAD": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, + "SLOAD": {GAS: (800, 800), STACK: UN_OPERATOR_TUPLE}, "SSTORE": {GAS: (5000, 25000), STACK: (1, 0)}, "JUMP": {GAS: (8, 8), STACK: (1, 0)}, "JUMPI": {GAS: (10, 10), STACK: (2, 0)}, diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index cebd961d..0293dae1 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -929,6 +929,27 @@ class Instruction: state.stack.append(environment.sender) return [global_state] + @StateTransition() + def chainid_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ + global_state.mstate.stack.append(global_state.environment.chainid) + return [global_state] + + @StateTransition() + def selfbalance_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ + balance = global_state.environment.active_account.balance() + global_state.mstate.stack.append(balance) + return [global_state] + @StateTransition() def codesize_(self, global_state: GlobalState) -> List[GlobalState]: """ diff --git a/mythril/laser/ethereum/natives.py b/mythril/laser/ethereum/natives.py index e0f92a76..78a6dabc 100644 --- a/mythril/laser/ethereum/natives.py +++ b/mythril/laser/ethereum/natives.py @@ -2,13 +2,17 @@ import hashlib import logging -from typing import List, Union +import blake2b +from typing import List from ethereum.utils import ecrecover_to_pub from py_ecc.secp256k1 import N as secp256k1n import py_ecc.optimized_bn128 as bn128 from rlp.utils import ALL_BYTES +from eth_utils import ValidationError +from eth._utils.blake2.coders import extract_blake2b_parameters + from mythril.laser.ethereum.state.calldata import BaseCalldata, ConcreteCalldata from mythril.laser.ethereum.util import extract_copy, extract32 from ethereum.utils import ( @@ -192,6 +196,20 @@ def ec_pair(data: List[int]) -> List[int]: return [0] * 31 + [1 if result else 0] +def blake2b_fcompress(data: List[int]) -> List[int]: + """ + blake2b hashing + :param data: + :return: + """ + try: + parameters = extract_blake2b_parameters(bytes(data)) + except ValidationError as v: + logging.debug("Invalid blake2b params: {}".format(v)) + return [] + return list(bytearray(blake2b.compress(*parameters))) + + PRECOMPILE_FUNCTIONS = ( ecrecover, sha256, @@ -201,7 +219,9 @@ PRECOMPILE_FUNCTIONS = ( ec_add, ec_mul, ec_pair, + blake2b_fcompress, ) + PRECOMPILE_COUNT = len(PRECOMPILE_FUNCTIONS) diff --git a/mythril/laser/ethereum/state/environment.py b/mythril/laser/ethereum/state/environment.py index 4cfb51e0..f3dbfcce 100644 --- a/mythril/laser/ethereum/state/environment.py +++ b/mythril/laser/ethereum/state/environment.py @@ -41,7 +41,10 @@ class Environment: self.active_function_name = "" self.address = active_account.address + + # TODO: Add tx_2 > tx_1 then block_no(tx_2) > block_no(tx_1) self.block_number = symbol_factory.BitVecSym("block_number", 256) + self.chainid = symbol_factory.BitVecSym("chain_id", 256) # Ib self.code = active_account.code if code is None else code diff --git a/mythril/support/opcodes.py b/mythril/support/opcodes.py index d70f0309..70501a91 100644 --- a/mythril/support/opcodes.py +++ b/mythril/support/opcodes.py @@ -51,6 +51,8 @@ opcodes = { 0x43: ("NUMBER", 0, 1, 2), 0x44: ("DIFFICULTY", 0, 1, 2), 0x45: ("GASLIMIT", 0, 1, 2), + 0x46: ("CHAINID", 0, 1, 2), + 0x47: ("SELFBALANCE", 0, 1, 5), 0x50: ("POP", 1, 0, 2), 0x51: ("MLOAD", 1, 1, 3), 0x52: ("MSTORE", 2, 0, 3), diff --git a/requirements.txt b/requirements.txt index 13b69082..0c5a2394 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ mock persistent>=4.2.0 plyvel py-flags +py-evm==0.3.0a13 py-solc-x==0.6.0 py-solc pytest>=3.6.0 diff --git a/setup.py b/setup.py index 5955ab26..168fe6f4 100755 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ DESCRIPTION = "Security analysis tool for Ethereum smart contracts" URL = "https://github.com/ConsenSys/mythril" AUTHOR = "ConsenSys Dilligence" AUTHOR_MAIL = None -REQUIRES_PYTHON = ">=3.5.0" +REQUIRES_PYTHON = ">=3.6.0" # What packages are required for this module to be executed? @@ -52,6 +52,7 @@ REQUIRED = [ "ethereum-input-decoder>=0.2.2", "matplotlib", "pythx", + "py-evm==0.3.0a13", ] TESTS_REQUIRE = ["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"] @@ -118,7 +119,6 @@ setup( "Intended Audience :: Science/Research", "Topic :: Software Development :: Disassemblers", "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", ], diff --git a/tests/laser/Precompiles/test_blake2.py b/tests/laser/Precompiles/test_blake2.py new file mode 100644 index 00000000..5f400e38 --- /dev/null +++ b/tests/laser/Precompiles/test_blake2.py @@ -0,0 +1,42 @@ +import pytest +from mythril.laser.ethereum.natives import blake2b_fcompress + +# Test cases taken from https://eips.ethereum.org/EIPS/eip-152. +# One of the test case is expected to take a few hrs so I ignored it +@pytest.mark.parametrize( + "input_hex, expected_result", + ( + ("", ""), + ( + "00000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", # noqa: E501 + "", + ), + ( + "000000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", # noqa: E501 + "", + ), + ( + "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000002", # noqa: E501 + "", + ), + ( + "0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", # noqa: E501 + "08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b", # noqa: E501 + ), + ( + "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", # noqa: E501 + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923", # noqa: E501 + ), + ( + "0000000c48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000", # noqa: E501 + "75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735", # noqa: E501 + ), + ( + "0000000148c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001", # noqa: E501 + "b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421", # noqa: E501 + ), + ), +) +def test_blake2(input_hex, expected_result): + input_hex = bytearray.fromhex(input_hex) + assert blake2b_fcompress(input_hex) == list(bytearray.fromhex(expected_result)) diff --git a/tox.ini b/tox.ini index ec2ec40d..804b6791 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35, py36 +envlist = py36 [testenv] deps =