diff --git a/.gitignore b/.gitignore index 775ecfe0..761f1c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -175,7 +175,7 @@ lol* coverage_html_report/ tests/testdata/outputs_current/ tests/testdata/outputs_current_laser_result/ -tests/mythril_dir/signatures.db +tests/testdata/mythril_config_inputs/config.ini # VSCode .vscode diff --git a/README.md b/README.md index 67127df9..53fbd1fa 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ [![Sonarcloud - Maintainability](https://sonarcloud.io/api/project_badges/measure?project=mythril&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=mythril) [![Downloads](https://pepy.tech/badge/mythril)](https://pepy.tech/project/mythril) -Mythril Classic is an open-source security analysis tool for Ethereum smart contracts. It uses symbolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities. +Mythril Classic is an open-source security analysis tool for Ethereum smart contracts. It uses symbolic analysis, taint analysis and control flow checking to detect a variety of security vulnerabilities. It's also an experimental tool designed for security pros. If you a smart contract developer you might prefer easier-to-use tools such as: -This is an experimental tool designed for security guys. If you a smart contract developer you might prefer [Truffle Security](https://github.com/ConsenSys/truffle-security) or other convenient tools built on the [MythX API](https://mythx.io). +- [Sabre](https://github.com/b-mueller/sabre) +- [Truffle Security](https://github.com/ConsenSys/truffle-security) Whether you want to contribute, need support, or want to learn what we have cooking for the future, our [Discord server](https://discord.gg/E3YrVtG) will serve your needs. diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py index 46795b8d..a3dfed4f 100644 --- a/mythril/analysis/report.py +++ b/mythril/analysis/report.py @@ -3,6 +3,7 @@ import logging import json import operator from jinja2 import PackageLoader, Environment +from typing import Dict, List import _pysha3 as sha3 import hashlib @@ -106,6 +107,17 @@ class Issue: return issue + def _set_internal_compiler_error(self): + """ + Adds the false positive to description and changes severity to low + """ + self.severity = "Low" + self.description_tail += ( + " This issue is reported for internal compiler generated code." + ) + self.description = "%s\n%s" % (self.description_head, self.description_tail) + self.code = "" + def add_code_info(self, contract): """ @@ -118,6 +130,8 @@ class Issue: self.filename = codeinfo.filename self.code = codeinfo.code self.lineno = codeinfo.lineno + if self.lineno is None: + self._set_internal_compiler_error() self.source_mapping = codeinfo.solc_mapping else: self.source_mapping = self.address @@ -130,7 +144,7 @@ class Report: loader=PackageLoader("mythril.analysis"), trim_blocks=True ) - def __init__(self, verbose=False, source=None): + def __init__(self, verbose=False, source=None, exceptions=None): """ :param verbose: @@ -140,6 +154,7 @@ class Report: self.solc_version = "" self.meta = {} self.source = source or Source() + self.exceptions = exceptions or [] def sorted_issues(self): """ @@ -177,6 +192,14 @@ class Report: result = {"success": True, "error": None, "issues": self.sorted_issues()} return json.dumps(result, sort_keys=True) + def _get_exception_data(self) -> dict: + if not self.exceptions: + return {} + logs = [] # type: List[Dict] + for exception in self.exceptions: + logs += [{"level": "error", "hidden": "true", "error": exception}] + return {"logs": logs} + def as_swc_standard_format(self): """Format defined for integration and correlation. @@ -208,17 +231,17 @@ class Report: }, "severity": issue.severity, "locations": [{"sourceMap": "%d:1:%d" % (issue.address, idx)}], - "extra": {}, + "extra": {"discoveryTime": int(issue.discovery_time * 10 ** 9)}, } ) - + meta_data = self._get_exception_data() result = [ { "issues": _issues, "sourceType": "raw-bytecode", "sourceFormat": "evm-byzantium-bytecode", "sourceList": source_list, - "meta": {}, + "meta": meta_data, } ] diff --git a/mythril/analysis/security.py b/mythril/analysis/security.py index d8e804db..abacf49a 100644 --- a/mythril/analysis/security.py +++ b/mythril/analysis/security.py @@ -1,7 +1,7 @@ """This module contains functionality for hooking in detection modules and executing them.""" from collections import defaultdict -from ethereum.opcodes import opcodes +from mythril.support.opcodes import opcodes from mythril.analysis import modules import pkgutil import importlib.util diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index e2a0d1a2..f4477fad 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -193,7 +193,7 @@ class SymExecWrapper: ) elif op == "SSTORE": - stack = copy.deepcopy(state.mstate.stack) + stack = copy.copy(state.mstate.stack) address = state.environment.active_account.address index, value = stack.pop(), stack.pop() diff --git a/mythril/disassembler/asm.py b/mythril/disassembler/asm.py index cbbe1881..905b8d85 100644 --- a/mythril/disassembler/asm.py +++ b/mythril/disassembler/asm.py @@ -4,12 +4,12 @@ code disassembly.""" import re from collections import Generator -from ethereum.opcodes import opcodes +from mythril.support.opcodes import opcodes regex_PUSH = re.compile(r"^PUSH(\d*)$") # Additional mnemonic to catch failed assertions -opcodes[254] = ["ASSERT_FAIL", 0, 0, 0] +opcodes[254] = ("ASSERT_FAIL", 0, 0, 0) class EvmInstruction: diff --git a/mythril/ethereum/interface/leveldb/state.py b/mythril/ethereum/interface/leveldb/state.py index 4cd5c133..4306897e 100644 --- a/mythril/ethereum/interface/leveldb/state.py +++ b/mythril/ethereum/interface/leveldb/state.py @@ -150,7 +150,7 @@ class State: rlpdata = self.trie.get(addr) if rlpdata != trie.BLANK_NODE: - o = rlp.decode(rlpdata, Account, db=self.db, address=addr) + o = rlp.decode(rlpdata, Account, db=self.db, addr=addr) else: o = Account.blank_account(self.db, addr, 0) self.cache[addr] = o @@ -162,4 +162,4 @@ class State: """iterates through trie to and yields non-blank leafs as accounts.""" for address_hash, rlpdata in self.secure_trie.trie.iter_branch(): if rlpdata != trie.BLANK_NODE: - yield rlp.decode(rlpdata, Account, db=self.db, address=address_hash) + yield rlp.decode(rlpdata, Account, db=self.db, addr=address_hash) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 7ea614b8..514f039d 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -12,10 +12,16 @@ import os import sys import coloredlogs +import traceback import mythril.support.signatures as sigs from mythril.exceptions import AddressNotFoundError, CriticalError -from mythril.mythril import Mythril +from mythril.mythril import ( + MythrilAnalyzer, + MythrilDisassembler, + MythrilConfig, + MythrilLevelDB, +) from mythril.version import VERSION # logging.basicConfig(level=logging.DEBUG) @@ -25,23 +31,48 @@ log = logging.getLogger(__name__) def exit_with_error(format_, message): """ - :param format_: :param message: """ if format_ == "text" or format_ == "markdown": log.error(message) - else: + elif format_ == "json": result = {"success": False, "error": str(message), "issues": []} print(json.dumps(result)) + else: + result = [ + { + "issues": [], + "sourceType": "", + "sourceFormat": "", + "sourceList": [], + "meta": { + "logs": [{"level": "error", "hidden": "true", "error": message}] + }, + } + ] + print(json.dumps(result)) sys.exit() -def main(): +def main() -> None: """The main CLI interface entry point.""" parser = argparse.ArgumentParser( description="Security analysis of Ethereum smart contracts" ) + create_parser(parser) + + # Get config values + + args = parser.parse_args() + parse_args(parser=parser, args=args) + + +def create_parser(parser: argparse.ArgumentParser) -> None: + """ + Creates the parser by setting all the possible arguments + :param parser: The parser + """ parser.add_argument("solidity_file", nargs="*") commands = parser.add_argument_group("commands") @@ -227,25 +258,8 @@ def main(): ) parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) - # Get config values - - args = parser.parse_args() - - if args.epic: - path = os.path.dirname(os.path.realpath(__file__)) - sys.argv.remove("--epic") - os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") - sys.exit() - - if args.version: - if args.outform == "json": - print(json.dumps({"version_str": VERSION})) - else: - print("Mythril version {}".format(VERSION)) - sys.exit() - - # Parse cmdline args +def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace): if not ( args.search or args.hash @@ -298,194 +312,224 @@ def main(): "--enable-iprof must be used with one of -g, --graph, -x, --fire-lasers, -j and --statespace-json", ) - # -- commands -- + +def quick_commands(args: argparse.Namespace): if args.hash: - print(Mythril.hash_for_function_signature(args.hash)) + print(MythrilDisassembler.hash_for_function_signature(args.hash)) sys.exit() - try: - # the mythril object should be our main interface - # infura = None, rpc = None, rpctls = None - # solc_args = None, dynld = None, max_recursion_depth = 12): - - mythril = Mythril( - solv=args.solv, - dynld=args.dynld, - onchain_storage_access=(not args.no_onchain_storage_access), - solc_args=args.solc_args, - enable_online_lookup=args.query_signature, + +def set_config(args: argparse.Namespace): + config = MythrilConfig() + if args.dynld or not args.no_onchain_storage_access and not (args.rpc or args.i): + config.set_api_from_config_path() + + if args.address: + # Establish RPC connection if necessary + config.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) + elif args.search or args.contract_hash_to_address: + # Open LevelDB if necessary + config.set_api_leveldb( + config.leveldb_dir if not args.leveldb_dir else args.leveldb_dir ) - if ( - args.dynld - or not args.no_onchain_storage_access - and not (args.rpc or args.i) - ): - mythril.set_api_from_config_path() - - if args.address: - # Establish RPC connection if necessary - mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) - elif args.search or args.contract_hash_to_address: - # Open LevelDB if necessary - mythril.set_api_leveldb( - mythril.leveldb_dir if not args.leveldb_dir else args.leveldb_dir - ) + return config + +def leveldb_search(config: MythrilConfig, args: argparse.Namespace): + if args.search or args.contract_hash_to_address: + leveldb_searcher = MythrilLevelDB(config.eth_db) if args.search: # Database search ops - mythril.search_db(args.search) - sys.exit() + leveldb_searcher.search_db(args.search) - if args.contract_hash_to_address: + else: # search corresponding address try: - mythril.contract_hash_to_address(args.contract_hash_to_address) + leveldb_searcher.contract_hash_to_address(args.contract_hash_to_address) except AddressNotFoundError: print("Address not found.") - sys.exit() + sys.exit() - if args.truffle: - try: - # not really pythonic atm. needs refactoring - mythril.analyze_truffle_project(args) - except FileNotFoundError: - print( - "Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully." - ) - sys.exit() - # Load / compile input contracts - address = None - - if args.code: - # Load from bytecode - code = args.code[2:] if args.code.startswith("0x") else args.code - address, _ = mythril.load_from_bytecode(code, args.bin_runtime) - elif args.codefile: - bytecode = "".join([l.strip() for l in args.codefile if len(l.strip()) > 0]) - bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode - address, _ = mythril.load_from_bytecode(bytecode, args.bin_runtime) - elif args.address: - # Get bytecode from a contract address - address, _ = mythril.load_from_address(args.address) - elif args.solidity_file: - # Compile Solidity source file(s) - if args.graph and len(args.solidity_file) > 1: - exit_with_error( - args.outform, - "Cannot generate call graphs from multiple input files. Please do it one at a time.", - ) - address, _ = mythril.load_from_solidity(args.solidity_file) # list of files - else: +def get_code(disassembler: MythrilDisassembler, args: argparse.Namespace): + address = None + if args.code: + # Load from bytecode + code = args.code[2:] if args.code.startswith("0x") else args.code + address, _ = disassembler.load_from_bytecode(code, args.bin_runtime) + elif args.codefile: + bytecode = "".join([l.strip() for l in args.codefile if len(l.strip()) > 0]) + bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode + address, _ = disassembler.load_from_bytecode(bytecode, args.bin_runtime) + elif args.address: + # Get bytecode from a contract address + address, _ = disassembler.load_from_address(args.address) + elif args.solidity_file: + # Compile Solidity source file(s) + if args.graph and len(args.solidity_file) > 1: exit_with_error( args.outform, - "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES", + "Cannot generate call graphs from multiple input files. Please do it one at a time.", ) + address, _ = disassembler.load_from_solidity( + args.solidity_file + ) # list of files + else: + exit_with_error( + args.outform, + "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES", + ) + return address - # Commands - if args.storage: - if not args.address: - exit_with_error( - args.outform, - "To read storage, provide the address of a deployed contract with the -a option.", - ) +def execute_command( + disassembler: MythrilDisassembler, + address: str, + parser: argparse.ArgumentParser, + args: argparse.Namespace, +): - storage = mythril.get_state_variable_from_storage( - address=address, - params=[a.strip() for a in args.storage.strip().split(",")], + if args.storage: + if not args.address: + exit_with_error( + args.outform, + "To read storage, provide the address of a deployed contract with the -a option.", ) - print(storage) - elif args.disassemble: - # or mythril.disassemble(mythril.contracts[0]) + storage = disassembler.get_state_variable_from_storage( + address=address, params=[a.strip() for a in args.storage.strip().split(",")] + ) + print(storage) + return + + analyzer = MythrilAnalyzer( + strategy=args.strategy, + disassembler=disassembler, + address=address, + max_depth=args.max_depth, + execution_timeout=args.execution_timeout, + create_timeout=args.create_timeout, + enable_iprof=args.enable_iprof, + onchain_storage_access=not args.no_onchain_storage_access, + ) + + if args.disassemble: + # or mythril.disassemble(mythril.contracts[0]) + + if disassembler.contracts[0].code: + print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) + if disassembler.contracts[0].creation_code: + print("Disassembly: \n" + disassembler.contracts[0].get_creation_easm()) + + elif args.graph or args.fire_lasers: + if not disassembler.contracts: + exit_with_error( + args.outform, "input files do not contain any valid contracts" + ) - if mythril.contracts[0].code: - print("Runtime Disassembly: \n" + mythril.contracts[0].get_easm()) - if mythril.contracts[0].creation_code: - print("Disassembly: \n" + mythril.contracts[0].get_creation_easm()) + if args.graph: + html = analyzer.graph_html( + contract=analyzer.contracts[0], + enable_physics=args.enable_physics, + phrackify=args.phrack, + ) - elif args.graph or args.fire_lasers: - if not mythril.contracts: - exit_with_error( - args.outform, "input files do not contain any valid contracts" - ) + try: + with open(args.graph, "w") as f: + f.write(html) + except Exception as e: + exit_with_error(args.outform, "Error saving graph: " + str(e)) - if args.graph: - html = mythril.graph_html( - strategy=args.strategy, - contract=mythril.contracts[0], - address=address, - enable_physics=args.enable_physics, - phrackify=args.phrack, - max_depth=args.max_depth, - execution_timeout=args.execution_timeout, - create_timeout=args.create_timeout, - enable_iprof=args.enable_iprof, + else: + try: + report = analyzer.fire_lasers( + modules=[m.strip() for m in args.modules.strip().split(",")] + if args.modules + else [], + verbose_report=args.verbose_report, + transaction_count=args.transaction_count, ) - - try: - with open(args.graph, "w") as f: - f.write(html) - except Exception as e: - exit_with_error(args.outform, "Error saving graph: " + str(e)) - - else: - try: - report = mythril.fire_lasers( - strategy=args.strategy, - address=address, - modules=[m.strip() for m in args.modules.strip().split(",")] - if args.modules - else [], - verbose_report=args.verbose_report, - max_depth=args.max_depth, - execution_timeout=args.execution_timeout, - create_timeout=args.create_timeout, - transaction_count=args.transaction_count, - enable_iprof=args.enable_iprof, - ) - outputs = { - "json": report.as_json(), - "jsonv2": report.as_swc_standard_format(), - "text": report.as_text(), - "markdown": report.as_markdown(), - } - print(outputs[args.outform]) - except ModuleNotFoundError as e: - exit_with_error( - args.outform, "Error loading analyis modules: " + format(e) - ) - - elif args.statespace_json: - - if not mythril.contracts: + outputs = { + "json": report.as_json(), + "jsonv2": report.as_swc_standard_format(), + "text": report.as_text(), + "markdown": report.as_markdown(), + } + print(outputs[args.outform]) + except ModuleNotFoundError as e: exit_with_error( - args.outform, "input files do not contain any valid contracts" + args.outform, "Error loading analyis modules: " + format(e) ) - statespace = mythril.dump_statespace( - strategy=args.strategy, - contract=mythril.contracts[0], - address=address, - max_depth=args.max_depth, - execution_timeout=args.execution_timeout, - create_timeout=args.create_timeout, - enable_iprof=args.enable_iprof, + elif args.statespace_json: + + if not analyzer.contracts: + exit_with_error( + args.outform, "input files do not contain any valid contracts" ) - try: - with open(args.statespace_json, "w") as f: - json.dump(statespace, f) - except Exception as e: - exit_with_error(args.outform, "Error saving json: " + str(e)) + statespace = analyzer.dump_statespace(contract=analyzer.contracts[0]) + + try: + with open(args.statespace_json, "w") as f: + json.dump(statespace, f) + except Exception as e: + exit_with_error(args.outform, "Error saving json: " + str(e)) + else: + parser.print_help() + + +def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: + """ + Parses the arguments + :param parser: The parser + :param args: The args + """ + + if args.epic: + path = os.path.dirname(os.path.realpath(__file__)) + sys.argv.remove("--epic") + os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") + sys.exit() + + if args.version: + if args.outform == "json": + print(json.dumps({"version_str": VERSION})) else: - parser.print_help() + print("Mythril version {}".format(VERSION)) + sys.exit() + + # Parse cmdline args + validate_args(parser, args) + try: + quick_commands(args) + config = set_config(args) + leveldb_search(config, args) + dissasembler = MythrilDisassembler( + eth=config.eth, + solc_version=args.solv, + solc_args=args.solc_args, + enable_online_lookup=args.query_signature, + ) + if args.truffle: + try: + dissasembler.analyze_truffle_project(args) + except FileNotFoundError: + print( + "Build directory not found. Make sure that you start the analysis from the project root, and that 'truffle compile' has executed successfully." + ) + sys.exit() + address = get_code(dissasembler, args) + execute_command( + disassembler=dissasembler, address=address, parser=parser, args=args + ) except CriticalError as ce: exit_with_error(args.outform, str(ce)) + except Exception: + exit_with_error(args.outform, traceback.format_exc()) if __name__ == "__main__": diff --git a/mythril/laser/ethereum/gas.py b/mythril/laser/ethereum/gas.py index 8fae5f53..7e283d15 100644 --- a/mythril/laser/ethereum/gas.py +++ b/mythril/laser/ethereum/gas.py @@ -62,6 +62,9 @@ OPCODE_GAS = { "XOR": (3, 3), "NOT": (3, 3), "BYTE": (3, 3), + "SHL": (3, 3), + "SHR": (3, 3), + "SAR": (3, 3), "SHA3": ( 30, 30 + 6 * 8, @@ -80,6 +83,7 @@ OPCODE_GAS = { "GASPRICE": (2, 2), "EXTCODESIZE": (700, 700), "EXTCODECOPY": (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 + "EXTCODEHASH": (400, 400), "RETURNDATASIZE": (2, 2), "RETURNDATACOPY": (3, 3), "BLOCKHASH": (20, 20), @@ -176,6 +180,10 @@ OPCODE_GAS = { "LOG3": (4 * 375, 4 * 375 + 8 * 32), "LOG4": (5 * 375, 5 * 375 + 8 * 32), "CREATE": (32000, 32000), + "CREATE2": ( + 32000, + 32000, + ), # TODO: The gas value is dynamic, to be done while implementing create2 "CALL": (700, 700 + 9000 + 25000), "NATIVE_COST": calculate_native_gas, "CALLCODE": (700, 700 + 9000 + 25000), diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 054969a7..f573d933 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -26,6 +26,7 @@ from mythril.laser.smt import ( Bool, Or, Not, + LShR, ) from mythril.laser.smt import symbol_factory @@ -466,6 +467,33 @@ class Instruction: global_state.mstate.stack.append(0 if s1 == 0 else URem(s0, s1)) return [global_state] + @StateTransition() + def shl_(self, global_state: GlobalState) -> List[GlobalState]: + shift, value = ( + util.pop_bitvec(global_state.mstate), + util.pop_bitvec(global_state.mstate), + ) + global_state.mstate.stack.append(value << shift) + return [global_state] + + @StateTransition() + def shr_(self, global_state: GlobalState) -> List[GlobalState]: + shift, value = ( + util.pop_bitvec(global_state.mstate), + util.pop_bitvec(global_state.mstate), + ) + global_state.mstate.stack.append(LShR(value, shift)) + return [global_state] + + @StateTransition() + def sar_(self, global_state: GlobalState) -> List[GlobalState]: + shift, value = ( + util.pop_bitvec(global_state.mstate), + util.pop_bitvec(global_state.mstate), + ) + global_state.mstate.stack.append(value >> shift) + return [global_state] + @StateTransition() def smod_(self, global_state: GlobalState) -> List[GlobalState]: """ @@ -1084,6 +1112,20 @@ class Instruction: return [global_state] + @StateTransition + def extcodehash_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: List of global states possible, list of size 1 in this case + """ + # TODO: To be implemented + address = global_state.mstate.stack.pop() + global_state.mstate.stack.append( + global_state.new_bitvec("extcodehash_{}".format(str(address)), 256) + ) + return [global_state] + @StateTransition() def extcodecopy_(self, global_state: GlobalState) -> List[GlobalState]: """ @@ -1678,6 +1720,25 @@ class Instruction: state.stack.append(0) return [global_state] + @StateTransition() + def create2_(self, global_state: GlobalState) -> List[GlobalState]: + """ + + :param global_state: + :return: + """ + # TODO: implement me + state = global_state.mstate + endowment, memory_start, memory_length, salt = ( + state.stack.pop(), + state.stack.pop(), + state.stack.pop(), + state.stack.pop(), + ) + # Not supported + state.stack.append(0) + return [global_state] + @StateTransition() def return_(self, global_state: GlobalState): """ diff --git a/mythril/laser/ethereum/state/memory.py b/mythril/laser/ethereum/state/memory.py index 085a7ec8..ef27fe1a 100644 --- a/mythril/laser/ethereum/state/memory.py +++ b/mythril/laser/ethereum/state/memory.py @@ -29,6 +29,11 @@ class Memory: """ return len(self._memory) + def __copy__(self): + copy = Memory() + copy._memory = self._memory[:] + return copy + def extend(self, size): """ diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index 394069b1..daf82c31 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -449,5 +449,6 @@ class TaintRunner: "MSIZE": (0, 1), "GAS": (0, 1), "CREATE": (3, 1), + "CREATE2": (4, 1), "RETURN": (2, 0), } diff --git a/mythril/laser/smt/__init__.py b/mythril/laser/smt/__init__.py index f46a8968..d849d0c4 100644 --- a/mythril/laser/smt/__init__.py +++ b/mythril/laser/smt/__init__.py @@ -13,6 +13,7 @@ from mythril.laser.smt.bitvec import ( BVAddNoOverflow, BVMulNoOverflow, BVSubNoUnderflow, + LShR, ) from mythril.laser.smt.bitvecfunc import BitVecFunc from mythril.laser.smt.expression import Expression, simplify diff --git a/mythril/laser/smt/bitvec.py b/mythril/laser/smt/bitvec.py index 00b519a7..05081137 100644 --- a/mythril/laser/smt/bitvec.py +++ b/mythril/laser/smt/bitvec.py @@ -1,7 +1,7 @@ """This module provides classes for an SMT abstraction of bit vectors.""" from typing import Union, overload, List, cast, Any, Optional, Callable - +from operator import lshift, rshift import z3 from mythril.laser.smt.bool import Bool, And, Or @@ -211,6 +211,38 @@ class BitVec(Expression[z3.BitVecRef]): # MYPY: fix complaints due to z3 overriding __eq__ return Bool(cast(z3.BoolRef, self.raw != other.raw), annotations=union) + def _handle_shift(self, other: Union[int, "BitVec"], operator: Callable) -> "BitVec": + """ + Handles shift + :param other: The other BitVector + :param operator: The shift operator + :return: the resulting output + """ + if isinstance(other, BitVecFunc): + return operator(other, self) + if not isinstance(other, BitVec): + return BitVec( + operator(self.raw, other), annotations=self.annotations + ) + union = self.annotations + other.annotations + return BitVec(operator(self.raw, other.raw), annotations=union) + + def __lshift__(self, other: Union[int, "BitVec"]) -> "BitVec": + """ + + :param other: + :return: + """ + return self._handle_shift(other, lshift) + + def __rshift__(self, other: Union[int, "BitVec"]) -> "BitVec": + """ + + :param other: + :return: + """ + return self._handle_shift(other, rshift) + def _comparison_helper( a: BitVec, b: BitVec, operation: Callable, default_value: bool, inputs_equal: bool @@ -254,6 +286,10 @@ def _arithmetic_helper(a: BitVec, b: BitVec, operation: Callable) -> BitVec: return BitVec(raw, annotations=union) +def LShR(a: BitVec, b: BitVec): + return _arithmetic_helper(a, b, z3.LShR) + + def If(a: Union[Bool, bool], b: Union[BitVec, int], c: Union[BitVec, int]) -> BitVec: """Create an if-then-else expression. diff --git a/mythril/laser/smt/bitvecfunc.py b/mythril/laser/smt/bitvecfunc.py index d3e77601..f73ef504 100644 --- a/mythril/laser/smt/bitvecfunc.py +++ b/mythril/laser/smt/bitvecfunc.py @@ -207,3 +207,19 @@ class BitVecFunc(BitVec): return _comparison_helper( self, other, operator.eq, default_value=True, inputs_equal=False ) + + def __lshift__(self, other: Union[int, "BitVec"]) -> "BitVec": + """ + Left shift operation + :param other: The int or BitVec to shift on + :return The resulting left shifted output + """ + return _arithmetic_helper(self, other, operator.lshift) + + def __rshift__(self, other: Union[int, "BitVec"]) -> "BitVec": + """ + Right shift operation + :param other: The int or BitVec to shift on + :return The resulting right shifted output: + """ + return _arithmetic_helper(self, other, operator.rshift) diff --git a/mythril/mythril.py b/mythril/mythril.py index 4654b235..872f23ae 100644 --- a/mythril/mythril.py +++ b/mythril/mythril.py @@ -570,6 +570,7 @@ class Mythril(object): """ all_issues = [] SolverStatistics().enabled = True + exceptions = [] for contract in contracts or self.contracts: StartTime() # Reinitialize start time for new contracts try: @@ -600,7 +601,7 @@ class Mythril(object): + traceback.format_exc() ) issues = retrieve_callback_issues(modules) - + exceptions.append(traceback.format_exc()) for issue in issues: issue.add_code_info(contract) @@ -610,7 +611,7 @@ class Mythril(object): source_data = Source() source_data.get_source_from_contracts_list(self.contracts) # Finally, output the results - report = Report(verbose_report, source_data) + report = Report(verbose_report, source_data, exceptions=exceptions) for issue in all_issues: report.append_issue(issue) diff --git a/mythril/mythril/__init__.py b/mythril/mythril/__init__.py new file mode 100644 index 00000000..189a7812 --- /dev/null +++ b/mythril/mythril/__init__.py @@ -0,0 +1,4 @@ +from .mythril_disassembler import MythrilDisassembler +from .mythril_analyzer import MythrilAnalyzer +from .mythril_config import MythrilConfig +from .mythril_leveldb import MythrilLevelDB diff --git a/mythril/mythril/mythril_analyzer.py b/mythril/mythril/mythril_analyzer.py new file mode 100644 index 00000000..e42e943c --- /dev/null +++ b/mythril/mythril/mythril_analyzer.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import logging +import traceback +from typing import Optional, List + +from . import MythrilDisassembler +from mythril.support.source_support import Source +from mythril.support.loader import DynLoader +from mythril.analysis.symbolic import SymExecWrapper +from mythril.analysis.callgraph import generate_graph +from mythril.analysis.traceexplore import get_serializable_statespace +from mythril.analysis.security import fire_lasers, retrieve_callback_issues +from mythril.analysis.report import Report, Issue +from mythril.ethereum.evmcontract import EVMContract +from mythril.laser.smt import SolverStatistics +from mythril.support.start_time import StartTime + +log = logging.getLogger(__name__) + + +class MythrilAnalyzer: + """ + The Mythril Analyzer class + Responsible for the analysis of the smart contracts + """ + + def __init__( + self, + disassembler: MythrilDisassembler, + requires_dynld: bool = False, + onchain_storage_access: bool = True, + strategy: str = "dfs", + address: Optional[str] = None, + max_depth: Optional[int] = None, + execution_timeout: Optional[int] = None, + create_timeout: Optional[int] = None, + enable_iprof: bool = False, + ): + """ + + :param disassembler: The MythrilDisassembler class + :param requires_dynld: whether dynamic loading should be done or not + :param onchain_storage_access: Whether onchain access should be done or not + """ + self.eth = disassembler.eth + self.contracts = disassembler.contracts or [] # type: List[EVMContract] + self.enable_online_lookup = disassembler.enable_online_lookup + self.dynld = requires_dynld + self.onchain_storage_access = onchain_storage_access + self.strategy = strategy + self.address = address + self.max_depth = max_depth + self.execution_timeout = execution_timeout + self.create_timeout = create_timeout + self.enable_iprof = enable_iprof + + def dump_statespace(self, contract: EVMContract = None) -> str: + """ + Returns serializable statespace of the contract + :param contract: The Contract on which the analysis should be done + :return: The serialized state space + """ + sym = SymExecWrapper( + contract or self.contracts[0], + self.address, + self.strategy, + dynloader=DynLoader( + self.eth, + storage_loading=self.onchain_storage_access, + contract_loading=self.dynld, + ), + max_depth=self.max_depth, + execution_timeout=self.execution_timeout, + create_timeout=self.create_timeout, + enable_iprof=self.enable_iprof, + ) + + return get_serializable_statespace(sym) + + def graph_html( + self, + contract: EVMContract = None, + enable_physics: bool = False, + phrackify: bool = False, + transaction_count: Optional[int] = None, + ) -> str: + """ + + :param contract: The Contract on which the analysis should be done + :param enable_physics: If true then enables the graph physics simulation + :param phrackify: If true generates Phrack-style call graph + :param transaction_count: The amount of transactions to be executed + :return: The generated graph in html format + """ + sym = SymExecWrapper( + contract or self.contracts[0], + self.address, + self.strategy, + dynloader=DynLoader( + self.eth, + storage_loading=self.onchain_storage_access, + contract_loading=self.dynld, + ), + max_depth=self.max_depth, + execution_timeout=self.execution_timeout, + transaction_count=transaction_count, + create_timeout=self.create_timeout, + enable_iprof=self.enable_iprof, + ) + return generate_graph(sym, physics=enable_physics, phrackify=phrackify) + + def fire_lasers( + self, + modules: Optional[List[str]] = None, + verbose_report: bool = False, + transaction_count: Optional[int] = None, + ) -> Report: + """ + :param modules: The analysis modules which should be executed + :param verbose_report: Gives out the transaction sequence of the vulnerability + :param transaction_count: The amount of transactions to be executed + :return: The Report class which contains the all the issues/vulnerabilities + """ + all_issues = [] # type: List[Issue] + SolverStatistics().enabled = True + exceptions = [] + for contract in self.contracts: + StartTime() # Reinitialize start time for new contracts + try: + sym = SymExecWrapper( + contract, + self.address, + self.strategy, + dynloader=DynLoader( + self.eth, + storage_loading=self.onchain_storage_access, + contract_loading=self.dynld, + ), + max_depth=self.max_depth, + execution_timeout=self.execution_timeout, + create_timeout=self.create_timeout, + transaction_count=transaction_count, + modules=modules, + compulsory_statespace=False, + enable_iprof=self.enable_iprof, + ) + issues = fire_lasers(sym, modules) + except KeyboardInterrupt: + log.critical("Keyboard Interrupt") + issues = retrieve_callback_issues(modules) + except Exception: + log.critical( + "Exception occurred, aborting analysis. Please report this issue to the Mythril GitHub page.\n" + + traceback.format_exc() + ) + issues = retrieve_callback_issues(modules) + exceptions.append(traceback.format_exc()) + for issue in issues: + issue.add_code_info(contract) + + all_issues += issues + log.info("Solver statistics: \n{}".format(str(SolverStatistics()))) + + source_data = Source() + source_data.get_source_from_contracts_list(self.contracts) + # Finally, output the results + report = Report(verbose_report, source_data, exceptions=exceptions) + for issue in all_issues: + report.append_issue(issue) + + return report diff --git a/mythril/mythril/mythril_config.py b/mythril/mythril/mythril_config.py new file mode 100644 index 00000000..f2ad1217 --- /dev/null +++ b/mythril/mythril/mythril_config.py @@ -0,0 +1,226 @@ +import codecs +import logging +import os +import platform +import re + +from pathlib import Path +from shutil import copyfile +from configparser import ConfigParser +from typing import Optional + +from mythril.exceptions import CriticalError +from mythril.ethereum.interface.rpc.client import EthJsonRpc +from mythril.ethereum.interface.leveldb.client import EthLevelDB + +log = logging.getLogger(__name__) + + +class MythrilConfig: + """ + The Mythril Analyzer class + Responsible for setup of the mythril environment + """ + + def __init__(self): + self.mythril_dir = self._init_mythril_dir() + self.config_path = os.path.join(self.mythril_dir, "config.ini") + self.leveldb_dir = None + self._init_config() + self.eth = None # type: Optional[EthJsonRpc] + self.eth_db = None # type: Optional[EthLevelDB] + + @staticmethod + def _init_mythril_dir() -> str: + """ + Initializes the mythril dir and config.ini file + :return: The mythril dir's path + """ + + try: + mythril_dir = os.environ["MYTHRIL_DIR"] + except KeyError: + mythril_dir = os.path.join(os.path.expanduser("~"), ".mythril") + + if not os.path.exists(mythril_dir): + # Initialize data directory + log.info("Creating mythril data directory") + os.mkdir(mythril_dir) + + db_path = str(Path(mythril_dir) / "signatures.db") + if not os.path.exists(db_path): + # if the default mythril dir doesn't contain a signature DB + # initialize it with the default one from the project root + asset_dir = Path(__file__).parent.parent / "support" / "assets" + copyfile(str(asset_dir / "signatures.db"), db_path) + + return mythril_dir + + def _init_config(self): + """If no config file exists, create it and add default options. + Defaults:- + - Default LevelDB path is specified based on OS + - dynamic loading is set to infura by default in the file + This function also sets self.leveldb_dir path + """ + + leveldb_default_path = self._get_default_leveldb_path() + + if not os.path.exists(self.config_path): + log.info("No config file found. Creating default: " + self.config_path) + open(self.config_path, "a").close() + + config = ConfigParser(allow_no_value=True) + + config.optionxform = str + config.read(self.config_path, "utf-8") + if "defaults" not in config.sections(): + self._add_default_options(config) + + if not config.has_option("defaults", "leveldb_dir"): + self._add_leveldb_option(config, leveldb_default_path) + + if not config.has_option("defaults", "dynamic_loading"): + self._add_dynamic_loading_option(config) + + with codecs.open(self.config_path, "w", "utf-8") as fp: + config.write(fp) + + leveldb_dir = config.get( + "defaults", "leveldb_dir", fallback=leveldb_default_path + ) + self.leveldb_dir = os.path.expanduser(leveldb_dir) + + @staticmethod + def _get_default_leveldb_path() -> str: + """ + Returns the LevelDB path + :return: The LevelDB path + """ + system = platform.system().lower() + leveldb_fallback_dir = os.path.expanduser("~") + if system.startswith("darwin"): + leveldb_fallback_dir = os.path.join( + leveldb_fallback_dir, "Library", "Ethereum" + ) + elif system.startswith("windows"): + leveldb_fallback_dir = os.path.join( + leveldb_fallback_dir, "AppData", "Roaming", "Ethereum" + ) + else: + leveldb_fallback_dir = os.path.join(leveldb_fallback_dir, ".ethereum") + return os.path.join(leveldb_fallback_dir, "geth", "chaindata") + + @staticmethod + def _add_default_options(config: ConfigParser) -> None: + """ + Adds defaults option to config.ini + :param config: The config file object + :return: None + """ + config.add_section("defaults") + + @staticmethod + def _add_leveldb_option(config: ConfigParser, leveldb_fallback_dir: str) -> None: + """ + Sets a default leveldb path in .mythril/config.ini file + :param config: The config file object + :param leveldb_fallback_dir: The leveldb dir to use by default for searches + :return: None + """ + config.set("defaults", "#Default chaindata locations:", "") + config.set("defaults", "#– Mac: ~/Library/Ethereum/geth/chaindata", "") + config.set("defaults", "#– Linux: ~/.ethereum/geth/chaindata", "") + config.set( + "defaults", + "#– Windows: %USERPROFILE%\\AppData\\Roaming\\Ethereum\\geth\\chaindata", + "", + ) + config.set("defaults", "leveldb_dir", leveldb_fallback_dir) + + @staticmethod + def _add_dynamic_loading_option(config: ConfigParser) -> None: + """ + Sets the dynamic loading config option in .mythril/config.ini file + :param config: The config file object + :return: None + """ + config.set( + "defaults", "#– To connect to Infura use dynamic_loading: infura", "" + ) + config.set( + "defaults", + "#– To connect to Rpc use " + "dynamic_loading: HOST:PORT / ganache / infura-[network_name]", + "", + ) + config.set( + "defaults", "#– To connect to local host use dynamic_loading: localhost", "" + ) + config.set("defaults", "dynamic_loading", "infura") + + def set_api_leveldb(self, leveldb_path: str) -> None: + """ + """ + self.eth_db = EthLevelDB(leveldb_path) + + def set_api_rpc_infura(self) -> None: + """Set the RPC mode to INFURA on Mainnet.""" + log.info("Using INFURA Main Net for RPC queries") + self.eth = EthJsonRpc("mainnet.infura.io", 443, True) + + def set_api_rpc(self, rpc: str = None, rpctls: bool = False) -> None: + """ + Sets the RPC mode to either of ganache or infura + :param rpc: either of the strings - ganache, infura-mainnet, infura-rinkeby, infura-kovan, infura-ropsten + """ + if rpc == "ganache": + rpcconfig = ("localhost", 8545, False) + else: + m = re.match(r"infura-(.*)", rpc) + if m and m.group(1) in ["mainnet", "rinkeby", "kovan", "ropsten"]: + rpcconfig = (m.group(1) + ".infura.io", 443, True) + else: + try: + host, port = rpc.split(":") + rpcconfig = (host, int(port), rpctls) + except ValueError: + raise CriticalError( + "Invalid RPC argument, use 'ganache', 'infura-[network]' or 'HOST:PORT'" + ) + + if rpcconfig: + log.info("Using RPC settings: %s" % str(rpcconfig)) + self.eth = EthJsonRpc(rpcconfig[0], int(rpcconfig[1]), rpcconfig[2]) + else: + raise CriticalError("Invalid RPC settings, check help for details.") + + def set_api_rpc_localhost(self) -> None: + """Set the RPC mode to a local instance.""" + log.info("Using default RPC settings: http://localhost:8545") + self.eth = EthJsonRpc("localhost", 8545) + + def set_api_from_config_path(self) -> None: + """Set the RPC mode based on a given config file.""" + config = ConfigParser(allow_no_value=False) + # TODO: Remove this after this issue https://github.com/python/mypy/issues/2427 is closed + config.optionxform = str # type:ignore + config.read(self.config_path, "utf-8") + if config.has_option("defaults", "dynamic_loading"): + dynamic_loading = config.get("defaults", "dynamic_loading") + else: + dynamic_loading = "infura" + self._set_rpc(dynamic_loading) + + def _set_rpc(self, rpc_type: str) -> None: + """ + Sets rpc based on the type + :param rpc_type: The type of connection: like infura, ganache, localhost + :return: + """ + if rpc_type == "infura": + self.set_api_rpc_infura() + elif rpc_type == "localhost": + self.set_api_rpc_localhost() + else: + self.set_api_rpc(rpc_type) diff --git a/mythril/mythril/mythril_disassembler.py b/mythril/mythril/mythril_disassembler.py new file mode 100644 index 00000000..5e1e72c3 --- /dev/null +++ b/mythril/mythril/mythril_disassembler.py @@ -0,0 +1,303 @@ +import logging +import re +import solc +import os + +from ethereum import utils +from solc.exceptions import SolcError +from typing import List, Tuple, Optional +from mythril.ethereum import util +from mythril.ethereum.interface.rpc.client import EthJsonRpc +from mythril.exceptions import CriticalError, CompilerError, NoContractFoundError +from mythril.support import signatures +from mythril.support.truffle import analyze_truffle_project +from mythril.ethereum.evmcontract import EVMContract +from mythril.ethereum.interface.rpc.exceptions import ConnectionError +from mythril.solidity.soliditycontract import SolidityContract, get_contracts_from_file + +log = logging.getLogger(__name__) + + +class MythrilDisassembler: + """ + The Mythril Disassembler class + Responsible for generating disassembly of smart contracts + - Compiles solc code from file/onchain + - Can also be used to access onchain storage data + """ + + def __init__( + self, + eth: Optional[EthJsonRpc] = None, + solc_version: str = None, + solc_args: str = None, + enable_online_lookup: bool = False, + ) -> None: + self.solc_binary = self._init_solc_binary(solc_version) + self.solc_args = solc_args + self.eth = eth + self.enable_online_lookup = enable_online_lookup + self.sigs = signatures.SignatureDB(enable_online_lookup=enable_online_lookup) + self.contracts = [] # type: List[EVMContract] + + @staticmethod + def _init_solc_binary(version: str) -> str: + """ + Only proper versions are supported. No nightlies, commits etc (such as available in remix). + :param version: Version of the solc binary required + :return: The solc binary of the corresponding version + """ + + if not version: + return os.environ.get("SOLC") or "solc" + + # tried converting input to semver, seemed not necessary so just slicing for now + main_version = solc.main.get_solc_version_string() + main_version_number = re.match(r"\d+.\d+.\d+", main_version) + if main_version is None: + raise CriticalError( + "Could not extract solc version from string {}".format(main_version) + ) + if version == main_version_number: + log.info("Given version matches installed version") + solc_binary = os.environ.get("SOLC") or "solc" + else: + solc_binary = util.solc_exists(version) + if solc_binary: + log.info("Given version is already installed") + else: + try: + solc.install_solc("v" + version) + solc_binary = util.solc_exists(version) + if not solc_binary: + raise SolcError() + except SolcError: + raise CriticalError( + "There was an error when trying to install the specified solc version" + ) + + log.info("Setting the compiler to %s", solc_binary) + + return solc_binary + + def load_from_bytecode( + self, code: str, bin_runtime: bool = False, address: Optional[str] = None + ) -> Tuple[str, EVMContract]: + """ + Returns the address and the contract class for the given bytecode + :param code: Bytecode + :param bin_runtime: Whether the code is runtime code or creation code + :param address: address of contract + :return: tuple(address, Contract class) + """ + if address is None: + address = util.get_indexed_address(0) + if bin_runtime: + self.contracts.append( + EVMContract( + code=code, + name="MAIN", + enable_online_lookup=self.enable_online_lookup, + ) + ) + else: + self.contracts.append( + EVMContract( + creation_code=code, + name="MAIN", + enable_online_lookup=self.enable_online_lookup, + ) + ) + return address, self.contracts[-1] # return address and contract object + + def load_from_address(self, address: str) -> Tuple[str, EVMContract]: + """ + Returns the contract given it's on chain address + :param address: The on chain address of a contract + :return: tuple(address, contract) + """ + if not re.match(r"0x[a-fA-F0-9]{40}", address): + raise CriticalError("Invalid contract address. Expected format is '0x...'.") + + try: + code = self.eth.eth_getCode(address) + except FileNotFoundError as e: + raise CriticalError("IPC error: " + str(e)) + except ConnectionError: + raise CriticalError( + "Could not connect to RPC server. Make sure that your node is running and that RPC parameters are set correctly." + ) + except Exception as e: + raise CriticalError("IPC / RPC error: " + str(e)) + + if code == "0x" or code == "0x0": + raise CriticalError( + "Received an empty response from eth_getCode. Check the contract address and verify that you are on the correct chain." + ) + else: + self.contracts.append( + EVMContract( + code, name=address, enable_online_lookup=self.enable_online_lookup + ) + ) + return address, self.contracts[-1] # return address and contract object + + def load_from_solidity( + self, solidity_files: List[str] + ) -> Tuple[str, List[SolidityContract]]: + """ + + :param solidity_files: List of solidity_files + :return: tuple of address, contract class list + """ + address = util.get_indexed_address(0) + contracts = [] + for file in solidity_files: + if ":" in file: + file, contract_name = file.split(":") + else: + contract_name = None + + file = os.path.expanduser(file) + + try: + # import signatures from solidity source + self.sigs.import_solidity_file( + file, solc_binary=self.solc_binary, solc_args=self.solc_args + ) + if contract_name is not None: + contract = SolidityContract( + input_file=file, + name=contract_name, + solc_args=self.solc_args, + solc_binary=self.solc_binary, + ) + self.contracts.append(contract) + contracts.append(contract) + else: + for contract in get_contracts_from_file( + input_file=file, + solc_args=self.solc_args, + solc_binary=self.solc_binary, + ): + self.contracts.append(contract) + contracts.append(contract) + + except FileNotFoundError: + raise CriticalError("Input file not found: " + file) + except CompilerError as e: + raise CriticalError(e) + except NoContractFoundError: + log.error( + "The file " + file + " does not contain a compilable contract." + ) + + return address, contracts + + def analyze_truffle_project(self, *args, **kwargs) -> None: + """ + :param args: + :param kwargs: + :return: + """ + analyze_truffle_project( + self.sigs, *args, **kwargs + ) # just passthru by passing signatures for now + + @staticmethod + def hash_for_function_signature(func: str) -> str: + """ + Return function names corresponding signature hash + :param func: function name + :return: Its hash signature + """ + return "0x%s" % utils.sha3(func)[:4].hex() + + def get_state_variable_from_storage( + self, address: str, params: Optional[List[str]] = None + ) -> str: + """ + Get variables from the storage + :param address: The contract address + :param params: The list of parameters + param types: [position, length] or ["mapping", position, key1, key2, ... ] + or [position, length, array] + :return: The corresponding storage slot and its value + """ + params = params or [] + (position, length, mappings) = (0, 1, []) + try: + if params[0] == "mapping": + if len(params) < 3: + raise CriticalError("Invalid number of parameters.") + position = int(params[1]) + position_formatted = utils.zpad(utils.int_to_big_endian(position), 32) + for i in range(2, len(params)): + key = bytes(params[i], "utf8") + key_formatted = utils.rzpad(key, 32) + mappings.append( + int.from_bytes( + utils.sha3(key_formatted + position_formatted), + byteorder="big", + ) + ) + + length = len(mappings) + if length == 1: + position = mappings[0] + + else: + if len(params) >= 4: + raise CriticalError("Invalid number of parameters.") + + if len(params) >= 1: + position = int(params[0]) + if len(params) >= 2: + length = int(params[1]) + if len(params) == 3 and params[2] == "array": + position_formatted = utils.zpad( + utils.int_to_big_endian(position), 32 + ) + position = int.from_bytes( + utils.sha3(position_formatted), byteorder="big" + ) + + except ValueError: + raise CriticalError( + "Invalid storage index. Please provide a numeric value." + ) + + outtxt = [] + + try: + if length == 1: + outtxt.append( + "{}: {}".format( + position, self.eth.eth_getStorageAt(address, position) + ) + ) + else: + if len(mappings) > 0: + for i in range(0, len(mappings)): + position = mappings[i] + outtxt.append( + "{}: {}".format( + hex(position), + self.eth.eth_getStorageAt(address, position), + ) + ) + else: + for i in range(position, position + length): + outtxt.append( + "{}: {}".format( + hex(i), self.eth.eth_getStorageAt(address, i) + ) + ) + except FileNotFoundError as e: + raise CriticalError("IPC error: " + str(e)) + except ConnectionError: + raise CriticalError( + "Could not connect to RPC server. " + "Make sure that your node is running and that RPC parameters are set correctly." + ) + return "\n".join(outtxt) diff --git a/mythril/mythril/mythril_leveldb.py b/mythril/mythril/mythril_leveldb.py new file mode 100644 index 00000000..7ab78f56 --- /dev/null +++ b/mythril/mythril/mythril_leveldb.py @@ -0,0 +1,49 @@ +import re +from mythril.exceptions import CriticalError + + +class MythrilLevelDB: + """ + Class which does search operations on leveldb + There are two DBs + 1) Key value pairs of hashes and it's corresponding address + 2) The LevelDB Trie + """ + + def __init__(self, leveldb): + """ + + :param leveldb: Leveldb path + """ + self.leveldb = leveldb + + def search_db(self, search): + """ + Searches the corresponding code + :param search: The code part to be searched + """ + + def search_callback(_, address, balance): + """ + + :param _: + :param address: The address of the contract with the code in search + :param balance: The balance of the corresponding contract + """ + print("Address: " + address + ", balance: " + str(balance)) + + try: + self.leveldb.search(search, search_callback) + + except SyntaxError: + raise CriticalError("Syntax error in search expression.") + + def contract_hash_to_address(self, contract_hash): + """ + Returns address of the corresponding hash by searching the leveldb + :param contract_hash: Hash to be searched + """ + if not re.match(r"0x[a-fA-F0-9]{64}", contract_hash): + raise CriticalError("Invalid address hash. Expected format is '0x...'.") + + print(self.leveldb.contract_hash_to_address(contract_hash)) diff --git a/mythril/solidity/soliditycontract.py b/mythril/solidity/soliditycontract.py index d2669e7f..6f83ead7 100644 --- a/mythril/solidity/soliditycontract.py +++ b/mythril/solidity/soliditycontract.py @@ -161,11 +161,14 @@ class SolidityContract(EVMContract): if len(mapping) > 2 and len(mapping[2]) > 0: idx = int(mapping[2]) - lineno = ( - self.solidity_files[idx] - .data.encode("utf-8")[0:offset] - .count("\n".encode("utf-8")) - + 1 - ) + if idx == -1: + lineno = None + else: + lineno = ( + self.solidity_files[idx] + .data.encode("utf-8")[0:offset] + .count("\n".encode("utf-8")) + + 1 + ) prev_item = item mappings.append(SourceMapping(idx, offset, length, lineno, item)) diff --git a/mythril/support/opcodes.py b/mythril/support/opcodes.py new file mode 100644 index 00000000..d70f0309 --- /dev/null +++ b/mythril/support/opcodes.py @@ -0,0 +1,94 @@ +# This pyethereum opcodes file with added opcodes +from typing import Dict, Tuple + +opcodes = { + 0x00: ("STOP", 0, 0, 0), + 0x01: ("ADD", 2, 1, 3), + 0x02: ("MUL", 2, 1, 5), + 0x03: ("SUB", 2, 1, 3), + 0x04: ("DIV", 2, 1, 5), + 0x05: ("SDIV", 2, 1, 5), + 0x06: ("MOD", 2, 1, 5), + 0x07: ("SMOD", 2, 1, 5), + 0x08: ("ADDMOD", 3, 1, 8), + 0x09: ("MULMOD", 3, 1, 8), + 0x0A: ("EXP", 2, 1, 10), + 0x0B: ("SIGNEXTEND", 2, 1, 5), + 0x10: ("LT", 2, 1, 3), + 0x11: ("GT", 2, 1, 3), + 0x12: ("SLT", 2, 1, 3), + 0x13: ("SGT", 2, 1, 3), + 0x14: ("EQ", 2, 1, 3), + 0x15: ("ISZERO", 1, 1, 3), + 0x16: ("AND", 2, 1, 3), + 0x17: ("OR", 2, 1, 3), + 0x18: ("XOR", 2, 1, 3), + 0x19: ("NOT", 1, 1, 3), + 0x1A: ("BYTE", 2, 1, 3), + 0x1B: ("SHL", 2, 1, 3), + 0x1C: ("SHR", 2, 1, 3), + 0x1D: ("SAR", 2, 1, 3), + 0x20: ("SHA3", 2, 1, 30), + 0x30: ("ADDRESS", 0, 1, 2), + 0x31: ("BALANCE", 1, 1, 20), # now 400 + 0x32: ("ORIGIN", 0, 1, 2), + 0x33: ("CALLER", 0, 1, 2), + 0x34: ("CALLVALUE", 0, 1, 2), + 0x35: ("CALLDATALOAD", 1, 1, 3), + 0x36: ("CALLDATASIZE", 0, 1, 2), + 0x37: ("CALLDATACOPY", 3, 0, 3), + 0x38: ("CODESIZE", 0, 1, 2), + 0x39: ("CODECOPY", 3, 0, 3), + 0x3A: ("GASPRICE", 0, 1, 2), + 0x3B: ("EXTCODESIZE", 1, 1, 20), # now 700 + 0x3C: ("EXTCODECOPY", 4, 0, 20), # now 700 + 0x3D: ("RETURNDATASIZE", 0, 1, 2), + 0x3E: ("RETURNDATACOPY", 3, 0, 3), + 0x3F: ("EXTCODEHASH", 3, 0, 3), + 0x40: ("BLOCKHASH", 1, 1, 20), + 0x41: ("COINBASE", 0, 1, 2), + 0x42: ("TIMESTAMP", 0, 1, 2), + 0x43: ("NUMBER", 0, 1, 2), + 0x44: ("DIFFICULTY", 0, 1, 2), + 0x45: ("GASLIMIT", 0, 1, 2), + 0x50: ("POP", 1, 0, 2), + 0x51: ("MLOAD", 1, 1, 3), + 0x52: ("MSTORE", 2, 0, 3), + 0x53: ("MSTORE8", 2, 0, 3), + 0x54: ("SLOAD", 1, 1, 50), # 200 now + 0x55: ("SSTORE", 2, 0, 0), + 0x56: ("JUMP", 1, 0, 8), + 0x57: ("JUMPI", 2, 0, 10), + 0x58: ("PC", 0, 1, 2), + 0x59: ("MSIZE", 0, 1, 2), + 0x5A: ("GAS", 0, 1, 2), + 0x5B: ("JUMPDEST", 0, 0, 1), + 0xA0: ("LOG0", 2, 0, 375), + 0xA1: ("LOG1", 3, 0, 750), + 0xA2: ("LOG2", 4, 0, 1125), + 0xA3: ("LOG3", 5, 0, 1500), + 0xA4: ("LOG4", 6, 0, 1875), + 0xF0: ("CREATE", 3, 1, 32000), + 0xF1: ("CALL", 7, 1, 40), # 700 now + 0xF2: ("CALLCODE", 7, 1, 40), # 700 now + 0xF3: ("RETURN", 2, 0, 0), + 0xF4: ("DELEGATECALL", 6, 1, 40), # 700 now + 0xF5: ("CREATE2", 3, 1, 32000), + 0xFA: ("STATICCALL", 6, 1, 40), + 0xFD: ("REVERT", 2, 0, 0), + 0xFF: ("SUICIDE", 1, 0, 0), # 5000 now +} # type: Dict[int, Tuple[str, int, int, int]] + +opcodesMetropolis = {0x3D, 0x3E, 0xFA, 0xFD} + +for i in range(1, 33): + opcodes[0x5F + i] = ("PUSH" + str(i), 0, 1, 3) + +for i in range(1, 17): + opcodes[0x7F + i] = ("DUP" + str(i), i, i + 1, 3) + opcodes[0x8F + i] = ("SWAP" + str(i), i + 1, i + 1, 3) + +reverse_opcodes = {} +for o in opcodes: + vars()[opcodes[o][0]] = opcodes[o] + reverse_opcodes[opcodes[o][0]] = o diff --git a/mythril/support/truffle.py b/mythril/support/truffle.py index 800b6fca..b42b1e10 100644 --- a/mythril/support/truffle.py +++ b/mythril/support/truffle.py @@ -5,6 +5,7 @@ import logging import os import re import sys +import warnings from pathlib import PurePath from ethereum.utils import sha3 @@ -20,12 +21,24 @@ from mythril.solidity.soliditycontract import SourceMapping log = logging.getLogger(__name__) +def format_Warning(message, category, filename, lineno, line=""): + return "{}: {}\n\n".format(str(filename), str(message)) + + +warnings.formatwarning = format_Warning + + def analyze_truffle_project(sigs, args): """ :param sigs: :param args: """ + warnings.warn( + "The option --truffle is being deprecated, Please use the truffle-security plugin, https://github.com/ConsenSys/truffle-security", + FutureWarning, + ) + project_root = os.getcwd() build_dir = os.path.join(project_root, "build", "contracts") @@ -181,7 +194,10 @@ def get_mappings(source, deployed_source_map): if len(mapping) > 2 and len(mapping[2]) > 0: idx = int(mapping[2]) - lineno = source.encode("utf-8")[0:offset].count("\n".encode("utf-8")) + 1 + if idx == -1: + lineno = None + else: + lineno = source.encode("utf-8")[0:offset].count("\n".encode("utf-8")) + 1 prev_item = item mappings.append(SourceMapping(idx, offset, length, lineno, item)) diff --git a/mythril/version.py b/mythril/version.py index 5dcd9e2c..2754f21b 100644 --- a/mythril/version.py +++ b/mythril/version.py @@ -4,4 +4,4 @@ This file is suitable for sourcing inside POSIX shell, e.g. bash as well as for importing into Python. """ -VERSION = "v0.20.0" # NOQA +VERSION = "v0.20.2" # NOQA diff --git a/solidity_examples/WalletLibrary.sol b/solidity_examples/WalletLibrary.sol index d972cdd6..df10b56b 100644 --- a/solidity_examples/WalletLibrary.sol +++ b/solidity_examples/WalletLibrary.sol @@ -212,7 +212,7 @@ contract WalletLibrary is WalletEvents { } // throw unless the contract is not yet initialized. - modifier only_uninitialized { require(m_numOwners > 0); _; } + modifier only_uninitialized { require(m_numOwners == 0); _; } // constructor - just pass on the owner array to the multiowned and // the limit to daylimit diff --git a/solidity_examples/rubixi.sol b/solidity_examples/rubixi.sol index 8e1567c6..2a4f75fb 100644 --- a/solidity_examples/rubixi.sol +++ b/solidity_examples/rubixi.sol @@ -34,7 +34,7 @@ contract Rubixi { //Fee functions for creator function collectAllFees() public onlyowner { - require(collectedFees == 0); + require(collectedFees > 0); creator.transfer(collectedFees); collectedFees = 0; } @@ -43,14 +43,14 @@ contract Rubixi { _amt *= 1 ether; if (_amt > collectedFees) collectAllFees(); - require(collectedFees == 0); + require(collectedFees > 0); creator.transfer(_amt); collectedFees -= _amt; } function collectPercentOfFees(uint _pcent) public onlyowner { - require(collectedFees == 0 || _pcent > 100); + require(collectedFees > 0 && _pcent <= 100); uint feesToCollect = collectedFees / 100 * _pcent; creator.transfer(feesToCollect); @@ -63,12 +63,12 @@ contract Rubixi { } function changeMultiplier(uint _mult) public onlyowner { - require(_mult > 300 || _mult < 120); + require(_mult <= 300 && _mult >= 120); pyramidMultiplier = _mult; } function changeFeePercentage(uint _fee) public onlyowner { - require(_fee > 10); + require(_fee <= 10); feePercent = _fee; } diff --git a/tests/graph_test.py b/tests/graph_test.py index ba84f7da..1e1e3350 100644 --- a/tests/graph_test.py +++ b/tests/graph_test.py @@ -1,5 +1,5 @@ from mythril.analysis.callgraph import generate_graph -from mythril.analysis.symbolic import SymExecWrapper +from mythril.mythril import MythrilAnalyzer, MythrilDisassembler from mythril.ethereum import util from mythril.solidity.soliditycontract import EVMContract from tests import ( @@ -22,16 +22,17 @@ class GraphTest(BaseTestCase): ) contract = EVMContract(input_file.read_text()) - - sym = SymExecWrapper( - contract, - address=(util.get_indexed_address(0)), + disassembler = MythrilDisassembler() + disassembler.contracts.append(contract) + analyzer = MythrilAnalyzer( + disassembler=disassembler, strategy="dfs", - transaction_count=1, execution_timeout=5, + max_depth=30, + address=(util.get_indexed_address(0)), ) - html = generate_graph(sym) + html = analyzer.graph_html(transaction_count=1) output_current.write_text(html) lines_expected = re.findall( diff --git a/tests/instructions/sar_test.py b/tests/instructions/sar_test.py new file mode 100644 index 00000000..b618582f --- /dev/null +++ b/tests/instructions/sar_test.py @@ -0,0 +1,148 @@ +import pytest + +from mythril.disassembler.disassembly import Disassembly +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.instructions import Instruction +from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction +from mythril.laser.smt import symbol_factory, simplify + + +def get_state(): + active_account = Account("0x0", code=Disassembly("60606040")) + environment = Environment(active_account, None, None, None, None, None) + state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) + state.transaction_stack.append( + (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) + ) + return state + + +BVV = symbol_factory.BitVecVal +BV = symbol_factory.BitVecSym + +test_data = ( + ([BVV(-1, 256), BVV(1, 256)], BVV(-1, 256)), + ([BVV(23, 256), BVV(257, 256)], BVV(0, 256)), + ([BVV(23, 256), BVV(30, 256)], BVV(23 >> 30, 256)), + ([BVV(-10, 256), BVV(10, 256)], BVV(-1, 256)), + ([BV("a", 256), BV("b", 256)], BV("a", 256) >> BV("b", 256)), +) + + +@pytest.mark.parametrize("inputs,output", test_data) +def test_sar(inputs, output): + # Arrange + state = get_state() + + state.mstate.stack = inputs + instruction = Instruction("sar", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert simplify(new_state.mstate.stack[-1]) == output + + +@pytest.mark.parametrize( + # Test cases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#sar-arithmetic-shift-right + "val1, val2, expected ", + ( + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0xc000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xff", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x0100", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x0101", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x00", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0100", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x4000000000000000000000000000000000000000000000000000000000000000", + "0xfe", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xf8", + "0x000000000000000000000000000000000000000000000000000000000000007f", + ), + ( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xfe", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0100", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ), +) +def test_concrete_sar(val1, val2, expected): + # Arrange + state = get_state() + state.mstate.stack = [BVV(int(val1, 16), 256), BVV(int(val2, 16), 256)] + expected = BVV(int(expected, 16), 256) + instruction = Instruction("sar", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert simplify(new_state.mstate.stack[-1]) == expected diff --git a/tests/instructions/shl_test.py b/tests/instructions/shl_test.py new file mode 100644 index 00000000..0e36c088 --- /dev/null +++ b/tests/instructions/shl_test.py @@ -0,0 +1,123 @@ +import pytest + +from mythril.disassembler.disassembly import Disassembly +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.instructions import Instruction +from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction +from mythril.laser.smt import symbol_factory, simplify + + +def get_state(): + active_account = Account("0x0", code=Disassembly("60606040")) + environment = Environment(active_account, None, None, None, None, None) + state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) + state.transaction_stack.append( + (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) + ) + return state + + +BVV = symbol_factory.BitVecVal +BV = symbol_factory.BitVecSym + +test_data = ( + ([BVV(2, 256), BVV(2, 256)], BVV(8, 256)), + ([BVV(23, 256), BVV(257, 256)], BVV(0, 256)), + ([BVV(23, 256), BVV(30, 256)], BVV(23 * (1 << 30), 256)), + ([BV("a", 256), BVV(270, 256)], 0), + ([BV("a", 256), BV("b", 256)], BV("a", 256) << BV("b", 256)), +) + + +@pytest.mark.parametrize("inputs,output,", test_data) +def test_shl(inputs, output): + # Arrange + state = get_state() + + state.mstate.stack = inputs + instruction = Instruction("shl", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert simplify(new_state.mstate.stack[-1]) == output + + +@pytest.mark.parametrize( + # Testcases from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shl-shift-left + "val1, val2, expected", + ( + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000002", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xff", + "0x8000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0100", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0101", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x00", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0x8000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0100", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + ), + ), +) +def test_concrete_shl(val1, val2, expected): + # Arrange + state = get_state() + state.mstate.stack = [BVV(int(val1, 16), 256), BVV(int(val2, 16), 256)] + expected = BVV(int(expected, 16), 256) + instruction = Instruction("shl", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert simplify(new_state.mstate.stack[-1]) == expected diff --git a/tests/instructions/shr_test.py b/tests/instructions/shr_test.py new file mode 100644 index 00000000..aaf370e2 --- /dev/null +++ b/tests/instructions/shr_test.py @@ -0,0 +1,125 @@ +import pytest + +from mythril.disassembler.disassembly import Disassembly +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.instructions import Instruction +from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction +from mythril.laser.smt import symbol_factory, simplify, LShR + + +def get_state(): + active_account = Account("0x0", code=Disassembly("60606040")) + environment = Environment(active_account, None, None, None, None, None) + state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) + state.transaction_stack.append( + (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) + ) + return state + + +BVV = symbol_factory.BitVecVal +BV = symbol_factory.BitVecSym + +test_data = ( + ([BVV(33, 256), BVV(4, 256)], BVV(2, 256)), + ([BVV(1 << 100, 256), BVV(257, 256)], BVV(0, 256)), + ([BVV(23233, 256), BVV(10, 256)], BVV(23233 // (1 << 10), 256)), + ([BV("a", 256), BVV(270, 256)], 0), + ( + [BV("a", 256), BV("b", 256)], + LShR(BV("a", 256), BV("b", 256)), + ), # Current approximate specs +) + + +@pytest.mark.parametrize("inputs,output,", test_data) +def test_shr(inputs, output): + # Arrange + state = get_state() + + state.mstate.stack = inputs + instruction = Instruction("shr", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert simplify(new_state.mstate.stack[-1]) == output + + +@pytest.mark.parametrize( + # Cases: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-145.md#shr-logical-shift-right + "val1, val2, expected", + ( + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x00", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x4000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x0100", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x8000000000000000000000000000000000000000000000000000000000000000", + "0x0101", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x00", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x01", + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0xff", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "0x0100", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x01", + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ), +) +def test_concrete_shr(val1, val2, expected): + state = get_state() + state.mstate.stack = [BVV(int(val1, 16), 256), BVV(int(val2, 16), 256)] + expected = BVV(int(expected, 16), 256) + instruction = Instruction("shr", dynamic_loader=None) + + # Act + new_state = instruction.evaluate(state)[0] + + # Assert + assert simplify(new_state.mstate.stack[-1]) == expected diff --git a/tests/laser/transaction/create_transaction_test.py b/tests/laser/transaction/create_transaction_test.py index 10a1f837..5c4e93ad 100644 --- a/tests/laser/transaction/create_transaction_test.py +++ b/tests/laser/transaction/create_transaction_test.py @@ -1,4 +1,4 @@ -from mythril.mythril import Mythril +from mythril.mythril import MythrilDisassembler from mythril.laser.ethereum.transaction import execute_contract_creation from mythril.ethereum import util import mythril.laser.ethereum.svm as svm @@ -13,7 +13,7 @@ from mythril.analysis.symbolic import SymExecWrapper def test_create(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"), - solc_binary=Mythril._init_solc_binary("0.5.0"), + solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) laser_evm = svm.LaserEVM({}) @@ -37,7 +37,7 @@ def test_create(): def test_sym_exec(): contract = SolidityContract( str(tests.TESTDATA_INPUTS_CONTRACTS / "calls.sol"), - solc_binary=Mythril._init_solc_binary("0.5.0"), + solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) sym = SymExecWrapper( diff --git a/tests/mythril/mythril_analyzer_test.py b/tests/mythril/mythril_analyzer_test.py new file mode 100644 index 00000000..0e826cc8 --- /dev/null +++ b/tests/mythril/mythril_analyzer_test.py @@ -0,0 +1,31 @@ +from pathlib import Path +from mythril.mythril import MythrilDisassembler, MythrilAnalyzer +from mythril.analysis.report import Issue +from mock import patch + + +@patch("mythril.analysis.report.Issue.add_code_info", return_value=None) +@patch( + "mythril.mythril.mythril_analyzer.fire_lasers", + return_value=[Issue("", "", "234", "101", "title", "0x02445")], +) +@patch("mythril.mythril.mythril_analyzer.SymExecWrapper", return_value=None) +def test_fire_lasers(mock_sym, mock_fire_lasers, mock_code_info): + disassembler = MythrilDisassembler(eth=None) + disassembler.load_from_solidity( + [ + str( + ( + Path(__file__).parent.parent / "testdata/input_contracts/origin.sol" + ).absolute() + ) + ] + ) + analyzer = MythrilAnalyzer(disassembler, strategy="dfs") + + issues = analyzer.fire_lasers(modules=[]).sorted_issues() + mock_sym.assert_called() + mock_fire_lasers.assert_called() + mock_code_info.assert_called() + assert len(issues) == 1 + assert issues[0]["swc-id"] == "101" diff --git a/tests/mythril/mythril_config_test.py b/tests/mythril/mythril_config_test.py new file mode 100644 index 00000000..19c23c39 --- /dev/null +++ b/tests/mythril/mythril_config_test.py @@ -0,0 +1,58 @@ +import pytest + +from configparser import ConfigParser +from pathlib import Path + +from mythril.mythril import MythrilConfig +from mythril.exceptions import CriticalError + + +def test_config_path_dynloading(): + config = MythrilConfig() + config.config_path = str( + Path(__file__).parent.parent / "testdata/mythril_config_inputs/config.ini" + ) + config.set_api_from_config_path() + assert config.eth.host == "mainnet.infura.io" + assert config.eth.port == 443 + + +rpc_types_tests = [ + ("infura", "mainnet.infura.io", 443, True), + ("ganache", "localhost", 8545, True), + ("infura-rinkeby", "rinkeby.infura.io", 443, True), + ("infura-ropsten", "ropsten.infura.io", 443, True), + ("infura-kovan", "kovan.infura.io", 443, True), + ("localhost", "localhost", 8545, True), + ("localhost:9022", "localhost", 9022, True), + ("pinfura", None, None, False), + ("infura-finkeby", None, None, False), +] + + +@pytest.mark.parametrize("rpc_type,host,port,success", rpc_types_tests) +def test_set_rpc(rpc_type, host, port, success): + config = MythrilConfig() + if success: + config._set_rpc(rpc_type) + assert config.eth.host == host + assert config.eth.port == port + else: + with pytest.raises(CriticalError): + config._set_rpc(rpc_type) + + +def test_leveldb_config_addition(): + config = ConfigParser() + config.add_section("defaults") + MythrilConfig._add_leveldb_option(config, "test") + assert config.has_section("defaults") + assert config.get("defaults", "leveldb_dir") == "test" + + +def test_dynld_config_addition(): + config = ConfigParser() + config.add_section("defaults") + MythrilConfig._add_dynamic_loading_option(config) + assert config.has_section("defaults") + assert config.get("defaults", "dynamic_loading") == "infura" diff --git a/tests/mythril/mythril_disassembler_test.py b/tests/mythril/mythril_disassembler_test.py new file mode 100644 index 00000000..8433128e --- /dev/null +++ b/tests/mythril/mythril_disassembler_test.py @@ -0,0 +1,70 @@ +import pytest +from mythril.mythril import MythrilConfig, MythrilDisassembler +from mythril.exceptions import CriticalError + +storage_test = [ + ( + ["438767356", "3"], + [ + "0x1a270efc: 0x0000000000000000000000000000000000000000000000000000000000000000", + "0x1a270efd: 0x0000000000000000000000000000000000000000000000000000000000000000", + "0x1a270efe: 0x0000000000000000000000000000000000000000000000000000000000000000", + ], + ), + ( + ["mapping", "4588934759847", "1", "2"], + [ + "0x7e523d5aeb10cdb378b0b1f76138c28063a2cb9ec8ff710f42a0972f4d53cf44: " + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xba36da34ceec88853a2ebdde88e023c6919b90348f41e8905b422dc9ce22301c: " + "0x0000000000000000000000000000000000000000000000000000000000000000", + ], + ), + ( + ["mapping", "4588934759847", "10"], + [ + "45998575720532480608987132552042185415362901038635143236141343153058112000553: " + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + ), + ( + ["4588934759847", "1", "array"], + [ + "30699902832541380821728647136767910246735388184559883985790189062258823875816: " + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + ), +] + + +@pytest.mark.parametrize("params,ans", storage_test) +def test_get_data_from_storage(params, ans): + config = MythrilConfig() + config.set_api_rpc_infura() + disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23") + outtext = disassembler.get_state_variable_from_storage( + "0x76799f77587738bfeef09452df215b63d2cfb08a", params + ).split("\n") + assert outtext == ans + + +storage_test_incorrect_params = [ + (["1", "2", "3", "4"]), + (["mapping", "1"]), + (["a", "b", "c"]), +] + + +@pytest.mark.parametrize("params", storage_test_incorrect_params) +def test_get_data_from_storage_incorrect_params(params): + config = MythrilConfig() + config.set_api_rpc_infura() + disassembler = MythrilDisassembler(eth=config.eth, solc_version="0.4.23") + with pytest.raises(CriticalError): + disassembler.get_state_variable_from_storage( + "0x76799f77587738bfeef09452df215b63d2cfb08a", params + ) + + +def test_solc_install(): + MythrilDisassembler(eth=None, solc_version="0.4.19") diff --git a/tests/mythril/mythril_leveldb_test.py b/tests/mythril/mythril_leveldb_test.py new file mode 100644 index 00000000..73e46827 --- /dev/null +++ b/tests/mythril/mythril_leveldb_test.py @@ -0,0 +1,51 @@ +import io +import pytest +from contextlib import redirect_stdout +from mock import patch + +from mythril.mythril import MythrilLevelDB, MythrilConfig +from mythril.exceptions import CriticalError + + +@patch("mythril.ethereum.interface.leveldb.client.EthLevelDB.search") +@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None) +@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None) +@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None) +def test_leveldb_code_search(mock_leveldb, f1, f2, f3): + config = MythrilConfig() + config.set_api_leveldb("some path") + leveldb_search = MythrilLevelDB(leveldb=config.eth_db) + leveldb_search.search_db("code#PUSH#") + mock_leveldb.assert_called() + + +@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None) +@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None) +@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None) +def test_leveldb_hash_search_incorrect_input(f1, f2, f3): + config = MythrilConfig() + config.set_api_leveldb("some path") + leveldb_search = MythrilLevelDB(leveldb=config.eth_db) + with pytest.raises(CriticalError): + leveldb_search.contract_hash_to_address("0x23") + + +@patch( + "mythril.ethereum.interface.leveldb.client.EthLevelDB.contract_hash_to_address", + return_value="0xddbb615cb2ffaff7233d8a6f3601621de94795e1", +) +@patch("mythril.ethereum.interface.leveldb.client.ETH_DB", return_value=None) +@patch("mythril.ethereum.interface.leveldb.client.LevelDBReader", return_value=None) +@patch("mythril.ethereum.interface.leveldb.client.LevelDBWriter", return_value=None) +def test_leveldb_hash_search_correct_input(mock_hash_to_address, f1, f2, f3): + config = MythrilConfig() + config.set_api_leveldb("some path") + leveldb_search = MythrilLevelDB(leveldb=config.eth_db) + f = io.StringIO() + with redirect_stdout(f): + leveldb_search.contract_hash_to_address( + "0x0464e651bcc40de28fc7fcde269218d16850bac9689da5f4a6bd640fd3cdf6aa" + ) + out = f.getvalue() + mock_hash_to_address.assert_called() + assert out == "0xddbb615cb2ffaff7233d8a6f3601621de94795e1\n" diff --git a/tests/native_test.py b/tests/native_test.py index 3d0438ff..c58a25e3 100644 --- a/tests/native_test.py +++ b/tests/native_test.py @@ -1,5 +1,5 @@ from mythril.solidity.soliditycontract import SolidityContract -from mythril.mythril import Mythril +from mythril.mythril import MythrilDisassembler from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.state.machine_state import MachineState from mythril.laser.ethereum.state.global_state import GlobalState @@ -84,7 +84,8 @@ class NativeTests(BaseTestCase): def runTest(): """""" disassembly = SolidityContract( - "./tests/native_tests.sol", solc_binary=Mythril._init_solc_binary("0.5.0") + "./tests/native_tests.sol", + solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ).disassembly account = Account("0x0000000000000000000000000000000000000000", disassembly) accounts = {account.address: account} diff --git a/tests/report_test.py b/tests/report_test.py index a95eb63d..e9f21001 100644 --- a/tests/report_test.py +++ b/tests/report_test.py @@ -21,6 +21,13 @@ def _fix_debug_data(json_str): return json.dumps(read_json, sort_keys=True, indent=4) +def _add_jsonv2_stubs(json_str): + read_json = json.loads(json_str) + for issue in read_json[0]["issues"]: + issue["extra"]["discoveryTime"] = "" + return json.dumps(read_json, sort_keys=True, indent=4) + + def _generate_report(input_file): contract = EVMContract(input_file.read_text(), enable_online_lookup=False) sym = SymExecWrapper( @@ -181,7 +188,9 @@ def test_text_report(reports): def test_jsonv2_report(reports): _assert_empty_json( _get_changed_files_json( - lambda report: _fix_path(report.as_swc_standard_format()).strip(), + lambda report: _fix_path( + _add_jsonv2_stubs(report.as_swc_standard_format()) + ).strip(), reports, ".jsonv2", ), diff --git a/tests/solidity_contract_test.py b/tests/solidity_contract_test.py index 6b55aad1..4edb8e6e 100644 --- a/tests/solidity_contract_test.py +++ b/tests/solidity_contract_test.py @@ -1,6 +1,6 @@ from pathlib import Path -from mythril.mythril import Mythril +from mythril.mythril import MythrilDisassembler from mythril.solidity.soliditycontract import SolidityContract from tests import BaseTestCase @@ -11,7 +11,7 @@ class SolidityContractTest(BaseTestCase): def test_get_source_info_without_name_gets_latest_contract_info(self): input_file = TEST_FILES / "multi_contracts.sol" contract = SolidityContract( - str(input_file), solc_binary=Mythril._init_solc_binary("0.5.0") + str(input_file), solc_binary=MythrilDisassembler._init_solc_binary("0.5.0") ) code_info = contract.get_source_info(142) @@ -25,7 +25,7 @@ class SolidityContractTest(BaseTestCase): contract = SolidityContract( str(input_file), name="Transfer1", - solc_binary=Mythril._init_solc_binary("0.5.0"), + solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) code_info = contract.get_source_info(142) @@ -39,7 +39,7 @@ class SolidityContractTest(BaseTestCase): contract = SolidityContract( str(input_file), name="AssertFail", - solc_binary=Mythril._init_solc_binary("0.5.0"), + solc_binary=MythrilDisassembler._init_solc_binary("0.5.0"), ) code_info = contract.get_source_info(70, constructor=True) diff --git a/tests/testdata/mythril_config_inputs/config.ini b/tests/testdata/mythril_config_inputs/config.ini new file mode 100644 index 00000000..bb6e7061 --- /dev/null +++ b/tests/testdata/mythril_config_inputs/config.ini @@ -0,0 +1,2 @@ +[defaults] +dynamic_loading = infura diff --git a/tests/testdata/outputs_expected/calls.sol.o.jsonv2 b/tests/testdata/outputs_expected/calls.sol.o.jsonv2 index 67c4957d..da380624 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/calls.sol.o.jsonv2 @@ -1,150 +1,148 @@ [ - { - "issues": [ - { - "description": { - "head": "The contract executes an external message call.", - "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "661:1:0" - } + { + "issues": [ + { + "description": { + "head": "The contract executes an external message call.", + "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "661:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-107", + "swcTitle": "Reentrancy" + }, + { + "description": { + "head": "The contract executes an external message call.", + "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "779:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-107", + "swcTitle": "Reentrancy" + }, + { + "description": { + "head": "The contract executes an external message call.", + "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "858:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-107", + "swcTitle": "Reentrancy" + }, + { + "description": { + "head": "A call to a user-supplied address is executed.", + "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "912:1:0" + } + ], + "severity": "Medium", + "swcID": "SWC-107", + "swcTitle": "Reentrancy" + }, + { + "description": { + "head": "The return value of a message call is not checked.", + "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "661:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + }, + { + "description": { + "head": "The return value of a message call is not checked.", + "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "779:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + }, + { + "description": { + "head": "The return value of a message call is not checked.", + "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "858:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + }, + { + "description": { + "head": "The return value of a message call is not checked.", + "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "912:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + } ], - "severity": "Low", - "swcID": "SWC-107", - "swcTitle": "Reentrancy" - }, - { - "description": { - "head": "The contract executes an external message call.", - "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "779:1:0" - } + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0x7cbb77986c6b1bf6e945cd3fba06d3ea3d28cfc49cdfdc9571ec30703ac5862f" ], - "severity": "Low", - "swcID": "SWC-107", - "swcTitle": "Reentrancy" - }, - { - "description": { - "head": "The contract executes an external message call.", - "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "858:1:0" - } - ], - "severity": "Low", - "swcID": "SWC-107", - "swcTitle": "Reentrancy" - }, - { - "description": { - "head": "A call to a user-supplied address is executed.", - "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "912:1:0" - } - ], - "severity": "Medium", - "swcID": "SWC-107", - "swcTitle": "Reentrancy" - }, - { - "description": { - "head": "The return value of a message call is not checked.", - "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "661:1:0" - } - ], - "severity": "Low", - "swcID": "SWC-104", - "swcTitle": "Unchecked Call Return Value" - }, - { - "description": { - "head": "The return value of a message call is not checked.", - "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "779:1:0" - } - ], - "severity": "Low", - "swcID": "SWC-104", - "swcTitle": "Unchecked Call Return Value" - }, - { - "description": { - "head": "The return value of a message call is not checked.", - "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "858:1:0" - } - ], - "severity": "Low", - "swcID": "SWC-104", - "swcTitle": "Unchecked Call Return Value" - }, - { - "description": { - "head": "The return value of a message call is not checked.", - "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." - }, - "extra": { - - }, - "locations": [ - { - "sourceMap": "912:1:0" - } - ], - "severity": "Low", - "swcID": "SWC-104", - "swcTitle": "Unchecked Call Return Value" - } - ], - "meta": { - - }, - "sourceFormat": "evm-byzantium-bytecode", - "sourceList": [ - "0x7cbb77986c6b1bf6e945cd3fba06d3ea3d28cfc49cdfdc9571ec30703ac5862f" - ], - "sourceType": "raw-bytecode" - } -] + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 b/tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 index c4f5f390..032cfc01 100644 --- a/tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 @@ -6,7 +6,9 @@ "head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "446:1:0" @@ -21,7 +23,9 @@ "head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "484:1:0" @@ -36,7 +40,9 @@ "head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "506:1:0" @@ -51,7 +57,9 @@ "head": "A reachable exception has been detected.", "tail": "It is possible to trigger an exception (opcode 0xfe). Exceptions can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "531:1:0" diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json index 180eb4aa..b6bc99ac 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json @@ -33,8 +33,8 @@ "debug": "", "description": "The return value of a message call is not checked.\nExternal calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.", "function": "_function_0x9b58bc26", - "max_gas_used": 35922, - "min_gas_used": 1170, + "max_gas_used": 35928, + "min_gas_used": 1176, "severity": "Low", "sourceMap": null, "swc-id": "104", @@ -46,8 +46,8 @@ "debug": "", "description": "A call to a user-supplied address is executed.\nThe callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state.", "function": "_function_0xeea4c864", - "max_gas_used": 1223, - "min_gas_used": 471, + "max_gas_used": 1229, + "min_gas_used": 477, "severity": "Medium", "sourceMap": null, "swc-id": "107", @@ -59,8 +59,8 @@ "debug": "", "description": "The return value of a message call is not checked.\nExternal calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states.", "function": "_function_0xeea4c864", - "max_gas_used": 35947, - "min_gas_used": 1195, + "max_gas_used": 35953, + "min_gas_used": 1201, "severity": "Low", "sourceMap": null, "swc-id": "104", diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2 b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2 index e132d037..4f0d13e0 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2 @@ -1,67 +1,97 @@ -[{ - "issues": [{ - "description": { - "head": "Use of callcode is deprecated.", - "tail": "The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead." - }, - "extra": {}, - "locations": [{ - "sourceMap": "618:1:0" - }], - "severity": "Medium", - "swcID": "SWC-111", - "swcTitle": "Use of Deprecated Solidity Functions" - }, { - "description": { - "head": "A call to a user-supplied address is executed.", - "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state." - }, - "extra": {}, - "locations": [{ - "sourceMap": "1038:1:0" - }], - "severity": "Medium", - "swcID": "SWC-107", - "swcTitle": "Reentrancy" - }, { - "description": { - "head": "The return value of a message call is not checked.", - "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." - }, - "extra": {}, - "locations": [{ - "sourceMap": "618:1:0" - }], - "severity": "Low", - "swcID": "SWC-104", - "swcTitle": "Unchecked Call Return Value" - }, { - "description": { - "head": "The return value of a message call is not checked.", - "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." - }, - "extra": {}, - "locations": [{ - "sourceMap": "849:1:0" - }], - "severity": "Low", - "swcID": "SWC-104", - "swcTitle": "Unchecked Call Return Value" - }, { - "description": { - "head": "The return value of a message call is not checked.", - "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." - }, - "extra": {}, - "locations": [{ - "sourceMap": "1038:1:0" - }], - "severity": "Low", - "swcID": "SWC-104", - "swcTitle": "Unchecked Call Return Value" - }], - "meta": {}, - "sourceFormat": "evm-byzantium-bytecode", - "sourceList": ["0x6daec61d05d8f1210661e7e7d1ed6d72bd6ade639398fac1e867aff50abfc1c1"], - "sourceType": "raw-bytecode" -}] +[ + { + "issues": [ + { + "description": { + "head": "Use of callcode is deprecated.", + "tail": "The callcode method executes code of another contract in the context of the caller account. Due to a bug in the implementation it does not persist sender and value over the call. It was therefore deprecated and may be removed in the future. Use the delegatecall method instead." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "618:1:0" + } + ], + "severity": "Medium", + "swcID": "SWC-111", + "swcTitle": "Use of Deprecated Solidity Functions" + }, + { + "description": { + "head": "A call to a user-supplied address is executed.", + "tail": "The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "1038:1:0" + } + ], + "severity": "Medium", + "swcID": "SWC-107", + "swcTitle": "Reentrancy" + }, + { + "description": { + "head": "The return value of a message call is not checked.", + "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "618:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + }, + { + "description": { + "head": "The return value of a message call is not checked.", + "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "849:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + }, + { + "description": { + "head": "The return value of a message call is not checked.", + "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "1038:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0x6daec61d05d8f1210661e7e7d1ed6d72bd6ade639398fac1e867aff50abfc1c1" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown index 29001c95..4e222b18 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown @@ -32,7 +32,7 @@ The callcode method executes code of another contract in the context of the call - Contract: Unknown - Function name: `_function_0x9b58bc26` - PC address: 849 -- Estimated Gas Usage: 1170 - 35922 +- Estimated Gas Usage: 1176 - 35928 ### Description @@ -45,7 +45,7 @@ External calls return a boolean value. If the callee contract halts with an exce - Contract: Unknown - Function name: `_function_0xeea4c864` - PC address: 1038 -- Estimated Gas Usage: 471 - 1223 +- Estimated Gas Usage: 477 - 1229 ### Description @@ -58,7 +58,7 @@ The callee address of an external message call can be set by the caller. Note th - Contract: Unknown - Function name: `_function_0xeea4c864` - PC address: 1038 -- Estimated Gas Usage: 1195 - 35947 +- Estimated Gas Usage: 1201 - 35953 ### Description diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text index 40db8117..9e9761c3 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text @@ -26,7 +26,7 @@ Severity: Low Contract: Unknown Function name: _function_0x9b58bc26 PC address: 849 -Estimated Gas Usage: 1170 - 35922 +Estimated Gas Usage: 1176 - 35928 The return value of a message call is not checked. External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states. -------------------- @@ -37,7 +37,7 @@ Severity: Medium Contract: Unknown Function name: _function_0xeea4c864 PC address: 1038 -Estimated Gas Usage: 471 - 1223 +Estimated Gas Usage: 477 - 1229 A call to a user-supplied address is executed. The callee address of an external message call can be set by the caller. Note that the callee can contain arbitrary code and may re-enter any function in this contract. Review the business logic carefully to prevent averse effects on the contract state. -------------------- @@ -48,7 +48,7 @@ Severity: Low Contract: Unknown Function name: _function_0xeea4c864 PC address: 1038 -Estimated Gas Usage: 1195 - 35947 +Estimated Gas Usage: 1201 - 35953 The return value of a message call is not checked. External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states. -------------------- diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 b/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 index 2afee3f5..21672449 100644 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 @@ -6,7 +6,9 @@ "head": "Anyone can withdraw ETH from the contract account.", "tail": "Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "142:1:0" diff --git a/tests/testdata/outputs_expected/origin.sol.o.jsonv2 b/tests/testdata/outputs_expected/origin.sol.o.jsonv2 index b266a973..27322fde 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/origin.sol.o.jsonv2 @@ -6,7 +6,9 @@ "head": "Use of tx.origin is deprecated.", "tail": "The smart contract retrieves the transaction origin (tx.origin) using msg.origin. Use of msg.origin is deprecated and the instruction may be removed in the future. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin" }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "317:1:0" diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json index 285fdf31..89fdb839 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.json +++ b/tests/testdata/outputs_expected/overflow.sol.o.json @@ -7,8 +7,8 @@ "debug": "", "description": "The binary subtraction can underflow.\nThe operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.", "function": "sendeth(address,uint256)", - "max_gas_used": 78152, - "min_gas_used": 17016, + "max_gas_used": 78155, + "min_gas_used": 17019, "severity": "High", "sourceMap": null, "swc-id": "101", @@ -20,8 +20,8 @@ "debug": "", "description": "The binary subtraction can underflow.\nThe operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.", "function": "sendeth(address,uint256)", - "max_gas_used": 78152, - "min_gas_used": 17016, + "max_gas_used": 78155, + "min_gas_used": 17019, "severity": "High", "sourceMap": null, "swc-id": "101", diff --git a/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 b/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 index ee33041a..dfcc29d5 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 @@ -6,7 +6,9 @@ "head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "567:1:0" @@ -21,7 +23,9 @@ "head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "649:1:0" diff --git a/tests/testdata/outputs_expected/overflow.sol.o.markdown b/tests/testdata/outputs_expected/overflow.sol.o.markdown index 0d589f0d..8774d894 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.o.markdown @@ -6,7 +6,7 @@ - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 567 -- Estimated Gas Usage: 17016 - 78152 +- Estimated Gas Usage: 17019 - 78155 ### Description @@ -19,7 +19,7 @@ The operands of the subtraction operation are not sufficiently constrained. The - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 649 -- Estimated Gas Usage: 17016 - 78152 +- Estimated Gas Usage: 17019 - 78155 ### Description diff --git a/tests/testdata/outputs_expected/overflow.sol.o.text b/tests/testdata/outputs_expected/overflow.sol.o.text index 934ccdd7..698665d8 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.text +++ b/tests/testdata/outputs_expected/overflow.sol.o.text @@ -4,7 +4,7 @@ Severity: High Contract: Unknown Function name: sendeth(address,uint256) PC address: 567 -Estimated Gas Usage: 17016 - 78152 +Estimated Gas Usage: 17019 - 78155 The binary subtraction can underflow. The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion. -------------------- @@ -15,7 +15,7 @@ Severity: High Contract: Unknown Function name: sendeth(address,uint256) PC address: 649 -Estimated Gas Usage: 17016 - 78152 +Estimated Gas Usage: 17019 - 78155 The binary subtraction can underflow. The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion. -------------------- diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 b/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 index 8c6fa02a..03fb9c0d 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 @@ -6,7 +6,9 @@ "head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "196:1:0" @@ -21,7 +23,9 @@ "head": "The contract executes an external message call.", "tail": "An external function call to a fixed contract address is executed. Make sure that the callee contract has been reviewed carefully." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "285:1:0" @@ -36,7 +40,9 @@ "head": "The return value of a message call is not checked.", "tail": "External calls return a boolean value. If the callee contract halts with an exception, 'false' is returned and execution continues in the caller. It is usually recommended to wrap external calls into a require statement to prevent unexpected states." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "285:1:0" diff --git a/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 b/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 index ee5c2c44..c492c24c 100644 --- a/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 @@ -1,27 +1,29 @@ [ - { - "issues" : [ - { - "swcTitle" : "Unprotected SELFDESTRUCT Instruction", - "locations" : [ - { - "sourceMap" : "146:1:0" - } - ], - "extra" : {}, - "description" : { - "tail" : "Anyone can kill this contract and withdraw its balance to an arbitrary address.", - "head" : "The contract can be killed by anyone." - }, - "severity" : "High", - "swcID" : "SWC-106" - } - ], - "sourceFormat" : "evm-byzantium-bytecode", - "meta" : {}, - "sourceType" : "raw-bytecode", - "sourceList" : [ - "0x2fb801366b61a05b30550481a1c8f7d5f20de0b93d9f2f2ce2b28c4e322033c9" - ] - } + { + "issues": [ + { + "description": { + "head": "The contract can be killed by anyone.", + "tail": "Anyone can kill this contract and withdraw its balance to an arbitrary address." + }, + "extra": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "146:1:0" + } + ], + "severity": "High", + "swcID": "SWC-106", + "swcTitle": "Unprotected SELFDESTRUCT Instruction" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0x2fb801366b61a05b30550481a1c8f7d5f20de0b93d9f2f2ce2b28c4e322033c9" + ], + "sourceType": "raw-bytecode" + } ] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json index 5761ec2e..6a2464d5 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.json +++ b/tests/testdata/outputs_expected/underflow.sol.o.json @@ -7,8 +7,8 @@ "debug": "", "description": "The binary subtraction can underflow.\nThe operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.", "function": "sendeth(address,uint256)", - "max_gas_used": 52858, - "min_gas_used": 11912, + "max_gas_used": 52861, + "min_gas_used": 11915, "severity": "High", "sourceMap": null, "swc-id": "101", @@ -20,8 +20,8 @@ "debug": "", "description": "The binary subtraction can underflow.\nThe operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.", "function": "sendeth(address,uint256)", - "max_gas_used": 52858, - "min_gas_used": 11912, + "max_gas_used": 52861, + "min_gas_used": 11915, "severity": "High", "sourceMap": null, "swc-id": "101", diff --git a/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 b/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 index eee3d146..94854e04 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 @@ -6,7 +6,9 @@ "head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "567:1:0" @@ -21,7 +23,9 @@ "head": "The binary subtraction can underflow.", "tail": "The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion." }, - "extra": {}, + "extra": { + "discoveryTime": "" + }, "locations": [ { "sourceMap": "649:1:0" diff --git a/tests/testdata/outputs_expected/underflow.sol.o.markdown b/tests/testdata/outputs_expected/underflow.sol.o.markdown index 19f4ca9c..48399072 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.o.markdown @@ -6,7 +6,7 @@ - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 567 -- Estimated Gas Usage: 11912 - 52858 +- Estimated Gas Usage: 11915 - 52861 ### Description @@ -19,7 +19,7 @@ The operands of the subtraction operation are not sufficiently constrained. The - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 649 -- Estimated Gas Usage: 11912 - 52858 +- Estimated Gas Usage: 11915 - 52861 ### Description diff --git a/tests/testdata/outputs_expected/underflow.sol.o.text b/tests/testdata/outputs_expected/underflow.sol.o.text index 84a35ab7..16701a04 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.text +++ b/tests/testdata/outputs_expected/underflow.sol.o.text @@ -4,7 +4,7 @@ Severity: High Contract: Unknown Function name: sendeth(address,uint256) PC address: 567 -Estimated Gas Usage: 11912 - 52858 +Estimated Gas Usage: 11915 - 52861 The binary subtraction can underflow. The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion. -------------------- @@ -15,7 +15,7 @@ Severity: High Contract: Unknown Function name: sendeth(address,uint256) PC address: 649 -Estimated Gas Usage: 11912 - 52858 +Estimated Gas Usage: 11915 - 52861 The binary subtraction can underflow. The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion. -------------------- diff --git a/tox.ini b/tox.ini index 93ee48a2..e50f07fe 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ commands = mkdir -p {toxinidir}/tests/testdata/outputs_current_laser_result/ py.test -v \ --junitxml={toxworkdir}/output/{envname}/junit.xml \ + --disable-pytest-warnings \ {posargs} [testenv:py36] @@ -35,6 +36,7 @@ commands = --cov-report=xml:{toxworkdir}/output/{envname}/coverage.xml \ --cov-report=html:{toxworkdir}/output/{envname}/covhtml \ --junitxml={toxworkdir}/output/{envname}/junit.xml \ + --disable-pytest-warnings \ {posargs}