From 4efe4b2fb573522b63e069ce34633acbe1e5ed65 Mon Sep 17 00:00:00 2001 From: David Pokora Date: Tue, 28 May 2019 21:17:59 -0400 Subject: [PATCH 01/47] -stdout/stderr redirect to capture output and output in JSON results -JSON result output cleanup --- slither/__main__.py | 80 ++++++++++++++------------------ slither/utils/output_redirect.py | 67 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 44 deletions(-) create mode 100644 slither/utils/output_redirect.py diff --git a/slither/__main__.py b/slither/__main__.py index 5a0563fa3..38b5c25ef 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -19,6 +19,7 @@ from slither.detectors.abstract_detector import (AbstractDetector, from slither.printers import all_printers from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither +from slither.utils.output_redirect import StandardOutputRedirect from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_results_to_markdown, output_detectors_json, output_printers, @@ -100,27 +101,16 @@ def process_files(filenames, args, detector_classes, printer_classes): ################################################################################### -def wrap_json_detectors_results(success, error_message, results=None): - """ - Wrap the detector results. - :param success: - :param error_message: - :param results: - :return: - """ - results_json = {} - if results: - results_json['detectors'] = results - return { - "success": success, - "error": error_message, - "results": results_json +def output_json(filename, error, results): + # Create our encapsulated JSON result. + json_result = { + "success": error is None, + "error": error, + "results": results } - -def output_json(results, filename): - json_result = wrap_json_detectors_results(True, None, results) - if filename is None: + # Determine if our filename is referring to stdout + if filename == "-": # Write json to console print(json.dumps(json_result)) else: @@ -350,13 +340,11 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=defaults_flag_in_config['exclude_high']) - group_misc.add_argument('--json', help='Export the results as a JSON file ("--json -" to export to stdout)', action='store', default=defaults_flag_in_config['json']) - group_misc.add_argument('--disable-color', help='Disable output colorization', action='store_true', @@ -396,7 +384,6 @@ def parse_args(detector_classes, printer_classes): action=OutputMarkdown, default=False) - group_misc.add_argument('--checklist', help=argparse.SUPPRESS, action='store_true', @@ -524,10 +511,12 @@ def main_impl(all_detector_classes, all_printer_classes): # Set colorization option set_colorization_enabled(not args.disable_color) - # If we are outputting json to stdout, we'll want to disable any logging. - stdout_json = args.json == "-" - if stdout_json: - logging.disable(logging.CRITICAL) + # If we are outputting json to stdout, we'll want to define some variables and redirect stdout + output_error = None + json_results = {} + outputting_json = args.json is not None + if outputting_json: + StandardOutputRedirect.enable() printer_classes = choose_printers(args, all_printer_classes) detector_classes = choose_detectors(args, all_detector_classes) @@ -586,8 +575,8 @@ def main_impl(all_detector_classes, all_printer_classes): else: raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) - if args.json: - output_json(results, None if stdout_json else args.json) + if args.json and results: + json_results['detectors'] = results if args.checklist: output_results_to_markdown(results) # Dont print the number of result for printers @@ -599,27 +588,30 @@ def main_impl(all_detector_classes, all_printer_classes): logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) if args.ignore_return_value: return - exit(results) except SlitherException as se: - # Output our error accordingly, via JSON or logging. - if stdout_json: - print(json.dumps(wrap_json_detectors_results(False, str(se), []))) - else: - logging.error(red('Error:')) - logging.error(red(se)) - logging.error('Please report an issue to https://github.com/crytic/slither/issues') - sys.exit(-1) + output_error = str(se) + logging.error(red('Error:')) + logging.error(red(output_error)) + logging.error('Please report an issue to https://github.com/crytic/slither/issues') except Exception: - # Output our error accordingly, via JSON or logging. - if stdout_json: - print(json.dumps(wrap_json_detectors_results(False, traceback.format_exc(), []))) - else: - logging.error('Error in %s' % args.filename) - logging.error(traceback.format_exc()) + output_error = traceback.format_exc() + logging.error('Error in %s' % args.filename) + logging.error(output_error) + + # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON. + if outputting_json: + json_results['stdout'] = StandardOutputRedirect.get_stdout_output() + json_results['stderr'] = StandardOutputRedirect.get_stderr_output() + StandardOutputRedirect.disable() + output_json(args.json, output_error, json_results) + + # Exit with the appropriate status code + if output_error: sys.exit(-1) - + else: + exit(results) if __name__ == '__main__': diff --git a/slither/utils/output_redirect.py b/slither/utils/output_redirect.py new file mode 100644 index 000000000..9811b21b2 --- /dev/null +++ b/slither/utils/output_redirect.py @@ -0,0 +1,67 @@ +import io +import logging +import sys + + +class StandardOutputRedirect: + """ + Redirects and captures standard output/errors. + """ + original_stdout = None + original_stderr = None + + @staticmethod + def enable(): + """ + Redirects stdout and/or stderr to a captureable StringIO. + :param redirect_stdout: True if redirection is desired for stdout. + :param redirect_stderr: True if redirection is desired for stderr. + :return: None + """ + # Redirect stdout + if StandardOutputRedirect.original_stdout is None: + StandardOutputRedirect.original_stdout = sys.stdout + sys.stdout = io.StringIO() + + # Redirect stderr + if StandardOutputRedirect.original_stderr is None: + StandardOutputRedirect.original_stderr = sys.stderr + sys.stderr = io.StringIO() + root_logger = logging.getLogger() + root_logger.handlers = [logging.StreamHandler(sys.stderr)] + + @staticmethod + def disable(): + """ + Disables redirection of stdout/stderr, if previously enabled. + :return: None + """ + # If we have a stdout backup, restore it. + if StandardOutputRedirect.original_stdout is not None: + sys.stdout.close() + sys.stdout = StandardOutputRedirect.original_stdout + StandardOutputRedirect.original_stdout = None + + # If we have an stderr backup, restore it. + if StandardOutputRedirect.original_stderr is not None: + sys.stderr.close() + sys.stderr = StandardOutputRedirect.original_stderr + StandardOutputRedirect.original_stderr = None + + @staticmethod + def get_stdout_output(): + """ + Obtains the output from stdout + :return: Returns stdout output as a string + """ + sys.stdout.seek(0) + return sys.stdout.read() + + @staticmethod + def get_stderr_output(): + """ + Obtains the output from stdout + :return: Returns stdout output as a string + """ + sys.stderr.seek(0) + return sys.stderr.read() \ No newline at end of file From 53a3bee4de2100b8e8826380d32a8e88701f51cd Mon Sep 17 00:00:00 2001 From: David Pokora Date: Wed, 29 May 2019 15:56:27 -0400 Subject: [PATCH 02/47] Updated standard output capturing to optionally mirror to stdout/stderr (needed for outputting JSON to file, which retains output to console as well). --- slither/__main__.py | 24 +++++---- slither/utils/output_capture.py | 90 ++++++++++++++++++++++++++++++++ slither/utils/output_redirect.py | 67 ------------------------ 3 files changed, 104 insertions(+), 77 deletions(-) create mode 100644 slither/utils/output_capture.py delete mode 100644 slither/utils/output_redirect.py diff --git a/slither/__main__.py b/slither/__main__.py index 38b5c25ef..7dbec0077 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -19,7 +19,7 @@ from slither.detectors.abstract_detector import (AbstractDetector, from slither.printers import all_printers from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither -from slither.utils.output_redirect import StandardOutputRedirect +from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_results_to_markdown, output_detectors_json, output_printers, @@ -109,8 +109,8 @@ def output_json(filename, error, results): "results": results } - # Determine if our filename is referring to stdout - if filename == "-": + # Determine if we should output to stdout + if filename is None: # Write json to console print(json.dumps(json_result)) else: @@ -511,12 +511,16 @@ def main_impl(all_detector_classes, all_printer_classes): # Set colorization option set_colorization_enabled(not args.disable_color) - # If we are outputting json to stdout, we'll want to define some variables and redirect stdout - output_error = None + # Define some variables for potential JSON output json_results = {} + output_error = None outputting_json = args.json is not None + outputting_json_stdout = args.json == '-' + + # If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout + # output. if outputting_json: - StandardOutputRedirect.enable() + StandardOutputCapture.enable(outputting_json_stdout) printer_classes = choose_printers(args, all_printer_classes) detector_classes = choose_detectors(args, all_detector_classes) @@ -602,10 +606,10 @@ def main_impl(all_detector_classes, all_printer_classes): # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON. if outputting_json: - json_results['stdout'] = StandardOutputRedirect.get_stdout_output() - json_results['stderr'] = StandardOutputRedirect.get_stderr_output() - StandardOutputRedirect.disable() - output_json(args.json, output_error, json_results) + json_results['stdout'] = StandardOutputCapture.get_stdout_output() + json_results['stderr'] = StandardOutputCapture.get_stderr_output() + StandardOutputCapture.disable() + output_json(None if outputting_json_stdout else args.json, output_error, json_results) # Exit with the appropriate status code if output_error: diff --git a/slither/utils/output_capture.py b/slither/utils/output_capture.py new file mode 100644 index 000000000..d557ede1e --- /dev/null +++ b/slither/utils/output_capture.py @@ -0,0 +1,90 @@ +import io +import logging +import sys + + +class CapturingStringIO(io.StringIO): + """ + I/O implementation which captures output, and optionally mirrors it to the original I/O stream it replaces. + """ + def __init__(self, original_io=None): + super(CapturingStringIO, self).__init__() + self.original_io = original_io + + def write(self, s): + super().write(s) + if self.original_io: + self.original_io.write(s) + + +class StandardOutputCapture: + """ + Redirects and captures standard output/errors. + """ + original_stdout = None + original_stderr = None + original_logger_handlers = None + + @staticmethod + def enable(block_original=True): + """ + Redirects stdout and stderr to a capturable StringIO. + :param block_original: If True, blocks all output to the original stream. If False, duplicates output. + :return: None + """ + # Redirect stdout + if StandardOutputCapture.original_stdout is None: + StandardOutputCapture.original_stdout = sys.stdout + sys.stdout = CapturingStringIO(None if block_original else StandardOutputCapture.original_stdout) + + # Redirect stderr + if StandardOutputCapture.original_stderr is None: + StandardOutputCapture.original_stderr = sys.stderr + sys.stderr = CapturingStringIO(None if block_original else StandardOutputCapture.original_stderr) + + # Backup and swap root logger handlers + root_logger = logging.getLogger() + StandardOutputCapture.original_logger_handlers = root_logger.handlers + root_logger.handlers = [logging.StreamHandler(sys.stderr)] + + @staticmethod + def disable(): + """ + Disables redirection of stdout/stderr, if previously enabled. + :return: None + """ + # If we have a stdout backup, restore it. + if StandardOutputCapture.original_stdout is not None: + sys.stdout.close() + sys.stdout = StandardOutputCapture.original_stdout + StandardOutputCapture.original_stdout = None + + # If we have an stderr backup, restore it. + if StandardOutputCapture.original_stderr is not None: + sys.stderr.close() + sys.stderr = StandardOutputCapture.original_stderr + StandardOutputCapture.original_stderr = None + + # Restore our logging handlers + if StandardOutputCapture.original_logger_handlers is not None: + root_logger = logging.getLogger() + root_logger.handlers = StandardOutputCapture.original_logger_handlers + StandardOutputCapture.original_logger_handlers = None + + @staticmethod + def get_stdout_output(): + """ + Obtains the output from the currently set stdout + :return: Returns stdout output as a string + """ + sys.stdout.seek(0) + return sys.stdout.read() + + @staticmethod + def get_stderr_output(): + """ + Obtains the output from the currently set stderr + :return: Returns stderr output as a string + """ + sys.stderr.seek(0) + return sys.stderr.read() diff --git a/slither/utils/output_redirect.py b/slither/utils/output_redirect.py deleted file mode 100644 index 9811b21b2..000000000 --- a/slither/utils/output_redirect.py +++ /dev/null @@ -1,67 +0,0 @@ -import io -import logging -import sys - - -class StandardOutputRedirect: - """ - Redirects and captures standard output/errors. - """ - original_stdout = None - original_stderr = None - - @staticmethod - def enable(): - """ - Redirects stdout and/or stderr to a captureable StringIO. - :param redirect_stdout: True if redirection is desired for stdout. - :param redirect_stderr: True if redirection is desired for stderr. - :return: None - """ - # Redirect stdout - if StandardOutputRedirect.original_stdout is None: - StandardOutputRedirect.original_stdout = sys.stdout - sys.stdout = io.StringIO() - - # Redirect stderr - if StandardOutputRedirect.original_stderr is None: - StandardOutputRedirect.original_stderr = sys.stderr - sys.stderr = io.StringIO() - root_logger = logging.getLogger() - root_logger.handlers = [logging.StreamHandler(sys.stderr)] - - @staticmethod - def disable(): - """ - Disables redirection of stdout/stderr, if previously enabled. - :return: None - """ - # If we have a stdout backup, restore it. - if StandardOutputRedirect.original_stdout is not None: - sys.stdout.close() - sys.stdout = StandardOutputRedirect.original_stdout - StandardOutputRedirect.original_stdout = None - - # If we have an stderr backup, restore it. - if StandardOutputRedirect.original_stderr is not None: - sys.stderr.close() - sys.stderr = StandardOutputRedirect.original_stderr - StandardOutputRedirect.original_stderr = None - - @staticmethod - def get_stdout_output(): - """ - Obtains the output from stdout - :return: Returns stdout output as a string - """ - sys.stdout.seek(0) - return sys.stdout.read() - - @staticmethod - def get_stderr_output(): - """ - Obtains the output from stdout - :return: Returns stdout output as a string - """ - sys.stderr.seek(0) - return sys.stderr.read() \ No newline at end of file From e85848b9988e183800adc460c53c70e0935e0eb6 Mon Sep 17 00:00:00 2001 From: David Pokora Date: Thu, 30 May 2019 16:11:12 -0400 Subject: [PATCH 03/47] -Added --json-types command line argument to individually specify result output options. -Console output in JSON is now optional via --json-types -Detector types can be listed via --json-types now (retained --list-detectors-json for compatibility, to be deprecated later). --- slither/__main__.py | 39 +++++++++++++++++++++++++++++------ slither/utils/command_line.py | 5 +++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 7dbec0077..73433d6f0 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -22,7 +22,7 @@ from slither.slither import Slither from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_results_to_markdown, - output_detectors_json, output_printers, + get_detector_types_json, output_printers, output_to_markdown, output_wiki) from crytic_compile import is_supported from slither.exceptions import SlitherException @@ -30,6 +30,8 @@ from slither.exceptions import SlitherException logging.basicConfig() logger = logging.getLogger("Slither") +JSON_OUTPUT_TYPES = ["console", "detectors", "detector-types"] + ################################################################################### ################################################################################### # region Process functions @@ -256,6 +258,7 @@ defaults_flag_in_config = { 'solc_args': None, 'disable_solc_warnings': False, 'json': None, + 'json-types': ','.join(JSON_OUTPUT_TYPES), 'truffle_version': None, 'disable_color': False, 'filter_paths': None, @@ -345,6 +348,13 @@ def parse_args(detector_classes, printer_classes): action='store', default=defaults_flag_in_config['json']) + group_misc.add_argument('--json-types', + help='Comma-separated list of result types to output to JSON, defaults to all, ' + 'available types: {}'.format( + ', '.join(output_type for output_type in JSON_OUTPUT_TYPES)), + action='store', + default=defaults_flag_in_config['json-types']) + group_misc.add_argument('--disable-color', help='Disable output colorization', action='store_true', @@ -435,6 +445,12 @@ def parse_args(detector_classes, printer_classes): except json.decoder.JSONDecodeError as e: logger.error(red('Impossible to read {}, please check the file {}'.format(args.config_file, e))) + # Verify our json-type output is valid + args.json_types = set(args.json_types.split(',')) + for json_type in args.json_types: + if json_type not in JSON_OUTPUT_TYPES: + raise Exception(f"Error: \"{json_type}\" is not a valid JSON result output type.") + return args class ListDetectors(argparse.Action): @@ -446,7 +462,8 @@ class ListDetectors(argparse.Action): class ListDetectorsJson(argparse.Action): def __call__(self, parser, *args, **kwargs): detectors, _ = get_detectors_and_printers() - output_detectors_json(detectors) + detector_types_json = get_detector_types_json(detectors) + print(json.dumps(detector_types_json)) parser.exit() class ListPrinters(argparse.Action): @@ -579,8 +596,17 @@ def main_impl(all_detector_classes, all_printer_classes): else: raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) - if args.json and results: - json_results['detectors'] = results + # Determine if we are outputting JSON + if outputting_json: + # Add our detector results to JSON if desired. + if results and 'detectors' in args.json_types: + json_results['detectors'] = results + + # Add our detector types to JSON + if 'detector-types' in args.json_types: + detectors, _ = get_detectors_and_printers() + json_results['detector-types'] = get_detector_types_json(detectors) + if args.checklist: output_results_to_markdown(results) # Dont print the number of result for printers @@ -606,8 +632,9 @@ def main_impl(all_detector_classes, all_printer_classes): # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON. if outputting_json: - json_results['stdout'] = StandardOutputCapture.get_stdout_output() - json_results['stderr'] = StandardOutputCapture.get_stderr_output() + if 'console' in args.json_types: + json_results['stdout'] = StandardOutputCapture.get_stdout_output() + json_results['stderr'] = StandardOutputCapture.get_stderr_output() StandardOutputCapture.disable() output_json(None if outputting_json_stdout else args.json, output_error, json_results) diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index d1a495a23..531181c4a 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -155,7 +155,8 @@ def output_detectors(detector_classes): idx = idx + 1 print(table) -def output_detectors_json(detector_classes): + +def get_detector_types_json(detector_classes): detectors_list = [] for detector in detector_classes: argument = detector.ARGUMENT @@ -193,7 +194,7 @@ def output_detectors_json(detector_classes): 'exploit_scenario':exploit, 'recommendation':recommendation}) idx = idx + 1 - print(json.dumps(table)) + return table def output_printers(printer_classes): printers_list = [] From f526a74071976125a233bdd9daf4bf1ff433760c Mon Sep 17 00:00:00 2001 From: David Pokora Date: Thu, 30 May 2019 18:57:07 -0400 Subject: [PATCH 04/47] Added json result types for compilation (ast, bytecode, srcmap, filenames, etc). --- slither/__main__.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 73433d6f0..ea386504e 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -30,7 +30,7 @@ from slither.exceptions import SlitherException logging.basicConfig() logger = logging.getLogger("Slither") -JSON_OUTPUT_TYPES = ["console", "detectors", "detector-types"] +JSON_OUTPUT_TYPES = ["compilation", "console", "detectors", "detector-types"] ################################################################################### ################################################################################### @@ -75,7 +75,7 @@ def _process(slither, detector_classes, printer_classes): slither.run_printers() # Currently printers does not return results - return results, analyzed_contracts_count + return slither, results, analyzed_contracts_count def process_files(filenames, args, detector_classes, printer_classes): @@ -576,7 +576,7 @@ def main_impl(all_detector_classes, all_printer_classes): globbed_filenames = glob.glob(filename, recursive=True) if os.path.isfile(filename) or is_supported(filename): - (results, number_contracts) = process(filename, args, detector_classes, printer_classes) + (slither, results, number_contracts) = process(filename, args, detector_classes, printer_classes) elif os.path.isdir(filename) or len(globbed_filenames) > 0: extension = "*.sol" if not args.solc_ast else "*.json" @@ -586,10 +586,10 @@ def main_impl(all_detector_classes, all_printer_classes): number_contracts = 0 results = [] if args.splitted and args.solc_ast: - (results, number_contracts) = process_files(filenames, args, detector_classes, printer_classes) + (slither, results, number_contracts) = process_files(filenames, args, detector_classes, printer_classes) else: for filename in filenames: - (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) + (slither, results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) number_contracts += number_contracts_tmp results += results_tmp @@ -598,6 +598,21 @@ def main_impl(all_detector_classes, all_printer_classes): # Determine if we are outputting JSON if outputting_json: + # Add our compilation information to JSON + if 'compilation' in args.json_types: + json_results['compilation'] = { + "abis": slither.crytic_compile.abis, + "asts": slither.crytic_compile.asts, + "bytecodes_init": slither.crytic_compile.bytecodes_init, + "bytecodes_runtime": slither.crytic_compile.bytecodes_runtime, + "compiler_version": slither.crytic_compile.compiler_version, + "contracts_filenames": { key: value._asdict() for key, value in slither.crytic_compile.contracts_filenames.items()}, + "filenames": [x._asdict() for x in slither.crytic_compile.filenames], + "srcmaps_init": slither.crytic_compile.srcmaps_init, + "srcmaps_runtime": slither.crytic_compile.srcmaps_runtime, + "type": str(slither.crytic_compile.type) + } + # Add our detector results to JSON if desired. if results and 'detectors' in args.json_types: json_results['detectors'] = results @@ -607,8 +622,10 @@ def main_impl(all_detector_classes, all_printer_classes): detectors, _ = get_detectors_and_printers() json_results['detector-types'] = get_detector_types_json(detectors) + # Output our results to markdown if we wish to compile a checklist. if args.checklist: output_results_to_markdown(results) + # Dont print the number of result for printers if number_contracts == 0: logger.warn(red('No contract was analyzed')) @@ -633,8 +650,10 @@ def main_impl(all_detector_classes, all_printer_classes): # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON. if outputting_json: if 'console' in args.json_types: - json_results['stdout'] = StandardOutputCapture.get_stdout_output() - json_results['stderr'] = StandardOutputCapture.get_stderr_output() + json_results['console'] = { + 'stdout': StandardOutputCapture.get_stdout_output(), + 'stderr': StandardOutputCapture.get_stderr_output() + } StandardOutputCapture.disable() output_json(None if outputting_json_stdout else args.json, output_error, json_results) From 5d88be8328522efe84d3c0c13803d9366de2b44c Mon Sep 17 00:00:00 2001 From: Eric Rafaloff Date: Wed, 12 Jun 2019 11:26:04 -0400 Subject: [PATCH 05/47] Detect Aragon OS --- slither/utils/standard_libraries.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/slither/utils/standard_libraries.py b/slither/utils/standard_libraries.py index c73a29b93..8ae3e5978 100644 --- a/slither/utils/standard_libraries.py +++ b/slither/utils/standard_libraries.py @@ -13,6 +13,7 @@ libraries = { 'Dapphub-DSToken': lambda x: is_dapphub_ds_token(x), 'Dapphub-DSProxy': lambda x: is_dapphub_ds_proxy(x), 'Dapphub-DSGroup': lambda x: is_dapphub_ds_group(x), + 'AragonOS-App': lambda x: is_aragonos_app(x) } def is_standard_library(contract): @@ -41,6 +42,12 @@ def is_zos(contract): return 'zos-lib' in Path(contract.source_mapping['filename_absolute']).parts +def is_aragonos(contract): + if not contract.is_from_dependency(): + return False + return '@aragon/os' in Path(contract.source_mapping['filename_absolute']).parts + + # endregion ################################################################################### ################################################################################### @@ -191,3 +198,13 @@ def is_ds_group(contract): def is_dapphub_ds_group(contract): return _is_dappdhub_ds(contract, 'DSGroup') + +# endregion +################################################################################### +################################################################################### +# region Aragon +################################################################################### +################################################################################### + +def is_aragonos_app(contract): + return contract.name == "AragonApp" and is_aragonos(contract) From 99ad5b1bf3eb53fadac8bd74802d12e7eceed0a0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Jun 2019 10:56:10 +0200 Subject: [PATCH 06/47] Add CONTRIBUTING.md --- CONTRIBUTING.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..c5b078541 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# Contributing to Manticore +First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements. + +If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels. + +# Bug reports and feature suggestions +Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead. + +# Questions +Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel). + +# Code +Slither uses the pull request contribution model. Please make an account on Github, fork this repo, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/). + +Some pull request guidelines: + +- Work from the [`dev`](https://github.com/crytic/slither/tree/dev) branch. We performed extensive tests prior to merging anything to `master`, working from `dev` will allow us to merge your work faster. +- Minimize irrelevant changes (formatting, whitespace, etc) to code that would otherwise not be touched by this patch. Save formatting or style corrections for a separate pull request that does not make any semantic changes. +- When possible, large changes should be split up into smaller focused pull requests. +- Fill out the pull request description with a summary of what your patch does, key changes that have been made, and any further points of discussion, if applicable. +- Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own. + +# Development Environment +Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation). From b275bcc824b1b932310cf03b6bfb1a1fef0ebae1 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 13 Jun 2019 11:27:37 +0200 Subject: [PATCH 07/47] Add demo utility --- utils/demo/README.md | 6 ++++++ utils/demo/__init__.py | 0 utils/demo/__main__.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 utils/demo/README.md create mode 100644 utils/demo/__init__.py create mode 100644 utils/demo/__main__.py diff --git a/utils/demo/README.md b/utils/demo/README.md new file mode 100644 index 000000000..00bdec0b4 --- /dev/null +++ b/utils/demo/README.md @@ -0,0 +1,6 @@ +## Demo + +This directory contains an example of Slither utility. + +See the [utility documentation](https://github.com/crytic/slither/wiki/Adding-a-new-utility) + diff --git a/utils/demo/__init__.py b/utils/demo/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/demo/__main__.py b/utils/demo/__main__.py new file mode 100644 index 000000000..4bee3b449 --- /dev/null +++ b/utils/demo/__main__.py @@ -0,0 +1,38 @@ +import os +import argparse +import logging +from slither import Slither +from crytic_compile import cryticparser + +logging.basicConfig() +logging.getLogger("Slither").setLevel(logging.INFO) + +logger = logging.getLogger("Slither-demo") + +def parse_args(): + """ + Parse the underlying arguments for the program. + :return: Returns the arguments for the program. + """ + parser = argparse.ArgumentParser(description='Demo', + usage='slither-demo filename') + + parser.add_argument('filename', + help='The filename of the contract or truffle directory to analyze.') + + # Add default arguments from crytic-compile + cryticparser.init(parser) + + return parser.parse_args() + + +def main(): + args = parse_args() + + # Perform slither analysis on the given filename + slither = Slither(args.filename, **vars(args)) + + logger.info('Analysis done!') + +if __name__ == '__main__': + main() From df17fb877996da93a62872d6b78402aeeca9a556 Mon Sep 17 00:00:00 2001 From: Eric Rafaloff Date: Thu, 20 Jun 2019 10:50:03 -0400 Subject: [PATCH 08/47] Detect more AragonOS contracts --- slither/utils/standard_libraries.py | 78 ++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/slither/utils/standard_libraries.py b/slither/utils/standard_libraries.py index 8ae3e5978..d1c0bdbec 100644 --- a/slither/utils/standard_libraries.py +++ b/slither/utils/standard_libraries.py @@ -13,7 +13,24 @@ libraries = { 'Dapphub-DSToken': lambda x: is_dapphub_ds_token(x), 'Dapphub-DSProxy': lambda x: is_dapphub_ds_proxy(x), 'Dapphub-DSGroup': lambda x: is_dapphub_ds_group(x), - 'AragonOS-App': lambda x: is_aragonos_app(x) + 'AragonOS-SafeMath': lambda x: is_aragonos_safemath(x), + 'AragonOS-ERC20': lambda x: is_aragonos_erc20(x), + 'AragonOS-AppProxyBase': lambda x: is_aragonos_app_proxy_base(x), + 'AragonOS-AppProxyPinned': lambda x: is_aragonos_app_proxy_pinned(x), + 'AragonOS-AppProxyUpgradeable': lambda x: is_aragonos_app_proxy_upgradeable(x), + 'AragonOS-AppStorage': lambda x: is_aragonos_app_storage(x), + 'AragonOS-AragonApp': lambda x: is_aragonos_aragon_app(x), + 'AragonOS-UnsafeAragonApp': lambda x: is_aragonos_unsafe_aragon_app(x), + 'AragonOS-Autopetrified': lambda x: is_aragonos_autopetrified(x), + 'AragonOS-DelegateProxy': lambda x: is_aragonos_delegate_proxy(x), + 'AragonOS-DepositableDelegateProxy': lambda x: is_aragonos_depositable_delegate_proxy(x), + 'AragonOS-DepositableStorage': lambda x: is_aragonos_delegate_proxy(x), + 'AragonOS-Initializable': lambda x: is_aragonos_initializable(x), + 'AragonOS-IsContract': lambda x: is_aragonos_is_contract(x), + 'AragonOS-Petrifiable': lambda x: is_aragonos_petrifiable(x), + 'AragonOS-ReentrancyGuard': lambda x: is_aragonos_reentrancy_guard(x), + 'AragonOS-TimeHelpers': lambda x: is_aragonos_time_helpers(x), + 'AragonOS-VaultRecoverable': lambda x: is_aragonos_vault_recoverable(x) } def is_standard_library(contract): @@ -63,6 +80,11 @@ def is_safemath(contract): def is_openzepellin_safemath(contract): return is_safemath(contract) and is_openzepellin(contract) + +def is_aragonos_safemath(contract): + return is_safemath(contract) and is_aragonos(contract) + + # endregion ################################################################################### ################################################################################### @@ -111,6 +133,10 @@ def is_openzepellin_erc20(contract): return is_erc20(contract) and is_openzepellin(contract) +def is_aragonos_erc20(contract): + return is_erc20(contract) and is_openzepellin(contract) + + # endregion ################################################################################### ################################################################################### @@ -206,5 +232,53 @@ def is_dapphub_ds_group(contract): ################################################################################### ################################################################################### -def is_aragonos_app(contract): +def is_aragonos_app_proxy_base(contract): + return contract.name == "AppProxyBase" and is_aragonos(contract) + +def is_aragonos_app_proxy_pinned(contract): + return contract.name == "AppProxyPinned" and is_aragonos(contract) + +def is_aragonos_app_proxy_upgradeable(contract): + return contract.name == "AppProxyUpgradeable" and is_aragonos(contract) + +def is_aragonos_app_storage(contract): + return contract.name == "AppStorage" and is_aragonos(contract) + +def is_aragonos_aragon_app(contract): return contract.name == "AragonApp" and is_aragonos(contract) + +def is_aragonos_unsafe_aragon_app(contract): + return contract.name == "UnsafeAragonApp" and is_aragonos(contract) + +def is_aragonos_autopetrified(contract): + return contract.name == "Autopetrified" and is_aragonos(contract) + +def is_aragonos_delegate_proxy(contract): + return contract.name == "DelegateProxy" and is_aragonos(contract) + +def is_aragonos_depositable_delegate_proxy(contract): + return contract.name == "DepositableDelegateProxy" and is_aragonos(contract) + +def is_aragonos_depositable_storage(contract): + return contract.name == "DepositableStorage" and is_aragonos(contract) + +def is_aragonos_ether_token_contract(contract): + return contract.name == "EtherTokenConstant" and is_aragonos(contract) + +def is_aragonos_initializable(contract): + return contract.name == "Initializable" and is_aragonos(contract) + +def is_aragonos_is_contract(contract): + return contract.name == "IsContract" and is_aragonos(contract) + +def is_aragonos_petrifiable(contract): + return contract.name == "Petrifiable" and is_aragonos(contract) + +def is_aragonos_reentrancy_guard(contract): + return contract.name == "ReentrancyGuard" and is_aragonos(contract) + +def is_aragonos_time_helpers(contract): + return contract.name == "TimeHelpers" and is_aragonos(contract) + +def is_aragonos_vault_recoverable(contract): + return contract.name == "VaultRecoverable" and is_aragonos(contract) From 76eb43930a000cffecb598df10f749550ab74dc0 Mon Sep 17 00:00:00 2001 From: Eric Rafaloff Date: Thu, 20 Jun 2019 10:50:46 -0400 Subject: [PATCH 09/47] Fix typo in check --- slither/utils/standard_libraries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/utils/standard_libraries.py b/slither/utils/standard_libraries.py index d1c0bdbec..e138e6c35 100644 --- a/slither/utils/standard_libraries.py +++ b/slither/utils/standard_libraries.py @@ -134,7 +134,7 @@ def is_openzepellin_erc20(contract): def is_aragonos_erc20(contract): - return is_erc20(contract) and is_openzepellin(contract) + return is_erc20(contract) and is_aragonos(contract) # endregion From 2d8e4bb1f1dd745d47a2f754793bc91cfee73fe5 Mon Sep 17 00:00:00 2001 From: David Pokora Date: Thu, 20 Jun 2019 17:27:31 -0400 Subject: [PATCH 10/47] -Added support for self-contained archives exported by crytic compile. -Rewrote main entry point to move glob pattern matching and other compilation into crytic compile. --- slither/__main__.py | 55 ++++++++++--------- slither/core/slither_core.py | 7 ++- slither/core/source_mapping/source_mapping.py | 13 +++-- slither/slither.py | 19 ++++--- 4 files changed, 55 insertions(+), 39 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index a1b4ce4ad..01f3de96e 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -23,7 +23,7 @@ from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_results_to_markdown, output_detectors_json, output_printers, output_to_markdown, output_wiki) -from crytic_compile import is_supported +from crytic_compile import CryticCompile from slither.exceptions import SlitherException logging.basicConfig() @@ -35,7 +35,8 @@ logger = logging.getLogger("Slither") ################################################################################### ################################################################################### -def process(filename, args, detector_classes, printer_classes): + +def process_single(target, args, detector_classes, printer_classes): """ The core high-level code for running Slither static analysis. @@ -46,12 +47,25 @@ def process(filename, args, detector_classes, printer_classes): if args.legacy_ast: ast = '--ast-json' args.filter_paths = parse_filter_paths(args) - slither = Slither(filename, + slither = Slither(target, ast_format=ast, + solc_arguments=args.solc_args, **vars(args)) return _process(slither, detector_classes, printer_classes) + +def process_all(target, args, detector_classes, printer_classes): + compilations = CryticCompile.compile_all(target, **vars(args)) + results = [] + analyzed_contracts_count = 0 + for compilation in compilations: + (current_results, current_analyzed_count) = process_single(compilation, args, detector_classes, printer_classes) + results.extend(current_results) + analyzed_contracts_count += current_analyzed_count + return results, analyzed_contracts_count + + def _process(slither, detector_classes, printer_classes): for detector_cls in detector_classes: slither.register_detector(detector_cls) @@ -75,7 +89,7 @@ def _process(slither, detector_classes, printer_classes): return results, analyzed_contracts_count -def process_files(filenames, args, detector_classes, printer_classes): +def process_from_asts(filenames, args, detector_classes, printer_classes): all_contracts = [] for filename in filenames: @@ -83,15 +97,9 @@ def process_files(filenames, args, detector_classes, printer_classes): contract_loaded = json.load(f) all_contracts.append(contract_loaded['ast']) - slither = Slither(all_contracts, - solc=args.solc, - disable_solc_warnings=args.disable_solc_warnings, - solc_arguments=args.solc_args, - filter_paths=parse_filter_paths(args), - triage_mode=args.triage_mode, - exclude_dependencies=args.exclude_dependencies) + return process_single(all_contracts, args, detector_classes, printer_classes) + - return _process(slither, detector_classes, printer_classes) # endregion ################################################################################### @@ -296,7 +304,7 @@ def parse_args(detector_classes, printer_classes): group_detector = parser.add_argument_group('Detectors') group_printer = parser.add_argument_group('Printers') - group_misc = parser.add_argument_group('Additional option') + group_misc = parser.add_argument_group('Additional options') group_detector.add_argument('--detect', help='Comma-separated list of detectors, defaults to all, ' @@ -570,28 +578,25 @@ def main_impl(all_detector_classes, all_printer_classes): try: filename = args.filename - globbed_filenames = glob.glob(filename, recursive=True) - - if os.path.isfile(filename) or is_supported(filename): - (results, number_contracts) = process(filename, args, detector_classes, printer_classes) - - elif os.path.isdir(filename) or len(globbed_filenames) > 0: - extension = "*.sol" if not args.solc_ast else "*.json" - filenames = glob.glob(os.path.join(filename, extension)) + # Determine if we are handling ast from solc + if args.solc_ast: + globbed_filenames = glob.glob(filename, recursive=True) + filenames = glob.glob(os.path.join(filename, "*.json")) if not filenames: filenames = globbed_filenames number_contracts = 0 results = [] - if args.splitted and args.solc_ast: - (results, number_contracts) = process_files(filenames, args, detector_classes, printer_classes) + if args.splitted: + (results, number_contracts) = process_from_asts(filenames, args, detector_classes, printer_classes) else: for filename in filenames: - (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) + (results_tmp, number_contracts_tmp) = process_single(filename, args, detector_classes, printer_classes) number_contracts += number_contracts_tmp results += results_tmp + # Rely on CryticCompile to discern the underlying type of compilations. else: - raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) + (results, number_contracts) = process_all(filename, args, detector_classes, printer_classes) if args.json: output_json(results, None if stdout_json else args.json) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 94bd7eb35..f10831ffe 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -61,8 +61,11 @@ class Slither(Context): :param path: :return: """ - with open(path, encoding='utf8', newline='') as f: - self.source_code[path] = f.read() + if path in self.crytic_compile.src_content: + self.source_code[path] = self.crytic_compile.src_content[path] + else: + with open(path, encoding='utf8', newline='') as f: + self.source_code[path] = f.read() # endregion ################################################################################### diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 9217b12fc..8f922c05c 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -90,18 +90,23 @@ class SourceMapping(Context): is_dependency = slither.crytic_compile.is_dependency(filename_absolute) - if filename_absolute in slither.source_code: + if filename_absolute in slither.source_code or filename_absolute in slither.crytic_compile.src_content: filename = filename_absolute elif filename_relative in slither.source_code: filename = filename_relative elif filename_short in slither.source_code: filename = filename_short - else:# - filename = filename_used.used + else: + filename = filename_used else: filename = filename_used - if filename in slither.source_code: + if filename in slither.crytic_compile.src_content: + source_code = slither.crytic_compile.src_content[filename] + (lines, starting_column, ending_column) = SourceMapping._compute_line(source_code, + s, + l) + elif filename in slither.source_code: source_code = slither.source_code[filename] (lines, starting_column, ending_column) = SourceMapping._compute_line(source_code, s, diff --git a/slither/slither.py b/slither/slither.py index d0c0ccf02..9a7732404 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -22,14 +22,14 @@ logger_printer = logging.getLogger("Printers") class Slither(SlitherSolc): - def __init__(self, contract, **kwargs): + def __init__(self, target, **kwargs): ''' Args: - contract (str| list(json)) + target (str | list(json) | CryticCompile) Keyword Args: solc (str): solc binary location (default 'solc') disable_solc_warnings (bool): True to disable solc warnings (default false) - solc_argeuments (str): solc arguments (default '') + solc_arguments (str): solc arguments (default '') ast_format (str): ast format (default '--ast-compact-json') filter_paths (list(str)): list of path to filter (default []) triage_mode (bool): if true, switch to triage mode (default false) @@ -46,14 +46,17 @@ class Slither(SlitherSolc): ''' # list of files provided (see --splitted option) - if isinstance(contract, list): - self._init_from_list(contract) - elif contract.endswith('.json'): - self._init_from_raw_json(contract) + if isinstance(target, list): + self._init_from_list(target) + elif isinstance(target, str) and target.endswith('.json'): + self._init_from_raw_json(target) else: super(Slither, self).__init__('') try: - crytic_compile = CryticCompile(contract, **kwargs) + if isinstance(target, CryticCompile): + crytic_compile = target + else: + crytic_compile = CryticCompile(target, **kwargs) self._crytic_compile = crytic_compile except InvalidCompilation as e: raise SlitherError('Invalid compilation: \n'+str(e)) From 0c7b22d50021d6eb09495435b540bce27cdbb2b0 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Jun 2019 19:23:04 +0200 Subject: [PATCH 11/47] Update to crytic-compile 8539bc38e9f6efb94351ae8a21820bf5d205803e --- slither/__main__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 01f3de96e..a71368e28 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -6,7 +6,6 @@ import inspect import json import logging import os -import subprocess import sys import traceback @@ -23,7 +22,7 @@ from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_results_to_markdown, output_detectors_json, output_printers, output_to_markdown, output_wiki) -from crytic_compile import CryticCompile +from crytic_compile import compile_all, is_supported from slither.exceptions import SlitherException logging.basicConfig() @@ -56,7 +55,7 @@ def process_single(target, args, detector_classes, printer_classes): def process_all(target, args, detector_classes, printer_classes): - compilations = CryticCompile.compile_all(target, **vars(args)) + compilations = compile_all(target, **vars(args)) results = [] analyzed_contracts_count = 0 for compilation in compilations: @@ -579,7 +578,7 @@ def main_impl(all_detector_classes, all_printer_classes): filename = args.filename # Determine if we are handling ast from solc - if args.solc_ast: + if args.solc_ast or (filename.endswith('.json') and not is_supported(filename)): globbed_filenames = glob.glob(filename, recursive=True) filenames = glob.glob(os.path.join(filename, "*.json")) if not filenames: From 341157f7e225d49c928d852507b41b03f6a0d8bd Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 25 Jun 2019 21:17:46 +0200 Subject: [PATCH 12/47] source_mapping: Check for crytic_compile before acceding to it --- slither/core/source_mapping/source_mapping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 8f922c05c..564b2f0e1 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -101,7 +101,7 @@ class SourceMapping(Context): else: filename = filename_used - if filename in slither.crytic_compile.src_content: + if slither.crytic_compile and filename in slither.crytic_compile.src_content: source_code = slither.crytic_compile.src_content[filename] (lines, starting_column, ending_column) = SourceMapping._compute_line(source_code, s, From c34ecd4320608d23527585a7f3205affe06f699e Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Thu, 27 Jun 2019 15:01:34 +0200 Subject: [PATCH 13/47] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c5b078541..8e767e47c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to Manticore +# Contributing to Slither First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements. If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels. From 12a5a75b38544f1b8d249b663fe6e43012d4bc34 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 3 Jul 2019 17:43:53 +0200 Subject: [PATCH 14/47] Fix filter-paths argument in case of raw directory (fix #292) --- slither/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 06fabe1a9..6c66d6bbc 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -46,7 +46,6 @@ def process(filename, args, detector_classes, printer_classes): ast = '--ast-compact-json' if args.legacy_ast: ast = '--ast-json' - args.filter_paths = parse_filter_paths(args) slither = Slither(filename, ast_format=ast, **vars(args)) @@ -85,7 +84,6 @@ def process_files(filenames, args, detector_classes, printer_classes): all_contracts.append(contract_loaded['ast']) slither = Slither(all_contracts, - filter_paths=parse_filter_paths(args), **vars(args)) return _process(slither, detector_classes, printer_classes) @@ -423,6 +421,8 @@ def parse_args(detector_classes, printer_classes): args = parser.parse_args() read_config_file(args) + args.filter_paths = parse_filter_paths(args) + return args class ListDetectors(argparse.Action): From 66830b3362679c25000681d58df2a27542edb111 Mon Sep 17 00:00:00 2001 From: Josselin Date: Thu, 18 Jul 2019 09:17:58 +0200 Subject: [PATCH 15/47] Naming convention: allow _ in name for private/internal function + fix regex (fix #287) Solc parsing: dont translate to scope variable without name (SSA) --- slither/detectors/naming_convention/naming_convention.py | 4 +++- slither/solc_parsing/declarations/function.py | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 9c69e41ba..6c72f4438 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -41,7 +41,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 def is_mixed_case_with_underscore(name): # Allow _ at the beginning to represent private variable # or unused parameters - return re.search('^[a-z_]([A-Za-z0-9]+)?_?$', name) is not None + return re.search('^[_]?[a-z]([A-Za-z0-9]+)?_?$', name) is not None @staticmethod def is_upper_case_with_underscores(name): @@ -93,6 +93,8 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 for func in contract.functions_declared: if not self.is_mixed_case(func.name): + if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name): + continue info = "Function '{}' ({}) is not in mixedCase\n" info = info.format(func.canonical_name, func.source_mapping_str) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 5c95cc231..bfe0d4ea2 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -98,9 +98,10 @@ class FunctionSolc(Function): # This is done to prevent collision during SSA translation # Use of while in case of collision # In the worst case, the name will be really long - while local_var.name in self._variables: - local_var.name += "_scope_{}".format(self._counter_scope_local_variables) - self._counter_scope_local_variables += 1 + if local_var.name: + while local_var.name in self._variables: + local_var.name += "_scope_{}".format(self._counter_scope_local_variables) + self._counter_scope_local_variables += 1 if not local_var.reference_id is None: self._variables_renamed[local_var.reference_id] = local_var self._variables[local_var.name] = local_var From 6ebc8bb35e2d7edd259c0f0f1aaaa65447dcc778 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 19 Jul 2019 10:12:37 +0200 Subject: [PATCH 16/47] Add source mapping to constructor_variable --- slither/solc_parsing/declarations/contract.py | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index e931dddb7..c7a9a7793 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -387,27 +387,29 @@ class ContractSolc04(Contract): def add_constructor_variables(self): if self.state_variables: - found_candidate = False for (idx, variable_candidate) in enumerate(self.state_variables): if variable_candidate.expression and not variable_candidate.is_constant: - found_candidate = True + + constructor_variable = Function() + constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_VARIABLES) + constructor_variable.set_contract(self) + constructor_variable.set_contract_declarer(self) + constructor_variable.set_visibility('internal') + # For now, source mapping of the constructor variable is the whole contract + # Could be improved with a targeted source mapping + constructor_variable.set_offset(self.source_mapping, self.slither) + self._functions[constructor_variable.canonical_name] = constructor_variable + + prev_node = self._create_node(constructor_variable, 0, variable_candidate) + counter = 1 + for v in self.state_variables[idx+1:]: + if v.expression and not v.is_constant: + next_node = self._create_node(constructor_variable, counter, v) + prev_node.add_son(next_node) + next_node.add_father(prev_node) + counter += 1 + break - if found_candidate: - constructor_variable = Function() - constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_VARIABLES) - constructor_variable.set_contract(self) - constructor_variable.set_contract_declarer(self) - constructor_variable.set_visibility('internal') - self._functions[constructor_variable.canonical_name] = constructor_variable - - prev_node = self._create_node(constructor_variable, 0, variable_candidate) - counter = 1 - for v in self.state_variables[idx+1:]: - if v.expression and not v.is_constant: - next_node = self._create_node(constructor_variable, counter, v) - prev_node.add_son(next_node) - next_node.add_father(prev_node) - counter += 1 From ccfdfd7dbb7bfa4b382a26746a10f23c0c7752a0 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Mon, 22 Jul 2019 21:36:47 +0200 Subject: [PATCH 17/47] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d0ff6c85..55e55d9c4 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,13 @@ $ pip install slither-analyzer ### Using Git +Installing through git requires installing [crytic-compile](https://github.com/crytic/crytic-compile) from git too: + ```bash -$ git clone https://github.com/trailofbits/slither.git && cd slither +$ git clone https://github.com/crytic/crytic-compile.git && cd crytic-compile +$ python setup.py install +$ cd .. +$ git clone https://github.com/crytic/slither.git && cd slither $ python setup.py install ``` From 119ef0684d1eddd4222c2a05a0c51c66f134824f Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Jul 2019 09:57:02 +0200 Subject: [PATCH 18/47] rename detectors-types to list-detectors add list-printers --- slither/__main__.py | 19 ++++++++++++------- slither/utils/command_line.py | 24 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index db5857276..96c9dc41a 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -22,16 +22,16 @@ from slither.slither import Slither from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_results_to_markdown, - get_detector_types_json, output_printers, - output_to_markdown, output_wiki) + output_detectors_json, output_printers, + output_to_markdown, output_wiki, output_printers_json) from crytic_compile import compile_all, is_supported from slither.exceptions import SlitherException logging.basicConfig() logger = logging.getLogger("Slither") -JSON_OUTPUT_TYPES = ["compilations", "console", "detectors", "detector-types"] -DEFAULT_JSON_OUTPUT_TYPES = ["console", "detectors", "detector-types"] +JSON_OUTPUT_TYPES = ["compilations", "console", "detectors", "list-detectors", "list-printers"] +DEFAULT_JSON_OUTPUT_TYPES = ["console", "detectors", "list-detectors", "list-printers"] ################################################################################### ################################################################################### @@ -480,7 +480,7 @@ class ListDetectors(argparse.Action): class ListDetectorsJson(argparse.Action): def __call__(self, parser, *args, **kwargs): detectors, _ = get_detectors_and_printers() - detector_types_json = get_detector_types_json(detectors) + detector_types_json = output_detectors_json(detectors) print(json.dumps(detector_types_json)) parser.exit() @@ -628,9 +628,14 @@ def main_impl(all_detector_classes, all_printer_classes): json_results['detectors'] = results # Add our detector types to JSON - if 'detector-types' in args.json_types: + if 'list-detectors' in args.json_types: detectors, _ = get_detectors_and_printers() - json_results['detector-types'] = get_detector_types_json(detectors) + json_results['list-detectors'] = output_detectors_json(detectors) + + # Add our detector types to JSON + if 'list-printers' in args.json_types: + _, printers = get_detectors_and_printers() + json_results['list-printers'] = output_printers_json(printers) # Output our results to markdown if we wish to compile a checklist. if args.checklist: diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 531181c4a..ed4759038 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -156,7 +156,7 @@ def output_detectors(detector_classes): print(table) -def get_detector_types_json(detector_classes): +def output_detectors_json(detector_classes): detectors_list = [] for detector in detector_classes: argument = detector.ARGUMENT @@ -213,3 +213,25 @@ def output_printers(printer_classes): table.add_row([idx, argument, help_info]) idx = idx + 1 print(table) + + +def output_printers_json(printer_classes): + printers_list = [] + for printer in printer_classes: + argument = printer.ARGUMENT + help_info = printer.HELP + + printers_list.append((argument, + help_info)) + + # Sort by name + printers_list = sorted(printers_list, key=lambda element: (element[0])) + idx = 1 + table = [] + for (argument, help_info) in printers_list: + table.append({'index': idx, + 'check': argument, + 'title': help_info}) + idx = idx + 1 + return table + From 595e8b982862e7fa8f8580a2fd7aafc689d297a1 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Jul 2019 10:13:26 +0200 Subject: [PATCH 19/47] Fix travis --- ...deprecated_calls.deprecated-standards.json | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/expected_json/deprecated_calls.deprecated-standards.json b/tests/expected_json/deprecated_calls.deprecated-standards.json index 2b9c385d1..9680b948d 100644 --- a/tests/expected_json/deprecated_calls.deprecated-standards.json +++ b/tests/expected_json/deprecated_calls.deprecated-standards.json @@ -720,7 +720,46 @@ "parent": { "type": "function", "name": "slitherConstructorVariables", - "source_mapping": null, + "source_mapping": { + "start": 0, + "length": 906, + "filename_used": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_relative": "tests/deprecated_calls.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/deprecated_calls.sol", + "filename_short": "tests/deprecated_calls.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27 + ], + "starting_column": 1, + "ending_column": null + }, "type_specific_fields": { "parent": { "type": "contract", From 590b8b698c4c798669effd73fce3b74b19bf0f9b Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 23 Jul 2019 10:14:54 +0200 Subject: [PATCH 20/47] Change default json to only output detectors --- slither/utils/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 5d0140be6..c83c5dbdf 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -10,7 +10,7 @@ from .colors import yellow, red logger = logging.getLogger("Slither") -DEFAULT_JSON_OUTPUT_TYPES = ["console", "detectors", "list-detectors", "list-printers"] +DEFAULT_JSON_OUTPUT_TYPES = ["detectors"] JSON_OUTPUT_TYPES = ["compilations", "console", "detectors", "list-detectors", "list-printers"] From 9d93e7bd9f732fcc2dbabd827cc3bdd4da418c6d Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Jul 2019 08:38:19 +0200 Subject: [PATCH 21/47] Add support for ternary operator in modifier/constructor calls Add NOP IR to represent void constructor Add void-constructor detector --- scripts/tests_generate_expected_json_4.sh | 2 +- scripts/tests_generate_expected_json_5.sh | 1 + scripts/travis_test_5.sh | 1 + slither/core/cfg/node.py | 19 ++- slither/core/declarations/function.py | 36 ++++- slither/detectors/all_detectors.py | 1 + .../detectors/operations/void_constructor.py | 46 +++++++ slither/printers/summary/slithir.py | 8 +- slither/slithir/convert.py | 5 +- slither/slithir/operations/__init__.py | 1 + slither/slithir/operations/nop.py | 14 ++ slither/solc_parsing/declarations/contract.py | 2 +- slither/solc_parsing/declarations/function.py | 35 +++-- .../visitors/slithir/expression_to_slithir.py | 2 +- tests/expected_json/void-cst.void-cst.json | 130 ++++++++++++++++++ tests/expected_json/void-cst.void-cst.txt | 5 + tests/void-cst.sol | 14 ++ 17 files changed, 294 insertions(+), 28 deletions(-) create mode 100644 slither/detectors/operations/void_constructor.py create mode 100644 slither/slithir/operations/nop.py create mode 100644 tests/expected_json/void-cst.void-cst.json create mode 100644 tests/expected_json/void-cst.void-cst.txt create mode 100644 tests/void-cst.sol diff --git a/scripts/tests_generate_expected_json_4.sh b/scripts/tests_generate_expected_json_4.sh index e748e4f42..4e1def80c 100755 --- a/scripts/tests_generate_expected_json_4.sh +++ b/scripts/tests_generate_expected_json_4.sh @@ -21,7 +21,7 @@ generate_expected_json(){ } -generate_expected_json tests/deprecated_calls.sol "deprecated-standards" +#generate_expected_json tests/deprecated_calls.sol "deprecated-standards" #generate_expected_json tests/erc20_indexed.sol "erc20-indexed" #generate_expected_json tests/incorrect_erc20_interface.sol "erc20-interface" #generate_expected_json tests/incorrect_erc721_interface.sol "erc721-interface" diff --git a/scripts/tests_generate_expected_json_5.sh b/scripts/tests_generate_expected_json_5.sh index fb9552437..78cc2b3ef 100755 --- a/scripts/tests_generate_expected_json_5.sh +++ b/scripts/tests_generate_expected_json_5.sh @@ -20,6 +20,7 @@ generate_expected_json(){ sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i } +generate_expected_json tests/void-cst.sol "void-cst" #generate_expected_json tests/solc_version_incorrect_05.ast.json "solc-version" #generate_expected_json tests/uninitialized-0.5.1.sol "uninitialized-state" #generate_expected_json tests/backdoor.sol "backdoor" diff --git a/scripts/travis_test_5.sh b/scripts/travis_test_5.sh index 7224638d7..bb598ac1f 100755 --- a/scripts/travis_test_5.sh +++ b/scripts/travis_test_5.sh @@ -69,6 +69,7 @@ test_slither(){ } +test_slither tests/void-cst.json "void-cst" test_slither tests/solc_version_incorrect_05.ast.json "solc-version" test_slither tests/unchecked_lowlevel-0.5.1.sol "unchecked-lowlevel" test_slither tests/unchecked_send-0.5.1.sol "unchecked-send" diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 8a94a7caa..4251b0bad 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -64,7 +64,7 @@ class NodeType: # Node not related to the CFG # Use for state variable declaration, or modifier calls - STANDALONE = 0x50 + OTHER_ENTRYPOINT = 0x50 # @staticmethod @@ -111,6 +111,23 @@ def link_nodes(n1, n2): n1.add_son(n2) n2.add_father(n1) +def recheable(node): + ''' + Return the set of nodes reacheable from the node + :param node: + :return: set(Node) + ''' + nodes = node.sons + visited = set() + while nodes: + next = nodes[0] + nodes = nodes[1:] + if not next in visited: + visited.add(next) + for son in next.sons: + if not son in visited: + nodes.append(son) + return visited # endregion diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 7243a7c03..c0f5b2687 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -22,7 +22,33 @@ logger = logging.getLogger("Function") ReacheableNode = namedtuple('ReacheableNode', ['node', 'ir']) -ModifierStatements = namedtuple('Modifier', ['modifier', 'node']) +class ModifierStatements: + + def __init__(self, modifier, entry_point, nodes): + self._modifier = modifier + self._entry_point = entry_point + self._nodes = nodes + + + @property + def modifier(self): + return self._modifier + + @property + def entry_point(self): + return self._entry_point + + @entry_point.setter + def entry_point(self, entry_point): + self._entry_point = entry_point + + @property + def nodes(self): + return self._nodes + + @nodes.setter + def nodes(self, nodes): + self._nodes = nodes class FunctionType(Enum): NORMAL = 0 @@ -403,7 +429,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): """ # This is a list of contracts internally, so we convert it to a list of constructor functions. - return [c for c in self._explicit_base_constructor_calls if c.modifier.constructors_declared] + return list(self._explicit_base_constructor_calls) # endregion @@ -1266,10 +1292,12 @@ class Function(ChildContract, ChildInheritance, SourceMapping): node.slithir_generation() for modifier_statement in self.modifiers_statements: - modifier_statement.node.slithir_generation() + for node in modifier_statement.nodes: + node.slithir_generation() for modifier_statement in self.explicit_base_constructor_calls_statements: - modifier_statement.node.slithir_generation() + for node in modifier_statement.nodes: + node.slithir_generation() self._analyze_read_write() self._analyze_calls() diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 9fd21ac83..39822221b 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -36,5 +36,6 @@ from .source.rtlo import RightToLeftOverride from .statements.too_many_digits import TooManyDigits from .operations.unchecked_low_level_return_values import UncheckedLowLevel from .operations.unchecked_send_return_value import UncheckedSend +from .operations.void_constructor import VoidConstructor # # diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py new file mode 100644 index 000000000..6f2097e75 --- /dev/null +++ b/slither/detectors/operations/void_constructor.py @@ -0,0 +1,46 @@ + +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import Nop + +class VoidConstructor(AbstractDetector): + + ARGUMENT = 'void-cst' + HELP = 'Constructor called not implemented' + IMPACT = DetectorClassification.LOW + CONFIDENCE = DetectorClassification.HIGH + + WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor' + + WIKI_TITLE = 'Void Constructor' + WIKI_DESCRIPTION = 'Detect the call to a constructor not implemented' + WIKI_RECOMMENDATION = 'Remove the constructor call.' + WIKI_EXPLOIT_SCENARIO = ''' + ```solidity +contract A{} +contract B is A{ + constructor() public A(){} +} +``` +By reading B's constructor definition, the reader might assume that `A()` initiate the contract, while no code is executed.''' + + + def _detect(self): + """ + """ + results = [] + for c in self.contracts: + cst = c.constructor + if cst: + + for constructor_call in cst.explicit_base_constructor_calls_statements: + for node in constructor_call.nodes: + if any(isinstance(ir, Nop) for ir in node.irs): + info = "Void constructor called in {} ({}):\n" + info = info.format(cst.canonical_name, cst.source_mapping_str) + info += "\t-{} {}\n".format(str(node.expression), node.source_mapping_str) + + json = self.generate_json_result(info) + self.add_function_to_json(cst, json) + self.add_nodes_to_json([node], json) + results.append(json) + return results diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index c6e30073f..4e936e038 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -35,13 +35,9 @@ class PrinterSlithIR(AbstractPrinter): for ir in node.irs: print('\t\t\t{}'.format(ir)) for modifier_statement in function.modifiers_statements: - print(f'\t\tModifier Call {modifier_statement.node.expression}') - for ir in modifier_statement.node.irs: - print('\t\t\t{}'.format(ir)) + print(f'\t\tModifier Call {modifier_statement.entry_point.expression}') for modifier_statement in function.explicit_base_constructor_calls_statements: - print(f'\t\tConstructor Call {modifier_statement.node.expression}') - for ir in modifier_statement.node.irs: - print('\t\t\t{}'.format(ir)) + print(f'\t\tConstructor Call {modifier_statement.entry_point.expression}') for modifier in contract.modifiers: print('\tModifier {}'.format(modifier.canonical_name)) for node in modifier.nodes: diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 101604d55..c50a4f8a6 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -21,7 +21,7 @@ from slither.slithir.operations import (Assignment, Balance, Binary, NewElementaryType, NewStructure, OperationWithLValue, Push, Return, Send, SolidityCall, Transfer, - TypeConversion, Unary, Unpack) + TypeConversion, Unary, Unpack, Nop) from slither.slithir.tmp_operations.argument import Argument, ArgumentType from slither.slithir.tmp_operations.tmp_call import TmpCall from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray @@ -587,6 +587,9 @@ def extract_tmp_call(ins, contract): return EventCall(ins.called.name) if isinstance(ins.called, Contract): + # Called a base constructor, where there is no constructor + if ins.called.constructor is None: + return Nop() internalcall = InternalCall(ins.called.constructor, ins.nbr_arguments, ins.lvalue, ins.type_call) internalcall.call_id = ins.call_id diff --git a/slither/slithir/operations/__init__.py b/slither/slithir/operations/__init__.py index b20df950d..cccc812e5 100644 --- a/slither/slithir/operations/__init__.py +++ b/slither/slithir/operations/__init__.py @@ -30,3 +30,4 @@ from .length import Length from .balance import Balance from .phi import Phi from .phi_callback import PhiCallback +from .nop import Nop diff --git a/slither/slithir/operations/nop.py b/slither/slithir/operations/nop.py new file mode 100644 index 000000000..fb26813c1 --- /dev/null +++ b/slither/slithir/operations/nop.py @@ -0,0 +1,14 @@ +from .operation import Operation + +class Nop(Operation): + + @property + def read(self): + return [] + + @property + def used(self): + return [] + + def __str__(self): + return "NOP" \ No newline at end of file diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index c7a9a7793..eb6021966 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -373,7 +373,7 @@ class ContractSolc04(Contract): def _create_node(self, func, counter, variable): # Function uses to create node for state variable declaration statements - node = Node(NodeType.STANDALONE, counter) + node = Node(NodeType.OTHER_ENTRYPOINT, counter) node.set_offset(variable.source_mapping, self.slither) node.set_function(func) func.add_node(node) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index bfe0d4ea2..9ec13cef8 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -2,7 +2,7 @@ """ import logging -from slither.core.cfg.node import NodeType, link_nodes +from slither.core.cfg.node import NodeType, link_nodes, recheable from slither.core.declarations.contract import Contract from slither.core.declarations.function import Function, ModifierStatements, FunctionType @@ -220,13 +220,13 @@ class FunctionSolc(Function): for node in self.nodes: node.analyze_expressions(self) - for modifier_statement in self.modifiers_statements: - modifier_statement.node.analyze_expressions(self) + if self._filter_ternary(): + for modifier_statement in self.modifiers_statements: + modifier_statement.nodes = recheable(modifier_statement.entry_point) - for modifier_statement in self.explicit_base_constructor_calls_statements: - modifier_statement.node.analyze_expressions(self) + for modifier_statement in self.explicit_base_constructor_calls_statements: + modifier_statement.nodes = recheable(modifier_statement.entry_point) - self._filter_ternary() self._remove_alone_endif() @@ -903,15 +903,21 @@ class FunctionSolc(Function): self._expression_modifiers.append(m) for m in ExportValues(m).result(): if isinstance(m, Function): - node = self._new_node(NodeType.STANDALONE, modifier['src']) + entry_point = self._new_node(NodeType.OTHER_ENTRYPOINT, modifier['src']) + node = self._new_node(NodeType.EXPRESSION, modifier['src']) node.add_unparsed_expression(modifier) + link_nodes(entry_point, node) self._modifiers.append(ModifierStatements(modifier=m, - node=node)) + entry_point=entry_point, + nodes=[entry_point, node])) elif isinstance(m, Contract): - node = self._new_node(NodeType.STANDALONE, modifier['src']) + entry_point = self._new_node(NodeType.OTHER_ENTRYPOINT, modifier['src']) + node = self._new_node(NodeType.EXPRESSION, modifier['src']) node.add_unparsed_expression(modifier) + link_nodes(entry_point, node) self._explicit_base_constructor_calls.append(ModifierStatements(modifier=m, - node=node)) + entry_point=entry_point, + nodes=[entry_point, node])) # endregion ################################################################################### @@ -971,9 +977,10 @@ class FunctionSolc(Function): def _filter_ternary(self): ternary_found = True + updated = False while ternary_found: ternary_found = False - for node in self.nodes: + for node in self._nodes: has_cond = HasConditional(node.expression) if has_cond.result(): st = SplitTernaryExpression(node.expression) @@ -982,11 +989,13 @@ class FunctionSolc(Function): raise ParsingError(f'Incorrect ternary conversion {node.expression} {node.source_mapping_str}') true_expr = st.true_expression false_expr = st.false_expression - self.split_ternary_node(node, condition, true_expr, false_expr) + self._split_ternary_node(node, condition, true_expr, false_expr) ternary_found = True + updated = True break + return updated - def split_ternary_node(self, node, condition, true_expr, false_expr): + def _split_ternary_node(self, node, condition, true_expr, false_expr): condition_node = self._new_node(NodeType.IF, node.source_mapping) condition_node.add_expression(condition) condition_node.analyze_expressions(self) diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 753bd82eb..f4ffd91a1 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -256,5 +256,5 @@ class ExpressionToSlithIR(ExpressionVisitor): self._result.append(operation) set_val(expression, lvalue) else: - raise Exception('Unary operation to IR not supported {}'.format(expression)) + raise SlithIRError('Unary operation to IR not supported {}'.format(expression)) diff --git a/tests/expected_json/void-cst.void-cst.json b/tests/expected_json/void-cst.void-cst.json new file mode 100644 index 000000000..ab97b9d7d --- /dev/null +++ b/tests/expected_json/void-cst.void-cst.json @@ -0,0 +1,130 @@ +{ + "success": true, + "error": null, + "results": { + "detectors": [ + { + "check": "void-cst", + "impact": "Low", + "confidence": "High", + "description": "Void constructor called in D.constructor() (tests/void-cst.sol#10-12):\n\t-C() tests/void-cst.sol#10\n", + "elements": [ + { + "type": "function", + "name": "constructor", + "source_mapping": { + "start": 41, + "length": 32, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 10, + 11, + 12 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "D", + "source_mapping": { + "start": 19, + "length": 57, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "constructor()" + } + }, + { + "type": "node", + "name": "C()", + "source_mapping": { + "start": 62, + "length": 3, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 10 + ], + "starting_column": 26, + "ending_column": 29 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "constructor", + "source_mapping": { + "start": 41, + "length": 32, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 10, + 11, + 12 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "D", + "source_mapping": { + "start": 19, + "length": 57, + "filename_used": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_relative": "tests/void-cst.sol", + "filename_absolute": "/home/travis/build/crytic/slither/tests/void-cst.sol", + "filename_short": "tests/void-cst.sol", + "is_dependency": false, + "lines": [ + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "constructor()" + } + } + } + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/expected_json/void-cst.void-cst.txt b/tests/expected_json/void-cst.void-cst.txt new file mode 100644 index 000000000..cc3473967 --- /dev/null +++ b/tests/expected_json/void-cst.void-cst.txt @@ -0,0 +1,5 @@ +INFO:Detectors: +Void constructor called in D.constructor() (tests/void-cst.sol#10-12): + -C() tests/void-cst.sol#10 +Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor +INFO:Slither:tests/void-cst.sol analyzed (2 contracts), 1 result(s) found diff --git a/tests/void-cst.sol b/tests/void-cst.sol new file mode 100644 index 000000000..43edc9d9a --- /dev/null +++ b/tests/void-cst.sol @@ -0,0 +1,14 @@ + + +contract C{ + + +} + +contract D is C{ + + constructor() public C(){ + + } + +} From ab96c09d0885cd62e0161882b39aea689586509f Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Jul 2019 08:57:16 +0200 Subject: [PATCH 22/47] Minor --- scripts/travis_test_5.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/travis_test_5.sh b/scripts/travis_test_5.sh index bb598ac1f..4b3928b5c 100755 --- a/scripts/travis_test_5.sh +++ b/scripts/travis_test_5.sh @@ -69,7 +69,7 @@ test_slither(){ } -test_slither tests/void-cst.json "void-cst" +test_slither tests/void-cst.sol "void-cst" test_slither tests/solc_version_incorrect_05.ast.json "solc-version" test_slither tests/unchecked_lowlevel-0.5.1.sol "unchecked-lowlevel" test_slither tests/unchecked_send-0.5.1.sol "unchecked-send" From 8bac6f5d87c99707a065da48b38a92a5e6c3a0a3 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Jul 2019 10:32:33 +0200 Subject: [PATCH 23/47] Use the master branch from crytic-compile --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d6642a502..48e51c1a8 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,9 @@ setup( version='0.6.4', packages=find_packages(), python_requires='>=3.6', - install_requires=['prettytable>=0.7.2', 'pysha3>=1.0.2', 'crytic-compile>=0.1.1'], + install_requires=['prettytable>=0.7.2', + 'pysha3>=1.0.2', + 'git+https://github.com/crytic/crytic-compile.git'], license='AGPL-3.0', long_description=open('README.md').read(), entry_points={ From e5175e79a4bbcd4e26cf25c4f8ddfa3ab8950b1a Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Jul 2019 10:46:44 +0200 Subject: [PATCH 24/47] Use dependency_links in setup.py --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 48e51c1a8..cd1248087 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,8 @@ setup( python_requires='>=3.6', install_requires=['prettytable>=0.7.2', 'pysha3>=1.0.2', - 'git+https://github.com/crytic/crytic-compile.git'], + 'crytic-compile'], + dependency_links=['git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile'], license='AGPL-3.0', long_description=open('README.md').read(), entry_points={ From 17e8f1b7b06ac97b54b47c6d1895cb50cf3cc4d8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 24 Jul 2019 10:48:23 +0200 Subject: [PATCH 25/47] Remove custom crytic-compile install in travis_install.sh --- scripts/travis_install.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scripts/travis_install.sh b/scripts/travis_install.sh index e043c9c16..2f55834a3 100755 --- a/scripts/travis_install.sh +++ b/scripts/travis_install.sh @@ -1,12 +1,5 @@ #!/usr/bin/env bash -# TODO: temporary until the next crytic-compile release -git clone https://github.com/crytic/crytic-compile -cd crytic-compile -git checkout dev -python setup.py install -cd .. - python setup.py install # Used by travis_test.sh pip install deepdiff From e30709932c1e440ce5eb5fbbf58d78e858d4b8a2 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Wed, 24 Jul 2019 10:52:05 +0200 Subject: [PATCH 26/47] Update README.md --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 55e55d9c4..0a2f48f3e 100644 --- a/README.md +++ b/README.md @@ -112,12 +112,7 @@ $ pip install slither-analyzer ### Using Git -Installing through git requires installing [crytic-compile](https://github.com/crytic/crytic-compile) from git too: - ```bash -$ git clone https://github.com/crytic/crytic-compile.git && cd crytic-compile -$ python setup.py install -$ cd .. $ git clone https://github.com/crytic/slither.git && cd slither $ python setup.py install ``` From 3805fdf6df7726d77029076f77ecda676b58c95c Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 27 Jul 2019 21:32:47 +0200 Subject: [PATCH 27/47] Improve abi.decode parsing support --- slither/solc_parsing/expressions/expression_parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 5055c886f..d6e41f0d9 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -579,7 +579,7 @@ def parse_expression(expression, caller_context): # if abi.decode is used # For example, abi.decode(data, ...(uint[]) ) if right is None: - return _parse_elementary_type_name_expression(left, is_compact_ast, caller_context) + return parse_expression(left, caller_context) left_expression = parse_expression(left, caller_context) right_expression = parse_expression(right, caller_context) From ab309ec81e51d44ba7600bcec26578d85e0f22fe Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 27 Jul 2019 21:51:12 +0200 Subject: [PATCH 28/47] Parsing/IR: late conversion of the subdenomination value to improve too many digit detector (fix #297) --- slither/core/expressions/literal.py | 10 +++- .../detectors/statements/too_many_digits.py | 1 - slither/slithir/variables/constant.py | 8 ++- .../expressions/expression_parsing.py | 49 +++---------------- slither/utils/arithmetic.py | 32 ++++++++++++ .../visitors/slithir/expression_to_slithir.py | 2 +- tests/too_many_digits.sol | 5 ++ 7 files changed, 61 insertions(+), 46 deletions(-) create mode 100644 slither/utils/arithmetic.py diff --git a/slither/core/expressions/literal.py b/slither/core/expressions/literal.py index c1923eff4..1e9b96ec3 100644 --- a/slither/core/expressions/literal.py +++ b/slither/core/expressions/literal.py @@ -1,11 +1,13 @@ from slither.core.expressions.expression import Expression +from slither.utils.arithmetic import convert_subdenomination class Literal(Expression): - def __init__(self, value, type): + def __init__(self, value, type, subdenomination=None): super(Literal, self).__init__() self._value = value self._type = type + self._subdenomination = subdenomination @property def value(self): @@ -15,6 +17,12 @@ class Literal(Expression): def type(self): return self._type + @property + def subdenomination(self): + return self._subdenomination + def __str__(self): + if self.subdenomination: + return str(convert_subdenomination(self._value, self.subdenomination)) # be sure to handle any character return str(self._value) diff --git a/slither/detectors/statements/too_many_digits.py b/slither/detectors/statements/too_many_digits.py index b5565094d..6b74fb9f4 100644 --- a/slither/detectors/statements/too_many_digits.py +++ b/slither/detectors/statements/too_many_digits.py @@ -48,7 +48,6 @@ Use: if isinstance(read, Constant): # read.value can return an int or a str. Convert it to str value_as_str = read.original_value - line_of_code = str(node.expression) if '00000' in value_as_str: # Info to be printed ret.append(node) diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 64c3e4fdf..10802ac09 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -1,14 +1,18 @@ from .variable import SlithIRVariable from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint - +from slither.utils.arithmetic import convert_subdenomination class Constant(SlithIRVariable): - def __init__(self, val, type=None): + def __init__(self, val, type=None, subdenomination=None): super(Constant, self).__init__() assert isinstance(val, str) self._original_value = val + self._subdenomination = subdenomination + + if subdenomination: + val = str(convert_subdenomination(val, subdenomination)) if type: assert isinstance(type, ElementaryType) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index d6e41f0d9..12837733c 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -220,45 +220,7 @@ def filter_name(value): return value # endregion -################################################################################### -################################################################################### -# region Conversion -################################################################################### -################################################################################### - -def convert_subdenomination(value, sub): - if sub is None: - return value - # to allow 0.1 ether conversion - if value[0:2] == "0x": - value = float(int(value, 16)) - else: - value = float(value) - if sub == 'wei': - return int(value) - if sub == 'szabo': - return int(value * int(1e12)) - if sub == 'finney': - return int(value * int(1e15)) - if sub == 'ether': - return int(value * int(1e18)) - if sub == 'seconds': - return int(value) - if sub == 'minutes': - return int(value * 60) - if sub == 'hours': - return int(value * 60 * 60) - if sub == 'days': - return int(value * 60 * 60 * 24) - if sub == 'weeks': - return int(value * 60 * 60 * 24 * 7) - if sub == 'years': - return int(value * 60 * 60 * 24 * 7 * 365) - - logger.error('Subdemoniation not found {}'.format(sub)) - return int(value) -# endregion ################################################################################### ################################################################################### # region Parsing @@ -489,14 +451,19 @@ def parse_expression(expression, caller_context): assignement = AssignmentOperation(left_expression, right_expression, operation_type, operation_return_type) return assignement + + elif name == 'Literal': + + subdenomination = None + assert 'children' not in expression if is_compact_ast: value = expression['value'] if value: if 'subdenomination' in expression and expression['subdenomination']: - value = str(convert_subdenomination(value, expression['subdenomination'])) + subdenomination = expression['subdenomination'] elif not value and value != "": value = '0x'+expression['hexValue'] type = expression['typeDescriptions']['typeString'] @@ -509,7 +476,7 @@ def parse_expression(expression, caller_context): value = expression['attributes']['value'] if value: if 'subdenomination' in expression['attributes'] and expression['attributes']['subdenomination']: - value = str(convert_subdenomination(value, expression['attributes']['subdenomination'])) + subdenomination = expression['subdenomination'] elif value is None: # for literal declared as hex # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals @@ -530,7 +497,7 @@ def parse_expression(expression, caller_context): type = ElementaryType('address') else: type = ElementaryType('string') - literal = Literal(value, type) + literal = Literal(value, type, subdenomination) return literal elif name == 'Identifier': diff --git a/slither/utils/arithmetic.py b/slither/utils/arithmetic.py new file mode 100644 index 000000000..241eca28a --- /dev/null +++ b/slither/utils/arithmetic.py @@ -0,0 +1,32 @@ +from slither.exceptions import SlitherException + + +def convert_subdenomination(value, sub): + + # to allow 0.1 ether conversion + if value[0:2] == "0x": + value = float(int(value, 16)) + else: + value = float(value) + if sub == 'wei': + return int(value) + if sub == 'szabo': + return int(value * int(1e12)) + if sub == 'finney': + return int(value * int(1e15)) + if sub == 'ether': + return int(value * int(1e18)) + if sub == 'seconds': + return int(value) + if sub == 'minutes': + return int(value * 60) + if sub == 'hours': + return int(value * 60 * 60) + if sub == 'days': + return int(value * 60 * 60 * 24) + if sub == 'weeks': + return int(value * 60 * 60 * 24 * 7) + if sub == 'years': + return int(value * 60 * 60 * 24 * 7 * 365) + + raise SlitherException(f'Subdemonination conversion impossible {value} {sub}') \ No newline at end of file diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index f4ffd91a1..2ad5670fd 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -171,7 +171,7 @@ class ExpressionToSlithIR(ExpressionVisitor): set_val(expression, val) def _post_literal(self, expression): - cst = Constant(expression.value, expression.type) + cst = Constant(expression.value, expression.type, expression.subdenomination) set_val(expression, cst) def _post_member_access(self, expression): diff --git a/tests/too_many_digits.sol b/tests/too_many_digits.sol index 84e930056..6156d0da5 100644 --- a/tests/too_many_digits.sol +++ b/tests/too_many_digits.sol @@ -31,5 +31,10 @@ contract C { uint x2 = 1 szabo + 10 szabo + 100 szabo + 1000 szabo + 10000 szabo; balance += x1 + x2; } + + function good() external{ + + uint x = 1 ether; + } } From cc3377bd338e09586f9ddd830132620be4d46ff9 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 27 Jul 2019 22:31:29 +0200 Subject: [PATCH 29/47] Minor --- slither/core/declarations/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index c0f5b2687..b9e981375 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -143,7 +143,7 @@ class Function(ChildContract, ChildInheritance, SourceMapping): """ str: function name """ - if self._function_type == FunctionType.CONSTRUCTOR: + if self._name == '' and self._function_type == FunctionType.CONSTRUCTOR: return 'constructor' elif self._function_type == FunctionType.FALLBACK: return 'fallback' From 24eb049ffb1ec82f4410ead2195315ab759fa474 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 28 Jul 2019 09:46:30 +0200 Subject: [PATCH 30/47] Improve raw json support --- slither/core/slither_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index f10831ffe..2747416fd 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -61,7 +61,7 @@ class Slither(Context): :param path: :return: """ - if path in self.crytic_compile.src_content: + if self.crytic_compile and path in self.crytic_compile.src_content: self.source_code[path] = self.crytic_compile.src_content[path] else: with open(path, encoding='utf8', newline='') as f: From 36d56d0b939ba07210190d26e3dada31a5661372 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 28 Jul 2019 10:47:23 +0200 Subject: [PATCH 31/47] Update README.md --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0a2f48f3e..ec9e762b0 100644 --- a/README.md +++ b/README.md @@ -61,20 +61,22 @@ Num | Detector | What it Detects | Impact | Confidence 20 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium 21 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High 22 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High -23 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop) | Low | Medium -24 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium -25 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium -26 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High -27 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High -28 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High -29 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High -30 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High -31 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High -32 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High -33 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High -34 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium -35 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High -36 | `external-function` | [Public function that could be declared as external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external) | Optimization | High +23 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High +24 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop) | Low | Medium +25 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium +26 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium +27 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High +28 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High +29 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High +30 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High +31 | `naming-convention` | [Conformance to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High +32 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High +33 | `solc-version` | [Incorrect Solidity version (< 0.4.24 or complex pragma)](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High +34 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High +35 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium +36 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High +37 | `external-function` | [Public function that could be declared as external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external) | Optimization | High + [Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors. From 1ac85718729809b205a6c3b7f78d8f2b9a66cd1c Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 28 Jul 2019 10:50:04 +0200 Subject: [PATCH 32/47] Markdown typo in void constructor --- slither/detectors/operations/void_constructor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py index 6f2097e75..756f88538 100644 --- a/slither/detectors/operations/void_constructor.py +++ b/slither/detectors/operations/void_constructor.py @@ -15,7 +15,7 @@ class VoidConstructor(AbstractDetector): WIKI_DESCRIPTION = 'Detect the call to a constructor not implemented' WIKI_RECOMMENDATION = 'Remove the constructor call.' WIKI_EXPLOIT_SCENARIO = ''' - ```solidity +```solidity contract A{} contract B is A{ constructor() public A(){} From a9dea21ca83ad31abd93d31388a02899c079b3e4 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 28 Jul 2019 10:57:40 +0200 Subject: [PATCH 33/47] Update travis tests --- scripts/tests_generate_expected_json_5.sh | 2 +- .../too_many_digits.too-many-digits.json | 148 +++++------------- .../too_many_digits.too-many-digits.txt | 6 +- 3 files changed, 38 insertions(+), 118 deletions(-) diff --git a/scripts/tests_generate_expected_json_5.sh b/scripts/tests_generate_expected_json_5.sh index 78cc2b3ef..620fc0b0c 100755 --- a/scripts/tests_generate_expected_json_5.sh +++ b/scripts/tests_generate_expected_json_5.sh @@ -20,7 +20,7 @@ generate_expected_json(){ sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i } -generate_expected_json tests/void-cst.sol "void-cst" +#generate_expected_json tests/void-cst.sol "void-cst" #generate_expected_json tests/solc_version_incorrect_05.ast.json "solc-version" #generate_expected_json tests/uninitialized-0.5.1.sol "uninitialized-state" #generate_expected_json tests/backdoor.sol "backdoor" diff --git a/tests/expected_json/too_many_digits.too-many-digits.json b/tests/expected_json/too_many_digits.too-many-digits.json index a8f2311f6..0e8cf635f 100644 --- a/tests/expected_json/too_many_digits.too-many-digits.json +++ b/tests/expected_json/too_many_digits.too-many-digits.json @@ -56,7 +56,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -95,7 +95,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -161,7 +166,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -200,7 +205,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -266,7 +276,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -305,7 +315,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -371,7 +386,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -410,7 +425,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -474,7 +494,7 @@ "name": "C", "source_mapping": { "start": 25, - "length": 833, + "length": 897, "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", "filename_relative": "tests/too_many_digits.sol", "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", @@ -513,7 +533,12 @@ 32, 33, 34, - 35 + 35, + 36, + 37, + 38, + 39, + 40 ], "starting_column": 1, "ending_column": 2 @@ -525,109 +550,6 @@ } } ] - }, - { - "check": "too-many-digits", - "impact": "Informational", - "confidence": "Medium", - "description": "C.i (tests/too_many_digits.sol#29-33) uses literals with too many digits:\n\t- x2 = 1000000000000 + 10000000000000 + 100000000000000 + 1000000000000000 + 10000000000000000\n", - "elements": [ - { - "type": "node", - "name": "x2 = 1000000000000 + 10000000000000 + 100000000000000 + 1000000000000000 + 10000000000000000", - "source_mapping": { - "start": 749, - "length": 67, - "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_relative": "tests/too_many_digits.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_short": "tests/too_many_digits.sol", - "is_dependency": false, - "lines": [ - 31 - ], - "starting_column": 9, - "ending_column": 76 - }, - "type_specific_fields": { - "parent": { - "type": "function", - "name": "i", - "source_mapping": { - "start": 650, - "length": 201, - "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_relative": "tests/too_many_digits.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_short": "tests/too_many_digits.sol", - "is_dependency": false, - "lines": [ - 29, - 30, - 31, - 32, - 33 - ], - "starting_column": 5, - "ending_column": 6 - }, - "type_specific_fields": { - "parent": { - "type": "contract", - "name": "C", - "source_mapping": { - "start": 25, - "length": 833, - "filename_used": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_relative": "tests/too_many_digits.sol", - "filename_absolute": "/home/travis/build/crytic/slither/tests/too_many_digits.sol", - "filename_short": "tests/too_many_digits.sol", - "is_dependency": false, - "lines": [ - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35 - ], - "starting_column": 1, - "ending_column": 2 - } - }, - "signature": "i()" - } - } - } - } - ] } ] } diff --git a/tests/expected_json/too_many_digits.too-many-digits.txt b/tests/expected_json/too_many_digits.too-many-digits.txt index ccc93c52d..4e5ecbaa1 100644 --- a/tests/expected_json/too_many_digits.too-many-digits.txt +++ b/tests/expected_json/too_many_digits.too-many-digits.txt @@ -1,4 +1,4 @@ -INFO:Detectors: + C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits: - x1 = 0x000001 C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits: @@ -9,7 +9,5 @@ C.f (tests/too_many_digits.sol#9-15) uses literals with too many digits: - x4 = 100000 C.h (tests/too_many_digits.sol#20-24) uses literals with too many digits: - x2 = 100000 -C.i (tests/too_many_digits.sol#29-33) uses literals with too many digits: - - x2 = 1000000000000 + 10000000000000 + 100000000000000 + 1000000000000000 + 10000000000000000 Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits -INFO:Slither:tests/too_many_digits.sol analyzed (1 contracts), 6 result(s) found +tests/too_many_digits.sol analyzed (1 contracts), 5 result(s) found From aec680c5feee219094357bef251cd07d7212411c Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 28 Jul 2019 11:04:33 +0200 Subject: [PATCH 34/47] Naming convetion: do not report incorrect naming convention for constructor --- slither/detectors/naming_convention/naming_convention.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 6c72f4438..f1f35507c 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -92,6 +92,8 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 results.append(json) for func in contract.functions_declared: + if func.is_constructor: + continue if not self.is_mixed_case(func.name): if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name): continue From 2ff1b296a103c8fa082836710f4f3f7623a67a00 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Jul 2019 11:16:54 +0200 Subject: [PATCH 35/47] Minor --- slither/solc_parsing/slitherSolc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index e6ac1c932..70e276f19 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -224,11 +224,11 @@ class SlitherSolc(Slither): else: father_constructors.append(self._contracts_by_id[i]) - except KeyError: - txt = 'A contract was not found, it is likely that your codebase contains muliple contracts with the same name' - txt += 'Truffle does not handle this case during compilation' - txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error' - txt += 'And update your code to remove the duplicate' + except KeyError as e: + txt = f'A contract was not found (id {e}), it is likely that your codebase contains muliple contracts with the same name. ' + txt += 'Truffle does not handle this case during compilation. ' + txt += 'Please read https://github.com/trailofbits/slither/wiki#keyerror-or-nonetype-error, ' + txt += 'and update your code to remove the duplicate' raise ParsingContractNotFound(txt) contract.setInheritance(ancestors, fathers, father_constructors) From 13528d26d94ef56c0c05f54540cc04accba14a8d Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 30 Jul 2019 17:48:49 +0200 Subject: [PATCH 36/47] Minor --- slither/solc_parsing/expressions/expression_parsing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 12837733c..e76deb8fd 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -476,7 +476,7 @@ def parse_expression(expression, caller_context): value = expression['attributes']['value'] if value: if 'subdenomination' in expression['attributes'] and expression['attributes']['subdenomination']: - subdenomination = expression['subdenomination'] + subdenomination = expression['attributes']['subdenomination'] elif value is None: # for literal declared as hex # see https://solidity.readthedocs.io/en/v0.4.25/types.html?highlight=hex#hexadecimal-literals From 58abfbb477ad976294fe5df580966b93030794ce Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 6 Aug 2019 10:27:01 +0200 Subject: [PATCH 37/47] Refactor reentrancy + API changes: - Move can reenter / can send eth logics to the IR level - Add create-based reentrancy (https://github.com/crytic/slither/issues/310) --- slither/core/cfg/node.py | 37 ++++++++++++++ slither/core/children/child_node.py | 4 ++ slither/core/declarations/function.py | 41 +++++++++++++-- slither/detectors/reentrancy/reentrancy.py | 39 +++------------ slither/slithir/operations/call.py | 13 +++++ slither/slithir/operations/high_level_call.py | 50 +++++++++++++++++++ slither/slithir/operations/library_call.py | 12 +++++ slither/slithir/operations/low_level_call.py | 14 ++++++ slither/slithir/operations/new_contract.py | 40 ++++++++++++++- slither/utils/function.py | 1 - 10 files changed, 214 insertions(+), 37 deletions(-) diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 4251b0bad..5bcb000b3 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -200,6 +200,10 @@ class Node(SourceMapping, ChildFunction): self._expression_vars_read = [] self._expression_calls = [] + # Computed on the fly, can be True of False + self._can_reenter = None + self._can_send_eth = None + ################################################################################### ################################################################################### # region General's properties @@ -402,6 +406,39 @@ class Node(SourceMapping, ChildFunction): def calls_as_expression(self): return list(self._expression_calls) + def can_reenter(self, callstack=None): + ''' + Check if the node can re-enter + Do not consider CREATE as potential re-enter, but check if the + destination's constructor can contain a call (recurs. follow nested CREATE) + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + Do not consider Send/Transfer as there is not enough gas + :param callstack: used internally to check for recursion + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_reenter is None: + self._can_reenter = False + for ir in self.irs: + if isinstance(ir, Call) and ir.can_reenter(callstack): + self._can_reenter = True + return True + return self._can_reenter + + def can_send_eth(self): + ''' + Check if the node can send eth + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_send_eth is None: + for ir in self.all_slithir_operations(): + if isinstance(ir, Call) and ir.can_send_eth(): + self._can_send_eth = True + return True + return self._can_reenter + # endregion ################################################################################### ################################################################################### diff --git a/slither/core/children/child_node.py b/slither/core/children/child_node.py index 8c16e3106..bd6fd4e6f 100644 --- a/slither/core/children/child_node.py +++ b/slither/core/children/child_node.py @@ -18,3 +18,7 @@ class ChildNode(object): @property def contract(self): return self.node.function.contract + + @property + def slither(self): + return self.contract.slither \ No newline at end of file diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index b9e981375..71f22ddfc 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -132,6 +132,10 @@ class Function(ChildContract, ChildInheritance, SourceMapping): self._function_type = None self._is_constructor = None + # Computed on the fly, can be True of False + self._can_reenter = None + self._can_send_eth = None + ################################################################################### ################################################################################### # region General properties @@ -169,11 +173,44 @@ class Function(ChildContract, ChildInheritance, SourceMapping): name, parameters, _ = self.signature return self.contract_declarer.name + '.' + name + '(' + ','.join(parameters) + ')' - @property def contains_assembly(self): return self._contains_assembly + def can_reenter(self, callstack=None): + ''' + Check if the function can re-enter + Follow internal calls. + Do not consider CREATE as potential re-enter, but check if the + destination's constructor can contain a call (recurs. follow nested CREATE) + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + Do not consider Send/Transfer as there is not enough gas + :param callstack: used internally to check for recursion + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_reenter is None: + self._can_reenter = False + for ir in self.all_slithir_operations(): + if isinstance(ir, Call) and ir.can_reenter(callstack): + self._can_reenter = True + return True + return self._can_reenter + + def can_send_eth(self): + ''' + Check if the function can send eth + :return bool: + ''' + from slither.slithir.operations import Call + if self._can_send_eth is None: + for ir in self.all_slithir_operations(): + if isinstance(ir, Call) and ir.can_send_eth(): + self._can_send_eth = True + return True + return self._can_reenter + @property def slither(self): return self.contract.slither @@ -1184,8 +1221,6 @@ class Function(ChildContract, ChildInheritance, SourceMapping): external_calls_as_expressions = [item for sublist in external_calls_as_expressions for item in sublist] self._external_calls_as_expressions = list(set(external_calls_as_expressions)) - - # endregion ################################################################################### ################################################################################### diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index be5f95dd9..add10ad58 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -11,9 +11,8 @@ from slither.core.expressions import UnaryOperation, UnaryOperationType from slither.detectors.abstract_detector import (AbstractDetector, DetectorClassification) from slither.slithir.operations import (HighLevelCall, LowLevelCall, - LibraryCall, + Call, Send, Transfer) -from slither.core.variables.variable import Variable def union_dict(d1, d2): d3 = {k: d1.get(k, set()) | d2.get(k, set()) for k in set(list(d1.keys()) + list(d2.keys()))} @@ -25,12 +24,6 @@ def dict_are_equal(d1, d2): return all(set(d1[k]) == set(d2[k]) for k in d1.keys()) class Reentrancy(AbstractDetector): -# This detector is not meant to be registered -# It is inherited by reentrancy variantsœ -# ARGUMENT = 'reentrancy' -# HELP = 'Reentrancy vulnerabilities' -# IMPACT = DetectorClassification.HIGH -# CONFIDENCE = DetectorClassification.HIGH KEY = 'REENTRANCY' @@ -43,27 +36,10 @@ class Reentrancy(AbstractDetector): - low level call - high level call - Do not consider Send/Transfer as there is not enough gas + """ for ir in irs: - if isinstance(ir, LowLevelCall): - return True - if isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall): - # If solidity >0.5, STATICCALL is used - if self.slither.solc_version and self.slither.solc_version.startswith('0.5.'): - if isinstance(ir.function, Function) and (ir.function.view or ir.function.pure): - continue - if isinstance(ir.function, Variable): - continue - # If there is a call to itself - # We can check that the function called is - # reentrancy-safe - if ir.destination == SolidityVariable('this'): - if isinstance(ir.function, Variable): - continue - if not ir.function.all_high_level_calls(): - if not ir.function.all_low_level_calls(): - continue + if isinstance(ir, Call) and ir.can_reenter(): return True return False @@ -73,9 +49,11 @@ class Reentrancy(AbstractDetector): Detect if the node can send eth """ for ir in irs: - if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): - if ir.call_value: - return True + if isinstance(ir, Call) and ir.can_send_eth(): + return True + # if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)): + # if ir.call_value: + # return True return False def _filter_if(self, node): @@ -174,7 +152,6 @@ class Reentrancy(AbstractDetector): self._explore(son, visited, node) sons = [sons[0]] - for son in sons: self._explore(son, visited) diff --git a/slither/slithir/operations/call.py b/slither/slithir/operations/call.py index 25d929c92..60d150f7f 100644 --- a/slither/slithir/operations/call.py +++ b/slither/slithir/operations/call.py @@ -15,3 +15,16 @@ class Call(Operation): def arguments(self, v): self._arguments = v + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return False + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return False \ No newline at end of file diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index 87a67a80f..38314c1aa 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -2,6 +2,7 @@ from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable from slither.core.declarations.solidity_variables import SolidityVariable +from slither.core.declarations.function import Function from slither.slithir.utils.utils import is_valid_lvalue from slither.slithir.variables.constant import Constant @@ -86,6 +87,55 @@ class HighLevelCall(Call, OperationWithLValue): def type_call(self): return self._type_call + ################################################################################### + ################################################################################### + # region Analyses + ################################################################################### + ################################################################################### + + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + :param callstack: check for recursion + :return: bool + ''' + # If solidity >0.5, STATICCALL is used + if self.slither.solc_version and self.slither.solc_version.startswith('0.5.'): + if isinstance(self.function, Function) and (self.function.view or self.function.pure): + return False + if isinstance(self.function, Variable): + return False + # If there is a call to itself + # We can check that the function called is + # reentrancy-safe + if self.destination == SolidityVariable('this'): + if isinstance(self.function, Variable): + return False + # In case of recursion, return False + callstack = [] if callstack is None else callstack + if self.function in callstack: + return False + callstack = callstack + [self.function] + if self.function.can_reenter(callstack): + return True + return True + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return self._call_value is not None + + # endregion + ################################################################################### + ################################################################################### + # region Built in + ################################################################################### + ################################################################################### + def __str__(self): value = '' gas = '' diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py index 76abfbe8a..ae3858834 100644 --- a/slither/slithir/operations/library_call.py +++ b/slither/slithir/operations/library_call.py @@ -9,6 +9,18 @@ class LibraryCall(HighLevelCall): def _check_destination(self, destination): assert isinstance(destination, (Contract)) + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + # In case of recursion, return False + callstack = [] if callstack is None else callstack + if self.function in callstack: + return False + callstack = callstack + [self.function] + return self.function.can_reenter(callstack) + def __str__(self): gas = '' if self.call_gas: diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index a55482ad7..2f8f03b12 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -54,6 +54,20 @@ class LowLevelCall(Call, OperationWithLValue): # remove None return self._unroll([x for x in all_read if x]) + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return True + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return self._call_value is not None + @property def destination(self): return self._destination diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py index 7326a659e..52060ba2b 100644 --- a/slither/slithir/operations/new_contract.py +++ b/slither/slithir/operations/new_contract.py @@ -31,16 +31,52 @@ class NewContract(Call, OperationWithLValue): def call_id(self, c): self._callid = c - @property def contract_name(self): return self._contract_name - @property def read(self): return self._unroll(self.arguments) + @property + def contract_created(self): + contract_name = self.contract_name + contract_instance = self.slither.get_contract_from_name(contract_name) + return contract_instance + + ################################################################################### + ################################################################################### + # region Analyses + ################################################################################### + ################################################################################### + + def can_reenter(self, callstack=None): + ''' + Must be called after slithIR analysis pass + For Solidity > 0.5, filter access to public variables and constant/pure/view + For call to this. check if the destination can re-enter + :param callstack: check for recursion + :return: bool + ''' + callstack = [] if callstack is None else callstack + constructor = self.contract_created.constructor + if constructor is None: + return False + if constructor in callstack: + return False + callstack = callstack + [constructor] + return constructor.can_reenter(callstack) + + def can_send_eth(self): + ''' + Must be called after slithIR analysis pass + :return: bool + ''' + return self._call_value is not None + + # endregion + def __str__(self): value = '' if self.call_value: diff --git a/slither/utils/function.py b/slither/utils/function.py index 7e142e342..a7448d48e 100644 --- a/slither/utils/function.py +++ b/slither/utils/function.py @@ -1,4 +1,3 @@ -import hashlib import sha3 def get_function_id(sig): From de4d863e193057f87b9dfb72f4b4b3c26a75dd9e Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 12 Aug 2019 10:01:03 +0200 Subject: [PATCH 38/47] Improve solc_version information --- slither/core/slither_core.py | 3 ++ slither/solc_parsing/declarations/contract.py | 2 +- slither/solc_parsing/slitherSolc.py | 45 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 2747416fd..ec1b16b28 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -77,6 +77,9 @@ class Slither(Context): @property def solc_version(self): """str: Solidity version.""" + if self.crytic_compile: + print(self.crytic_compile.compiler_version.version) + return self.crytic_compile.compiler_version.version return self._solc_version @property diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index eb6021966..d36e9150a 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -20,7 +20,7 @@ class ContractSolc04(Contract): def __init__(self, slitherSolc, data): - assert slitherSolc.solc_version.startswith('0.4') + #assert slitherSolc.solc_version.startswith('0.4') super(ContractSolc04, self).__init__() self.set_slither(slitherSolc) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index 70e276f19..8a695ae6a 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -104,30 +104,27 @@ class SlitherSolc(Slither): return for contract_data in data_loaded[self.get_children()]: - # if self.solc_version == '0.3': - # assert contract_data[self.get_key()] == 'Contract' - # contract = ContractSolc03(self, contract_data) - if self.solc_version == '0.4': - assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] - if contract_data[self.get_key()] == 'ContractDefinition': - contract = ContractSolc04(self, contract_data) - if 'src' in contract_data: - contract.set_offset(contract_data['src'], self) - self._contractsNotParsed.append(contract) - elif contract_data[self.get_key()] == 'PragmaDirective': - if self._is_compact_ast: - pragma = Pragma(contract_data['literals']) - else: - pragma = Pragma(contract_data['attributes']["literals"]) - pragma.set_offset(contract_data['src'], self) - self._pragma_directives.append(pragma) - elif contract_data[self.get_key()] == 'ImportDirective': - if self.is_compact_ast: - import_directive = Import(contract_data["absolutePath"]) - else: - import_directive = Import(contract_data['attributes']["absolutePath"]) - import_directive.set_offset(contract_data['src'], self) - self._import_directives.append(import_directive) + + assert contract_data[self.get_key()] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective'] + if contract_data[self.get_key()] == 'ContractDefinition': + contract = ContractSolc04(self, contract_data) + if 'src' in contract_data: + contract.set_offset(contract_data['src'], self) + self._contractsNotParsed.append(contract) + elif contract_data[self.get_key()] == 'PragmaDirective': + if self._is_compact_ast: + pragma = Pragma(contract_data['literals']) + else: + pragma = Pragma(contract_data['attributes']["literals"]) + pragma.set_offset(contract_data['src'], self) + self._pragma_directives.append(pragma) + elif contract_data[self.get_key()] == 'ImportDirective': + if self.is_compact_ast: + import_directive = Import(contract_data["absolutePath"]) + else: + import_directive = Import(contract_data['attributes']["absolutePath"]) + import_directive.set_offset(contract_data['src'], self) + self._import_directives.append(import_directive) def _parse_source_unit(self, data, filename): From 923d82acfc7ca4ac55f6db951d5c9f910b50af26 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 12 Aug 2019 10:07:10 +0200 Subject: [PATCH 39/47] Remove print --- slither/core/slither_core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index ec1b16b28..8106930ef 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -78,7 +78,6 @@ class Slither(Context): def solc_version(self): """str: Solidity version.""" if self.crytic_compile: - print(self.crytic_compile.compiler_version.version) return self.crytic_compile.compiler_version.version return self._solc_version From df0dacc2a931d25e70e78fffad82a3250bc86d0e Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 13 Aug 2019 08:26:14 +0200 Subject: [PATCH 40/47] Improve constant folding --- slither/visitors/expression/constants_folding.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 60568881b..372a383b5 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -99,6 +99,13 @@ class ConstantFolding(ExpressionVisitor): raise NotConstant def _post_tuple_expression(self, expression): + if expression.expressions: + if len(expression.expressions) == 1: + cf = ConstantFolding(expression.expressions[0], self._type) + expr = cf.result() + assert isinstance(expr, Literal) + set_val(expression, int(expr.value)) + return raise NotConstant def _post_type_conversion(self, expression): From 48879668e13c1ca26b42c0b2704151ac348a6a39 Mon Sep 17 00:00:00 2001 From: Josselin Date: Wed, 14 Aug 2019 15:59:24 +0200 Subject: [PATCH 41/47] Move .utils to slither.tools to allow use in external tools --- setup.py | 6 +++--- {utils => slither/tools}/__init__.py | 0 {utils => slither/tools}/demo/README.md | 0 {utils => slither/tools}/demo/__init__.py | 0 {utils => slither/tools}/demo/__main__.py | 0 {utils => slither/tools}/possible_paths/__init__.py | 0 {utils => slither/tools}/possible_paths/__main__.py | 0 {utils => slither/tools}/possible_paths/possible_paths.py | 0 {utils => slither/tools}/similarity/__init__.py | 0 {utils => slither/tools}/similarity/__main__.py | 0 {utils => slither/tools}/similarity/cache.py | 0 {utils => slither/tools}/similarity/encode.py | 0 {utils => slither/tools}/similarity/info.py | 0 {utils => slither/tools}/similarity/model.py | 0 {utils => slither/tools}/similarity/plot.py | 0 {utils => slither/tools}/similarity/similarity.py | 0 {utils => slither/tools}/similarity/test.py | 0 {utils => slither/tools}/similarity/train.py | 0 {utils => slither/tools}/upgradeability/__init__.py | 0 {utils => slither/tools}/upgradeability/__main__.py | 0 .../tools}/upgradeability/check_initialization.py | 2 +- .../tools}/upgradeability/compare_function_ids.py | 0 .../tools}/upgradeability/compare_variables_order.py | 0 slither/tools/utils/__init__.py | 0 24 files changed, 4 insertions(+), 4 deletions(-) rename {utils => slither/tools}/__init__.py (100%) rename {utils => slither/tools}/demo/README.md (100%) rename {utils => slither/tools}/demo/__init__.py (100%) rename {utils => slither/tools}/demo/__main__.py (100%) rename {utils => slither/tools}/possible_paths/__init__.py (100%) rename {utils => slither/tools}/possible_paths/__main__.py (100%) rename {utils => slither/tools}/possible_paths/possible_paths.py (100%) rename {utils => slither/tools}/similarity/__init__.py (100%) rename {utils => slither/tools}/similarity/__main__.py (100%) rename {utils => slither/tools}/similarity/cache.py (100%) rename {utils => slither/tools}/similarity/encode.py (100%) rename {utils => slither/tools}/similarity/info.py (100%) rename {utils => slither/tools}/similarity/model.py (100%) rename {utils => slither/tools}/similarity/plot.py (100%) rename {utils => slither/tools}/similarity/similarity.py (100%) rename {utils => slither/tools}/similarity/test.py (100%) rename {utils => slither/tools}/similarity/train.py (100%) rename {utils => slither/tools}/upgradeability/__init__.py (100%) rename {utils => slither/tools}/upgradeability/__main__.py (100%) rename {utils => slither/tools}/upgradeability/check_initialization.py (99%) rename {utils => slither/tools}/upgradeability/compare_function_ids.py (100%) rename {utils => slither/tools}/upgradeability/compare_variables_order.py (100%) create mode 100644 slither/tools/utils/__init__.py diff --git a/setup.py b/setup.py index cd1248087..e013405e6 100644 --- a/setup.py +++ b/setup.py @@ -17,9 +17,9 @@ setup( entry_points={ 'console_scripts': [ 'slither = slither.__main__:main', - 'slither-check-upgradeability = utils.upgradeability.__main__:main', - 'slither-find-paths = utils.possible_paths.__main__:main', - 'slither-simil = utils.similarity.__main__:main' + 'slither-check-upgradeability = slither.tools.upgradeability.__main__:main', + 'slither-find-paths = slither.tools.possible_paths.__main__:main', + 'slither-simil = slither.tools.similarity.__main__:main' ] } ) diff --git a/utils/__init__.py b/slither/tools/__init__.py similarity index 100% rename from utils/__init__.py rename to slither/tools/__init__.py diff --git a/utils/demo/README.md b/slither/tools/demo/README.md similarity index 100% rename from utils/demo/README.md rename to slither/tools/demo/README.md diff --git a/utils/demo/__init__.py b/slither/tools/demo/__init__.py similarity index 100% rename from utils/demo/__init__.py rename to slither/tools/demo/__init__.py diff --git a/utils/demo/__main__.py b/slither/tools/demo/__main__.py similarity index 100% rename from utils/demo/__main__.py rename to slither/tools/demo/__main__.py diff --git a/utils/possible_paths/__init__.py b/slither/tools/possible_paths/__init__.py similarity index 100% rename from utils/possible_paths/__init__.py rename to slither/tools/possible_paths/__init__.py diff --git a/utils/possible_paths/__main__.py b/slither/tools/possible_paths/__main__.py similarity index 100% rename from utils/possible_paths/__main__.py rename to slither/tools/possible_paths/__main__.py diff --git a/utils/possible_paths/possible_paths.py b/slither/tools/possible_paths/possible_paths.py similarity index 100% rename from utils/possible_paths/possible_paths.py rename to slither/tools/possible_paths/possible_paths.py diff --git a/utils/similarity/__init__.py b/slither/tools/similarity/__init__.py similarity index 100% rename from utils/similarity/__init__.py rename to slither/tools/similarity/__init__.py diff --git a/utils/similarity/__main__.py b/slither/tools/similarity/__main__.py similarity index 100% rename from utils/similarity/__main__.py rename to slither/tools/similarity/__main__.py diff --git a/utils/similarity/cache.py b/slither/tools/similarity/cache.py similarity index 100% rename from utils/similarity/cache.py rename to slither/tools/similarity/cache.py diff --git a/utils/similarity/encode.py b/slither/tools/similarity/encode.py similarity index 100% rename from utils/similarity/encode.py rename to slither/tools/similarity/encode.py diff --git a/utils/similarity/info.py b/slither/tools/similarity/info.py similarity index 100% rename from utils/similarity/info.py rename to slither/tools/similarity/info.py diff --git a/utils/similarity/model.py b/slither/tools/similarity/model.py similarity index 100% rename from utils/similarity/model.py rename to slither/tools/similarity/model.py diff --git a/utils/similarity/plot.py b/slither/tools/similarity/plot.py similarity index 100% rename from utils/similarity/plot.py rename to slither/tools/similarity/plot.py diff --git a/utils/similarity/similarity.py b/slither/tools/similarity/similarity.py similarity index 100% rename from utils/similarity/similarity.py rename to slither/tools/similarity/similarity.py diff --git a/utils/similarity/test.py b/slither/tools/similarity/test.py similarity index 100% rename from utils/similarity/test.py rename to slither/tools/similarity/test.py diff --git a/utils/similarity/train.py b/slither/tools/similarity/train.py similarity index 100% rename from utils/similarity/train.py rename to slither/tools/similarity/train.py diff --git a/utils/upgradeability/__init__.py b/slither/tools/upgradeability/__init__.py similarity index 100% rename from utils/upgradeability/__init__.py rename to slither/tools/upgradeability/__init__.py diff --git a/utils/upgradeability/__main__.py b/slither/tools/upgradeability/__main__.py similarity index 100% rename from utils/upgradeability/__main__.py rename to slither/tools/upgradeability/__main__.py diff --git a/utils/upgradeability/check_initialization.py b/slither/tools/upgradeability/check_initialization.py similarity index 99% rename from utils/upgradeability/check_initialization.py rename to slither/tools/upgradeability/check_initialization.py index 22885ed31..c81d7a249 100644 --- a/utils/upgradeability/check_initialization.py +++ b/slither/tools/upgradeability/check_initialization.py @@ -11,7 +11,7 @@ class MultipleInitTarget(Exception): pass def _get_initialize_functions(contract): - return [f for f in contract.functions if f.name == 'initialize'] + return [f for f in contract.functions if f.name == 'initialize' and f.is_implemented] def _get_all_internal_calls(function): all_ir = function.all_slithir_operations() diff --git a/utils/upgradeability/compare_function_ids.py b/slither/tools/upgradeability/compare_function_ids.py similarity index 100% rename from utils/upgradeability/compare_function_ids.py rename to slither/tools/upgradeability/compare_function_ids.py diff --git a/utils/upgradeability/compare_variables_order.py b/slither/tools/upgradeability/compare_variables_order.py similarity index 100% rename from utils/upgradeability/compare_variables_order.py rename to slither/tools/upgradeability/compare_variables_order.py diff --git a/slither/tools/utils/__init__.py b/slither/tools/utils/__init__.py new file mode 100644 index 000000000..e69de29bb From accb6875445d2eae578d0ff59e6d9eddab7e9d81 Mon Sep 17 00:00:00 2001 From: Abhimanyu121 Date: Fri, 19 Jul 2019 14:31:31 +0530 Subject: [PATCH 42/47] New Printer: Prints Contructor call sequence inital commit it just gets instance of constructor of every contract following c3 linearization made proper constructor printer improved code readablity added check when there are no contructors added link to wiki updated argument to "constructor-calls" --- examples/printers/constructors.sol | 26 ++++++++++ slither/printers/all_printers.py | 1 + slither/printers/summary/constructor_calls.py | 47 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 examples/printers/constructors.sol create mode 100644 slither/printers/summary/constructor_calls.py diff --git a/examples/printers/constructors.sol b/examples/printers/constructors.sol new file mode 100644 index 000000000..845039032 --- /dev/null +++ b/examples/printers/constructors.sol @@ -0,0 +1,26 @@ +pragma solidity >=0.4.22 <0.6.0; +contract test{ + uint a; + constructor()public{ + a =5; + } + +} +contract test2 is test{ + constructor()public{ + a=10; + } +} +contract test3 is test2{ + address owner; + bytes32 name; + constructor(bytes32 _name)public{ + owner = msg.sender; + name = _name; + a=20; + } + function print() public returns(uint b){ + b=a; + + } +} \ No newline at end of file diff --git a/slither/printers/all_printers.py b/slither/printers/all_printers.py index 3c6ad58d3..25f7a3ee0 100644 --- a/slither/printers/all_printers.py +++ b/slither/printers/all_printers.py @@ -13,3 +13,4 @@ from .summary.variable_order import VariableOrder from .summary.data_depenency import DataDependency from .summary.modifier_calls import Modifiers from .summary.require_calls import RequireOrAssert +from .summary.constructor_calls import ConstructorPrinter \ No newline at end of file diff --git a/slither/printers/summary/constructor_calls.py b/slither/printers/summary/constructor_calls.py new file mode 100644 index 000000000..66e64d626 --- /dev/null +++ b/slither/printers/summary/constructor_calls.py @@ -0,0 +1,47 @@ +""" + Module printing summary of the contract +""" +from slither.printers.abstract_printer import AbstractPrinter + + + +class ConstructorPrinter(AbstractPrinter): + WIKI = 'https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls' + ARGUMENT = 'constructor-calls' + HELP = 'Print the constructors executed' + + def _get_soruce_code(self,cst): + src_mapping = cst.source_mapping + content= self.slither.source_code[src_mapping['filename_absolute']] + start = src_mapping['start'] + end = src_mapping['start'] + src_mapping['length'] + initial_space = src_mapping['starting_column'] + return ' ' * initial_space + content[start:end] + + def output(self,_filename): + for contract in self.contracts: + stack_name = [] + stack_definition = [] + print("\n\nContact Name:",contract.name) + print(" Constructor Call Sequence: ", sep=' ', end='', flush=True) + cst = contract.constructors_declared + if cst: + stack_name.append(contract.name) + stack_definition.append(self._get_soruce_code(cst)) + for inherited_contract in contract.inheritance: + cst = inherited_contract.constructors_declared + if cst: + stack_name.append(inherited_contract.name) + stack_definition.append(self._get_soruce_code(cst)) + if len(stack_name)>0: + print(" ",stack_name[len(stack_name)-1], sep=' ', end='', flush=True) + count = len(stack_name)-2; + while count>=0: + print("-->",stack_name[count], sep=' ', end='', flush=True) + count= count-1; + print("\n Constructor Definitions:") + count = len(stack_definition)-1 + while count>=0: + print("\n Contract name:", stack_name[count]) + print ("\n", stack_definition[count]) + count = count-1; From c417c04bf0c4c791924e324fd27ae04ddf6ae4d2 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 16 Aug 2019 17:38:44 +0200 Subject: [PATCH 43/47] v0.6.5 --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e013405e6..ae6194d6c 100644 --- a/setup.py +++ b/setup.py @@ -5,13 +5,13 @@ setup( description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/crytic/slither', author='Trail of Bits', - version='0.6.4', + version='0.6.5', packages=find_packages(), python_requires='>=3.6', install_requires=['prettytable>=0.7.2', 'pysha3>=1.0.2', - 'crytic-compile'], - dependency_links=['git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile'], + 'crytic-compile>=0.1.3'], + #dependency_links=['git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile'], license='AGPL-3.0', long_description=open('README.md').read(), entry_points={ From e2ed64c97bf8d42b89f860b95e5842d041b8a99e Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 16 Aug 2019 19:50:09 +0200 Subject: [PATCH 44/47] Fix reentrancy detector --- slither/slithir/operations/send.py | 3 +++ slither/slithir/operations/transfer.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/slither/slithir/operations/send.py b/slither/slithir/operations/send.py index 201de989a..690459cf2 100644 --- a/slither/slithir/operations/send.py +++ b/slither/slithir/operations/send.py @@ -17,6 +17,9 @@ class Send(Call, OperationWithLValue): self._call_value = value + def can_send_eth(self): + return True + @property def call_value(self): return self._call_value diff --git a/slither/slithir/operations/transfer.py b/slither/slithir/operations/transfer.py index b334d02ce..b46d96b73 100644 --- a/slither/slithir/operations/transfer.py +++ b/slither/slithir/operations/transfer.py @@ -11,6 +11,8 @@ class Transfer(Call): self._call_value = value + def can_send_eth(self): + return True @property def call_value(self): From b43d4c92223b1c9418b374bb0a0ad72d7493e69a Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 16 Aug 2019 20:26:24 +0200 Subject: [PATCH 45/47] v0.6.6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ae6194d6c..8cb5f1f77 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( description='Slither is a Solidity static analysis framework written in Python 3.', url='https://github.com/crytic/slither', author='Trail of Bits', - version='0.6.5', + version='0.6.6', packages=find_packages(), python_requires='>=3.6', install_requires=['prettytable>=0.7.2', From d3ba27baea26b8dd3388c8469f5943ac13b7df34 Mon Sep 17 00:00:00 2001 From: agroce Date: Tue, 20 Aug 2019 10:23:19 -0700 Subject: [PATCH 46/47] ignore naming convention restriction on echidna_ and crytic_ functions --- slither/detectors/naming_convention/naming_convention.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index f1f35507c..a4237e07b 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -10,6 +10,7 @@ class NamingConvention(AbstractDetector): Exceptions: - Allow constant variables name/symbol/decimals to be lowercase (ERC20) - Allow '_' at the beggining of the mixed_case match for private variables and unused parameters + - Ignore echidna properties (functions with names starting 'echidna_' or 'crytic_' """ ARGUMENT = 'naming-convention' @@ -97,6 +98,8 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_mixed_case(func.name): if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name): continue + if (func.name.find("echidna_") == 0) or (func.name.find("crytic_") == 0): + continue info = "Function '{}' ({}) is not in mixedCase\n" info = info.format(func.canonical_name, func.source_mapping_str) From 5468bc1bb1fedbbc3c32ef3d38cbed29756f18cc Mon Sep 17 00:00:00 2001 From: Alex Groce Date: Wed, 21 Aug 2019 04:42:28 -0700 Subject: [PATCH 47/47] Change to startswith --- slither/detectors/naming_convention/naming_convention.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index a4237e07b..f19fad0fc 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -98,7 +98,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_mixed_case(func.name): if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name): continue - if (func.name.find("echidna_") == 0) or (func.name.find("crytic_") == 0): + if func.name.startswith("echidna_") or func.name.startswith("crytic_"): continue info = "Function '{}' ({}) is not in mixedCase\n" info = info.format(func.canonical_name, func.source_mapping_str)