From e02a2612d0cbee2f750966cdf5866bb1891484dd Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 13 May 2019 19:29:14 +0530 Subject: [PATCH 01/28] Refactor cli --- mythril/interfaces/cli.py | 384 ++++++++++++++++++++------------------ tests/cmd_line_test.py | 20 +- tests/test_cli_opts.py | 4 +- 3 files changed, 214 insertions(+), 194 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index adb0e8f2..4a7b6d98 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -57,10 +57,35 @@ def exit_with_error(format_, message): def main() -> None: """The main CLI interface entry point.""" + common_parser = argparse.ArgumentParser(add_help=False) + create_parser(common_parser) + parser = argparse.ArgumentParser( description="Security analysis of Ethereum smart contracts" ) - create_parser(parser) + + subparsers = parser.add_subparsers(dest="command", help="Commands") + analyzer_parser = subparsers.add_parser("analyze", parents=[common_parser]) + subparsers.add_parser("disassemble", parents=[common_parser]) + read_storage_parser = subparsers.add_parser("read-storage", parents=[common_parser]) + leveldb_search_parser = subparsers.add_parser( + "leveldb-search", parents=[common_parser] + ) + contract_func_to_hash = subparsers.add_parser( + "contract-function-to-hash", parents=[common_parser] + ) + contract_hash_to_addr = subparsers.add_parser( + "contract-hash-to-address", parents=[common_parser] + ) + subparsers.add_parser("version", parents=[common_parser]) + + create_analyzer_parser(analyzer_parser) + create_read_storage_parser(read_storage_parser) + create_hash_to_addr_parser(contract_hash_to_addr) + create_func_to_hash_parser(contract_func_to_hash) + create_leveldb_parser(leveldb_search_parser) + + subparsers.add_parser("truffle", parents=[analyzer_parser], add_help=False) # Get config values @@ -68,94 +93,15 @@ def main() -> None: 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") - commands.add_argument("-g", "--graph", help="generate a control flow graph") - commands.add_argument( - "-V", - "--version", - action="store_true", - help="print the Mythril version number and exit", - ) - commands.add_argument( - "-x", - "--fire-lasers", - action="store_true", - help="detect vulnerabilities, use with -c, -a or solidity file(s)", - ) - commands.add_argument( - "--truffle", - action="store_true", - help="analyze a truffle project (run from project dir)", - ) - commands.add_argument( - "-d", "--disassemble", action="store_true", help="print disassembly" - ) - commands.add_argument( - "-j", - "--statespace-json", - help="dumps the statespace json", - metavar="OUTPUT_FILE", - ) - - inputs = parser.add_argument_group("input arguments") - inputs.add_argument( - "-c", - "--code", - help='hex-encoded bytecode string ("6060604052...")', - metavar="BYTECODE", - ) - inputs.add_argument( - "-f", - "--codefile", - help="file containing hex-encoded bytecode string", - metavar="BYTECODEFILE", - type=argparse.FileType("r"), - ) - inputs.add_argument( - "-a", - "--address", - help="pull contract from the blockchain", - metavar="CONTRACT_ADDRESS", - ) - inputs.add_argument( - "-l", - "--dynld", - action="store_true", - help="auto-load dependencies from the blockchain", - ) - inputs.add_argument( - "--no-onchain-storage-access", - action="store_true", - help="turns off getting the data from onchain contracts", - ) - inputs.add_argument( - "--bin-runtime", - action="store_true", - help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", +def create_read_storage_parser(read_storage_parser: argparse.ArgumentParser): + read_storage_parser.add_argument( + "--storage-slots", + help="read state vasriables from storage index, use with -a", + metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", ) - outputs = parser.add_argument_group("output formats") - outputs.add_argument( - "-o", - "--outform", - choices=["text", "markdown", "json", "jsonv2"], - default="text", - help="report output format", - metavar="", - ) - outputs.add_argument( - "--verbose-report", - action="store_true", - help="Include debugging information in report", - ) +def create_leveldb_parser(parser: argparse.ArgumentParser): database = parser.add_argument_group("local contracts database") database.add_argument( "-s", "--search", help="search the contract database", metavar="EXPRESSION" @@ -166,27 +112,40 @@ def create_parser(parser: argparse.ArgumentParser) -> None: metavar="LEVELDB_PATH", ) - utilities = parser.add_argument_group("utilities") - utilities.add_argument( - "--hash", help="calculate function signature hash", metavar="SIGNATURE" - ) - utilities.add_argument( - "--storage", - help="read state variables from storage index, use with -a", - metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", + +def create_version_parser(hash_parser: argparse.ArgumentParser): + hash_parser.add_argument( + "version", help="print the Mythril version number and exit", metavar="VERSION" ) - utilities.add_argument( - "--solv", - help="specify solidity compiler version. If not present, will try to install it (Experimental)", - metavar="SOLV", + + +def create_func_to_hash_parser(hash_parser: argparse.ArgumentParser): + hash_parser.add_argument( + "func_name", help="calculate function signature hash", metavar="SIGNATURE" ) - utilities.add_argument( - "--contract-hash-to-address", - help="returns corresponding address for a contract address hash", - metavar="SHA3_TO_LOOK_FOR", + + +def create_hash_to_addr_parser(hash_parser: argparse.ArgumentParser): + hash_parser.add_argument( + "--hash", help="Find the address from hash", metavar="FUNCTION_NAME" ) - options = parser.add_argument_group("options") + +def create_analyzer_parser(analyzer_parser: argparse.ArgumentParser): + commands = analyzer_parser.add_argument_group("commands") + commands.add_argument("-g", "--graph", help="generate a control flow graph") + commands.add_argument( + "-j", + "--statespace-json", + help="dumps the statespace json", + metavar="OUTPUT_FILE", + ) + commands.add_argument( + "--truffle", + action="store_true", + help="analyze a truffle project (run from project dir)", + ) + options = analyzer_parser.add_argument_group("options") options.add_argument( "-m", "--modules", @@ -225,15 +184,23 @@ def create_parser(parser: argparse.ArgumentParser) -> None: default=10, help="The amount of seconds to spend on " "the initial contract creation", ) - options.add_argument("--solc-args", help="Extra arguments for solc") options.add_argument( - "--phrack", action="store_true", help="Phrack-style call graph" + "-l", + "--dynld", + action="store_true", + help="auto-load dependencies from the blockchain", ) options.add_argument( - "--enable-physics", action="store_true", help="enable graph physics simulation" + "--no-onchain-storage-access", + action="store_true", + help="turns off getting the data from onchain contracts", ) + options.add_argument( - "-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 + "--phrack", action="store_true", help="Phrack-style call graph" + ) + options.add_argument( + "--enable-physics", action="store_true", help="enable graph physics simulation" ) options.add_argument( "-q", @@ -245,6 +212,43 @@ def create_parser(parser: argparse.ArgumentParser) -> None: "--enable-iprof", action="store_true", help="enable the instruction profiler" ) + +def create_parser(parser: argparse.ArgumentParser) -> None: + parser.add_argument("solidity_file", nargs="*") + inputs = parser.add_argument_group("input arguments") + inputs.add_argument( + "-c", + "--code", + help='hex-encoded bytecode string ("6060604052...")', + metavar="BYTECODE", + ) + inputs.add_argument( + "-f", + "--codefile", + help="file containing hex-encoded bytecode string", + metavar="BYTECODEFILE", + type=argparse.FileType("r"), + ) + inputs.add_argument( + "-a", + "--address", + help="pull contract from the blockchain", + metavar="CONTRACT_ADDRESS", + ) + inputs.add_argument( + "--bin-runtime", + action="store_true", + help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", + ) + utilities = parser.add_argument_group("utilities") + + utilities.add_argument("--solc-args", help="Extra arguments for solc") + utilities.add_argument( + "--solv", + help="specify solidity compiler version. If not present, will try to install it (Experimental)", + metavar="SOLV", + ) + rpc = parser.add_argument_group("RPC options") rpc.add_argument( @@ -258,22 +262,26 @@ def create_parser(parser: argparse.ArgumentParser) -> None: ) parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) + outputs = parser.add_argument_group("output formats") + outputs.add_argument( + "-o", + "--outform", + choices=["text", "markdown", "json", "jsonv2"], + default="text", + help="report output format", + metavar="", + ) + outputs.add_argument( + "--verbose-report", + action="store_true", + help="Include debugging information in report", + ) + outputs.add_argument( + "-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 + ) -def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace): - if not ( - args.search - or args.hash - or args.disassemble - or args.graph - or args.fire_lasers - or args.storage - or args.truffle - or args.statespace_json - or args.contract_hash_to_address - ): - parser.print_help() - sys.exit() +def validate_args(args: argparse.Namespace): if args.v: if 0 <= args.v < 6: log_levels = [ @@ -293,59 +301,56 @@ def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace): args.outform, "Invalid -v value, you can find valid values in usage" ) - if args.query_signature: - if sigs.ethereum_input_decoder is None: + if args.command == "analyze": + if args.query_signature and sigs.ethereum_input_decoder is None: exit_with_error( args.outform, "The --query-signature function requires the python package ethereum-input-decoder", ) - if args.enable_iprof: - if args.v < 4: - exit_with_error( - args.outform, - "--enable-iprof must be used with -v LOG_LEVEL where LOG_LEVEL >= 4", - ) - elif not (args.graph or args.fire_lasers or args.statespace_json): - exit_with_error( - args.outform, - "--enable-iprof must be used with one of -g, --graph, -x, --fire-lasers, -j and --statespace-json", - ) - - -def quick_commands(args: argparse.Namespace): - if args.hash: - print(MythrilDisassembler.hash_for_function_signature(args.hash)) - sys.exit() + if args.enable_iprof: + if args.v < 4: + exit_with_error( + args.outform, + "--enable-iprof must be used with -v LOG_LEVEL where LOG_LEVEL >= 4", + ) + elif not (args.graph or args.fire_lasers or args.statespace_json): + exit_with_error( + args.outform, + "--enable-iprof must be used with one of -g, --graph, -x, --fire-lasers, -j and --statespace-json", + ) 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): + if ( + args.command == "analyze" and (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: + if args.command in ("contract-hash-to-address", "leveldb-search"): # Open LevelDB if necessary - config.set_api_leveldb( - config.leveldb_dir if not args.leveldb_dir else args.leveldb_dir + leveldb_dir = ( + args.leveldb_dir if "leveldb_dir" in args.__dict__ else config.leveldb_dir ) + config.set_api_leveldb(leveldb_dir) return config def leveldb_search(config: MythrilConfig, args: argparse.Namespace): - if args.search or args.contract_hash_to_address: + if args.command in ("contract-hash-to-address", "leveldb-search"): leveldb_searcher = MythrilLevelDB(config.eth_db) - if args.search: + if args.command == "leveldb-search": # Database search ops leveldb_searcher.search_db(args.search) else: # search corresponding address try: - leveldb_searcher.contract_hash_to_address(args.contract_hash_to_address) + leveldb_searcher.contract_hash_to_address(args.hash) except AddressNotFoundError: print("Address not found.") @@ -367,7 +372,7 @@ def get_code(disassembler: MythrilDisassembler, args: argparse.Namespace): 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: + if args.command == "analyze" and 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.", @@ -390,7 +395,7 @@ def execute_command( args: argparse.Namespace, ): - if args.storage: + if args.command == "read-storage": if not args.address: exit_with_error( args.outform, @@ -398,23 +403,13 @@ def execute_command( ) storage = disassembler.get_state_variable_from_storage( - address=address, params=[a.strip() for a in args.storage.strip().split(",")] + address=address, + params=[a.strip() for a in args.storage_slots.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: + if args.command == "disassemble": # or mythril.disassemble(mythril.contracts[0]) if disassembler.contracts[0].code: @@ -422,7 +417,18 @@ def execute_command( if disassembler.contracts[0].creation_code: print("Disassembly: \n" + disassembler.contracts[0].get_creation_easm()) - elif args.graph or args.fire_lasers: + elif args.command == "analyze": + 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 not disassembler.contracts: exit_with_error( args.outform, "input files do not contain any valid contracts" @@ -442,6 +448,21 @@ def execute_command( except Exception as e: exit_with_error(args.outform, "Error saving graph: " + str(e)) + elif args.statespace_json: + + if not analyzer.contracts: + exit_with_error( + args.outform, "input files do not contain any valid contracts" + ) + + 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: try: report = analyzer.fire_lasers( @@ -463,25 +484,15 @@ def execute_command( args.outform, "Error loading analyis modules: " + format(e) ) - elif args.statespace_json: - - if not analyzer.contracts: - exit_with_error( - args.outform, "input files do not contain any valid contracts" - ) - - 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 contract_hash_to_address(args: argparse.Namespace): + print(MythrilDisassembler.hash_for_function_signature(args.func_name)) + sys.exit() + + def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: """ Parses the arguments @@ -495,7 +506,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") sys.exit() - if args.version: + if args.command == "version": if args.outform == "json": print(json.dumps({"version_str": VERSION})) else: @@ -503,23 +514,28 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non sys.exit() # Parse cmdline args - validate_args(parser, args) + validate_args(args) try: - quick_commands(args) + if args.command == "contract-function-to-hash": + contract_hash_to_address(args) config = set_config(args) leveldb_search(config, args) + query_signature = ( + args.query_signature if "query_signature" in args.__dict__ else None + ) disassembler = MythrilDisassembler( eth=config.eth, solc_version=args.solv, solc_args=args.solc_args, - enable_online_lookup=args.query_signature, + enable_online_lookup=query_signature, ) - if args.truffle: + if args.command == "truffle": try: disassembler.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." + "Build directory not found. Make sure that you start the analysis from the project root, " + "and that 'truffle compile' has executed successfully." ) sys.exit() diff --git a/tests/cmd_line_test.py b/tests/cmd_line_test.py index 0862cf9e..dad51e5f 100644 --- a/tests/cmd_line_test.py +++ b/tests/cmd_line_test.py @@ -15,23 +15,27 @@ def output_of(command): class CommandLineToolTestCase(BaseTestCase): def test_disassemble_code_correctly(self): - command = "python3 {} MYTH -d --bin-runtime -c 0x5050 --solv 0.5.0".format(MYTH) + command = "python3 {} disassemble --bin-runtime -c 0x5050 --solv 0.5.0".format( + MYTH + ) self.assertIn("0 POP\n1 POP\n", output_of(command)) def test_disassemble_solidity_file_correctly(self): solidity_file = str(TESTDATA / "input_contracts" / "metacoin.sol") - command = "python3 {} -d {} --solv 0.5.0".format(MYTH, solidity_file) + command = "python3 {} disassemble {} --solv 0.5.0".format(MYTH, solidity_file) self.assertIn("2 PUSH1 0x40\n4 MSTORE", output_of(command)) def test_hash_a_function_correctly(self): - command = "python3 {} --solv 0.5.0 --hash 'setOwner(address)'".format(MYTH) + command = "python3 {} contract-function-to-hash --solv 0.5.0 'setOwner(address)'".format( + MYTH + ) self.assertIn("0x13af4035\n", output_of(command)) class TruffleTestCase(BaseTestCase): def test_analysis_truffle_project(self): truffle_project_root = str(TESTS_DIR / "truffle_project") - command = "cd {}; truffle compile; python3 {} --truffle -t 2".format( + command = "cd {}; truffle compile; python3 {} truffle -t 2".format( truffle_project_root, MYTH ) self.assertIn("=== Unprotected Ether Withdrawal ====", output_of(command)) @@ -39,7 +43,7 @@ class TruffleTestCase(BaseTestCase): class InfuraTestCase(BaseTestCase): def test_infura_mainnet(self): - command = "python3 {} --rpc infura-mainnet -d -a 0x2a0c0dbecc7e4d658f48e01e3fa353f44050c208".format( + command = "python3 {} disassemble --rpc infura-mainnet -a 0x2a0c0dbecc7e4d658f48e01e3fa353f44050c208".format( MYTH ) output = output_of(command) @@ -47,21 +51,21 @@ class InfuraTestCase(BaseTestCase): self.assertIn("7278 POP\n7279 POP\n7280 JUMP\n7281 STOP", output) def test_infura_rinkeby(self): - command = "python3 {} --rpc infura-rinkeby -d -a 0xB6f2bFED892a662bBF26258ceDD443f50Fa307F5".format( + command = "python3 {} disassemble --rpc infura-rinkeby -a 0xB6f2bFED892a662bBF26258ceDD443f50Fa307F5".format( MYTH ) output = output_of(command) self.assertIn("34 JUMPDEST\n35 CALLVALUE", output) def test_infura_kovan(self): - command = "python3 {} --rpc infura-kovan -d -a 0xE6bBF9B5A3451242F82f8cd458675092617a1235".format( + command = "python3 {} disassemble --rpc infura-kovan -a 0xE6bBF9B5A3451242F82f8cd458675092617a1235".format( MYTH ) output = output_of(command) self.assertIn("9999 PUSH1 0x00\n10001 NOT\n10002 AND\n10003 PUSH1 0x00", output) def test_infura_ropsten(self): - command = "python3 {} --rpc infura-ropsten -d -a 0x6e0E0e02377Bc1d90E8a7c21f12BA385C2C35f78".format( + command = "python3 {} disassemble --rpc infura-ropsten -a 0x6e0E0e02377Bc1d90E8a7c21f12BA385C2C35f78".format( MYTH ) output = output_of(command) diff --git a/tests/test_cli_opts.py b/tests/test_cli_opts.py index 5de6cdcd..1985e45e 100644 --- a/tests/test_cli_opts.py +++ b/tests/test_cli_opts.py @@ -8,7 +8,7 @@ import sys def test_version_opt(capsys): # Check that "myth --version" returns a string with the word # "version" in it - sys.argv = ["mythril", "--version"] + sys.argv = ["mythril", "version"] with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit @@ -16,7 +16,7 @@ def test_version_opt(capsys): assert captured.out.find(" version ") >= 1 # Check that "myth --version -o json" returns a JSON object - sys.argv = ["mythril", "--version", "-o", "json"] + sys.argv = ["mythril", "version", "-o", "json"] with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert pytest_wrapped_e.type == SystemExit From c14f6a32925ccb161bdd8e208f6a4dd6422eed4e Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Tue, 14 May 2019 13:15:12 +0530 Subject: [PATCH 02/28] enhance cli --- mythril/interfaces/cli.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 4a7b6d98..0ee241dd 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -66,7 +66,7 @@ def main() -> None: subparsers = parser.add_subparsers(dest="command", help="Commands") analyzer_parser = subparsers.add_parser("analyze", parents=[common_parser]) - subparsers.add_parser("disassemble", parents=[common_parser]) + disassemble_parser = subparsers.add_parser("disassemble", parents=[common_parser]) read_storage_parser = subparsers.add_parser("read-storage", parents=[common_parser]) leveldb_search_parser = subparsers.add_parser( "leveldb-search", parents=[common_parser] @@ -79,6 +79,7 @@ def main() -> None: ) subparsers.add_parser("version", parents=[common_parser]) + create_disassemble_parser(disassemble_parser) create_analyzer_parser(analyzer_parser) create_read_storage_parser(read_storage_parser) create_hash_to_addr_parser(contract_hash_to_addr) @@ -93,6 +94,10 @@ def main() -> None: parse_args(parser=parser, args=args) +def create_disassemble_parser(parser): + parser.add_argument("solidity_file", nargs="*") + + def create_read_storage_parser(read_storage_parser: argparse.ArgumentParser): read_storage_parser.add_argument( "--storage-slots", @@ -102,11 +107,8 @@ def create_read_storage_parser(read_storage_parser: argparse.ArgumentParser): def create_leveldb_parser(parser: argparse.ArgumentParser): - database = parser.add_argument_group("local contracts database") - database.add_argument( - "-s", "--search", help="search the contract database", metavar="EXPRESSION" - ) - database.add_argument( + parser.add_argument("search") + parser.add_argument( "--leveldb-dir", help="specify leveldb directory for search or direct access operations", metavar="LEVELDB_PATH", @@ -127,11 +129,12 @@ def create_func_to_hash_parser(hash_parser: argparse.ArgumentParser): def create_hash_to_addr_parser(hash_parser: argparse.ArgumentParser): hash_parser.add_argument( - "--hash", help="Find the address from hash", metavar="FUNCTION_NAME" + "hash", help="Find the address from hash", metavar="FUNCTION_NAME" ) def create_analyzer_parser(analyzer_parser: argparse.ArgumentParser): + analyzer_parser.add_argument("solidity_file", nargs="*") commands = analyzer_parser.add_argument_group("commands") commands.add_argument("-g", "--graph", help="generate a control flow graph") commands.add_argument( @@ -214,7 +217,6 @@ def create_analyzer_parser(analyzer_parser: argparse.ArgumentParser): def create_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument("solidity_file", nargs="*") inputs = parser.add_argument_group("input arguments") inputs.add_argument( "-c", @@ -333,9 +335,10 @@ def set_config(args: argparse.Namespace): config.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) if args.command in ("contract-hash-to-address", "leveldb-search"): # Open LevelDB if necessary - leveldb_dir = ( - args.leveldb_dir if "leveldb_dir" in args.__dict__ else config.leveldb_dir - ) + if "leveldb_dir" not in args.__dict__ or args.leveldb_dir is None: + leveldb_dir = config.leveldb_dir + else: + leveldb_dir = args.leveldb_dir config.set_api_leveldb(leveldb_dir) return config From 7611d2b01ad10c35a17516c37144c6bf41c49dd8 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Thu, 16 May 2019 12:23:39 +0530 Subject: [PATCH 03/28] Re-Refactor the cli structure for more flexibility --- mythril/interfaces/cli.py | 235 +++++++++++++++---------- tests/{ => cli_tests}/cmd_line_test.py | 10 +- tests/{ => cli_tests}/test_cli_opts.py | 0 3 files changed, 143 insertions(+), 102 deletions(-) rename tests/{ => cli_tests}/cmd_line_test.py (90%) rename tests/{ => cli_tests}/test_cli_opts.py (100%) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 0ee241dd..1fd1f140 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -55,29 +55,125 @@ def exit_with_error(format_, message): sys.exit() +def get_input_parser(): + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument( + "-c", + "--code", + help='hex-encoded bytecode string ("6060604052...")', + metavar="BYTECODE", + ) + parser.add_argument( + "-f", + "--codefile", + help="file containing hex-encoded bytecode string", + metavar="BYTECODEFILE", + type=argparse.FileType("r"), + ) + parser.add_argument( + "-a", + "--address", + help="pull contract from the blockchain", + metavar="CONTRACT_ADDRESS", + ) + parser.add_argument( + "--bin-runtime", + action="store_true", + help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", + ) + return parser + + +def get_output_parser(): + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) + parser.add_argument( + "-o", + "--outform", + choices=["text", "markdown", "json", "jsonv2"], + default="text", + help="report output format", + metavar="", + ) + parser.add_argument( + "--verbose-report", + action="store_true", + help="Include debugging information in report", + ) + parser.add_argument( + "-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 + ) + return parser + + +def get_rpc_parser(): + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument( + "--rpc", + help="custom RPC settings", + metavar="HOST:PORT / ganache / infura-[network_name]", + default="infura-mainnet", + ) + parser.add_argument( + "--rpctls", type=bool, default=False, help="RPC connection over TLS" + ) + return parser + + +def get_utilities_parser(): + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--solc-args", help="Extra arguments for solc") + parser.add_argument( + "--solv", + help="specify solidity compiler version. If not present, will try to install it (Experimental)", + metavar="SOLV", + ) + return parser + + def main() -> None: """The main CLI interface entry point.""" - common_parser = argparse.ArgumentParser(add_help=False) - create_parser(common_parser) + rpc_parser = get_rpc_parser() + utilities_parser = get_utilities_parser() + input_parser = get_input_parser() + output_parser = get_output_parser() parser = argparse.ArgumentParser( description="Security analysis of Ethereum smart contracts" ) subparsers = parser.add_subparsers(dest="command", help="Commands") - analyzer_parser = subparsers.add_parser("analyze", parents=[common_parser]) - disassemble_parser = subparsers.add_parser("disassemble", parents=[common_parser]) - read_storage_parser = subparsers.add_parser("read-storage", parents=[common_parser]) + analyzer_parser = subparsers.add_parser( + "analyze", + help="Triggers analysis of smart contract", + parents=[rpc_parser, utilities_parser, input_parser, output_parser], + ) + disassemble_parser = subparsers.add_parser( + "disassemble", + help="Disassembles smart contract", + parents=[rpc_parser, utilities_parser, input_parser, output_parser], + ) + read_storage_parser = subparsers.add_parser( + "read-storage", + help="Retrieves storage slots from rpc address", + parents=[rpc_parser, output_parser], + ) leveldb_search_parser = subparsers.add_parser( - "leveldb-search", parents=[common_parser] + "leveldb-search", parents=[output_parser], help="Search code in local leveldb" ) contract_func_to_hash = subparsers.add_parser( - "contract-function-to-hash", parents=[common_parser] + "function-to-hash", + parents=[output_parser], + help="Returns the hash signature of the function", ) contract_hash_to_addr = subparsers.add_parser( - "contract-hash-to-address", parents=[common_parser] + "hash-to-address", + parents=[output_parser], + help="converts the hashes in the blockchain to ethereum address", + ) + subparsers.add_parser( + "version", parents=[output_parser], help="Outputs the version" ) - subparsers.add_parser("version", parents=[common_parser]) create_disassemble_parser(disassemble_parser) create_analyzer_parser(analyzer_parser) @@ -99,11 +195,15 @@ def create_disassemble_parser(parser): def create_read_storage_parser(read_storage_parser: argparse.ArgumentParser): + read_storage_parser.add_argument( - "--storage-slots", - help="read state vasriables from storage index, use with -a", + "storage_slots", + help="read state variables from storage index, use with -a", metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", ) + read_storage_parser.add_argument( + "address", help="contract address", metavar="ADDRESS" + ) def create_leveldb_parser(parser: argparse.ArgumentParser): @@ -131,6 +231,11 @@ def create_hash_to_addr_parser(hash_parser: argparse.ArgumentParser): hash_parser.add_argument( "hash", help="Find the address from hash", metavar="FUNCTION_NAME" ) + hash_parser.add_argument( + "--leveldb-dir", + help="specify leveldb directory for search or direct access operations", + metavar="LEVELDB_PATH", + ) def create_analyzer_parser(analyzer_parser: argparse.ArgumentParser): @@ -216,75 +321,8 @@ def create_analyzer_parser(analyzer_parser: argparse.ArgumentParser): ) -def create_parser(parser: argparse.ArgumentParser) -> None: - inputs = parser.add_argument_group("input arguments") - inputs.add_argument( - "-c", - "--code", - help='hex-encoded bytecode string ("6060604052...")', - metavar="BYTECODE", - ) - inputs.add_argument( - "-f", - "--codefile", - help="file containing hex-encoded bytecode string", - metavar="BYTECODEFILE", - type=argparse.FileType("r"), - ) - inputs.add_argument( - "-a", - "--address", - help="pull contract from the blockchain", - metavar="CONTRACT_ADDRESS", - ) - inputs.add_argument( - "--bin-runtime", - action="store_true", - help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", - ) - utilities = parser.add_argument_group("utilities") - - utilities.add_argument("--solc-args", help="Extra arguments for solc") - utilities.add_argument( - "--solv", - help="specify solidity compiler version. If not present, will try to install it (Experimental)", - metavar="SOLV", - ) - - rpc = parser.add_argument_group("RPC options") - - rpc.add_argument( - "--rpc", - help="custom RPC settings", - metavar="HOST:PORT / ganache / infura-[network_name]", - default="infura-mainnet", - ) - rpc.add_argument( - "--rpctls", type=bool, default=False, help="RPC connection over TLS" - ) - parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) - - outputs = parser.add_argument_group("output formats") - outputs.add_argument( - "-o", - "--outform", - choices=["text", "markdown", "json", "jsonv2"], - default="text", - help="report output format", - metavar="", - ) - outputs.add_argument( - "--verbose-report", - action="store_true", - help="Include debugging information in report", - ) - outputs.add_argument( - "-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 - ) - - def validate_args(args: argparse.Namespace): - if args.v: + if args.__dict__.get("v", False): if 0 <= args.v < 6: log_levels = [ logging.NOTSET, @@ -330,12 +368,12 @@ def set_config(args: argparse.Namespace): ) and not (args.rpc or args.i): config.set_api_from_config_path() - if args.address: + if args.__dict__.get("address", None): # Establish RPC connection if necessary config.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) - if args.command in ("contract-hash-to-address", "leveldb-search"): + if args.command in ("hash-to-address", "leveldb-search"): # Open LevelDB if necessary - if "leveldb_dir" not in args.__dict__ or args.leveldb_dir is None: + if not args.__dict__.get("leveldb_dir", None): leveldb_dir = config.leveldb_dir else: leveldb_dir = args.leveldb_dir @@ -344,7 +382,7 @@ def set_config(args: argparse.Namespace): def leveldb_search(config: MythrilConfig, args: argparse.Namespace): - if args.command in ("contract-hash-to-address", "leveldb-search"): + if args.command in ("hash-to-address", "leveldb-search"): leveldb_searcher = MythrilLevelDB(config.eth_db) if args.command == "leveldb-search": # Database search ops @@ -360,20 +398,27 @@ def leveldb_search(config: MythrilConfig, args: argparse.Namespace): sys.exit() -def get_code(disassembler: MythrilDisassembler, args: argparse.Namespace): +def load_code(disassembler: MythrilDisassembler, args: argparse.Namespace): + """ + Loads code into disassembly and returns address + :param disassembler: + :param args: + :return: Address + """ + address = None - if args.code: + if args.__dict__.get("code", False): # 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: + elif args.__dict__.get("codefile", False): 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: + elif args.__dict__.get("address", False): # Get bytecode from a contract address address, _ = disassembler.load_from_address(args.address) - elif args.solidity_file: + elif args.__dict__.get("solidity_file", False): # Compile Solidity source file(s) if args.command == "analyze" and args.graph and len(args.solidity_file) > 1: exit_with_error( @@ -519,17 +564,17 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non # Parse cmdline args validate_args(args) try: - if args.command == "contract-function-to-hash": + if args.command == "function-to-hash": contract_hash_to_address(args) config = set_config(args) leveldb_search(config, args) - query_signature = ( - args.query_signature if "query_signature" in args.__dict__ else None - ) + query_signature = args.__dict__.get("query_signature", None) + solc_args = args.__dict__.get("solc_args", None) + solv = args.__dict__.get("solv", None) disassembler = MythrilDisassembler( eth=config.eth, - solc_version=args.solv, - solc_args=args.solc_args, + solc_version=solv, + solc_args=solc_args, enable_online_lookup=query_signature, ) if args.command == "truffle": @@ -542,7 +587,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non ) sys.exit() - address = get_code(disassembler, args) + address = load_code(disassembler, args) execute_command( disassembler=disassembler, address=address, parser=parser, args=args ) diff --git a/tests/cmd_line_test.py b/tests/cli_tests/cmd_line_test.py similarity index 90% rename from tests/cmd_line_test.py rename to tests/cli_tests/cmd_line_test.py index dad51e5f..d7ed5967 100644 --- a/tests/cmd_line_test.py +++ b/tests/cli_tests/cmd_line_test.py @@ -15,20 +15,16 @@ def output_of(command): class CommandLineToolTestCase(BaseTestCase): def test_disassemble_code_correctly(self): - command = "python3 {} disassemble --bin-runtime -c 0x5050 --solv 0.5.0".format( - MYTH - ) + command = "python3 {} disassemble --bin-runtime -c 0x5050".format(MYTH) self.assertIn("0 POP\n1 POP\n", output_of(command)) def test_disassemble_solidity_file_correctly(self): solidity_file = str(TESTDATA / "input_contracts" / "metacoin.sol") - command = "python3 {} disassemble {} --solv 0.5.0".format(MYTH, solidity_file) + command = "python3 {} disassemble {}".format(MYTH, solidity_file) self.assertIn("2 PUSH1 0x40\n4 MSTORE", output_of(command)) def test_hash_a_function_correctly(self): - command = "python3 {} contract-function-to-hash --solv 0.5.0 'setOwner(address)'".format( - MYTH - ) + command = "python3 {} function-to-hash 'setOwner(address)'".format(MYTH) self.assertIn("0x13af4035\n", output_of(command)) diff --git a/tests/test_cli_opts.py b/tests/cli_tests/test_cli_opts.py similarity index 100% rename from tests/test_cli_opts.py rename to tests/cli_tests/test_cli_opts.py From 2eb3d33b23bf20b60f8594f1fcb5b1a44493cade Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Thu, 16 May 2019 13:40:05 +0530 Subject: [PATCH 04/28] Add cli tests for error --- mythril/interfaces/cli.py | 6 ------ tests/cli_tests/cmd_line_test.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 1fd1f140..6bbc4885 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -215,12 +215,6 @@ def create_leveldb_parser(parser: argparse.ArgumentParser): ) -def create_version_parser(hash_parser: argparse.ArgumentParser): - hash_parser.add_argument( - "version", help="print the Mythril version number and exit", metavar="VERSION" - ) - - def create_func_to_hash_parser(hash_parser: argparse.ArgumentParser): hash_parser.add_argument( "func_name", help="calculate function signature hash", metavar="SIGNATURE" diff --git a/tests/cli_tests/cmd_line_test.py b/tests/cli_tests/cmd_line_test.py index d7ed5967..765e4022 100644 --- a/tests/cli_tests/cmd_line_test.py +++ b/tests/cli_tests/cmd_line_test.py @@ -27,6 +27,19 @@ class CommandLineToolTestCase(BaseTestCase): command = "python3 {} function-to-hash 'setOwner(address)'".format(MYTH) self.assertIn("0x13af4035\n", output_of(command)) + def test_failure_json(self): + command = "python3 {} analyze doesnt_exist.sol -o json".format(MYTH) + print(output_of(command)) + self.assertIn(""""success": false""", output_of(command)) + + def test_failure_text(self): + command = "python3 {} analyze doesnt_exist.sol".format(MYTH) + assert output_of(command) == "" + + def test_failure_jsonv2(self): + command = "python3 {} analyze doesnt_exist.sol -o jsonv2".format(MYTH) + self.assertIn(""""level": "error""" "", output_of(command)) + class TruffleTestCase(BaseTestCase): def test_analysis_truffle_project(self): From ef55c1a0d5c2be13a543eb5b43c275da101af844 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Thu, 16 May 2019 13:50:13 +0530 Subject: [PATCH 05/28] Move the cmd_line_test to the previous directory --- tests/{cli_tests => }/cmd_line_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{cli_tests => }/cmd_line_test.py (100%) diff --git a/tests/cli_tests/cmd_line_test.py b/tests/cmd_line_test.py similarity index 100% rename from tests/cli_tests/cmd_line_test.py rename to tests/cmd_line_test.py From 6c4fe8b291897f744ac01ff25616420002bb3a92 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Thu, 16 May 2019 15:19:02 +0530 Subject: [PATCH 06/28] Add documentation --- mythril/interfaces/cli.py | 121 +++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 22 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 6bbc4885..ddb1cb32 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -15,6 +15,7 @@ import coloredlogs import traceback import mythril.support.signatures as sigs +from argparse import ArgumentParser, Namespace from mythril.exceptions import AddressNotFoundError, CriticalError from mythril.mythril import ( MythrilAnalyzer, @@ -31,8 +32,9 @@ log = logging.getLogger(__name__) def exit_with_error(format_, message): """ - :param format_: - :param message: + Exits with error + :param format_: The format of the message + :param message: message """ if format_ == "text" or format_ == "markdown": log.error(message) @@ -55,8 +57,12 @@ def exit_with_error(format_, message): sys.exit() -def get_input_parser(): - parser = argparse.ArgumentParser(add_help=False) +def get_input_parser() -> ArgumentParser: + """ + Returns Parser which handles input + :return: Parser which handles input + """ + parser = ArgumentParser(add_help=False) parser.add_argument( "-c", "--code", @@ -84,7 +90,11 @@ def get_input_parser(): return parser -def get_output_parser(): +def get_output_parser() -> ArgumentParser: + """ + Get parser which handles output + :return: Parser which handles output + """ parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) parser.add_argument( @@ -106,7 +116,11 @@ def get_output_parser(): return parser -def get_rpc_parser(): +def get_rpc_parser() -> ArgumentParser: + """ + Get parser which handles RPC flags + :return: c + """ parser = argparse.ArgumentParser(add_help=False) parser.add_argument( "--rpc", @@ -120,7 +134,11 @@ def get_rpc_parser(): return parser -def get_utilities_parser(): +def get_utilities_parser() -> ArgumentParser: + """ + Get parser which handles utilities flags + :return: Get parser which handles utility flags + """ parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--solc-args", help="Extra arguments for solc") parser.add_argument( @@ -190,11 +208,21 @@ def main() -> None: parse_args(parser=parser, args=args) -def create_disassemble_parser(parser): +def create_disassemble_parser(parser: ArgumentParser): + """ + Modify parser to handle disassembly + :param parser: + :return: + """ parser.add_argument("solidity_file", nargs="*") -def create_read_storage_parser(read_storage_parser: argparse.ArgumentParser): +def create_read_storage_parser(read_storage_parser: ArgumentParser): + """ + Modify parser to handle storage slots + :param read_storage_parser: + :return: + """ read_storage_parser.add_argument( "storage_slots", @@ -206,7 +234,12 @@ def create_read_storage_parser(read_storage_parser: argparse.ArgumentParser): ) -def create_leveldb_parser(parser: argparse.ArgumentParser): +def create_leveldb_parser(parser: ArgumentParser): + """ + Modify parser to handle leveldb-search + :param parser: + :return: + """ parser.add_argument("search") parser.add_argument( "--leveldb-dir", @@ -215,13 +248,23 @@ def create_leveldb_parser(parser: argparse.ArgumentParser): ) -def create_func_to_hash_parser(hash_parser: argparse.ArgumentParser): - hash_parser.add_argument( +def create_func_to_hash_parser(parser: ArgumentParser): + """ + Modify parser to handle func_to_hash command + :param parser: + :return: + """ + parser.add_argument( "func_name", help="calculate function signature hash", metavar="SIGNATURE" ) -def create_hash_to_addr_parser(hash_parser: argparse.ArgumentParser): +def create_hash_to_addr_parser(hash_parser: ArgumentParser): + """ + Modify parser to handle hash_to_addr command + :param hash_parser: + :return: + """ hash_parser.add_argument( "hash", help="Find the address from hash", metavar="FUNCTION_NAME" ) @@ -232,7 +275,12 @@ def create_hash_to_addr_parser(hash_parser: argparse.ArgumentParser): ) -def create_analyzer_parser(analyzer_parser: argparse.ArgumentParser): +def create_analyzer_parser(analyzer_parser: ArgumentParser): + """ + Modify parser to handle analyze command + :param analyzer_parser: + :return: + """ analyzer_parser.add_argument("solidity_file", nargs="*") commands = analyzer_parser.add_argument_group("commands") commands.add_argument("-g", "--graph", help="generate a control flow graph") @@ -315,7 +363,12 @@ def create_analyzer_parser(analyzer_parser: argparse.ArgumentParser): ) -def validate_args(args: argparse.Namespace): +def validate_args(args: Namespace): + """ + Validate cli args + :param args: + :return: + """ if args.__dict__.get("v", False): if 0 <= args.v < 6: log_levels = [ @@ -355,7 +408,12 @@ def validate_args(args: argparse.Namespace): ) -def set_config(args: argparse.Namespace): +def set_config(args: Namespace): + """ + Set config based on args + :param args: + :return: modified config + """ config = MythrilConfig() if ( args.command == "analyze" and (args.dynld or not args.no_onchain_storage_access) @@ -375,7 +433,13 @@ def set_config(args: argparse.Namespace): return config -def leveldb_search(config: MythrilConfig, args: argparse.Namespace): +def leveldb_search(config: MythrilConfig, args: Namespace): + """ + Handle leveldb search + :param config: + :param args: + :return: + """ if args.command in ("hash-to-address", "leveldb-search"): leveldb_searcher = MythrilLevelDB(config.eth_db) if args.command == "leveldb-search": @@ -392,7 +456,7 @@ def leveldb_search(config: MythrilConfig, args: argparse.Namespace): sys.exit() -def load_code(disassembler: MythrilDisassembler, args: argparse.Namespace): +def load_code(disassembler: MythrilDisassembler, args: Namespace): """ Loads code into disassembly and returns address :param disassembler: @@ -433,9 +497,17 @@ def load_code(disassembler: MythrilDisassembler, args: argparse.Namespace): def execute_command( disassembler: MythrilDisassembler, address: str, - parser: argparse.ArgumentParser, - args: argparse.Namespace, + parser: ArgumentParser, + args: Namespace, ): + """ + Execute command + :param disassembler: + :param address: + :param parser: + :param args: + :return: + """ if args.command == "read-storage": if not args.address: @@ -530,12 +602,17 @@ def execute_command( parser.print_help() -def contract_hash_to_address(args: argparse.Namespace): +def contract_hash_to_address(args: Namespace): + """ + prints the hash from function signature + :param args: + :return: + """ print(MythrilDisassembler.hash_for_function_signature(args.func_name)) sys.exit() -def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: +def parse_args(parser: ArgumentParser, args: Namespace) -> None: """ Parses the arguments :param parser: The parser From cbe5a656f0fd151f70e72373032b187b754fa17f Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 17 May 2019 11:37:23 +0530 Subject: [PATCH 07/28] Add more tests and change docs --- mythril/interfaces/cli.py | 14 ++++++++------ tests/cmd_line_test.py | 6 ++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index ddb1cb32..bfa5dd1d 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -119,7 +119,7 @@ def get_output_parser() -> ArgumentParser: def get_rpc_parser() -> ArgumentParser: """ Get parser which handles RPC flags - :return: c + :return: Parser which handles rpc inputs """ parser = argparse.ArgumentParser(add_help=False) parser.add_argument( @@ -137,7 +137,7 @@ def get_rpc_parser() -> ArgumentParser: def get_utilities_parser() -> ArgumentParser: """ Get parser which handles utilities flags - :return: Get parser which handles utility flags + :return: Parser which handles utility flags """ parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--solc-args", help="Extra arguments for solc") @@ -163,21 +163,23 @@ def main() -> None: subparsers = parser.add_subparsers(dest="command", help="Commands") analyzer_parser = subparsers.add_parser( "analyze", - help="Triggers analysis of smart contract", + help="Triggers the analysis of the smart contract", parents=[rpc_parser, utilities_parser, input_parser, output_parser], ) disassemble_parser = subparsers.add_parser( "disassemble", - help="Disassembles smart contract", + help="Disassembles the smart contract", parents=[rpc_parser, utilities_parser, input_parser, output_parser], ) read_storage_parser = subparsers.add_parser( "read-storage", - help="Retrieves storage slots from rpc address", + help="Retrieves storage slots from a given address through rpc", parents=[rpc_parser, output_parser], ) leveldb_search_parser = subparsers.add_parser( - "leveldb-search", parents=[output_parser], help="Search code in local leveldb" + "leveldb-search", + parents=[output_parser], + help="Searches the code fragment in local leveldb", ) contract_func_to_hash = subparsers.add_parser( "function-to-hash", diff --git a/tests/cmd_line_test.py b/tests/cmd_line_test.py index 765e4022..528bbf9a 100644 --- a/tests/cmd_line_test.py +++ b/tests/cmd_line_test.py @@ -1,5 +1,6 @@ from subprocess import check_output from tests import BaseTestCase, TESTDATA, PROJECT_DIR, TESTS_DIR +from mock import patch MYTH = str(PROJECT_DIR / "myth") @@ -40,6 +41,11 @@ class CommandLineToolTestCase(BaseTestCase): command = "python3 {} analyze doesnt_exist.sol -o jsonv2".format(MYTH) self.assertIn(""""level": "error""" "", output_of(command)) + def test_analyze(self): + solidity_file = str(TESTDATA / "input_contracts" / "origin.sol") + command = "python3 {} analyze {}".format(MYTH, solidity_file) + self.assertIn("111", output_of(command)) + class TruffleTestCase(BaseTestCase): def test_analysis_truffle_project(self): From 46c1b29528447f2bdb3d0ea598ff51923251f495 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 17 May 2019 13:25:48 +0530 Subject: [PATCH 08/28] Add more tests for storage slots and execution --- mythril/interfaces/cli.py | 22 +++++----------------- tests/cmd_line_test.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index bfa5dd1d..e1759915 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -397,17 +397,11 @@ def validate_args(args: Namespace): "The --query-signature function requires the python package ethereum-input-decoder", ) - if args.enable_iprof: - if args.v < 4: - exit_with_error( - args.outform, - "--enable-iprof must be used with -v LOG_LEVEL where LOG_LEVEL >= 4", - ) - elif not (args.graph or args.fire_lasers or args.statespace_json): - exit_with_error( - args.outform, - "--enable-iprof must be used with one of -g, --graph, -x, --fire-lasers, -j and --statespace-json", - ) + if args.enable_iprof and args.v < 4: + exit_with_error( + args.outform, + "--enable-iprof must be used with -v LOG_LEVEL where LOG_LEVEL >= 4", + ) def set_config(args: Namespace): @@ -512,12 +506,6 @@ def execute_command( """ if args.command == "read-storage": - if not args.address: - exit_with_error( - args.outform, - "To read storage, provide the address of a deployed contract with the -a option.", - ) - storage = disassembler.get_state_variable_from_storage( address=address, params=[a.strip() for a in args.storage_slots.strip().split(",")], diff --git a/tests/cmd_line_test.py b/tests/cmd_line_test.py index 528bbf9a..a3ed1949 100644 --- a/tests/cmd_line_test.py +++ b/tests/cmd_line_test.py @@ -46,6 +46,18 @@ class CommandLineToolTestCase(BaseTestCase): command = "python3 {} analyze {}".format(MYTH, solidity_file) self.assertIn("111", output_of(command)) + def test_analyze_bytecode(self): + solidity_file = str(TESTDATA / "inputs" / "origin.sol.o") + command = "python3 {} analyze --bin-runtime -f {}".format(MYTH, solidity_file) + self.assertIn("111", output_of(command)) + + def test_invalid_args_iprof(self): + solidity_file = str(TESTDATA / "input_contracts" / "origin.sol") + command = "python3 {} analyze {} --enable-iprof -o json".format( + MYTH, solidity_file + ) + self.assertIn(""""success": false""", output_of(command)) + class TruffleTestCase(BaseTestCase): def test_analysis_truffle_project(self): From 70c036cc26c14da89f746e11b1f30ba5273c8238 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 17 May 2019 13:55:29 +0530 Subject: [PATCH 09/28] support a for analyze and add more tests --- mythril/interfaces/cli.py | 43 +++++++++++++++++++++++---------------- tests/cmd_line_test.py | 7 +++++++ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index e1759915..0227acaf 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -27,6 +27,9 @@ from mythril.version import VERSION # logging.basicConfig(level=logging.DEBUG) +ANALYZE_LIST = ("a", "analyze") +DISASSEMBLE_LIST = ("d", "disassemble") + log = logging.getLogger(__name__) @@ -161,16 +164,22 @@ def main() -> None: ) subparsers = parser.add_subparsers(dest="command", help="Commands") - analyzer_parser = subparsers.add_parser( - "analyze", - help="Triggers the analysis of the smart contract", - parents=[rpc_parser, utilities_parser, input_parser, output_parser], - ) - disassemble_parser = subparsers.add_parser( - "disassemble", - help="Disassembles the smart contract", - parents=[rpc_parser, utilities_parser, input_parser, output_parser], - ) + for analyze_string in ANALYZE_LIST: + analyzer_parser = subparsers.add_parser( + analyze_string, + help="Triggers the analysis of the smart contract", + parents=[rpc_parser, utilities_parser, input_parser, output_parser], + ) + create_analyzer_parser(analyzer_parser) + + for disassemble_string in DISASSEMBLE_LIST: + disassemble_parser = subparsers.add_parser( + disassemble_string, + help="Disassembles the smart contract", + parents=[rpc_parser, utilities_parser, input_parser, output_parser], + ) + create_disassemble_parser(disassemble_parser) + read_storage_parser = subparsers.add_parser( "read-storage", help="Retrieves storage slots from a given address through rpc", @@ -195,8 +204,6 @@ def main() -> None: "version", parents=[output_parser], help="Outputs the version" ) - create_disassemble_parser(disassemble_parser) - create_analyzer_parser(analyzer_parser) create_read_storage_parser(read_storage_parser) create_hash_to_addr_parser(contract_hash_to_addr) create_func_to_hash_parser(contract_func_to_hash) @@ -390,7 +397,7 @@ def validate_args(args: Namespace): args.outform, "Invalid -v value, you can find valid values in usage" ) - if args.command == "analyze": + if args.command in ANALYZE_LIST: if args.query_signature and sigs.ethereum_input_decoder is None: exit_with_error( args.outform, @@ -412,7 +419,8 @@ def set_config(args: Namespace): """ config = MythrilConfig() if ( - args.command == "analyze" and (args.dynld or not args.no_onchain_storage_access) + args.command in ANALYZE_LIST + and (args.dynld or not args.no_onchain_storage_access) ) and not (args.rpc or args.i): config.set_api_from_config_path() @@ -474,7 +482,7 @@ def load_code(disassembler: MythrilDisassembler, args: Namespace): address, _ = disassembler.load_from_address(args.address) elif args.__dict__.get("solidity_file", False): # Compile Solidity source file(s) - if args.command == "analyze" and args.graph and len(args.solidity_file) > 1: + if args.command in ANALYZE_LIST and 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.", @@ -513,15 +521,14 @@ def execute_command( print(storage) return - if args.command == "disassemble": - # or mythril.disassemble(mythril.contracts[0]) + if args.command in DISASSEMBLE_LIST: 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.command == "analyze": + elif args.command in ANALYZE_LIST: analyzer = MythrilAnalyzer( strategy=args.strategy, disassembler=disassembler, diff --git a/tests/cmd_line_test.py b/tests/cmd_line_test.py index a3ed1949..5fa6e3bb 100644 --- a/tests/cmd_line_test.py +++ b/tests/cmd_line_test.py @@ -58,6 +58,13 @@ class CommandLineToolTestCase(BaseTestCase): ) self.assertIn(""""success": false""", output_of(command)) + def test_storage(self): + solidity_file = str(TESTDATA / "input_contracts" / "origin.sol") + command = """python3 {} read-storage "438767356, 3" 0x76799f77587738bfeef09452df215b63d2cfb08a """.format( + MYTH + ) + self.assertIn("0x1a270efc", output_of(command)) + class TruffleTestCase(BaseTestCase): def test_analysis_truffle_project(self): From 33c9e4b48459758e2b87a77dea8c6ca34b4047a3 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 21 Jun 2019 00:05:26 +0530 Subject: [PATCH 10/28] Improve cli interface --- mythril/interfaces/cli.py | 61 ++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index d9dc5b15..a0d03204 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -97,7 +97,6 @@ def get_output_parser() -> ArgumentParser: :return: Parser which handles output """ parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) parser.add_argument( "-o", "--outform", @@ -111,9 +110,6 @@ def get_output_parser() -> ArgumentParser: action="store_true", help="Include debugging information in report", ) - parser.add_argument( - "-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 - ) return parser @@ -160,42 +156,41 @@ def main() -> None: parser = argparse.ArgumentParser( description="Security analysis of Ethereum smart contracts" ) + parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) + parser.add_argument( + "-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 + ) subparsers = parser.add_subparsers(dest="command", help="Commands") - for analyze_string in ANALYZE_LIST: - analyzer_parser = subparsers.add_parser( - analyze_string, - help="Triggers the analysis of the smart contract", - parents=[rpc_parser, utilities_parser, input_parser, output_parser], - ) - create_analyzer_parser(analyzer_parser) + analyzer_parser = subparsers.add_parser( + "analyze", + help="Triggers the analysis of the smart contract", + parents=[rpc_parser, utilities_parser, input_parser, output_parser], + aliases=["a"], + ) + create_analyzer_parser(analyzer_parser) - for disassemble_string in DISASSEMBLE_LIST: - disassemble_parser = subparsers.add_parser( - disassemble_string, - help="Disassembles the smart contract", - parents=[rpc_parser, utilities_parser, input_parser, output_parser], - ) - create_disassemble_parser(disassemble_parser) + disassemble_parser = subparsers.add_parser( + "disassemble", + help="Disassembles the smart contract", + aliases=["d"], + parents=[rpc_parser, utilities_parser, input_parser], + ) + create_disassemble_parser(disassemble_parser) read_storage_parser = subparsers.add_parser( "read-storage", help="Retrieves storage slots from a given address through rpc", - parents=[rpc_parser, output_parser], + parents=[rpc_parser], ) leveldb_search_parser = subparsers.add_parser( - "leveldb-search", - parents=[output_parser], - help="Searches the code fragment in local leveldb", + "leveldb-search", help="Searches the code fragment in local leveldb" ) contract_func_to_hash = subparsers.add_parser( - "function-to-hash", - parents=[output_parser], - help="Returns the hash signature of the function", + "function-to-hash", help="Returns the hash signature of the function" ) contract_hash_to_addr = subparsers.add_parser( "hash-to-address", - parents=[output_parser], help="converts the hashes in the blockchain to ethereum address", ) subparsers.add_parser( @@ -233,7 +228,7 @@ def create_read_storage_parser(read_storage_parser: ArgumentParser): read_storage_parser.add_argument( "storage_slots", - help="read state variables from storage index, use with -a", + help="read state variables from storage index", metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", ) read_storage_parser.add_argument( @@ -502,8 +497,8 @@ def load_code(disassembler: MythrilDisassembler, args: Namespace): ) # 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", + args.__dict__.get("outform", "text"), + "No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, -f BYTECODE_FILE or ", ) return address @@ -602,7 +597,7 @@ def execute_command( print(outputs[args.outform]) except ModuleNotFoundError as e: exit_with_error( - args.outform, "Error loading analyis modules: " + format(e) + args.outform, "Error loading analysis modules: " + format(e) ) else: @@ -626,7 +621,7 @@ def parse_args(parser: ArgumentParser, args: Namespace) -> None: :param args: The args """ - if args.epic: + if args.__dict__.get("epic", None): path = os.path.dirname(os.path.realpath(__file__)) sys.argv.remove("--epic") os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") @@ -670,9 +665,9 @@ def parse_args(parser: ArgumentParser, args: Namespace) -> None: disassembler=disassembler, address=address, parser=parser, args=args ) except CriticalError as ce: - exit_with_error(args.outform, str(ce)) + exit_with_error(args.__dict__.get("outform", "text"), str(ce)) except Exception: - exit_with_error(args.outform, traceback.format_exc()) + exit_with_error(args.__dict__.get("outform", "text"), traceback.format_exc()) if __name__ == "__main__": From 6172d55fc982ef63d011837108783e4ee252d990 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 21 Jun 2019 00:24:37 +0530 Subject: [PATCH 11/28] Fix previous errors --- mythril/interfaces/cli.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index a0d03204..45f8253b 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -25,11 +25,20 @@ from mythril.mythril import ( ) from mythril.__version__ import __version__ as VERSION -ANALYZE_LIST = ("a", "analyze") -DISASSEMBLE_LIST = ("d", "disassemble") +ANALYZE_LIST = ("analyze", "a") +DISASSEMBLE_LIST = ("disassemble", "d") log = logging.getLogger(__name__) +COMMAND_LIST = ANALYZE_LIST + DISASSEMBLE_LIST + ( + "read-storage", + "leveldb-search", + "function-to-hash", + "hash-to-address", + "version", + "truffle", +) + def exit_with_error(format_, message): """ @@ -163,17 +172,17 @@ def main() -> None: subparsers = parser.add_subparsers(dest="command", help="Commands") analyzer_parser = subparsers.add_parser( - "analyze", + ANALYZE_LIST[0], help="Triggers the analysis of the smart contract", parents=[rpc_parser, utilities_parser, input_parser, output_parser], - aliases=["a"], + aliases=ANALYZE_LIST[1:], ) create_analyzer_parser(analyzer_parser) disassemble_parser = subparsers.add_parser( - "disassemble", + DISASSEMBLE_LIST[0], help="Disassembles the smart contract", - aliases=["d"], + aliases=DISASSEMBLE_LIST[1:], parents=[rpc_parser, utilities_parser, input_parser], ) create_disassemble_parser(disassemble_parser) @@ -620,8 +629,10 @@ def parse_args(parser: ArgumentParser, args: Namespace) -> None: :param parser: The parser :param args: The args """ + if args.command not in COMMAND_LIST: + parser.print_usage() - if args.__dict__.get("epic", None): + if args.epic: path = os.path.dirname(os.path.realpath(__file__)) sys.argv.remove("--epic") os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") From fc6c97c3e891ac43060a7bb679cefff21f024d22 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 21 Jun 2019 00:27:31 +0530 Subject: [PATCH 12/28] Refactor with black --- mythril/interfaces/cli.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 45f8253b..a0f4e2f1 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -30,13 +30,17 @@ DISASSEMBLE_LIST = ("disassemble", "d") log = logging.getLogger(__name__) -COMMAND_LIST = ANALYZE_LIST + DISASSEMBLE_LIST + ( - "read-storage", - "leveldb-search", - "function-to-hash", - "hash-to-address", - "version", - "truffle", +COMMAND_LIST = ( + ANALYZE_LIST + + DISASSEMBLE_LIST + + ( + "read-storage", + "leveldb-search", + "function-to-hash", + "hash-to-address", + "version", + "truffle", + ) ) From 41c7ce03d8c7dfb5ecf23beee1e3d690fa88e3d3 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Fri, 21 Jun 2019 00:34:26 +0530 Subject: [PATCH 13/28] Add help command --- mythril/interfaces/cli.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index a0f4e2f1..97e5c57d 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -40,6 +40,7 @@ COMMAND_LIST = ( "hash-to-address", "version", "truffle", + "help", ) ) @@ -209,18 +210,18 @@ def main() -> None: subparsers.add_parser( "version", parents=[output_parser], help="Outputs the version" ) - create_read_storage_parser(read_storage_parser) create_hash_to_addr_parser(contract_hash_to_addr) create_func_to_hash_parser(contract_func_to_hash) create_leveldb_parser(leveldb_search_parser) subparsers.add_parser("truffle", parents=[analyzer_parser], add_help=False) + subparsers.add_parser("help", add_help=False) # Get config values args = parser.parse_args() - parse_args(parser=parser, args=args) + parse_args_and_execute(parser=parser, args=args) def create_disassemble_parser(parser: ArgumentParser): @@ -627,7 +628,7 @@ def contract_hash_to_address(args: Namespace): sys.exit() -def parse_args(parser: ArgumentParser, args: Namespace) -> None: +def parse_args_and_execute(parser: ArgumentParser, args: Namespace) -> None: """ Parses the arguments :param parser: The parser @@ -649,6 +650,10 @@ def parse_args(parser: ArgumentParser, args: Namespace) -> None: print("Mythril version {}".format(VERSION)) sys.exit() + if args.command == "help": + parser.print_help() + sys.exit() + # Parse cmdline args validate_args(args) try: From fd397ed2327e9afb0b692228c1c3a63f1eb307b2 Mon Sep 17 00:00:00 2001 From: Nikhil Parasaram Date: Mon, 24 Jun 2019 18:48:31 +0530 Subject: [PATCH 14/28] Add new tests, fix an edge case and improve code and help messages --- mythril/interfaces/cli.py | 9 +++++---- tests/cmd_line_test.py | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 97e5c57d..cf8a2e08 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -538,9 +538,8 @@ def execute_command( params=[a.strip() for a in args.storage_slots.strip().split(",")], ) print(storage) - return - if args.command in DISASSEMBLE_LIST: + elif args.command in DISASSEMBLE_LIST: if disassembler.contracts[0].code: print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) if disassembler.contracts[0].creation_code: @@ -634,8 +633,6 @@ def parse_args_and_execute(parser: ArgumentParser, args: Namespace) -> None: :param parser: The parser :param args: The args """ - if args.command not in COMMAND_LIST: - parser.print_usage() if args.epic: path = os.path.dirname(os.path.realpath(__file__)) @@ -643,6 +640,10 @@ def parse_args_and_execute(parser: ArgumentParser, args: Namespace) -> None: os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") sys.exit() + if args.command not in COMMAND_LIST or args.command is None: + parser.print_help() + sys.exit() + if args.command == "version": if args.outform == "json": print(json.dumps({"version_str": VERSION})) diff --git a/tests/cmd_line_test.py b/tests/cmd_line_test.py index 5fa6e3bb..258a6882 100644 --- a/tests/cmd_line_test.py +++ b/tests/cmd_line_test.py @@ -58,6 +58,10 @@ class CommandLineToolTestCase(BaseTestCase): ) self.assertIn(""""success": false""", output_of(command)) + def test_only_epic(self): + command = "python3 {}".format(MYTH) + self.assertIn("usage: ", output_of(command)) + def test_storage(self): solidity_file = str(TESTDATA / "input_contracts" / "origin.sol") command = """python3 {} read-storage "438767356, 3" 0x76799f77587738bfeef09452df215b63d2cfb08a """.format( From 1080184b8a311633ab2d096b9a12362c9e68735a Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 25 Jun 2019 12:00:29 -0700 Subject: [PATCH 15/28] Turn mythril into a MythX client --- .../templates/report_as_markdown.jinja2 | 6 ++ .../analysis/templates/report_as_text.jinja2 | 4 + mythril/ethereum/util.py | 21 ++++- mythril/interfaces/cli.py | 89 +++++++++++++++--- mythril/mythx/__init__.py | 93 +++++++++++++++++++ mythril/solidity/soliditycontract.py | 58 ++++++------ requirements.txt | 1 + setup.py | 1 + tests/disassembler_test.py | 6 -- 9 files changed, 225 insertions(+), 54 deletions(-) create mode 100644 mythril/mythx/__init__.py diff --git a/mythril/analysis/templates/report_as_markdown.jinja2 b/mythril/analysis/templates/report_as_markdown.jinja2 index 289d1871..60579705 100644 --- a/mythril/analysis/templates/report_as_markdown.jinja2 +++ b/mythril/analysis/templates/report_as_markdown.jinja2 @@ -6,15 +6,21 @@ - SWC ID: {{ issue['swc-id'] }} - Severity: {{ issue.severity }} - Contract: {{ issue.contract | default("Unknown") }} +{% if issue.function %} - Function name: `{{ issue.function }}` +{% endif %} - PC address: {{ issue.address }} +{% if issue.min_gas_used or issue.max_gas_used %} - Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }} +{% endif %} ### Description {{ issue.description.rstrip() }} {% if issue.filename and issue.lineno %} In file: {{ issue.filename }}:{{ issue.lineno }} +{% elif issue.filename %} +In file: {{ issue.filename }} {% endif %} {% if issue.code %} diff --git a/mythril/analysis/templates/report_as_text.jinja2 b/mythril/analysis/templates/report_as_text.jinja2 index da962583..cfc32d2e 100644 --- a/mythril/analysis/templates/report_as_text.jinja2 +++ b/mythril/analysis/templates/report_as_text.jinja2 @@ -4,9 +4,13 @@ SWC ID: {{ issue['swc-id'] }} Severity: {{ issue.severity }} Contract: {{ issue.contract | default("Unknown") }} +{% if issue.function %} Function name: {{ issue.function }} +{% endif %} PC address: {{ issue.address }} +{% if issue.min_gas_used or issue.max_gas_used %} Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }} +{% endif %} {{ issue.description }} -------------------- {% if issue.filename and issue.lineno %} diff --git a/mythril/ethereum/util.py b/mythril/ethereum/util.py index 2b6c7771..3e608b3a 100644 --- a/mythril/ethereum/util.py +++ b/mythril/ethereum/util.py @@ -34,6 +34,7 @@ def get_solc_json(file, solc_binary="solc", solc_args=None): """ cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,ast"] + cmd = [solc_binary, "--standard-json", "bin,bin-runtime,srcmap,srcmap-runtime,ast"] if solc_args: cmd.extend(solc_args.split()) @@ -46,10 +47,24 @@ def get_solc_json(file, solc_binary="solc", solc_args=None): cmd.append(file) - try: - p = Popen(cmd, stdout=PIPE, stderr=PIPE) + input_json = json.dumps( + { + "language": "Solidity", + "sources": {file: {"urls": [file]}}, + "settings": { + "outputSelection": { + "*": { + "": ["ast"], + "*": ["metadata", "evm.bytecode", "evm.deployedBytecode"], + } + } + }, + } + ) - stdout, stderr = p.communicate() + try: + p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) + stdout, stderr = p.communicate(bytes(input_json, "utf8")) ret = p.returncode if ret != 0: diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index cf8a2e08..b94b360d 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -16,6 +16,8 @@ import traceback import mythril.support.signatures as sigs from argparse import ArgumentParser, Namespace + +from mythril import mythx from mythril.exceptions import AddressNotFoundError, CriticalError from mythril.mythril import ( MythrilAnalyzer, @@ -27,12 +29,14 @@ from mythril.__version__ import __version__ as VERSION ANALYZE_LIST = ("analyze", "a") DISASSEMBLE_LIST = ("disassemble", "d") +PRO_LIST = ("pro", "p") log = logging.getLogger(__name__) COMMAND_LIST = ( ANALYZE_LIST + DISASSEMBLE_LIST + + PRO_LIST + ( "read-storage", "leveldb-search", @@ -41,6 +45,7 @@ COMMAND_LIST = ( "version", "truffle", "help", + "pro", ) ) @@ -72,7 +77,27 @@ def exit_with_error(format_, message): sys.exit() -def get_input_parser() -> ArgumentParser: +def get_runtime_input_parser() -> ArgumentParser: + """ + Returns Parser which handles input + :return: Parser which handles input + """ + parser = ArgumentParser(add_help=False) + parser.add_argument( + "-a", + "--address", + help="pull contract from the blockchain", + metavar="CONTRACT_ADDRESS", + ) + parser.add_argument( + "--bin-runtime", + action="store_true", + help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", + ) + return parser + + +def get_creation_input_parser() -> ArgumentParser: """ Returns Parser which handles input :return: Parser which handles input @@ -91,17 +116,6 @@ def get_input_parser() -> ArgumentParser: metavar="BYTECODEFILE", type=argparse.FileType("r"), ) - parser.add_argument( - "-a", - "--address", - help="pull contract from the blockchain", - metavar="CONTRACT_ADDRESS", - ) - parser.add_argument( - "--bin-runtime", - action="store_true", - help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", - ) return parser @@ -165,7 +179,8 @@ def main() -> None: rpc_parser = get_rpc_parser() utilities_parser = get_utilities_parser() - input_parser = get_input_parser() + runtime_input_parser = get_runtime_input_parser() + creation_input_parser = get_creation_input_parser() output_parser = get_output_parser() parser = argparse.ArgumentParser( description="Security analysis of Ethereum smart contracts" @@ -179,7 +194,13 @@ def main() -> None: analyzer_parser = subparsers.add_parser( ANALYZE_LIST[0], help="Triggers the analysis of the smart contract", - parents=[rpc_parser, utilities_parser, input_parser, output_parser], + parents=[ + rpc_parser, + utilities_parser, + creation_input_parser, + runtime_input_parser, + output_parser, + ], aliases=ANALYZE_LIST[1:], ) create_analyzer_parser(analyzer_parser) @@ -188,10 +209,23 @@ def main() -> None: DISASSEMBLE_LIST[0], help="Disassembles the smart contract", aliases=DISASSEMBLE_LIST[1:], - parents=[rpc_parser, utilities_parser, input_parser], + parents=[ + rpc_parser, + utilities_parser, + creation_input_parser, + runtime_input_parser, + ], ) create_disassemble_parser(disassemble_parser) + pro_parser = subparsers.add_parser( + PRO_LIST[0], + help="Analyzes input with the MythX API (https://mythx.io)", + aliases=PRO_LIST[1], + parents=[utilities_parser, creation_input_parser, output_parser], + ) + create_pro_parser(pro_parser) + read_storage_parser = subparsers.add_parser( "read-storage", help="Retrieves storage slots from a given address through rpc", @@ -233,6 +267,20 @@ def create_disassemble_parser(parser: ArgumentParser): parser.add_argument("solidity_file", nargs="*") +def create_pro_parser(parser: ArgumentParser): + """ + Modify parser to handle mythx analysis + :param parser: + :return: + """ + parser.add_argument( + "--full", + help="Run a full analysis. Default: quick analysis", + action="store_true", + ) + parser.add_argument("solidity_file", nargs="*") + + def create_read_storage_parser(read_storage_parser: ArgumentParser): """ Modify parser to handle storage slots @@ -539,6 +587,17 @@ def execute_command( ) print(storage) + elif args.command in PRO_LIST: + mode = "full" if args.full else "quick" + report = mythx.analyze(disassembler.contracts, mode) + outputs = { + "json": report.as_json(), + "jsonv2": report.as_swc_standard_format(), + "text": report.as_text(), + "markdown": report.as_markdown(), + } + print(outputs[args.outform]) + elif args.command in DISASSEMBLE_LIST: if disassembler.contracts[0].code: print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) diff --git a/mythril/mythx/__init__.py b/mythril/mythx/__init__.py new file mode 100644 index 00000000..631e8ba8 --- /dev/null +++ b/mythril/mythx/__init__.py @@ -0,0 +1,93 @@ +import os +import time + +from typing import List, Dict, Any + +from mythril.analysis.report import Issue, Report +from mythril.solidity.soliditycontract import SolidityContract + +from pythx import Client + + +def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> Report: + """ + Analyze contracts via the MythX API. + + :param contracts: List of solidity contracts to analyze + :param analysis_mode: The mode to submit the analysis request with. "quick" or "full" (default: "quick") + :return: Report with analyzed contracts + """ + assert analysis_mode in ("quick", "full"), "analysis_mode must be 'quick' or 'full'" + + c = Client( + eth_address=os.environ.get( + "MYTHX_ETH_ADDRESS", "0x0000000000000000000000000000000000000000" + ), + password=os.environ.get("MYTHX_PASSWORD", "trial"), + ) + + issues = [] # type: List[Issue] + + # TODO: Analyze multiple contracts asynchronously. + for contract in contracts: + source_codes = {} + source_list = [] + sources = {} # type: Dict[str, Any] + main_source = None + + try: + main_source = contract.input_file + for solidity_file in contract.solidity_files: + source_codes[solidity_file.filename] = solidity_file.data + for filename in contract.solc_json["sources"].keys(): + sources[filename] = {} + if source_codes[filename]: + sources[filename]["source"] = source_codes[filename] + sources[filename]["ast"] = contract.solc_json["sources"][filename][ + "ast" + ] + + source_list.append(filename) + + source_list.sort( + key=lambda fname: contract.solc_json["sources"][fname]["id"] + ) + except AttributeError: + # No solidity file + pass + + assert contract.creation_code, "Creation bytecode must exist." + resp = c.analyze( + contract_name=contract.name, + analysis_mode=analysis_mode, + bytecode=contract.creation_code or None, + deployed_bytecode=contract.code or None, + sources=sources or None, + main_source=main_source, + source_list=source_list or None, + ) + + while not c.analysis_ready(resp.uuid): + print(c.status(resp.uuid).analysis) + time.sleep(5) + + for issue in c.report(resp.uuid): + issue = Issue( + contract=contract.name, + function_name=None, + address=int(issue.locations[0].source_map.split(":")[0]), + swc_id=issue.swc_id[4:], # remove 'SWC-' prefix + title=issue.swc_title, + bytecode=contract.creation_code, + severity=issue.severity.capitalize(), + description_head=issue.description_short, + description_tail=issue.description_long, + ) + issue.add_code_info(contract) + issues.append(issue) + + report = Report(contracts=contracts) + for issue in issues: + report.append_issue(issue) + + return report diff --git a/mythril/solidity/soliditycontract.py b/mythril/solidity/soliditycontract.py index f8594701..513916a7 100644 --- a/mythril/solidity/soliditycontract.py +++ b/mythril/solidity/soliditycontract.py @@ -54,12 +54,15 @@ def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"): data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) try: - for key, contract in data["contracts"].items(): - filename, name = key.split(":") - if filename == input_file and len(contract["bin-runtime"]): + for contractName in data["contracts"][input_file].keys(): + if len( + data["contracts"][input_file][contractName]["evm"]["deployedBytecode"][ + "object" + ] + ): yield SolidityContract( input_file=input_file, - name=name, + name=contractName, solc_args=solc_args, solc_binary=solc_binary, ) @@ -74,12 +77,14 @@ class SolidityContract(EVMContract): data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) self.solidity_files = [] + self.solc_json = data + self.input_file = input_file - for filename in data["sourceList"]: + for filename, contract in data["sources"].items(): with open(filename, "r", encoding="utf-8") as file: code = file.read() full_contract_src_maps = self.get_full_contract_src_maps( - data["sources"][filename]["AST"] + contract["ast"] ) self.solidity_files.append( SolidityFile(filename, code, full_contract_src_maps) @@ -91,32 +96,25 @@ class SolidityContract(EVMContract): srcmap_constructor = [] srcmap = [] if name: - for key, contract in sorted(data["contracts"].items()): - filename, _name = key.split(":") - - if ( - filename == input_file - and name == _name - and len(contract["bin-runtime"]) - ): - code = contract["bin-runtime"] - creation_code = contract["bin"] - srcmap = contract["srcmap-runtime"].split(";") - srcmap_constructor = contract["srcmap"].split(";") - has_contract = True - break + contract = data["contracts"][input_file][name] + if len(contract["evm"]["deployedBytecode"]["object"]): + code = contract["evm"]["deployedBytecode"]["object"] + creation_code = contract["evm"]["bytecode"]["object"] + srcmap = contract["evm"]["deployedBytecode"]["sourceMap"].split(";") + srcmap_constructor = contract["evm"]["bytecode"]["sourceMap"].split(";") + has_contract = True # If no contract name is specified, get the last bytecode entry for the input file else: - for key, contract in sorted(data["contracts"].items()): - filename, name = key.split(":") - - if filename == input_file and len(contract["bin-runtime"]): - code = contract["bin-runtime"] - creation_code = contract["bin"] - srcmap = contract["srcmap-runtime"].split(";") - srcmap_constructor = contract["srcmap"].split(";") + for filename, contract in sorted(data["contracts"][input_file].items()): + if len(contract["evm"]["deployedBytecode"]["object"]): + code = contract["evm"]["deployedBytecode"]["object"] + creation_code = contract["evm"]["bytecode"]["object"] + srcmap = contract["evm"]["deployedBytecode"]["sourceMap"].split(";") + srcmap_constructor = contract["evm"]["bytecode"]["sourceMap"].split( + ";" + ) has_contract = True if not has_contract: @@ -139,8 +137,8 @@ class SolidityContract(EVMContract): :return: The source maps """ source_maps = set() - for child in ast["children"]: - if "contractKind" in child["attributes"]: + for child in ast["nodes"]: + if child.get("contractKind"): source_maps.add(child["src"]) return source_maps diff --git a/requirements.txt b/requirements.txt index 1b49350c..6cfdffb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ transaction>=2.2.1 z3-solver>=4.8.5.0 pysha3 matplotlib +pythx diff --git a/setup.py b/setup.py index d76f546e..e88646e8 100755 --- a/setup.py +++ b/setup.py @@ -50,6 +50,7 @@ REQUIRED = [ "persistent>=4.2.0", "ethereum-input-decoder>=0.2.2", "matplotlib", + "pythx", ] TESTS_REQUIRE = ["mypy", "pytest>=3.6.0", "pytest_mock", "pytest-cov"] diff --git a/tests/disassembler_test.py b/tests/disassembler_test.py index d3955d82..ffdc5f08 100644 --- a/tests/disassembler_test.py +++ b/tests/disassembler_test.py @@ -3,12 +3,6 @@ from mythril.ethereum import util from tests import * -def _compile_to_code(input_file): - compiled = util.get_solc_json(str(input_file)) - code = list(compiled["contracts"].values())[0]["bin-runtime"] - return code - - class DisassemblerTestCase(BaseTestCase): def test_instruction_list(self): code = "0x606060405236156100ca5763ffffffff60e060020a600035041663054f7d9c81146100d3578063095c21e3146100f45780630ba50baa146101165780631a3719321461012857806366529e3f14610153578063682789a81461017257806389f21efc146101915780638da5cb5b146101ac5780638f4ffcb1146101d55780639a1f2a5714610240578063b5f522f71461025b578063bd94b005146102b6578063c5ab5a13146102c8578063cc424839146102f1578063deb077b914610303578063f3fef3a314610322575b6100d15b5b565b005b34610000576100e0610340565b604080519115158252519081900360200190f35b3461000057610104600435610361565b60408051918252519081900360200190f35b34610000576100d1600435610382565b005b3461000057610104600160a060020a03600435166103b0565b60408051918252519081900360200190f35b346100005761010461041e565b60408051918252519081900360200190f35b3461000057610104610424565b60408051918252519081900360200190f35b34610000576100d1600160a060020a036004351661042b565b005b34610000576101b961046f565b60408051600160a060020a039092168252519081900360200190f35b3461000057604080516020600460643581810135601f81018490048402850184019095528484526100d1948235600160a060020a039081169560248035966044359093169594608494929391019190819084018382808284375094965061048595505050505050565b005b34610000576100d1600160a060020a03600435166106e7565b005b346100005761026b60043561072b565b60408051600160a060020a0390991689526020890197909752878701959095526060870193909352608086019190915260a085015260c084015260e083015251908190036101000190f35b34610000576100d160043561077a565b005b34610000576101b9610830565b60408051600160a060020a039092168252519081900360200190f35b34610000576100d160043561083f565b005b34610000576101046108a1565b60408051918252519081900360200190f35b34610000576100d1600160a060020a03600435166024356108a7565b005b60015474010000000000000000000000000000000000000000900460ff1681565b600681815481101561000057906000526020600020900160005b5054905081565b600054600160a060020a036301000000909104811690331681146103a557610000565b60038290555b5b5050565b6005546040805160006020918201819052825160e260020a631d010437028152600160a060020a03868116600483015293519194939093169263740410dc92602480830193919282900301818787803b156100005760325a03f115610000575050604051519150505b919050565b60035481565b6006545b90565b600054600160a060020a0363010000009091048116903316811461044e57610000565b60018054600160a060020a031916600160a060020a0384161790555b5b5050565b60005463010000009004600160a060020a031681565b6000600060006000600060006000600160149054906101000a900460ff16156104ad57610000565b87600081518110156100005760209101015160005460f860020a918290048202975002600160f860020a031990811690871614156105405760009450600196505b600587101561053057878781518110156100005790602001015160f860020a900460f860020a0260f860020a900485610100020194505b6001909601956104ee565b61053b8b868c610955565b6106d6565b600054610100900460f860020a02600160f860020a0319908116908716141561069e57506001955060009250829150819050805b60058710156105b657878781518110156100005790602001015160f860020a900460f860020a0260f860020a900481610100020190505b600190960195610574565b600596505b60098710156105fd57878781518110156100005790602001015160f860020a900460f860020a0260f860020a900484610100020193505b6001909601956105bb565b600996505b600d87101561064457878781518110156100005790602001015160f860020a900460f860020a0260f860020a900483610100020192505b600190960195610602565b600d96505b601187101561068b57878781518110156100005790602001015160f860020a900460f860020a0260f860020a900482610100020191505b600190960195610649565b61053b8b828c878787610bc4565b6106d6565b60005462010000900460f860020a02600160f860020a031990811690871614156106d15761053b8b8b610e8e565b6106d6565b610000565b5b5b5b5b5050505050505050505050565b600054600160a060020a0363010000009091048116903316811461070a57610000565b60058054600160a060020a031916600160a060020a0384161790555b5b5050565b600760208190526000918252604090912080546001820154600283015460038401546004850154600586015460068701549690970154600160a060020a03909516969395929491939092909188565b600081815260076020526040812054600160a060020a0390811690331681146107a257610000565b600083815260076020526040902080546004820154600583015460038401549395506107dc93600160a060020a039093169291029061105e565b50600060058301556107ed83611151565b6040805184815290517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f9181900360200190a161082933611244565b5b5b505050565b600154600160a060020a031681565b600054600160a060020a0363010000009091048116903316811461086257610000565b600080546040516301000000909104600160a060020a0316916108fc851502918591818181858888f1935050505015156103ab57610000565b5b5b5050565b60025481565b600054600160a060020a036301000000909104811690331681146108ca57610000565b6000805460408051602090810184905281517fa9059cbb0000000000000000000000000000000000000000000000000000000081526301000000909304600160a060020a0390811660048501526024840187905291519187169363a9059cbb9360448082019492918390030190829087803b156100005760325a03f115610000575050505b5b505050565b610100604051908101604052806000600160a060020a03168152602001600081526020016000815260200160008152602001600081526020016000815260200160008152602001600081525060006007600085815260200190815260200160002061010060405190810160405290816000820160009054906101000a9004600160a060020a0316600160a060020a0316600160a060020a0316815260200160018201548152602001600282015481526020016003820154815260200160048201548152602001600582015481526020016006820154815260200160078201548152505091508260001415610a4857610000565b8160400151838115610000570615610a5f57610000565b6002548410610a6d57610000565b8160400151838115610000570490508160a00151811115610a8d57610000565b610a9c8584846020015161128e565b1515610aa757610000565b60a082018051829003815260008581526007602081815260409283902086518154600160a060020a031916600160a060020a038216178255918701516001820181905593870151600282015560608701516003820155608087015160048201559351600585015560c0860151600685015560e08601519390910192909255610b319190859061105e565b1515610b3c57610000565b610b518582846080015102846060015161105e565b1515610b5c57610000565b60a0820151158015610b71575060c082015115155b15610b7f57610b7f84611151565b5b6040805185815290517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f9181900360200190a1610bbc85611244565b5b5050505050565b831515610bd057610000565b82851415610bdd57610000565b801580610be8575081155b15610bf257610000565b80848115610000570615610c0557610000565b6005546040805160006020918201819052825160e260020a631d010437028152600160a060020a038b8116600483015293518695949094169363740410dc9360248084019491938390030190829087803b156100005760325a03f11561000057505050604051805190501015610c7a57610000565b610c8586858761128e565b1515610c9057610000565b600554604080517fbe0140a6000000000000000000000000000000000000000000000000000000008152600160a060020a03898116600483015260006024830181905260448301869052925193169263be0140a69260648084019391929182900301818387803b156100005760325a03f115610000575050506101006040519081016040528087600160a060020a03168152602001848152602001838152602001868152602001828681156100005704815260200182815260200160068054905081526020014281525060076000600254815260200190815260200160002060008201518160000160006101000a815481600160a060020a030219169083600160a060020a031602179055506020820151816001015560408201518160020155606082015181600301556080820151816004015560a0820151816005015560c0820151816006015560e0820151816007015590505060068054806001018281815481835581811511610e2757600083815260209020610e279181019083015b80821115610e235760008155600101610e0f565b5090565b5b505050916000526020600020900160005b50600280549182905560018201905560408051918252517fb5dc9baf0cb4e7e4759fa12eadebddf9316e26147d5a9ae150c4228d5a1dd23f92509081900360200190a1610e8586611244565b5b505050505050565b600354818115610000570615610ea357610000565b600160009054906101000a9004600160a060020a0316600160a060020a031663cf35bdd060016000604051602001526040518263ffffffff1660e060020a02815260040180828152602001915050602060405180830381600087803b156100005760325a03f115610000575050604080518051600080546020938401829052845160e060020a6323b872dd028152600160a060020a038981166004830152630100000090920482166024820152604481018890529451921694506323b872dd936064808201949392918390030190829087803b156100005760325a03f1156100005750506040515115159050610f9857610000565b600554600354600160a060020a039091169063be0140a6908490600190858115610000576040805160e060020a63ffffffff8816028152600160a060020a039095166004860152921515602485015204604483015251606480830192600092919082900301818387803b156100005760325a03f1156100005750505061101d82611244565b60408051600160a060020a038416815290517f30a29a0aa75376a69254bb98dbd11db423b7e8c3473fb5bf0fcba60bcbc42c4b9181900360200190a15b5050565b600081151561106c57610000565b6001546040805160006020918201819052825160e460020a630cf35bdd028152600481018790529251600160a060020a039094169363cf35bdd09360248082019493918390030190829087803b156100005760325a03f1156100005750505060405180519050600160a060020a031663a9059cbb85856000604051602001526040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050602060405180830381600087803b156100005760325a03f115610000575050604051519150505b9392505050565b6000818152600760205260409020600690810154815490919060001981019081101561000057906000526020600020900160005b5054600682815481101561000057906000526020600020900160005b50556006805460001981018083559091908280158290116111e7576000838152602090206111e79181019083015b80821115610e235760008155600101610e0f565b5090565b5b50506006548314915061122d9050578060076000600684815481101561000057906000526020600020900160005b505481526020810191909152604001600020600601555b6000828152600760205260408120600601555b5050565b60045481600160a060020a031631101561128957600454604051600160a060020a0383169180156108fc02916000818181858888f19350505050151561128957610000565b5b5b50565b600081151561129c57610000565b6001546040805160006020918201819052825160e460020a630cf35bdd028152600481018790529251600160a060020a039094169363cf35bdd09360248082019493918390030190829087803b156100005760325a03f11561000057505060408051805160006020928301819052835160e060020a6323b872dd028152600160a060020a038a811660048301523081166024830152604482018a905294519490921694506323b872dd93606480840194939192918390030190829087803b156100005760325a03f115610000575050604051519150505b93925050505600a165627a7a723058204dee0e1bf170a9d122508f3e876c4a84893b12a7345591521af4ca737bb765000029" From ebfe26cbb076b25d847271c1cd63738c4a2ca573 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 25 Jun 2019 15:30:44 -0700 Subject: [PATCH 16/28] Fix merge mistake --- myth | 20 +- mythril/interfaces/old_cli.py | 549 ++++++++++++++++++ tests/report_test.py | 200 ------- .../outputs_expected/calls.sol.o.json | 123 ---- .../outputs_expected/calls.sol.o.jsonv2 | 174 ------ .../outputs_expected/calls.sol.o.markdown | 117 ---- .../outputs_expected/calls.sol.o.text | 98 ---- .../outputs_expected/ether_send.sol.o.json | 32 - .../outputs_expected/ether_send.sol.o.jsonv2 | 48 -- .../ether_send.sol.o.markdown | 27 - .../outputs_expected/ether_send.sol.o.text | 22 - .../outputs_expected/exceptions.sol.o.json | 58 -- .../outputs_expected/exceptions.sol.o.jsonv2 | 84 --- .../exceptions.sol.o.markdown | 53 -- .../outputs_expected/exceptions.sol.o.text | 44 -- .../kinds_of_calls.sol.o.json | 84 --- .../kinds_of_calls.sol.o.jsonv2 | 120 ---- .../kinds_of_calls.sol.o.markdown | 79 --- .../kinds_of_calls.sol.o.text | 66 --- .../outputs_expected/metacoin.sol.o.json | 5 - .../outputs_expected/metacoin.sol.o.jsonv2 | 11 - .../outputs_expected/metacoin.sol.o.markdown | 3 - .../outputs_expected/metacoin.sol.o.text | 1 - .../multi_contracts.sol.o.json | 19 - .../multi_contracts.sol.o.jsonv2 | 30 - .../multi_contracts.sol.o.markdown | 14 - .../multi_contracts.sol.o.text | 11 - .../outputs_expected/nonascii.sol.o.json | 5 - .../outputs_expected/nonascii.sol.o.jsonv2 | 11 - .../outputs_expected/nonascii.sol.o.markdown | 3 - .../outputs_expected/nonascii.sol.o.text | 1 - .../outputs_expected/origin.sol.o.json | 19 - .../outputs_expected/origin.sol.o.jsonv2 | 30 - .../outputs_expected/origin.sol.o.markdown | 15 - .../outputs_expected/origin.sol.o.text | 12 - .../outputs_expected/overflow.sol.o.json | 45 -- .../outputs_expected/overflow.sol.o.jsonv2 | 66 --- .../outputs_expected/overflow.sol.o.markdown | 40 -- .../outputs_expected/overflow.sol.o.text | 33 -- .../outputs_expected/returnvalue.sol.o.json | 45 -- .../outputs_expected/returnvalue.sol.o.jsonv2 | 66 --- .../returnvalue.sol.o.markdown | 40 -- .../outputs_expected/returnvalue.sol.o.text | 33 -- .../outputs_expected/suicide.sol.o.json | 19 - .../outputs_expected/suicide.sol.o.jsonv2 | 30 - .../outputs_expected/suicide.sol.o.markdown | 14 - .../outputs_expected/suicide.sol.o.text | 11 - .../outputs_expected/underflow.sol.o.json | 45 -- .../outputs_expected/underflow.sol.o.jsonv2 | 66 --- .../outputs_expected/underflow.sol.o.markdown | 40 -- .../outputs_expected/underflow.sol.o.text | 33 -- 51 files changed, 568 insertions(+), 2246 deletions(-) create mode 100644 mythril/interfaces/old_cli.py delete mode 100644 tests/report_test.py delete mode 100644 tests/testdata/outputs_expected/calls.sol.o.json delete mode 100644 tests/testdata/outputs_expected/calls.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/calls.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/calls.sol.o.text delete mode 100644 tests/testdata/outputs_expected/ether_send.sol.o.json delete mode 100644 tests/testdata/outputs_expected/ether_send.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/ether_send.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/ether_send.sol.o.text delete mode 100644 tests/testdata/outputs_expected/exceptions.sol.o.json delete mode 100644 tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/exceptions.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/exceptions.sol.o.text delete mode 100644 tests/testdata/outputs_expected/kinds_of_calls.sol.o.json delete mode 100644 tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/kinds_of_calls.sol.o.text delete mode 100644 tests/testdata/outputs_expected/metacoin.sol.o.json delete mode 100644 tests/testdata/outputs_expected/metacoin.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/metacoin.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/metacoin.sol.o.text delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.o.json delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/multi_contracts.sol.o.text delete mode 100644 tests/testdata/outputs_expected/nonascii.sol.o.json delete mode 100644 tests/testdata/outputs_expected/nonascii.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/nonascii.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/nonascii.sol.o.text delete mode 100644 tests/testdata/outputs_expected/origin.sol.o.json delete mode 100644 tests/testdata/outputs_expected/origin.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/origin.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/origin.sol.o.text delete mode 100644 tests/testdata/outputs_expected/overflow.sol.o.json delete mode 100644 tests/testdata/outputs_expected/overflow.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/overflow.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/overflow.sol.o.text delete mode 100644 tests/testdata/outputs_expected/returnvalue.sol.o.json delete mode 100644 tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/returnvalue.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/returnvalue.sol.o.text delete mode 100644 tests/testdata/outputs_expected/suicide.sol.o.json delete mode 100644 tests/testdata/outputs_expected/suicide.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/suicide.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/suicide.sol.o.text delete mode 100644 tests/testdata/outputs_expected/underflow.sol.o.json delete mode 100644 tests/testdata/outputs_expected/underflow.sol.o.jsonv2 delete mode 100644 tests/testdata/outputs_expected/underflow.sol.o.markdown delete mode 100644 tests/testdata/outputs_expected/underflow.sol.o.text diff --git a/myth b/myth index a45d431f..f378aba7 100755 --- a/myth +++ b/myth @@ -3,7 +3,25 @@ """mythril.py: Bug hunting on the Ethereum blockchain http://www.github.com/b-mueller/mythril """ +from sys import argv, exit +from mythril.interfaces.cli import COMMAND_LIST import mythril.interfaces.cli +import mythril.interfaces.old_cli +import warnings + + +def format_Warning(message, category, filename, lineno, line=""): + return "Deprecated Warning: {}\n\n".format(str(message)) + + +warnings.formatwarning = format_Warning if __name__ == "__main__": - mythril.interfaces.cli.main() + for arg in argv: + if arg in COMMAND_LIST: + mythril.interfaces.cli.main() + exit() + warnings.warn("The old cli arguments are deprecated, Please use 'myth -h' to view the new command line interface") + mythril.interfaces.old_cli.main() + + diff --git a/mythril/interfaces/old_cli.py b/mythril/interfaces/old_cli.py new file mode 100644 index 00000000..4c9d4222 --- /dev/null +++ b/mythril/interfaces/old_cli.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""mythril.py: Bug hunting on the Ethereum blockchain + + http://www.github.com/ConsenSys/mythril +""" + +import argparse +import json +import logging +import os +import sys + +import coloredlogs +import traceback + +import mythril.support.signatures as sigs +from mythril.exceptions import AddressNotFoundError, CriticalError +from mythril.mythril import ( + MythrilAnalyzer, + MythrilDisassembler, + MythrilConfig, + MythrilLevelDB, +) +from mythril.__version__ import __version__ as VERSION + +log = logging.getLogger(__name__) + + +def exit_with_error(format_, message): + """ + :param format_: + :param message: + """ + if format_ == "text" or format_ == "markdown": + log.error(message) + 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", "msg": message}] + }, + } + ] + print(json.dumps(result)) + sys.exit() + + +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") + commands.add_argument("-g", "--graph", help="generate a control flow graph") + commands.add_argument( + "-V", + "--version", + action="store_true", + help="print the Mythril version number and exit", + ) + commands.add_argument( + "-x", + "--fire-lasers", + action="store_true", + help="detect vulnerabilities, use with -c, -a or solidity file(s)", + ) + commands.add_argument( + "--truffle", + action="store_true", + help="analyze a truffle project (run from project dir)", + ) + commands.add_argument( + "-d", "--disassemble", action="store_true", help="print disassembly" + ) + commands.add_argument( + "-j", + "--statespace-json", + help="dumps the statespace json", + metavar="OUTPUT_FILE", + ) + + inputs = parser.add_argument_group("input arguments") + inputs.add_argument( + "-c", + "--code", + help='hex-encoded bytecode string ("6060604052...")', + metavar="BYTECODE", + ) + inputs.add_argument( + "-f", + "--codefile", + help="file containing hex-encoded bytecode string", + metavar="BYTECODEFILE", + type=argparse.FileType("r"), + ) + inputs.add_argument( + "-a", + "--address", + help="pull contract from the blockchain", + metavar="CONTRACT_ADDRESS", + ) + inputs.add_argument( + "-l", + "--dynld", + action="store_true", + help="auto-load dependencies from the blockchain", + ) + inputs.add_argument( + "--no-onchain-storage-access", + action="store_true", + help="turns off getting the data from onchain contracts", + ) + inputs.add_argument( + "--bin-runtime", + action="store_true", + help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", + ) + + outputs = parser.add_argument_group("output formats") + outputs.add_argument( + "-o", + "--outform", + choices=["text", "markdown", "json", "jsonv2"], + default="text", + help="report output format", + metavar="", + ) + outputs.add_argument( + "--verbose-report", + action="store_true", + help="Include debugging information in report", + ) + + database = parser.add_argument_group("local contracts database") + database.add_argument( + "-s", "--search", help="search the contract database", metavar="EXPRESSION" + ) + database.add_argument( + "--leveldb-dir", + help="specify leveldb directory for search or direct access operations", + metavar="LEVELDB_PATH", + ) + + utilities = parser.add_argument_group("utilities") + utilities.add_argument( + "--hash", help="calculate function signature hash", metavar="SIGNATURE" + ) + utilities.add_argument( + "--storage", + help="read state variables from storage index, use with -a", + metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", + ) + utilities.add_argument( + "--solv", + help="specify solidity compiler version. If not present, will try to install it (Experimental)", + metavar="SOLV", + ) + utilities.add_argument( + "--contract-hash-to-address", + help="returns corresponding address for a contract address hash", + metavar="SHA3_TO_LOOK_FOR", + ) + + options = parser.add_argument_group("options") + options.add_argument( + "-m", + "--modules", + help="Comma-separated list of security analysis modules", + metavar="MODULES", + ) + options.add_argument( + "--max-depth", + type=int, + default=50, + help="Maximum recursion depth for symbolic execution", + ) + options.add_argument( + "--strategy", + choices=["dfs", "bfs", "naive-random", "weighted-random"], + default="bfs", + help="Symbolic execution strategy", + ) + options.add_argument( + "-b", + "--loop-bound", + type=int, + default=4, + help="Bound loops at n iterations", + metavar="N", + ) + options.add_argument( + "-t", + "--transaction-count", + type=int, + default=2, + help="Maximum number of transactions issued by laser", + ) + options.add_argument( + "--execution-timeout", + type=int, + default=86400, + help="The amount of seconds to spend on symbolic execution", + ) + options.add_argument( + "--create-timeout", + type=int, + default=10, + help="The amount of seconds to spend on " "the initial contract creation", + ) + options.add_argument("--solc-args", help="Extra arguments for solc") + options.add_argument( + "--phrack", action="store_true", help="Phrack-style call graph" + ) + options.add_argument( + "--enable-physics", action="store_true", help="enable graph physics simulation" + ) + options.add_argument( + "-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 + ) + options.add_argument( + "-q", + "--query-signature", + action="store_true", + help="Lookup function signatures through www.4byte.directory", + ) + options.add_argument( + "--enable-iprof", action="store_true", help="enable the instruction profiler" + ) + options.add_argument( + "--disable-dependency-pruning", + action="store_true", + help="Deactivate dependency-based pruning", + ) + + rpc = parser.add_argument_group("RPC options") + + rpc.add_argument( + "--rpc", + help="custom RPC settings", + metavar="HOST:PORT / ganache / infura-[network_name]", + default="infura-mainnet", + ) + rpc.add_argument( + "--rpctls", type=bool, default=False, help="RPC connection over TLS" + ) + parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) + + +def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace): + if not ( + args.search + or args.hash + or args.disassemble + or args.graph + or args.fire_lasers + or args.storage + or args.truffle + or args.statespace_json + or args.contract_hash_to_address + ): + parser.print_help() + sys.exit() + + if args.v: + if 0 <= args.v < 6: + log_levels = [ + logging.NOTSET, + logging.CRITICAL, + logging.ERROR, + logging.WARNING, + logging.INFO, + logging.DEBUG, + ] + coloredlogs.install( + fmt="%(name)s [%(levelname)s]: %(message)s", level=log_levels[args.v] + ) + logging.getLogger("mythril").setLevel(log_levels[args.v]) + else: + exit_with_error( + args.outform, "Invalid -v value, you can find valid values in usage" + ) + + if args.query_signature: + if sigs.ethereum_input_decoder is None: + exit_with_error( + args.outform, + "The --query-signature function requires the python package ethereum-input-decoder", + ) + + if args.enable_iprof: + if args.v < 4: + exit_with_error( + args.outform, + "--enable-iprof must be used with -v LOG_LEVEL where LOG_LEVEL >= 4", + ) + elif not (args.graph or args.fire_lasers or args.statespace_json): + exit_with_error( + args.outform, + "--enable-iprof must be used with one of -g, --graph, -x, --fire-lasers, -j and --statespace-json", + ) + + +def quick_commands(args: argparse.Namespace): + if args.hash: + print(MythrilDisassembler.hash_for_function_signature(args.hash)) + sys.exit() + + +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 + ) + 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 + leveldb_searcher.search_db(args.search) + + 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(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, + "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 + + +def execute_command( + disassembler: MythrilDisassembler, + address: str, + parser: argparse.ArgumentParser, + args: argparse.Namespace, +): + + 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.", + ) + + 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, + loop_bound=args.loop_bound, + create_timeout=args.create_timeout, + enable_iprof=args.enable_iprof, + disable_dependency_pruning=args.disable_dependency_pruning, + 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 args.graph: + html = analyzer.graph_html( + contract=analyzer.contracts[0], + enable_physics=args.enable_physics, + phrackify=args.phrack, + 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 = 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, + ) + 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 analyzer.contracts: + exit_with_error( + args.outform, "input files do not contain any valid contracts" + ) + + 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: + 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) + disassembler = MythrilDisassembler( + eth=config.eth, + solc_version=args.solv, + solc_args=args.solc_args, + enable_online_lookup=args.query_signature, + ) + if args.truffle: + try: + disassembler.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(disassembler, args) + execute_command( + disassembler=disassembler, 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__": + main() diff --git a/tests/report_test.py b/tests/report_test.py deleted file mode 100644 index 73554880..00000000 --- a/tests/report_test.py +++ /dev/null @@ -1,200 +0,0 @@ -from mythril.analysis.report import Report -from mythril.analysis.security import fire_lasers, reset_callback_modules -from mythril.analysis.symbolic import SymExecWrapper -from mythril.ethereum import util -from mythril.solidity.soliditycontract import EVMContract -from multiprocessing import Pool, cpu_count -import pytest -import json -from tests import * -import difflib - - -def _fix_path(text): - return text.replace(str(TESTDATA), "") - - -def _fix_debug_data(json_str): - read_json = json.loads(json_str) - for issue in read_json["issues"]: - issue["tx_sequence"] = "" - - 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"] = "" - issue["extra"]["testCase"] = "" - 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( - contract, - address=0xAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFEAFFE, - strategy="dfs", - execution_timeout=30, - transaction_count=1, - ) - issues = fire_lasers(sym) - - report = Report(contracts=[contract]) - for issue in issues: - issue.filename = "test-filename.sol" - report.append_issue(issue) - return report, input_file - - -@pytest.fixture(scope="module") -def reports(): - """Fixture that analyses all reports.""" - reset_callback_modules() - pool = Pool(cpu_count()) - input_files = sorted( - [f for f in TESTDATA_INPUTS.iterdir() if f.name != "environments.sol.o"] - ) - results = pool.map(_generate_report, input_files) - - return results - - -def _assert_empty(changed_files, postfix): - """Asserts there are no changed files and otherwise builds error - message.""" - message = "" - for input_file in changed_files: - output_expected = ( - (TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix)) - .read_text() - .splitlines(1) - ) - output_current = ( - (TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix)) - .read_text() - .splitlines(1) - ) - - difference = "".join(difflib.unified_diff(output_expected, output_current)) - message += "Found differing file for input: {} \n Difference: \n {} \n".format( - str(input_file), str(difference) - ) - - assert message == "", message - - -def _assert_empty_json(changed_files, postfix=".json"): - """Asserts there are no changed files and otherwise builds error - message.""" - expected = [] - actual = [] - - def ordered(obj): - """ - - :param obj: - :return: - """ - if isinstance(obj, dict): - return sorted((k, ordered(v)) for k, v in obj.items()) - elif isinstance(obj, list): - return sorted(ordered(x) for x in obj) - else: - return obj - - for input_file in changed_files: - output_expected = json.loads( - (TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix)).read_text() - ) - output_current = json.loads( - (TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix)).read_text() - ) - - if not ordered(output_expected) == ordered(output_current): - expected.append(output_expected) - actual.append(output_current) - print("Found difference in {}".format(str(input_file))) - - assert expected == actual - - -def _get_changed_files(postfix, report_builder, reports): - """Returns a generator for all unexpected changes in generated reports. - - :param postfix: The applicable postfix - :param report_builder: serialization function - :param reports: The reports to serialize - :return: Changed files - """ - for report, input_file in reports: - output_expected = TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix) - output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix) - output_current.write_text(report_builder(report)) - if not (output_expected.read_text() == output_current.read_text()): - yield input_file - - -def _get_changed_files_json(report_builder, reports, postfix=".json"): - def ordered(obj): - """ - - :param obj: - :return: - """ - if isinstance(obj, dict): - return sorted((k, ordered(v)) for k, v in obj.items()) - elif isinstance(obj, list): - return sorted(ordered(x) for x in obj) - else: - return obj - - for report, input_file in reports: - output_expected = TESTDATA_OUTPUTS_EXPECTED / (input_file.name + postfix) - output_current = TESTDATA_OUTPUTS_CURRENT / (input_file.name + postfix) - output_current.write_text(report_builder(report)) - - if not ordered(json.loads(output_expected.read_text())) == ordered( - json.loads(output_current.read_text()) - ): - yield input_file - - -def test_json_report(reports): - _assert_empty_json( - _get_changed_files_json( - lambda report: _fix_path(_fix_debug_data(report.as_json())).strip(), reports - ) - ) - - -def test_markdown_report(reports): - _assert_empty( - _get_changed_files( - ".markdown", lambda report: _fix_path(report.as_markdown()), reports - ), - ".markdown", - ) - - -def test_text_report(reports): - _assert_empty( - _get_changed_files( - ".text", lambda report: _fix_path(report.as_text()), reports - ), - ".text", - ) - - -def test_jsonv2_report(reports): - _assert_empty_json( - _get_changed_files_json( - lambda report: _fix_path( - _add_jsonv2_stubs(report.as_swc_standard_format()) - ).strip(), - reports, - ".jsonv2", - ), - ".jsonv2", - ) diff --git a/tests/testdata/outputs_expected/calls.sol.o.json b/tests/testdata/outputs_expected/calls.sol.o.json deleted file mode 100644 index 0219f575..00000000 --- a/tests/testdata/outputs_expected/calls.sol.o.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 661, - "contract": "Unknown", - "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": "thisisfine()", - "max_gas_used": 1254, - "min_gas_used": 643, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "External Call To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 661, - "contract": "Unknown", - "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": "thisisfine()", - "max_gas_used": 35972, - "min_gas_used": 1361, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - }, - { - "address": 779, - "contract": "Unknown", - "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": "callstoredaddress()", - "max_gas_used": 1298, - "min_gas_used": 687, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "External Call To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 779, - "contract": "Unknown", - "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": "callstoredaddress()", - "max_gas_used": 36016, - "min_gas_used": 1405, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - }, - { - "address": 858, - "contract": "Unknown", - "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": "reentrancy()", - "max_gas_used": 1320, - "min_gas_used": 709, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "External Call To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 858, - "contract": "Unknown", - "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": "reentrancy()", - "max_gas_used": 61052, - "min_gas_used": 6441, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - }, - { - "address": 869, - "contract": "Unknown", - "description": "The contract account state is changed after an external call. \nConsider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.", - "function": "reentrancy()", - "max_gas_used": null, - "min_gas_used": null, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "State change after external call", - "tx_sequence": "" - }, - { - "address": 912, - "contract": "Unknown", - "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": "calluseraddress(address)", - "max_gas_used": 616, - "min_gas_used": 335, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "External Call To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 912, - "contract": "Unknown", - "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": "calluseraddress(address)", - "max_gas_used": 35336, - "min_gas_used": 1055, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - } - ], - "success": true -} \ No newline at end of file diff --git a/tests/testdata/outputs_expected/calls.sol.o.jsonv2 b/tests/testdata/outputs_expected/calls.sol.o.jsonv2 deleted file mode 100644 index 9bab6f6a..00000000 --- a/tests/testdata/outputs_expected/calls.sol.o.jsonv2 +++ /dev/null @@ -1,174 +0,0 @@ -[ - { - "issues": [ - { - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "661:1:0" - } - ], - "severity": "Medium", - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "779:1:0" - } - ], - "severity": "Medium", - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "858:1:0" - } - ], - "severity": "Medium", - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "912:1:0" - } - ], - "severity": "Medium", - "swcID": "SWC-107", - "swcTitle": "Reentrancy" - }, - { - "description": { - "head": "The contract account state is changed after an external call. ", - "tail": "Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities." - }, - "extra": { - "discoveryTime": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "869: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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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/calls.sol.o.markdown b/tests/testdata/outputs_expected/calls.sol.o.markdown deleted file mode 100644 index 490ad89e..00000000 --- a/tests/testdata/outputs_expected/calls.sol.o.markdown +++ /dev/null @@ -1,117 +0,0 @@ -# Analysis results for test-filename.sol - -## External Call To User-Supplied Address -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `thisisfine()` -- PC address: 661 -- Estimated Gas Usage: 643 - 1254 - -### Description - -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. - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `thisisfine()` -- PC address: 661 -- Estimated Gas Usage: 1361 - 35972 - -### Description - -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. - -## External Call To User-Supplied Address -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `callstoredaddress()` -- PC address: 779 -- Estimated Gas Usage: 687 - 1298 - -### Description - -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. - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `callstoredaddress()` -- PC address: 779 -- Estimated Gas Usage: 1405 - 36016 - -### Description - -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. - -## External Call To User-Supplied Address -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `reentrancy()` -- PC address: 858 -- Estimated Gas Usage: 709 - 1320 - -### Description - -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. - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `reentrancy()` -- PC address: 858 -- Estimated Gas Usage: 6441 - 61052 - -### Description - -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. - -## State change after external call -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `reentrancy()` -- PC address: 869 - -### Description - -The contract account state is changed after an external call. -Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities. - -## External Call To User-Supplied Address -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `calluseraddress(address)` -- PC address: 912 -- Estimated Gas Usage: 335 - 616 - -### Description - -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. - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `calluseraddress(address)` -- PC address: 912 -- Estimated Gas Usage: 1055 - 35336 - -### Description - -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/calls.sol.o.text b/tests/testdata/outputs_expected/calls.sol.o.text deleted file mode 100644 index d38e8f03..00000000 --- a/tests/testdata/outputs_expected/calls.sol.o.text +++ /dev/null @@ -1,98 +0,0 @@ -==== External Call To User-Supplied Address ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: thisisfine() -PC address: 661 -Estimated Gas Usage: 643 - 1254 -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. --------------------- - -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: thisisfine() -PC address: 661 -Estimated Gas Usage: 1361 - 35972 -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. --------------------- - -==== External Call To User-Supplied Address ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: callstoredaddress() -PC address: 779 -Estimated Gas Usage: 687 - 1298 -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. --------------------- - -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: callstoredaddress() -PC address: 779 -Estimated Gas Usage: 1405 - 36016 -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. --------------------- - -==== External Call To User-Supplied Address ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: reentrancy() -PC address: 858 -Estimated Gas Usage: 709 - 1320 -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. --------------------- - -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: reentrancy() -PC address: 858 -Estimated Gas Usage: 6441 - 61052 -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. --------------------- - -==== State change after external call ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: reentrancy() -PC address: 869 -The contract account state is changed after an external call. -Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities. --------------------- - -==== External Call To User-Supplied Address ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: calluseraddress(address) -PC address: 912 -Estimated Gas Usage: 335 - 616 -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. --------------------- - -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: calluseraddress(address) -PC address: 912 -Estimated Gas Usage: 1055 - 35336 -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/ether_send.sol.o.json b/tests/testdata/outputs_expected/ether_send.sol.o.json deleted file mode 100644 index 1d2e4a19..00000000 --- a/tests/testdata/outputs_expected/ether_send.sol.o.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 722, - "contract": "Unknown", - "description": "Anyone can withdraw ETH from the contract account.\nArbitrary 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.", - "function": "withdrawfunds()", - "max_gas_used": 1749, - "min_gas_used": 1138, - "severity": "High", - "sourceMap": null, - "swc-id": "105", - "title": "Unprotected Ether Withdrawal", - "tx_sequence": "" - }, - { - "address": 883, - "contract": "Unknown", - "description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.", - "function": "invest()", - "max_gas_used": 26883, - "min_gas_used": 6598, - "severity": "High", - "sourceMap": null, - "swc-id": "101", - "title": "Integer Overflow", - "tx_sequence": "" - } - ], - "success": true -} \ 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 deleted file mode 100644 index e848bd2f..00000000 --- a/tests/testdata/outputs_expected/ether_send.sol.o.jsonv2 +++ /dev/null @@ -1,48 +0,0 @@ -[ - { - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "722:1:0" - } - ], - "severity": "High", - "swcID": "SWC-105", - "swcTitle": "Unprotected Ether Withdrawal" - }, - { - "description": { - "head": "The binary addition can overflow.", - "tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion." - }, - "extra": { - "discoveryTime": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "883:1:0" - } - ], - "severity": "High", - "swcID": "SWC-101", - "swcTitle": "Integer Overflow and Underflow" - } - ], - "meta": {}, - "sourceFormat": "evm-byzantium-bytecode", - "sourceList": [ - "0x3746c7c2ae7b0d4c3f8b1905df9a7ea169b9f93bec68a10a00b4c9d27a18c6fb" - ], - "sourceType": "raw-bytecode" - } -] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.markdown b/tests/testdata/outputs_expected/ether_send.sol.o.markdown deleted file mode 100644 index 2e1c2a9e..00000000 --- a/tests/testdata/outputs_expected/ether_send.sol.o.markdown +++ /dev/null @@ -1,27 +0,0 @@ -# Analysis results for test-filename.sol - -## Unprotected Ether Withdrawal -- SWC ID: 105 -- Severity: High -- Contract: Unknown -- Function name: `withdrawfunds()` -- PC address: 722 -- Estimated Gas Usage: 1138 - 1749 - -### Description - -Anyone can withdraw ETH from the contract account. -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. - -## Integer Overflow -- SWC ID: 101 -- Severity: High -- Contract: Unknown -- Function name: `invest()` -- PC address: 883 -- Estimated Gas Usage: 6598 - 26883 - -### Description - -The binary addition can overflow. -The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.text b/tests/testdata/outputs_expected/ether_send.sol.o.text deleted file mode 100644 index 493978be..00000000 --- a/tests/testdata/outputs_expected/ether_send.sol.o.text +++ /dev/null @@ -1,22 +0,0 @@ -==== Unprotected Ether Withdrawal ==== -SWC ID: 105 -Severity: High -Contract: Unknown -Function name: withdrawfunds() -PC address: 722 -Estimated Gas Usage: 1138 - 1749 -Anyone can withdraw ETH from the contract account. -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. --------------------- - -==== Integer Overflow ==== -SWC ID: 101 -Severity: High -Contract: Unknown -Function name: invest() -PC address: 883 -Estimated Gas Usage: 6598 - 26883 -The binary addition can overflow. -The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. --------------------- - diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.json b/tests/testdata/outputs_expected/exceptions.sol.o.json deleted file mode 100644 index 19030e55..00000000 --- a/tests/testdata/outputs_expected/exceptions.sol.o.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 446, - "contract": "Unknown", - "description": "A reachable exception has been detected.\nIt 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.", - "function": "assert3(uint256)", - "max_gas_used": 301, - "min_gas_used": 206, - "severity": "Low", - "sourceMap": null, - "swc-id": "110", - "title": "Exception State", - "tx_sequence": "" - }, - { - "address": 484, - "contract": "Unknown", - "description": "A reachable exception has been detected.\nIt 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.", - "function": "arrayaccess(uint256)", - "max_gas_used": 351, - "min_gas_used": 256, - "severity": "Low", - "sourceMap": null, - "swc-id": "110", - "title": "Exception State", - "tx_sequence": "" - }, - { - "address": 506, - "contract": "Unknown", - "description": "A reachable exception has been detected.\nIt 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.", - "function": "divisionby0(uint256)", - "max_gas_used": 367, - "min_gas_used": 272, - "severity": "Low", - "sourceMap": null, - "swc-id": "110", - "title": "Exception State", - "tx_sequence": "" - }, - { - "address": 531, - "contract": "Unknown", - "description": "A reachable exception has been detected.\nIt 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.", - "function": "assert1()", - "max_gas_used": 363, - "min_gas_used": 268, - "severity": "Low", - "sourceMap": null, - "swc-id": "110", - "title": "Exception State", - "tx_sequence": "" - } - ], - "success": true -} \ 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 deleted file mode 100644 index 43b6ca48..00000000 --- a/tests/testdata/outputs_expected/exceptions.sol.o.jsonv2 +++ /dev/null @@ -1,84 +0,0 @@ -[ - { - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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/exceptions.sol.o.markdown b/tests/testdata/outputs_expected/exceptions.sol.o.markdown deleted file mode 100644 index c5da9834..00000000 --- a/tests/testdata/outputs_expected/exceptions.sol.o.markdown +++ /dev/null @@ -1,53 +0,0 @@ -# Analysis results for test-filename.sol - -## Exception State -- SWC ID: 110 -- Severity: Low -- Contract: Unknown -- Function name: `assert3(uint256)` -- PC address: 446 -- Estimated Gas Usage: 206 - 301 - -### Description - -A reachable exception has been detected. -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. - -## Exception State -- SWC ID: 110 -- Severity: Low -- Contract: Unknown -- Function name: `arrayaccess(uint256)` -- PC address: 484 -- Estimated Gas Usage: 256 - 351 - -### Description - -A reachable exception has been detected. -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. - -## Exception State -- SWC ID: 110 -- Severity: Low -- Contract: Unknown -- Function name: `divisionby0(uint256)` -- PC address: 506 -- Estimated Gas Usage: 272 - 367 - -### Description - -A reachable exception has been detected. -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. - -## Exception State -- SWC ID: 110 -- Severity: Low -- Contract: Unknown -- Function name: `assert1()` -- PC address: 531 -- Estimated Gas Usage: 268 - 363 - -### Description - -A reachable exception has been detected. -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. diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.text b/tests/testdata/outputs_expected/exceptions.sol.o.text deleted file mode 100644 index cfee4d39..00000000 --- a/tests/testdata/outputs_expected/exceptions.sol.o.text +++ /dev/null @@ -1,44 +0,0 @@ -==== Exception State ==== -SWC ID: 110 -Severity: Low -Contract: Unknown -Function name: assert3(uint256) -PC address: 446 -Estimated Gas Usage: 206 - 301 -A reachable exception has been detected. -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. --------------------- - -==== Exception State ==== -SWC ID: 110 -Severity: Low -Contract: Unknown -Function name: arrayaccess(uint256) -PC address: 484 -Estimated Gas Usage: 256 - 351 -A reachable exception has been detected. -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. --------------------- - -==== Exception State ==== -SWC ID: 110 -Severity: Low -Contract: Unknown -Function name: divisionby0(uint256) -PC address: 506 -Estimated Gas Usage: 272 - 367 -A reachable exception has been detected. -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. --------------------- - -==== Exception State ==== -SWC ID: 110 -Severity: Low -Contract: Unknown -Function name: assert1() -PC address: 531 -Estimated Gas Usage: 268 - 363 -A reachable exception has been detected. -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. --------------------- - diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json deleted file mode 100644 index c2ee1fd0..00000000 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 618, - "contract": "Unknown", - "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_0x141f32ff", - "max_gas_used": 35865, - "min_gas_used": 1113, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - }, - { - "address": 618, - "contract": "Unknown", - "description": "Use of callcode is deprecated.\nThe 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.", - "function": "_function_0x141f32ff", - "max_gas_used": 1141, - "min_gas_used": 389, - "severity": "Medium", - "sourceMap": null, - "swc-id": "111", - "title": "Use of callcode", - "tx_sequence": "" - }, - { - "address": 849, - "contract": "Unknown", - "description": "The contract delegates execution to another contract with a user-supplied address.\nThe smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. ", - "function": "_function_0x9b58bc26", - "max_gas_used": 35928, - "min_gas_used": 1176, - "severity": "Medium", - "sourceMap": null, - "swc-id": "112", - "title": "Delegatecall Proxy To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 849, - "contract": "Unknown", - "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": 35928, - "min_gas_used": 1176, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - }, - { - "address": 1038, - "contract": "Unknown", - "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": 1229, - "min_gas_used": 477, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "External Call To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 1038, - "contract": "Unknown", - "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": 35953, - "min_gas_used": 1201, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - } - ], - "success": true -} \ 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 deleted file mode 100644 index d4f5cf82..00000000 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.jsonv2 +++ /dev/null @@ -1,120 +0,0 @@ -[ - { - "issues": [ - { - "description": { - "head": "The contract delegates execution to another contract with a user-supplied address.", - "tail": "The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. " - }, - "extra": { - "discoveryTime": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "849:1:0" - } - ], - "severity": "Medium", - "swcID": "SWC-112", - "swcTitle": "Delegatecall to Untrusted Callee" - }, - { - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "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 deleted file mode 100644 index e6f7f11e..00000000 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown +++ /dev/null @@ -1,79 +0,0 @@ -# Analysis results for test-filename.sol - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `_function_0x141f32ff` -- PC address: 618 -- Estimated Gas Usage: 1113 - 35865 - -### Description - -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. - -## Use of callcode -- SWC ID: 111 -- Severity: Medium -- Contract: Unknown -- Function name: `_function_0x141f32ff` -- PC address: 618 -- Estimated Gas Usage: 389 - 1141 - -### Description - -Use of callcode is deprecated. -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. - -## Delegatecall Proxy To User-Supplied Address -- SWC ID: 112 -- Severity: Medium -- Contract: Unknown -- Function name: `_function_0x9b58bc26` -- PC address: 849 -- Estimated Gas Usage: 1176 - 35928 - -### Description - -The contract delegates execution to another contract with a user-supplied address. -The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `_function_0x9b58bc26` -- PC address: 849 -- Estimated Gas Usage: 1176 - 35928 - -### Description - -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. - -## External Call To User-Supplied Address -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `_function_0xeea4c864` -- PC address: 1038 -- Estimated Gas Usage: 477 - 1229 - -### Description - -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. - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `_function_0xeea4c864` -- PC address: 1038 -- Estimated Gas Usage: 1201 - 35953 - -### Description - -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/kinds_of_calls.sol.o.text b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text deleted file mode 100644 index 1bb3abad..00000000 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text +++ /dev/null @@ -1,66 +0,0 @@ -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: _function_0x141f32ff -PC address: 618 -Estimated Gas Usage: 1113 - 35865 -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. --------------------- - -==== Use of callcode ==== -SWC ID: 111 -Severity: Medium -Contract: Unknown -Function name: _function_0x141f32ff -PC address: 618 -Estimated Gas Usage: 389 - 1141 -Use of callcode is deprecated. -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. --------------------- - -==== Delegatecall Proxy To User-Supplied Address ==== -SWC ID: 112 -Severity: Medium -Contract: Unknown -Function name: _function_0x9b58bc26 -PC address: 849 -Estimated Gas Usage: 1176 - 35928 -The contract delegates execution to another contract with a user-supplied address. -The smart contract delegates execution to a user-supplied address. Note that callers can execute arbitrary contracts and that the callee contract can access the storage of the calling contract. --------------------- - -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: _function_0x9b58bc26 -PC address: 849 -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. --------------------- - -==== External Call To User-Supplied Address ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: _function_0xeea4c864 -PC address: 1038 -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. --------------------- - -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: _function_0xeea4c864 -PC address: 1038 -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/metacoin.sol.o.json b/tests/testdata/outputs_expected/metacoin.sol.o.json deleted file mode 100644 index 712f50c1..00000000 --- a/tests/testdata/outputs_expected/metacoin.sol.o.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "error": null, - "issues": [], - "success": true -} \ 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 deleted file mode 100644 index 40de69b4..00000000 --- a/tests/testdata/outputs_expected/metacoin.sol.o.jsonv2 +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "issues": [], - "meta": {}, - "sourceFormat": "evm-byzantium-bytecode", - "sourceList": [ - "0x0e6f727bb3301e02d3be831bf34357522fd2f1d40e90dff8e2214553b06b5f6c" - ], - "sourceType": "raw-bytecode" - } -] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.markdown b/tests/testdata/outputs_expected/metacoin.sol.o.markdown deleted file mode 100644 index 321484fd..00000000 --- a/tests/testdata/outputs_expected/metacoin.sol.o.markdown +++ /dev/null @@ -1,3 +0,0 @@ -# Analysis results for None - -The analysis was completed successfully. No issues were detected. diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.text b/tests/testdata/outputs_expected/metacoin.sol.o.text deleted file mode 100644 index 729320d8..00000000 --- a/tests/testdata/outputs_expected/metacoin.sol.o.text +++ /dev/null @@ -1 +0,0 @@ -The analysis was completed successfully. No issues were detected. diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.json b/tests/testdata/outputs_expected/multi_contracts.sol.o.json deleted file mode 100644 index cf2fd3af..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 142, - "contract": "Unknown", - "description": "Anyone can withdraw ETH from the contract account.\nArbitrary 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.", - "function": "transfer()", - "max_gas_used": 467, - "min_gas_used": 186, - "severity": "High", - "sourceMap": null, - "swc-id": "105", - "title": "Unprotected Ether Withdrawal", - "tx_sequence": "" - } - ], - "success": true -} \ 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 deleted file mode 100644 index ec36d8ca..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.jsonv2 +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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": "", - "testCase": "" - }, - "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/multi_contracts.sol.o.markdown b/tests/testdata/outputs_expected/multi_contracts.sol.o.markdown deleted file mode 100644 index a7eac008..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.markdown +++ /dev/null @@ -1,14 +0,0 @@ -# Analysis results for test-filename.sol - -## Unprotected Ether Withdrawal -- SWC ID: 105 -- Severity: High -- Contract: Unknown -- Function name: `transfer()` -- PC address: 142 -- Estimated Gas Usage: 186 - 467 - -### Description - -Anyone can withdraw ETH from the contract account. -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. diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.text b/tests/testdata/outputs_expected/multi_contracts.sol.o.text deleted file mode 100644 index a8388020..00000000 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.text +++ /dev/null @@ -1,11 +0,0 @@ -==== Unprotected Ether Withdrawal ==== -SWC ID: 105 -Severity: High -Contract: Unknown -Function name: transfer() -PC address: 142 -Estimated Gas Usage: 186 - 467 -Anyone can withdraw ETH from the contract account. -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. --------------------- - diff --git a/tests/testdata/outputs_expected/nonascii.sol.o.json b/tests/testdata/outputs_expected/nonascii.sol.o.json deleted file mode 100644 index 712f50c1..00000000 --- a/tests/testdata/outputs_expected/nonascii.sol.o.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "error": null, - "issues": [], - "success": true -} \ 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 deleted file mode 100644 index 0667ad8c..00000000 --- a/tests/testdata/outputs_expected/nonascii.sol.o.jsonv2 +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "issues": [], - "meta": {}, - "sourceFormat": "evm-byzantium-bytecode", - "sourceList": [ - "0x11a78eb09819f505ba4f10747e6d1f7a44480e602c67573b7abac2f733a85d93" - ], - "sourceType": "raw-bytecode" - } -] \ No newline at end of file diff --git a/tests/testdata/outputs_expected/nonascii.sol.o.markdown b/tests/testdata/outputs_expected/nonascii.sol.o.markdown deleted file mode 100644 index 321484fd..00000000 --- a/tests/testdata/outputs_expected/nonascii.sol.o.markdown +++ /dev/null @@ -1,3 +0,0 @@ -# Analysis results for None - -The analysis was completed successfully. No issues were detected. diff --git a/tests/testdata/outputs_expected/nonascii.sol.o.text b/tests/testdata/outputs_expected/nonascii.sol.o.text deleted file mode 100644 index 729320d8..00000000 --- a/tests/testdata/outputs_expected/nonascii.sol.o.text +++ /dev/null @@ -1 +0,0 @@ -The analysis was completed successfully. No issues were detected. diff --git a/tests/testdata/outputs_expected/origin.sol.o.json b/tests/testdata/outputs_expected/origin.sol.o.json deleted file mode 100644 index 6d79baf7..00000000 --- a/tests/testdata/outputs_expected/origin.sol.o.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 317, - "contract": "Unknown", - "description": "Use of tx.origin is deprecated.\nThe 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", - "function": "transferOwnership(address)", - "max_gas_used": 1051, - "min_gas_used": 626, - "severity": "Medium", - "sourceMap": null, - "swc-id": "111", - "title": "Use of tx.origin", - "tx_sequence": "" - } - ], - "success": true -} \ 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 deleted file mode 100644 index ec679550..00000000 --- a/tests/testdata/outputs_expected/origin.sol.o.jsonv2 +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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": "", - "testCase": "" - }, - "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/origin.sol.o.markdown b/tests/testdata/outputs_expected/origin.sol.o.markdown deleted file mode 100644 index 1f5f83ac..00000000 --- a/tests/testdata/outputs_expected/origin.sol.o.markdown +++ /dev/null @@ -1,15 +0,0 @@ -# Analysis results for test-filename.sol - -## Use of tx.origin -- SWC ID: 111 -- Severity: Medium -- Contract: Unknown -- Function name: `transferOwnership(address)` -- PC address: 317 -- Estimated Gas Usage: 626 - 1051 - -### Description - -Use of tx.origin is deprecated. -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. -See also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin diff --git a/tests/testdata/outputs_expected/origin.sol.o.text b/tests/testdata/outputs_expected/origin.sol.o.text deleted file mode 100644 index b7ebc992..00000000 --- a/tests/testdata/outputs_expected/origin.sol.o.text +++ /dev/null @@ -1,12 +0,0 @@ -==== Use of tx.origin ==== -SWC ID: 111 -Severity: Medium -Contract: Unknown -Function name: transferOwnership(address) -PC address: 317 -Estimated Gas Usage: 626 - 1051 -Use of tx.origin is deprecated. -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. -See also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin --------------------- - diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json deleted file mode 100644 index 16a2253b..00000000 --- a/tests/testdata/outputs_expected/overflow.sol.o.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 567, - "contract": "Unknown", - "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": 78155, - "min_gas_used": 17019, - "severity": "High", - "sourceMap": null, - "swc-id": "101", - "title": "Integer Underflow", - "tx_sequence": "" - }, - { - "address": 649, - "contract": "Unknown", - "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": 78155, - "min_gas_used": 17019, - "severity": "High", - "sourceMap": null, - "swc-id": "101", - "title": "Integer Underflow", - "tx_sequence": "" - }, - { - "address": 725, - "contract": "Unknown", - "description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.", - "function": "sendeth(address,uint256)", - "max_gas_used": 78155, - "min_gas_used": 17019, - "severity": "High", - "sourceMap": null, - "swc-id": "101", - "title": "Integer Overflow", - "tx_sequence": "" - } - ], - "success": true -} diff --git a/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 b/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 deleted file mode 100644 index 53028f4a..00000000 --- a/tests/testdata/outputs_expected/overflow.sol.o.jsonv2 +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "649:1:0" - } - ], - "severity": "High", - "swcID": "SWC-101", - "swcTitle": "Integer Overflow and Underflow" - }, - { - "description": { - "head": "The binary addition can overflow.", - "tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion." - }, - "extra": { - "discoveryTime": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "725:1:0" - } - ], - "severity": "High", - "swcID": "SWC-101", - "swcTitle": "Integer Overflow and Underflow" - } - ], - "meta": {}, - "sourceFormat": "evm-byzantium-bytecode", - "sourceList": [ - "0xf230bec502569e8b7e7737616d0ad0f200c436624e3c223e5398c0615cd2d6b9" - ], - "sourceType": "raw-bytecode" - } -] diff --git a/tests/testdata/outputs_expected/overflow.sol.o.markdown b/tests/testdata/outputs_expected/overflow.sol.o.markdown deleted file mode 100644 index 82642a1e..00000000 --- a/tests/testdata/outputs_expected/overflow.sol.o.markdown +++ /dev/null @@ -1,40 +0,0 @@ -# Analysis results for test-filename.sol - -## Integer Underflow -- SWC ID: 101 -- Severity: High -- Contract: Unknown -- Function name: `sendeth(address,uint256)` -- PC address: 567 -- Estimated Gas Usage: 17019 - 78155 - -### Description - -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. - -## Integer Underflow -- SWC ID: 101 -- Severity: High -- Contract: Unknown -- Function name: `sendeth(address,uint256)` -- PC address: 649 -- Estimated Gas Usage: 17019 - 78155 - -### Description - -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. - -## Integer Overflow -- SWC ID: 101 -- Severity: High -- Contract: Unknown -- Function name: `sendeth(address,uint256)` -- PC address: 725 -- Estimated Gas Usage: 17019 - 78155 - -### Description - -The binary addition can overflow. -The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. diff --git a/tests/testdata/outputs_expected/overflow.sol.o.text b/tests/testdata/outputs_expected/overflow.sol.o.text deleted file mode 100644 index e70dda5b..00000000 --- a/tests/testdata/outputs_expected/overflow.sol.o.text +++ /dev/null @@ -1,33 +0,0 @@ -==== Integer Underflow ==== -SWC ID: 101 -Severity: High -Contract: Unknown -Function name: sendeth(address,uint256) -PC address: 567 -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. --------------------- - -==== Integer Underflow ==== -SWC ID: 101 -Severity: High -Contract: Unknown -Function name: sendeth(address,uint256) -PC address: 649 -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. --------------------- - -==== Integer Overflow ==== -SWC ID: 101 -Severity: High -Contract: Unknown -Function name: sendeth(address,uint256) -PC address: 725 -Estimated Gas Usage: 17019 - 78155 -The binary addition can overflow. -The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. --------------------- - diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.json b/tests/testdata/outputs_expected/returnvalue.sol.o.json deleted file mode 100644 index bd7c8a97..00000000 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 196, - "contract": "Unknown", - "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": "callchecked()", - "max_gas_used": 1210, - "min_gas_used": 599, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "External Call To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 285, - "contract": "Unknown", - "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": "callnotchecked()", - "max_gas_used": 1232, - "min_gas_used": 621, - "severity": "Medium", - "sourceMap": null, - "swc-id": "107", - "title": "External Call To User-Supplied Address", - "tx_sequence": "" - }, - { - "address": 285, - "contract": "Unknown", - "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": "callnotchecked()", - "max_gas_used": 35950, - "min_gas_used": 1339, - "severity": "Low", - "sourceMap": null, - "swc-id": "104", - "title": "Unchecked Call Return Value", - "tx_sequence": "" - } - ], - "success": true -} \ 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 deleted file mode 100644 index 8e5bf428..00000000 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.jsonv2 +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "issues": [ - { - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "196:1:0" - } - ], - "severity": "Medium", - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "285: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": "", - "testCase": "" - }, - "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/returnvalue.sol.o.markdown b/tests/testdata/outputs_expected/returnvalue.sol.o.markdown deleted file mode 100644 index 5309f405..00000000 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.markdown +++ /dev/null @@ -1,40 +0,0 @@ -# Analysis results for test-filename.sol - -## External Call To User-Supplied Address -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `callchecked()` -- PC address: 196 -- Estimated Gas Usage: 599 - 1210 - -### Description - -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. - -## External Call To User-Supplied Address -- SWC ID: 107 -- Severity: Medium -- Contract: Unknown -- Function name: `callnotchecked()` -- PC address: 285 -- Estimated Gas Usage: 621 - 1232 - -### Description - -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. - -## Unchecked Call Return Value -- SWC ID: 104 -- Severity: Low -- Contract: Unknown -- Function name: `callnotchecked()` -- PC address: 285 -- Estimated Gas Usage: 1339 - 35950 - -### Description - -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/returnvalue.sol.o.text b/tests/testdata/outputs_expected/returnvalue.sol.o.text deleted file mode 100644 index baff23ea..00000000 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.text +++ /dev/null @@ -1,33 +0,0 @@ -==== External Call To User-Supplied Address ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: callchecked() -PC address: 196 -Estimated Gas Usage: 599 - 1210 -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. --------------------- - -==== External Call To User-Supplied Address ==== -SWC ID: 107 -Severity: Medium -Contract: Unknown -Function name: callnotchecked() -PC address: 285 -Estimated Gas Usage: 621 - 1232 -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. --------------------- - -==== Unchecked Call Return Value ==== -SWC ID: 104 -Severity: Low -Contract: Unknown -Function name: callnotchecked() -PC address: 285 -Estimated Gas Usage: 1339 - 35950 -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/suicide.sol.o.json b/tests/testdata/outputs_expected/suicide.sol.o.json deleted file mode 100644 index 1c98a444..00000000 --- a/tests/testdata/outputs_expected/suicide.sol.o.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 146, - "contract": "Unknown", - "description": "The contract can be killed by anyone.\nAnyone can kill this contract and withdraw its balance to an arbitrary address.", - "function": "kill(address)", - "max_gas_used": 263, - "min_gas_used": 168, - "severity": "High", - "sourceMap": null, - "swc-id": "106", - "title": "Unprotected Selfdestruct", - "tx_sequence": "" - } - ], - "success": true -} \ 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 deleted file mode 100644 index 30daf88a..00000000 --- a/tests/testdata/outputs_expected/suicide.sol.o.jsonv2 +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "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": "", - "testCase": "" - }, - "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/suicide.sol.o.markdown b/tests/testdata/outputs_expected/suicide.sol.o.markdown deleted file mode 100644 index f31b9f3f..00000000 --- a/tests/testdata/outputs_expected/suicide.sol.o.markdown +++ /dev/null @@ -1,14 +0,0 @@ -# Analysis results for test-filename.sol - -## Unprotected Selfdestruct -- SWC ID: 106 -- Severity: High -- Contract: Unknown -- Function name: `kill(address)` -- PC address: 146 -- Estimated Gas Usage: 168 - 263 - -### Description - -The contract can be killed by anyone. -Anyone can kill this contract and withdraw its balance to an arbitrary address. diff --git a/tests/testdata/outputs_expected/suicide.sol.o.text b/tests/testdata/outputs_expected/suicide.sol.o.text deleted file mode 100644 index 45dd0295..00000000 --- a/tests/testdata/outputs_expected/suicide.sol.o.text +++ /dev/null @@ -1,11 +0,0 @@ -==== Unprotected Selfdestruct ==== -SWC ID: 106 -Severity: High -Contract: Unknown -Function name: kill(address) -PC address: 146 -Estimated Gas Usage: 168 - 263 -The contract can be killed by anyone. -Anyone can kill this contract and withdraw its balance to an arbitrary address. --------------------- - diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json deleted file mode 100644 index 416d1176..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.o.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "error": null, - "issues": [ - { - "address": 567, - "contract": "Unknown", - "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": 52861, - "min_gas_used": 11915, - "severity": "High", - "sourceMap": null, - "swc-id": "101", - "title": "Integer Underflow", - "tx_sequence": "" - }, - { - "address": 649, - "contract": "Unknown", - "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": 52861, - "min_gas_used": 11915, - "severity": "High", - "sourceMap": null, - "swc-id": "101", - "title": "Integer Underflow", - "tx_sequence": "" - }, - { - "address": 725, - "contract": "Unknown", - "description": "The binary addition can overflow.\nThe operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion.", - "function": "sendeth(address,uint256)", - "max_gas_used": 52861, - "min_gas_used": 11915, - "severity": "High", - "sourceMap": null, - "swc-id": "101", - "title": "Integer Overflow", - "tx_sequence": "" - } - ], - "success": true -} diff --git a/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 b/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 deleted file mode 100644 index c99aae49..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.o.jsonv2 +++ /dev/null @@ -1,66 +0,0 @@ -[ - { - "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": "", - "testCase": "" - }, - "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": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "649:1:0" - } - ], - "severity": "High", - "swcID": "SWC-101", - "swcTitle": "Integer Overflow and Underflow" - }, - { - "description": { - "head": "The binary addition can overflow.", - "tail": "The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion." - }, - "extra": { - "discoveryTime": "", - "testCase": "" - }, - "locations": [ - { - "sourceMap": "725:1:0" - } - ], - "severity": "High", - "swcID": "SWC-101", - "swcTitle": "Integer Overflow and Underflow" - } - ], - "meta": {}, - "sourceFormat": "evm-byzantium-bytecode", - "sourceList": [ - "0xabef56740bf7795a9f8732e4781ebd27f2977f8a4997e3ff11cee79a4ba6c0ce" - ], - "sourceType": "raw-bytecode" - } -] diff --git a/tests/testdata/outputs_expected/underflow.sol.o.markdown b/tests/testdata/outputs_expected/underflow.sol.o.markdown deleted file mode 100644 index acc444d4..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.o.markdown +++ /dev/null @@ -1,40 +0,0 @@ -# Analysis results for test-filename.sol - -## Integer Underflow -- SWC ID: 101 -- Severity: High -- Contract: Unknown -- Function name: `sendeth(address,uint256)` -- PC address: 567 -- Estimated Gas Usage: 11915 - 52861 - -### Description - -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. - -## Integer Underflow -- SWC ID: 101 -- Severity: High -- Contract: Unknown -- Function name: `sendeth(address,uint256)` -- PC address: 649 -- Estimated Gas Usage: 11915 - 52861 - -### Description - -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. - -## Integer Overflow -- SWC ID: 101 -- Severity: High -- Contract: Unknown -- Function name: `sendeth(address,uint256)` -- PC address: 725 -- Estimated Gas Usage: 11915 - 52861 - -### Description - -The binary addition can overflow. -The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. diff --git a/tests/testdata/outputs_expected/underflow.sol.o.text b/tests/testdata/outputs_expected/underflow.sol.o.text deleted file mode 100644 index 498ff588..00000000 --- a/tests/testdata/outputs_expected/underflow.sol.o.text +++ /dev/null @@ -1,33 +0,0 @@ -==== Integer Underflow ==== -SWC ID: 101 -Severity: High -Contract: Unknown -Function name: sendeth(address,uint256) -PC address: 567 -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. --------------------- - -==== Integer Underflow ==== -SWC ID: 101 -Severity: High -Contract: Unknown -Function name: sendeth(address,uint256) -PC address: 649 -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. --------------------- - -==== Integer Overflow ==== -SWC ID: 101 -Severity: High -Contract: Unknown -Function name: sendeth(address,uint256) -PC address: 725 -Estimated Gas Usage: 11915 - 52861 -The binary addition can overflow. -The operands of the addition operation are not sufficiently constrained. The addition could therefore result in an integer overflow. Prevent the overflow by checking inputs or ensure sure that the overflow is caught by an assertion. --------------------- - From 19b4659be52add4000ce98b90bcf058bc8f8cdc0 Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 28 Jun 2019 14:39:58 -0700 Subject: [PATCH 17/28] Convert --solc-args parameter to --solc-json --- mythril/ethereum/util.py | 45 +++++++++++------------ mythril/interfaces/cli.py | 9 +++-- mythril/interfaces/old_cli.py | 7 ++-- mythril/mythril/mythril_disassembler.py | 12 ++++--- mythril/solidity/soliditycontract.py | 18 ++++++---- mythril/support/signatures.py | 47 +++++-------------------- 6 files changed, 59 insertions(+), 79 deletions(-) diff --git a/mythril/ethereum/util.py b/mythril/ethereum/util.py index 3e608b3a..da1c6a9b 100644 --- a/mythril/ethereum/util.py +++ b/mythril/ethereum/util.py @@ -24,41 +24,37 @@ def safe_decode(hex_encoded_string): return bytes.fromhex(hex_encoded_string) -def get_solc_json(file, solc_binary="solc", solc_args=None): +def get_solc_json(file, solc_binary="solc", solc_settings_json=None): """ :param file: :param solc_binary: - :param solc_args: + :param solc_settings_json: :return: """ + cmd = [solc_binary, "--standard-json", "--allow-paths", "."] - cmd = [solc_binary, "--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,ast"] - cmd = [solc_binary, "--standard-json", "bin,bin-runtime,srcmap,srcmap-runtime,ast"] - - if solc_args: - cmd.extend(solc_args.split()) - if not "--allow-paths" in cmd: - cmd.extend(["--allow-paths", "."]) - else: - for i, arg in enumerate(cmd): - if arg == "--allow-paths": - cmd[i + 1] += ",." - - cmd.append(file) - + settings = json.loads(solc_settings_json) if solc_settings_json else {} + settings.update( + { + "outputSelection": { + "*": { + "": ["ast"], + "*": [ + "metadata", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + ], + } + } + } + ) input_json = json.dumps( { "language": "Solidity", "sources": {file: {"urls": [file]}}, - "settings": { - "outputSelection": { - "*": { - "": ["ast"], - "*": ["metadata", "evm.bytecode", "evm.deployedBytecode"], - } - } - }, + "settings": settings, } ) @@ -67,6 +63,7 @@ def get_solc_json(file, solc_binary="solc", solc_args=None): stdout, stderr = p.communicate(bytes(input_json, "utf8")) ret = p.returncode + # TODO: check json.loads(out)['errors'] for fatal errors. if ret != 0: raise CompilerError( "Solc experienced a fatal error (code %d).\n\n%s" diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 5afa4bf2..4977eb24 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -165,7 +165,10 @@ def get_utilities_parser() -> ArgumentParser: :return: Parser which handles utility flags """ parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--solc-args", help="Extra arguments for solc") + parser.add_argument( + "--solc-json", + help="Json for the optional 'settings' parameter of solc's standard-json input", + ) parser.add_argument( "--solv", help="specify solidity compiler version. If not present, will try to install it (Experimental)", @@ -723,12 +726,12 @@ def parse_args_and_execute(parser: ArgumentParser, args: Namespace) -> None: config = set_config(args) leveldb_search(config, args) query_signature = args.__dict__.get("query_signature", None) - solc_args = args.__dict__.get("solc_args", None) + solc_json = args.__dict__.get("solc_json", None) solv = args.__dict__.get("solv", None) disassembler = MythrilDisassembler( eth=config.eth, solc_version=solv, - solc_args=solc_args, + solc_settings_json=solc_json, enable_online_lookup=query_signature, ) if args.command == "truffle": diff --git a/mythril/interfaces/old_cli.py b/mythril/interfaces/old_cli.py index 4c9d4222..19d1326a 100644 --- a/mythril/interfaces/old_cli.py +++ b/mythril/interfaces/old_cli.py @@ -230,7 +230,10 @@ def create_parser(parser: argparse.ArgumentParser) -> None: default=10, help="The amount of seconds to spend on " "the initial contract creation", ) - options.add_argument("--solc-args", help="Extra arguments for solc") + options.add_argument( + "--solc-json", + help="Json for the optional 'settings' parameter of solc's standard-json input", + ) options.add_argument( "--phrack", action="store_true", help="Phrack-style call graph" ) @@ -523,7 +526,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non disassembler = MythrilDisassembler( eth=config.eth, solc_version=args.solv, - solc_args=args.solc_args, + solc_json=args.solc_json, enable_online_lookup=args.query_signature, ) if args.truffle: diff --git a/mythril/mythril/mythril_disassembler.py b/mythril/mythril/mythril_disassembler.py index 5e1e72c3..0f5fbed1 100644 --- a/mythril/mythril/mythril_disassembler.py +++ b/mythril/mythril/mythril_disassembler.py @@ -30,11 +30,11 @@ class MythrilDisassembler: self, eth: Optional[EthJsonRpc] = None, solc_version: str = None, - solc_args: str = None, + solc_settings_json: str = None, enable_online_lookup: bool = False, ) -> None: self.solc_binary = self._init_solc_binary(solc_version) - self.solc_args = solc_args + self.solc_settings_json = solc_settings_json self.eth = eth self.enable_online_lookup = enable_online_lookup self.sigs = signatures.SignatureDB(enable_online_lookup=enable_online_lookup) @@ -163,13 +163,15 @@ class MythrilDisassembler: try: # import signatures from solidity source self.sigs.import_solidity_file( - file, solc_binary=self.solc_binary, solc_args=self.solc_args + file, + solc_binary=self.solc_binary, + solc_settings_json=self.solc_settings_json, ) if contract_name is not None: contract = SolidityContract( input_file=file, name=contract_name, - solc_args=self.solc_args, + solc_settings_json=self.solc_settings_json, solc_binary=self.solc_binary, ) self.contracts.append(contract) @@ -177,7 +179,7 @@ class MythrilDisassembler: else: for contract in get_contracts_from_file( input_file=file, - solc_args=self.solc_args, + solc_settings_json=self.solc_settings_json, solc_binary=self.solc_binary, ): self.contracts.append(contract) diff --git a/mythril/solidity/soliditycontract.py b/mythril/solidity/soliditycontract.py index 513916a7..6adbc9ea 100644 --- a/mythril/solidity/soliditycontract.py +++ b/mythril/solidity/soliditycontract.py @@ -44,14 +44,16 @@ class SourceCodeInfo: self.solc_mapping = mapping -def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"): +def get_contracts_from_file(input_file, solc_settings_json=None, solc_binary="solc"): """ :param input_file: - :param solc_args: + :param solc_settings_json: :param solc_binary: """ - data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) + data = get_solc_json( + input_file, solc_settings_json=solc_settings_json, solc_binary=solc_binary + ) try: for contractName in data["contracts"][input_file].keys(): @@ -63,7 +65,7 @@ def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"): yield SolidityContract( input_file=input_file, name=contractName, - solc_args=solc_args, + solc_settings_json=solc_settings_json, solc_binary=solc_binary, ) except KeyError: @@ -73,8 +75,12 @@ def get_contracts_from_file(input_file, solc_args=None, solc_binary="solc"): class SolidityContract(EVMContract): """Representation of a Solidity contract.""" - def __init__(self, input_file, name=None, solc_args=None, solc_binary="solc"): - data = get_solc_json(input_file, solc_args=solc_args, solc_binary=solc_binary) + def __init__( + self, input_file, name=None, solc_settings_json=None, solc_binary="solc" + ): + data = get_solc_json( + input_file, solc_settings_json=solc_settings_json, solc_binary=solc_binary + ) self.solidity_files = [] self.solc_json = data diff --git a/mythril/support/signatures.py b/mythril/support/signatures.py index e0deb9ea..67a1bf1d 100644 --- a/mythril/support/signatures.py +++ b/mythril/support/signatures.py @@ -1,5 +1,6 @@ """The Mythril function signature database.""" import functools +import json import logging import multiprocessing import os @@ -9,6 +10,7 @@ from collections import defaultdict from subprocess import PIPE, Popen from typing import List, Set, DefaultDict, Dict +from mythril.ethereum.util import get_solc_json from mythril.exceptions import CompilerError log = logging.getLogger(__name__) @@ -231,53 +233,20 @@ class SignatureDB(object, metaclass=Singleton): return [] def import_solidity_file( - self, file_path: str, solc_binary: str = "solc", solc_args: str = None + self, file_path: str, solc_binary: str = "solc", solc_settings_json: str = None ): """Import Function Signatures from solidity source files. :param solc_binary: - :param solc_args: + :param solc_settings_json: :param file_path: solidity source code file path :return: """ - cmd = [solc_binary, "--hashes", file_path] - if solc_args: - cmd.extend(solc_args.split()) + solc_json = get_solc_json(file_path, solc_binary, solc_settings_json) - try: - p = Popen(cmd, stdout=PIPE, stderr=PIPE) - stdout, stderr = p.communicate() - ret = p.returncode - - if ret != 0: - raise CompilerError( - "Solc has experienced a fatal error (code {}).\n\n{}".format( - ret, stderr.decode("utf-8") - ) - ) - except FileNotFoundError: - raise CompilerError( - ( - "Compiler not found. Make sure that solc is installed and in PATH, " - "or the SOLC environment variable is set." - ) - ) - - stdout = stdout.decode("unicode_escape").split("\n") - for line in stdout: - # the ':' need not be checked but just to be sure - if all(map(lambda x: x in line, ["(", ")", ":"])): - solc_bytes = "0x" + line.split(":")[0] - solc_text = line.split(":")[1].strip() - self.solidity_sigs[solc_bytes].append(solc_text) - log.debug( - "Signatures: found %d signatures after parsing" % len(self.solidity_sigs) - ) - - # update DB with what we've found - for byte_sig, text_sigs in self.solidity_sigs.items(): - for text_sig in text_sigs: - self.add(byte_sig, text_sig) + for contract in solc_json["contracts"][file_path].values(): + for name, hash in contract["evm"]["methodIdentifiers"].items(): + self.add("0x" + hash, name) @staticmethod def lookup_online(byte_sig: str, timeout: int, proxies=None) -> List[str]: From b044fcd9b197dc792dd5d3b091a0a00265488a8b Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Tue, 30 Jul 2019 13:51:58 +0200 Subject: [PATCH 18/28] Fix wrongly named constructor arg --- mythril/interfaces/old_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/interfaces/old_cli.py b/mythril/interfaces/old_cli.py index 4a203320..ce8c4392 100644 --- a/mythril/interfaces/old_cli.py +++ b/mythril/interfaces/old_cli.py @@ -527,7 +527,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non disassembler = MythrilDisassembler( eth=config.eth, solc_version=args.solv, - solc_json=args.solc_json, + solc__settings_json=args.solc_json, enable_online_lookup=args.query_signature, ) if args.truffle: From daadda18860b52fc7f13581eb8a4c0ecea425264 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Tue, 30 Jul 2019 14:36:43 +0200 Subject: [PATCH 19/28] Update old_cli.py --- mythril/interfaces/old_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/interfaces/old_cli.py b/mythril/interfaces/old_cli.py index ce8c4392..dbf1e798 100644 --- a/mythril/interfaces/old_cli.py +++ b/mythril/interfaces/old_cli.py @@ -527,7 +527,7 @@ def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Non disassembler = MythrilDisassembler( eth=config.eth, solc_version=args.solv, - solc__settings_json=args.solc_json, + solc_settings_json=args.solc_json, enable_online_lookup=args.query_signature, ) if args.truffle: From a22e2ba81fd46b6355a5589776d89c7ef9d0e7fd Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 5 Aug 2019 17:55:18 -0700 Subject: [PATCH 20/28] Fix error preventing contract name from being set --- mythril/solidity/soliditycontract.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mythril/solidity/soliditycontract.py b/mythril/solidity/soliditycontract.py index 6adbc9ea..534189d3 100644 --- a/mythril/solidity/soliditycontract.py +++ b/mythril/solidity/soliditycontract.py @@ -113,8 +113,11 @@ class SolidityContract(EVMContract): # If no contract name is specified, get the last bytecode entry for the input file else: - for filename, contract in sorted(data["contracts"][input_file].items()): + for contract_name, contract in sorted( + data["contracts"][input_file].items() + ): if len(contract["evm"]["deployedBytecode"]["object"]): + name = contract_name code = contract["evm"]["deployedBytecode"]["object"] creation_code = contract["evm"]["bytecode"]["object"] srcmap = contract["evm"]["deployedBytecode"]["sourceMap"].split(";") From c0b74d8187ca0501ac807e175f0100108a425fd9 Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 5 Aug 2019 18:23:08 -0700 Subject: [PATCH 21/28] Fix compilation error handling --- mythril/ethereum/util.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/mythril/ethereum/util.py b/mythril/ethereum/util.py index da1c6a9b..69905681 100644 --- a/mythril/ethereum/util.py +++ b/mythril/ethereum/util.py @@ -61,14 +61,7 @@ def get_solc_json(file, solc_binary="solc", solc_settings_json=None): try: p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate(bytes(input_json, "utf8")) - ret = p.returncode - # TODO: check json.loads(out)['errors'] for fatal errors. - if ret != 0: - raise CompilerError( - "Solc experienced a fatal error (code %d).\n\n%s" - % (ret, stderr.decode("UTF-8")) - ) except FileNotFoundError: raise CompilerError( "Compiler not found. Make sure that solc is installed and in PATH, or set the SOLC environment variable." @@ -76,10 +69,15 @@ def get_solc_json(file, solc_binary="solc", solc_settings_json=None): out = stdout.decode("UTF-8") - if not len(out): - raise CompilerError("Compilation failed.") + result = json.loads(out) + + for error in result["errors"]: + if error["severity"] == "error": + raise CompilerError( + "Solc experienced a fatal error.\n\n%s" % error["formattedMessage"] + ) - return json.loads(out) + return result def encode_calldata(func_name, arg_types, args): From 6f337f8bc7f0eaf14444b530b288e1f3c2e3b779 Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 5 Aug 2019 19:05:16 -0700 Subject: [PATCH 22/28] Document MythX analysis --- docs/source/index.rst | 1 + docs/source/mythx-analysis.rst | 63 +++++++++++++++++++++++++++++++ docs/source/security-analysis.rst | 2 +- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 docs/source/mythx-analysis.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index e3f9df42..77ff628d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,6 +9,7 @@ Welcome to Mythril's documentation! installation security-analysis analysis-modules + mythx-analysis mythril diff --git a/docs/source/mythx-analysis.rst b/docs/source/mythx-analysis.rst new file mode 100644 index 00000000..eddb0db9 --- /dev/null +++ b/docs/source/mythx-analysis.rst @@ -0,0 +1,63 @@ +MythX Analysis +================= + +Run :code:`myth pro` with one of the input options described below will run a `MythX analysis `_ on the desired input. This includes a run of Mythril, the fuzzer Harvey, and the static analysis engine Maru and has some false-positive filtering only possible by combining the tool capabilities. + +************** +Authentication +************** + +In order to authenticate with the MythX API, set the environment variables ``MYTHX_PASSWORD`` and ``MYTHX_ETH_ADDRESS``. + +.. code-block:: bash + + $ export MYTHX_ETH_ADDRESS='0x0000000000000000000000000000000000000000' + $ export MYTHX_PASSWORD='password' + +*********************** +Analyzing Solidity Code +*********************** + +The input format is the same as a regular Mythril analysis. + +.. code-block:: bash + + $ myth pro ether_send.sol + ==== Unprotected Ether Withdrawal ==== + SWC ID: 105 + Severity: High + Contract: Crowdfunding + Function name: withdrawfunds() + PC address: 730 + Anyone can withdraw ETH from the contract account. + 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. + -------------------- + In file: tests/testdata/input_contracts/ether_send.sol:21 + + msg.sender.transfer(address(this).balance) + + -------------------- + +If an input file contains multiple contract definitions, Mythril analyzes the *last* bytecode output produced by solc. You can override this by specifying the contract name explicitly: + +.. code-block:: bash + + myth pro OmiseGo.sol:OMGToken + +To specify a contract address, use :code:`-a
` + +**************************** +Analyzing On-Chain Contracts +**************************** + +Analyzing a mainnet contract via INFURA: + +.. code-block:: bash + + myth pro -a 0x5c436ff914c458983414019195e0f4ecbef9e6dd + +Adding the :code:`-l` flag will cause mythril to automatically retrieve dependencies, such as dynamically linked library contracts: + +.. code-block:: bash + + myth -v4 pro -l -a 0xEbFD99838cb0c132016B9E117563CB41f2B02264 diff --git a/docs/source/security-analysis.rst b/docs/source/security-analysis.rst index 2e276f70..26166d97 100644 --- a/docs/source/security-analysis.rst +++ b/docs/source/security-analysis.rst @@ -1,7 +1,7 @@ Security Analysis ================= -Run :code:`myth -x` with one of the input options described below will run the analysis modules in the `/analysis/modules `_ directory. +Run :code:`myth analyze` with one of the input options described below will run the analysis modules in the `/analysis/modules `_ directory. *********************** Analyzing Solidity Code From 29796dd8dae23bbead276160905a816eefe75720 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 6 Aug 2019 16:20:11 -0700 Subject: [PATCH 23/28] Fix bug compiling contracts without solc warnings --- mythril/ethereum/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mythril/ethereum/util.py b/mythril/ethereum/util.py index 69905681..7c9020f5 100644 --- a/mythril/ethereum/util.py +++ b/mythril/ethereum/util.py @@ -71,7 +71,7 @@ def get_solc_json(file, solc_binary="solc", solc_settings_json=None): result = json.loads(out) - for error in result["errors"]: + for error in result.get("errors", []): if error["severity"] == "error": raise CompilerError( "Solc experienced a fatal error.\n\n%s" % error["formattedMessage"] From 9f04fa0c8a7e8bc16962e8303ac3a463adf6632c Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 30 Aug 2019 11:02:35 -0400 Subject: [PATCH 24/28] Fixes for latest pythx version --- mythril/laser/ethereum/util.py | 4 ++-- mythril/mythx/__init__.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mythril/laser/ethereum/util.py b/mythril/laser/ethereum/util.py index 9cb5d950..f0b42794 100644 --- a/mythril/laser/ethereum/util.py +++ b/mythril/laser/ethereum/util.py @@ -45,8 +45,8 @@ def get_instruction_index( """ index = 0 for instr in instruction_list: - if instr["address"] == address: - return index + if instr["address"] > address: + return index - 1 index += 1 return None diff --git a/mythril/mythx/__init__.py b/mythril/mythx/__init__.py index 631e8ba8..c5602459 100644 --- a/mythril/mythx/__init__.py +++ b/mythril/mythx/__init__.py @@ -75,7 +75,7 @@ def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> issue = Issue( contract=contract.name, function_name=None, - address=int(issue.locations[0].source_map.split(":")[0]), + address=issue.locations[0].source_map.components[0].offset, swc_id=issue.swc_id[4:], # remove 'SWC-' prefix title=issue.swc_title, bytecode=contract.creation_code, diff --git a/requirements.txt b/requirements.txt index 7e2032d8..3d96c2b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ py-solc pytest>=3.6.0 pytest-cov pytest_mock -requests +requests>=2.22.0 rlp>=1.0.1 transaction>=2.2.1 z3-solver>=4.8.5.0 diff --git a/setup.py b/setup.py index 969823b1..2c0662b9 100755 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ REQUIRED = [ "py_ecc==1.4.2", "ethereum>=2.3.2", "z3-solver>=4.8.5.0", - "requests", + "requests>=2.22.0", "py-solc", "plyvel", "eth_abi==1.3.0", From 134627e37e1582e2fb2f7fec30870ef7af7df5b4 Mon Sep 17 00:00:00 2001 From: Nathan Date: Fri, 30 Aug 2019 11:33:02 -0400 Subject: [PATCH 25/28] Fix up some missed bugs --- mythril/interfaces/cli.py | 10 ++++++++-- mythril/laser/ethereum/util.py | 4 ++-- mythril/mythx/__init__.py | 6 +++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mythril/interfaces/cli.py b/mythril/interfaces/cli.py index 6743e008..1501120f 100644 --- a/mythril/interfaces/cli.py +++ b/mythril/interfaces/cli.py @@ -218,8 +218,9 @@ def main() -> None: pro_parser = subparsers.add_parser( PRO_LIST[0], help="Analyzes input with the MythX API (https://mythx.io)", - aliases=PRO_LIST[1], + aliases=PRO_LIST[1:], parents=[utilities_parser, creation_input_parser, output_parser], + formatter_class=RawTextHelpFormatter, ) create_pro_parser(pro_parser) @@ -276,12 +277,17 @@ def create_pro_parser(parser: ArgumentParser): :param parser: :return: """ + parser.add_argument( + "solidity_files", + nargs="*", + help="Inputs file name and contract name. \n" + "usage: file1.sol:OptionalContractName file2.sol file3.sol:OptionalContractName", + ) parser.add_argument( "--full", help="Run a full analysis. Default: quick analysis", action="store_true", ) - parser.add_argument("solidity_file", nargs="*") def create_read_storage_parser(read_storage_parser: ArgumentParser): diff --git a/mythril/laser/ethereum/util.py b/mythril/laser/ethereum/util.py index 52b8cf94..ff544559 100644 --- a/mythril/laser/ethereum/util.py +++ b/mythril/laser/ethereum/util.py @@ -45,8 +45,8 @@ def get_instruction_index( """ index = 0 for instr in instruction_list: - if instr["address"] > address: - return index - 1 + if instr["address"] >= address: + return index index += 1 return None diff --git a/mythril/mythx/__init__.py b/mythril/mythx/__init__.py index c5602459..ae105470 100644 --- a/mythril/mythx/__init__.py +++ b/mythril/mythx/__init__.py @@ -8,6 +8,10 @@ from mythril.solidity.soliditycontract import SolidityContract from pythx import Client +import logging + +log = logging.getLogger(__name__) + def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> Report: """ @@ -68,7 +72,7 @@ def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> ) while not c.analysis_ready(resp.uuid): - print(c.status(resp.uuid).analysis) + log.info(c.status(resp.uuid).analysis) time.sleep(5) for issue in c.report(resp.uuid): From 74cabd3d3cf3c0d5e579cac15c4ce7b53c26f46e Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 4 Sep 2019 10:41:34 -0400 Subject: [PATCH 26/28] Fix issue reporting for trial analysis --- mythril/mythx/__init__.py | 6 ++++-- mythril/solidity/soliditycontract.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mythril/mythx/__init__.py b/mythril/mythx/__init__.py index ae105470..3f2b9616 100644 --- a/mythril/mythx/__init__.py +++ b/mythril/mythx/__init__.py @@ -79,8 +79,10 @@ def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> issue = Issue( contract=contract.name, function_name=None, - address=issue.locations[0].source_map.components[0].offset, - swc_id=issue.swc_id[4:], # remove 'SWC-' prefix + address=issue.locations[0].source_map.components[0].offset + if issue.locations + else -1, + swc_id=issue.swc_id[4:] or "None", # remove 'SWC-' prefix title=issue.swc_title, bytecode=contract.creation_code, severity=issue.severity.capitalize(), diff --git a/mythril/solidity/soliditycontract.py b/mythril/solidity/soliditycontract.py index 534189d3..772141c8 100644 --- a/mythril/solidity/soliditycontract.py +++ b/mythril/solidity/soliditycontract.py @@ -56,15 +56,15 @@ def get_contracts_from_file(input_file, solc_settings_json=None, solc_binary="so ) try: - for contractName in data["contracts"][input_file].keys(): + for contract_name in data["contracts"][input_file].keys(): if len( - data["contracts"][input_file][contractName]["evm"]["deployedBytecode"][ + data["contracts"][input_file][contract_name]["evm"]["deployedBytecode"][ "object" ] ): yield SolidityContract( input_file=input_file, - name=contractName, + name=contract_name, solc_settings_json=solc_settings_json, solc_binary=solc_binary, ) From 389a2cc810da3452474f610f2f45e20a3e969764 Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 9 Sep 2019 10:06:14 -0400 Subject: [PATCH 27/28] Add trial mode messaging --- mythril/mythx/__init__.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/mythril/mythx/__init__.py b/mythril/mythx/__init__.py index 3f2b9616..80d60e7d 100644 --- a/mythril/mythx/__init__.py +++ b/mythril/mythx/__init__.py @@ -1,6 +1,9 @@ +import sys + import os import time +from mythx_models.exceptions import MythXAPIError from typing import List, Dict, Any from mythril.analysis.report import Issue, Report @@ -30,6 +33,11 @@ def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> password=os.environ.get("MYTHX_PASSWORD", "trial"), ) + if c.eth_address == "0x0000000000000000000000000000000000000000": + print( + "You are currently running MythX in Trial mode. This mode reports only a partial analysis of your smart contracts, limited to three vulnerabilities. To get a more complete analysis, sign up for a free account at https://mythx.io." + ) + issues = [] # type: List[Issue] # TODO: Analyze multiple contracts asynchronously. @@ -61,15 +69,18 @@ def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> pass assert contract.creation_code, "Creation bytecode must exist." - resp = c.analyze( - contract_name=contract.name, - analysis_mode=analysis_mode, - bytecode=contract.creation_code or None, - deployed_bytecode=contract.code or None, - sources=sources or None, - main_source=main_source, - source_list=source_list or None, - ) + try: + resp = c.analyze( + contract_name=contract.name, + analysis_mode=analysis_mode, + bytecode=contract.creation_code or None, + deployed_bytecode=contract.code or None, + sources=sources or None, + main_source=main_source, + source_list=source_list or None, + ) + except MythXAPIError as e: + log.critical(e) while not c.analysis_ready(resp.uuid): log.info(c.status(resp.uuid).analysis) From ba8cb741ca44e9fa4f19a2798fc44bacf783bd49 Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 9 Sep 2019 10:23:06 -0400 Subject: [PATCH 28/28] Add variables for trial credentials --- mythril/mythx/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mythril/mythx/__init__.py b/mythril/mythx/__init__.py index 80d60e7d..f1ebede0 100644 --- a/mythril/mythx/__init__.py +++ b/mythril/mythx/__init__.py @@ -15,6 +15,9 @@ import logging log = logging.getLogger(__name__) +TRIAL_ETH_ADDRESS = "0x0000000000000000000000000000000000000000" +TRIAL_PASSWORD = "trial" + def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> Report: """ @@ -27,13 +30,11 @@ def analyze(contracts: List[SolidityContract], analysis_mode: str = "quick") -> assert analysis_mode in ("quick", "full"), "analysis_mode must be 'quick' or 'full'" c = Client( - eth_address=os.environ.get( - "MYTHX_ETH_ADDRESS", "0x0000000000000000000000000000000000000000" - ), - password=os.environ.get("MYTHX_PASSWORD", "trial"), + eth_address=os.environ.get("MYTHX_ETH_ADDRESS", TRIAL_ETH_ADDRESS), + password=os.environ.get("MYTHX_PASSWORD", TRIAL_PASSWORD), ) - if c.eth_address == "0x0000000000000000000000000000000000000000": + if c.eth_address == TRIAL_ETH_ADDRESS: print( "You are currently running MythX in Trial mode. This mode reports only a partial analysis of your smart contracts, limited to three vulnerabilities. To get a more complete analysis, sign up for a free account at https://mythx.io." )