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 a017d155..53fbd1fa 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ [![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. It's also an experimental tool designed for security pros. If you a smart contract developer you might prefer smoother tools such as: +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: -- [Mythos](https://github.com/cleanunicorn/mythos) +- [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 06c15c30..a3dfed4f 100644 --- a/mythril/analysis/report.py +++ b/mythril/analysis/report.py @@ -231,7 +231,7 @@ 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() 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 c51e8b2a..514f039d 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -10,13 +10,18 @@ import json import logging import os import sys -import traceback 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) @@ -26,7 +31,6 @@ log = logging.getLogger(__name__) def exit_with_error(format_, message): """ - :param format_: :param message: """ @@ -64,10 +68,6 @@ def main() -> None: parse_args(parser=parser, args=args) -if __name__ == "__main__": - main() - - def create_parser(parser: argparse.ArgumentParser) -> None: """ Creates the parser by setting all the possible arguments @@ -315,61 +315,56 @@ def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace): 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() def set_config(args: argparse.Namespace): - 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, - ) + config = MythrilConfig() if args.dynld or not args.no_onchain_storage_access and not (args.rpc or args.i): - mythril.set_api_from_config_path() + config.set_api_from_config_path() if args.address: # Establish RPC connection if necessary - mythril.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) + config.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 + config.set_api_leveldb( + config.leveldb_dir if not args.leveldb_dir else args.leveldb_dir ) - return mythril + return config -def leveldb_search(mythril: Mythril, args: argparse.Namespace): - if args.search: - # Database search ops - mythril.search_db(args.search) - sys.exit() +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 + leveldb_searcher.search_db(args.search) - if args.contract_hash_to_address: - # search corresponding address - try: - mythril.contract_hash_to_address(args.contract_hash_to_address) - except AddressNotFoundError: - print("Address not found.") + else: + # search corresponding address + try: + leveldb_searcher.contract_hash_to_address(args.contract_hash_to_address) + except AddressNotFoundError: + print("Address not found.") sys.exit() -def get_code(mythril: Mythril, args: argparse.Namespace): +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, _ = mythril.load_from_bytecode(code, args.bin_runtime) + 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, _ = mythril.load_from_bytecode(bytecode, args.bin_runtime) + address, _ = disassembler.load_from_bytecode(bytecode, args.bin_runtime) elif args.address: # Get bytecode from a contract address - address, _ = mythril.load_from_address(args.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: @@ -377,7 +372,9 @@ def get_code(mythril: Mythril, args: argparse.Namespace): 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 + address, _ = disassembler.load_from_solidity( + args.solidity_file + ) # list of files else: exit_with_error( args.outform, @@ -387,11 +384,12 @@ def get_code(mythril: Mythril, args: argparse.Namespace): def execute_command( - mythril: Mythril, + disassembler: MythrilDisassembler, address: str, parser: argparse.ArgumentParser, args: argparse.Namespace, ): + if args.storage: if not args.address: exit_with_error( @@ -399,36 +397,42 @@ def execute_command( "To read storage, provide the address of a deployed contract with the -a option.", ) - storage = mythril.get_state_variable_from_storage( + 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, + ) - elif args.disassemble: + if args.disassemble: # or mythril.disassemble(mythril.contracts[0]) - 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 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 mythril.contracts: + if not disassembler.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts" ) if args.graph: - html = mythril.graph_html( - strategy=args.strategy, - contract=mythril.contracts[0], - address=address, + html = analyzer.graph_html( + contract=analyzer.contracts[0], 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, ) try: @@ -439,18 +443,12 @@ def execute_command( else: try: - report = mythril.fire_lasers( - strategy=args.strategy, - address=address, + report = analyzer.fire_lasers( 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(), @@ -466,20 +464,12 @@ def execute_command( elif args.statespace_json: - if not mythril.contracts: + if not analyzer.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts" ) - 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, - ) + statespace = analyzer.dump_statespace(contract=analyzer.contracts[0]) try: with open(args.statespace_json, "w") as f: @@ -515,20 +505,27 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non validate_args(parser, args) try: quick_commands(args) - mythril = set_config(args) - leveldb_search(mythril, 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: - mythril.analyze_truffle_project(args) + 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(mythril, args) - execute_command(mythril=mythril, address=address, parser=parser, args=args) + 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: 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/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/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 d42f0a1c..da380624 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/calls.sol.o.jsonv2 @@ -1 +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"}], "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"}], "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"}] \ No newline at end of file +[ + { + "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" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0x7cbb77986c6b1bf6e945cd3fba06d3ea3d28cfc49cdfdc9571ec30703ac5862f" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2 b/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2 index 0710acc2..0d1e9df5 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2 @@ -1 +1,9 @@ -[{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": [], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [], + "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 6f71bb6a..032cfc01 100644 --- a/tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 @@ -1 +1,80 @@ -[{"issues": [{"description": {"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": {}, "locations": [{"sourceMap": "446:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"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": {}, "locations": [{"sourceMap": "484:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"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": {}, "locations": [{"sourceMap": "506:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}, {"description": {"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": {}, "locations": [{"sourceMap": "531:1:0"}], "severity": "Low", "swcID": "SWC-110", "swcTitle": "Assert Violation"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x4a773a86bc6fb269f88bf09bb3094de29b6073cf13b1760e9d01d957f50a9dfd"], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [ + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "446:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-110", + "swcTitle": "Assert Violation" + }, + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "484:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-110", + "swcTitle": "Assert Violation" + }, + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "506:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-110", + "swcTitle": "Assert Violation" + }, + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "531:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-110", + "swcTitle": "Assert Violation" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0x4a773a86bc6fb269f88bf09bb3094de29b6073cf13b1760e9d01d957f50a9dfd" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file 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 04cfe7f6..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 +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"}] \ No newline at end of file +[ + { + "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/metacoin.sol.o.jsonv2 b/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2 index 0710acc2..0d1e9df5 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2 @@ -1 +1,9 @@ -[{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": [], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 b/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 index dcd4c195..21672449 100644 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 @@ -1 +1,29 @@ -[{"issues": [{"description": {"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": {}, "locations": [{"sourceMap": "142:1:0"}], "severity": "High", "swcID": "SWC-105", "swcTitle": "Unprotected Ether Withdrawal"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xbc9c3d9db56d20cf4ca3b6fd88ff9215cf728a092cca1ed8edb83272b933ff5b"], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [ + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "142:1:0" + } + ], + "severity": "High", + "swcID": "SWC-105", + "swcTitle": "Unprotected Ether Withdrawal" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0xbc9c3d9db56d20cf4ca3b6fd88ff9215cf728a092cca1ed8edb83272b933ff5b" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2 b/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2 index 0710acc2..0d1e9df5 100644 --- a/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2 @@ -1 +1,9 @@ -[{"issues": [], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": [], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/origin.sol.o.jsonv2 b/tests/testdata/outputs_expected/origin.sol.o.jsonv2 index 2d9efb87..27322fde 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/origin.sol.o.jsonv2 @@ -1 +1,29 @@ -[{"issues": [{"description": {"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": {}, "locations": [{"sourceMap": "317:1:0"}], "severity": "Medium", "swcID": "SWC-111", "swcTitle": "Use of Deprecated Solidity Functions"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0x25b20ef097dfc0aa56a932c4e09f06ee02a69c005767df86877f48c6c2412f03"], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [ + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "317:1:0" + } + ], + "severity": "Medium", + "swcID": "SWC-111", + "swcTitle": "Use of Deprecated Solidity Functions" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0x25b20ef097dfc0aa56a932c4e09f06ee02a69c005767df86877f48c6c2412f03" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 b/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 index 9071dc3b..dfcc29d5 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 @@ -1 +1,46 @@ -[{"issues": [{"description": {"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": {}, "locations": [{"sourceMap": "567:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}, {"description": {"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": {}, "locations": [{"sourceMap": "649:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xf230bec502569e8b7e7737616d0ad0f200c436624e3c223e5398c0615cd2d6b9"], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [ + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "567:1:0" + } + ], + "severity": "High", + "swcID": "SWC-101", + "swcTitle": "Integer Overflow and Underflow" + }, + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "649:1:0" + } + ], + "severity": "High", + "swcID": "SWC-101", + "swcTitle": "Integer Overflow and Underflow" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0xf230bec502569e8b7e7737616d0ad0f200c436624e3c223e5398c0615cd2d6b9" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 b/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 index 00402e72..03fb9c0d 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 @@ -1 +1,63 @@ -[{"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": "196: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": {}, "locations": [{"sourceMap": "285:1:0"}], "severity": "Low", "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": "285:1:0"}], "severity": "Low", "swcID": "SWC-104", "swcTitle": "Unchecked Call Return Value"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xb191cf6cc0d8cc37a91c9d88019cc011b932169fb5776df616e2bb9cd93b4039"], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "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": "196: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": "285:1:0" + } + ], + "severity": "Low", + "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": "285:1:0" + } + ], + "severity": "Low", + "swcID": "SWC-104", + "swcTitle": "Unchecked Call Return Value" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0xb191cf6cc0d8cc37a91c9d88019cc011b932169fb5776df616e2bb9cd93b4039" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 b/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 index 6516a9a6..c492c24c 100644 --- a/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 @@ -1 +1,29 @@ -[{"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": {}, "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 +[ + { + "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.jsonv2 b/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 index 548c0ec6..94854e04 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 +++ b/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 @@ -1 +1,46 @@ -[{"issues": [{"description": {"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": {}, "locations": [{"sourceMap": "567:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}, {"description": {"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": {}, "locations": [{"sourceMap": "649:1:0"}], "severity": "High", "swcID": "SWC-101", "swcTitle": "Integer Overflow and Underflow"}], "meta": {}, "sourceFormat": "evm-byzantium-bytecode", "sourceList": ["0xabef56740bf7795a9f8732e4781ebd27f2977f8a4997e3ff11cee79a4ba6c0ce"], "sourceType": "raw-bytecode"}] \ No newline at end of file +[ + { + "issues": [ + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "567:1:0" + } + ], + "severity": "High", + "swcID": "SWC-101", + "swcTitle": "Integer Overflow and Underflow" + }, + { + "description": { + "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": { + "discoveryTime": "" + }, + "locations": [ + { + "sourceMap": "649:1:0" + } + ], + "severity": "High", + "swcID": "SWC-101", + "swcTitle": "Integer Overflow and Underflow" + } + ], + "meta": {}, + "sourceFormat": "evm-byzantium-bytecode", + "sourceList": [ + "0xabef56740bf7795a9f8732e4781ebd27f2977f8a4997e3ff11cee79a4ba6c0ce" + ], + "sourceType": "raw-bytecode" + } +] \ No newline at end of file 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}