diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..8e767e47c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +# 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. + +# 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). diff --git a/README.md b/README.md index 5d0ff6c85..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. @@ -113,7 +115,7 @@ $ pip install slither-analyzer ### Using Git ```bash -$ git clone https://github.com/trailofbits/slither.git && cd slither +$ git clone https://github.com/crytic/slither.git && cd slither $ python setup.py install ``` 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/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..620fc0b0c 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_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 diff --git a/scripts/travis_test_5.sh b/scripts/travis_test_5.sh index 7224638d7..4b3928b5c 100755 --- a/scripts/travis_test_5.sh +++ b/scripts/travis_test_5.sh @@ -69,6 +69,7 @@ test_slither(){ } +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" diff --git a/setup.py b/setup.py index 66de10fdd..921510d17 100644 --- a/setup.py +++ b/setup.py @@ -14,10 +14,10 @@ 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-format = utils.slither_format.__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' + 'slither-format = slither.tools.slither_format.__main__:main' ] } ) diff --git a/slither/__main__.py b/slither/__main__.py index 336b8d65d..e5196330e 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -11,6 +11,7 @@ import traceback from pkg_resources import iter_entry_points, require from crytic_compile import cryticparser +from crytic_compile.platform.standard import generate_standard_export from slither.detectors import all_detectors from slither.detectors.abstract_detector import (AbstractDetector, @@ -18,12 +19,13 @@ 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_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, + output_detectors_json, output_printers, output_printers_json, output_to_markdown, output_wiki, defaults_flag_in_config, - read_config_file) -from crytic_compile import is_supported + read_config_file, JSON_OUTPUT_TYPES) +from crytic_compile import compile_all, is_supported from slither.exceptions import SlitherException logging.basicConfig() @@ -35,7 +37,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. @@ -45,13 +48,26 @@ 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, + slither = Slither(target, ast_format=ast, **vars(args)) return _process(slither, detector_classes, printer_classes) + +def process_all(target, args, detector_classes, printer_classes): + compilations = compile_all(target, **vars(args)) + slither_instances = [] + results = [] + analyzed_contracts_count = 0 + for compilation in compilations: + (slither, current_results, current_analyzed_count) = process_single(compilation, args, detector_classes, printer_classes) + results.extend(current_results) + slither_instances.append(slither) + analyzed_contracts_count += current_analyzed_count + return slither_instances, results, analyzed_contracts_count + + def _process(slither, detector_classes, printer_classes): for detector_cls in detector_classes: slither.register_detector(detector_cls) @@ -72,10 +88,10 @@ 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): +def process_from_asts(filenames, args, detector_classes, printer_classes): all_contracts = [] for filename in filenames: @@ -83,11 +99,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, - filter_paths=parse_filter_paths(args), - **vars(args)) + return process_single(all_contracts, args, detector_classes, printer_classes) + - return _process(slither, detector_classes, printer_classes) # endregion ################################################################################### @@ -97,26 +111,15 @@ 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) + # Determine if we should output to stdout if filename is None: # Write json to console print(json.dumps(json_result)) @@ -271,7 +274,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, ' @@ -337,12 +340,17 @@ 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('--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', @@ -383,7 +391,6 @@ def parse_args(detector_classes, printer_classes): action=OutputMarkdown, default=False) - group_misc.add_argument('--checklist', help=argparse.SUPPRESS, action='store_true', @@ -423,6 +430,14 @@ def parse_args(detector_classes, printer_classes): args = parser.parse_args() read_config_file(args) + args.filter_paths = parse_filter_paths(args) + + # 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): @@ -434,7 +449,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 = output_detectors_json(detectors) + print(json.dumps(detector_types_json)) parser.exit() class ListPrinters(argparse.Action): @@ -501,10 +517,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 disable any logging. - stdout_json = args.json == "-" - if stdout_json: - logging.disable(logging.CRITICAL) + # 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: + StandardOutputCapture.enable(outputting_json_stdout) printer_classes = choose_printers(args, all_printer_classes) detector_classes = choose_detectors(args, all_detector_classes) @@ -540,33 +562,56 @@ 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 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: 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) + slither_instances = [] + if args.splitted: + (slither_instance, results, number_contracts) = process_from_asts(filenames, args, detector_classes, printer_classes) + slither_instances.append(slither_instance) else: for filename in filenames: - (results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes) + (slither_instance, results_tmp, number_contracts_tmp) = process_single(filename, args, detector_classes, printer_classes) number_contracts += number_contracts_tmp results += results_tmp + slither_instances.append(slither_instance) + # Rely on CryticCompile to discern the underlying type of compilations. else: - raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) - - if args.json: - output_json(results, None if stdout_json else args.json) + (slither_instances, results, number_contracts) = process_all(filename, args, detector_classes, printer_classes) + + # Determine if we are outputting JSON + if outputting_json: + # Add our compilation information to JSON + if 'compilations' in args.json_types: + compilation_results = [] + for slither_instance in slither_instances: + compilation_results.append(generate_standard_export(slither_instance.crytic_compile)) + json_results['compilations'] = compilation_results + + # 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 'list-detectors' in args.json_types: + detectors, _ = get_detectors_and_printers() + 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: output_results_to_markdown(results) + # Dont print the number of result for printers if number_contracts == 0: logger.warn(red('No contract was analyzed')) @@ -576,27 +621,33 @@ 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: + if 'console' in args.json_types: + 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) + + # Exit with the appropriate status code + if output_error: sys.exit(-1) - + else: + exit(results) if __name__ == '__main__': diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 8a94a7caa..5bcb000b3 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 @@ -183,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 @@ -385,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 7243a7c03..71f22ddfc 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 @@ -106,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 @@ -117,7 +147,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' @@ -143,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 @@ -403,7 +466,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 @@ -1158,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 ################################################################################### ################################################################################### @@ -1266,10 +1327,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/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/core/slither_core.py b/slither/core/slither_core.py index 94bd7eb35..8106930ef 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 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: + self.source_code[path] = f.read() # endregion ################################################################################### @@ -74,6 +77,8 @@ class Slither(Context): @property def solc_version(self): """str: Solidity version.""" + if self.crytic_compile: + return self.crytic_compile.compiler_version.version return self._solc_version @property diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index 9217b12fc..564b2f0e1 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 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, + 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/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/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 6c72f4438..f19fad0fc 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' @@ -92,9 +93,13 @@ 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 + 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) diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py new file mode 100644 index 000000000..756f88538 --- /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/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/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/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; 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/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)) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 975280cfd..b06073ce4 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 @@ -621,6 +621,9 @@ def extract_tmp_call(ins, contract): return e 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/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/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/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): 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/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index e931dddb7..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) @@ -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) @@ -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 diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 5c95cc231..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 @@ -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 @@ -219,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() @@ -902,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 ################################################################################### @@ -970,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) @@ -981,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/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 0d95dbb6e..92314a80a 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 @@ -499,14 +461,19 @@ def parse_expression(expression, caller_context): assignement.set_offset(expression['src'], caller_context.slither) 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'] @@ -519,7 +486,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['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 @@ -540,7 +507,7 @@ def parse_expression(expression, caller_context): type = ElementaryType('address') else: type = ElementaryType('string') - literal = Literal(value, type) + literal = Literal(value, type, subdenomination) literal.set_offset(expression['src'], caller_context.slither) return literal @@ -590,7 +557,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) diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index e6ac1c932..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): @@ -224,11 +221,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) 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/slither/tools/demo/README.md b/slither/tools/demo/README.md new file mode 100644 index 000000000..00bdec0b4 --- /dev/null +++ b/slither/tools/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/possible_paths/__init__.py b/slither/tools/demo/__init__.py similarity index 100% rename from utils/possible_paths/__init__.py rename to slither/tools/demo/__init__.py diff --git a/slither/tools/demo/__main__.py b/slither/tools/demo/__main__.py new file mode 100644 index 000000000..4bee3b449 --- /dev/null +++ b/slither/tools/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() diff --git a/utils/slither_format/__init__.py b/slither/tools/possible_paths/__init__.py similarity index 100% rename from utils/slither_format/__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/slither_format/.gitignore b/slither/tools/slither_format/.gitignore similarity index 100% rename from utils/slither_format/.gitignore rename to slither/tools/slither_format/.gitignore diff --git a/utils/slither_format/README.md b/slither/tools/slither_format/README.md similarity index 100% rename from utils/slither_format/README.md rename to slither/tools/slither_format/README.md diff --git a/utils/slither_format/formatters/__init__.py b/slither/tools/slither_format/__init__.py similarity index 100% rename from utils/slither_format/formatters/__init__.py rename to slither/tools/slither_format/__init__.py diff --git a/utils/slither_format/__main__.py b/slither/tools/slither_format/__main__.py similarity index 100% rename from utils/slither_format/__main__.py rename to slither/tools/slither_format/__main__.py diff --git a/utils/slither_format/exceptions.py b/slither/tools/slither_format/exceptions.py similarity index 100% rename from utils/slither_format/exceptions.py rename to slither/tools/slither_format/exceptions.py diff --git a/utils/slither_format/utils/__init__.py b/slither/tools/slither_format/formatters/__init__.py similarity index 100% rename from utils/slither_format/utils/__init__.py rename to slither/tools/slither_format/formatters/__init__.py diff --git a/utils/slither_format/formatters/constable_states.py b/slither/tools/slither_format/formatters/constable_states.py similarity index 100% rename from utils/slither_format/formatters/constable_states.py rename to slither/tools/slither_format/formatters/constable_states.py diff --git a/utils/slither_format/formatters/constant_function.py b/slither/tools/slither_format/formatters/constant_function.py similarity index 100% rename from utils/slither_format/formatters/constant_function.py rename to slither/tools/slither_format/formatters/constant_function.py diff --git a/utils/slither_format/formatters/external_function.py b/slither/tools/slither_format/formatters/external_function.py similarity index 100% rename from utils/slither_format/formatters/external_function.py rename to slither/tools/slither_format/formatters/external_function.py diff --git a/utils/slither_format/formatters/naming_convention.py b/slither/tools/slither_format/formatters/naming_convention.py similarity index 100% rename from utils/slither_format/formatters/naming_convention.py rename to slither/tools/slither_format/formatters/naming_convention.py diff --git a/utils/slither_format/formatters/pragma.py b/slither/tools/slither_format/formatters/pragma.py similarity index 100% rename from utils/slither_format/formatters/pragma.py rename to slither/tools/slither_format/formatters/pragma.py diff --git a/utils/slither_format/formatters/solc_version.py b/slither/tools/slither_format/formatters/solc_version.py similarity index 100% rename from utils/slither_format/formatters/solc_version.py rename to slither/tools/slither_format/formatters/solc_version.py diff --git a/utils/slither_format/formatters/unused_state.py b/slither/tools/slither_format/formatters/unused_state.py similarity index 100% rename from utils/slither_format/formatters/unused_state.py rename to slither/tools/slither_format/formatters/unused_state.py diff --git a/utils/slither_format/slither_format.py b/slither/tools/slither_format/slither_format.py similarity index 100% rename from utils/slither_format/slither_format.py rename to slither/tools/slither_format/slither_format.py diff --git a/utils/slither_format/tests/.gitignore b/slither/tools/slither_format/tests/.gitignore similarity index 100% rename from utils/slither_format/tests/.gitignore rename to slither/tools/slither_format/tests/.gitignore diff --git a/utils/upgradeability/__init__.py b/slither/tools/slither_format/tests/__init__.py similarity index 100% rename from utils/upgradeability/__init__.py rename to slither/tools/slither_format/tests/__init__.py diff --git a/utils/slither_format/tests/real_world/0x0000000000b3F879cb30FE243b4Dfee438691c04_GasToken2.sol b/slither/tools/slither_format/tests/real_world/0x0000000000b3F879cb30FE243b4Dfee438691c04_GasToken2.sol similarity index 100% rename from utils/slither_format/tests/real_world/0x0000000000b3F879cb30FE243b4Dfee438691c04_GasToken2.sol rename to slither/tools/slither_format/tests/real_world/0x0000000000b3F879cb30FE243b4Dfee438691c04_GasToken2.sol diff --git a/utils/slither_format/tests/real_world/0x006bea43baa3f7a6f765f14f10a1a1b08334ef45_StoxSmartToken.sol b/slither/tools/slither_format/tests/real_world/0x006bea43baa3f7a6f765f14f10a1a1b08334ef45_StoxSmartToken.sol similarity index 100% rename from utils/slither_format/tests/real_world/0x006bea43baa3f7a6f765f14f10a1a1b08334ef45_StoxSmartToken.sol rename to slither/tools/slither_format/tests/real_world/0x006bea43baa3f7a6f765f14f10a1a1b08334ef45_StoxSmartToken.sol diff --git a/utils/slither_format/tests/real_world/0x0235fe624e044a05eed7a43e16e3083bc8a4287a_OriginalToken.sol b/slither/tools/slither_format/tests/real_world/0x0235fe624e044a05eed7a43e16e3083bc8a4287a_OriginalToken.sol similarity index 100% rename from utils/slither_format/tests/real_world/0x0235fe624e044a05eed7a43e16e3083bc8a4287a_OriginalToken.sol rename to slither/tools/slither_format/tests/real_world/0x0235fe624e044a05eed7a43e16e3083bc8a4287a_OriginalToken.sol diff --git a/utils/slither_format/tests/real_world/0x05cf67329a262818e67c080e9d511a34d36152c0_MultiSigWallet.sol b/slither/tools/slither_format/tests/real_world/0x05cf67329a262818e67c080e9d511a34d36152c0_MultiSigWallet.sol similarity index 100% rename from utils/slither_format/tests/real_world/0x05cf67329a262818e67c080e9d511a34d36152c0_MultiSigWallet.sol rename to slither/tools/slither_format/tests/real_world/0x05cf67329a262818e67c080e9d511a34d36152c0_MultiSigWallet.sol diff --git a/utils/slither_format/tests/real_world/0x05f4a42e251f2d52b8ed15e9fedaacfcef1fad27_ZilliqaToken.sol b/slither/tools/slither_format/tests/real_world/0x05f4a42e251f2d52b8ed15e9fedaacfcef1fad27_ZilliqaToken.sol similarity index 100% rename from utils/slither_format/tests/real_world/0x05f4a42e251f2d52b8ed15e9fedaacfcef1fad27_ZilliqaToken.sol rename to slither/tools/slither_format/tests/real_world/0x05f4a42e251f2d52b8ed15e9fedaacfcef1fad27_ZilliqaToken.sol diff --git a/utils/slither_format/tests/real_world/0x06012c8cf97bead5deae237070f9587f8e7a266d_KittyCore.sol b/slither/tools/slither_format/tests/real_world/0x06012c8cf97bead5deae237070f9587f8e7a266d_KittyCore.sol similarity index 100% rename from utils/slither_format/tests/real_world/0x06012c8cf97bead5deae237070f9587f8e7a266d_KittyCore.sol rename to slither/tools/slither_format/tests/real_world/0x06012c8cf97bead5deae237070f9587f8e7a266d_KittyCore.sol diff --git a/utils/slither_format/tests/real_world/0x5d0d76787d9d564061dd23f8209f804a3b8ad2f2_FoMo3Dlong.sol b/slither/tools/slither_format/tests/real_world/0x5d0d76787d9d564061dd23f8209f804a3b8ad2f2_FoMo3Dlong.sol similarity index 100% rename from utils/slither_format/tests/real_world/0x5d0d76787d9d564061dd23f8209f804a3b8ad2f2_FoMo3Dlong.sol rename to slither/tools/slither_format/tests/real_world/0x5d0d76787d9d564061dd23f8209f804a3b8ad2f2_FoMo3Dlong.sol diff --git a/utils/slither_format/tests/real_world/0xbf45f4280cfbe7c2d2515a7d984b8c71c15e82b7_EnclavesDEXProxy.sol b/slither/tools/slither_format/tests/real_world/0xbf45f4280cfbe7c2d2515a7d984b8c71c15e82b7_EnclavesDEXProxy.sol similarity index 100% rename from utils/slither_format/tests/real_world/0xbf45f4280cfbe7c2d2515a7d984b8c71c15e82b7_EnclavesDEXProxy.sol rename to slither/tools/slither_format/tests/real_world/0xbf45f4280cfbe7c2d2515a7d984b8c71c15e82b7_EnclavesDEXProxy.sol diff --git a/utils/slither_format/tests/real_world/0xc6725ae749677f21e4d8f85f41cfb6de49b9db29_BancorConverter.sol b/slither/tools/slither_format/tests/real_world/0xc6725ae749677f21e4d8f85f41cfb6de49b9db29_BancorConverter.sol similarity index 100% rename from utils/slither_format/tests/real_world/0xc6725ae749677f21e4d8f85f41cfb6de49b9db29_BancorConverter.sol rename to slither/tools/slither_format/tests/real_world/0xc6725ae749677f21e4d8f85f41cfb6de49b9db29_BancorConverter.sol diff --git a/utils/slither_format/tests/real_world/0xf5ed2dc77f0d1ea7f106ecbd1850e406adc41b51_OceanToken.sol b/slither/tools/slither_format/tests/real_world/0xf5ed2dc77f0d1ea7f106ecbd1850e406adc41b51_OceanToken.sol similarity index 100% rename from utils/slither_format/tests/real_world/0xf5ed2dc77f0d1ea7f106ecbd1850e406adc41b51_OceanToken.sol rename to slither/tools/slither_format/tests/real_world/0xf5ed2dc77f0d1ea7f106ecbd1850e406adc41b51_OceanToken.sol diff --git a/utils/slither_format/tests/runSlitherFormat.py b/slither/tools/slither_format/tests/runSlitherFormat.py similarity index 100% rename from utils/slither_format/tests/runSlitherFormat.py rename to slither/tools/slither_format/tests/runSlitherFormat.py diff --git a/utils/slither_format/tests/run_all_tests.py b/slither/tools/slither_format/tests/run_all_tests.py similarity index 100% rename from utils/slither_format/tests/run_all_tests.py rename to slither/tools/slither_format/tests/run_all_tests.py diff --git a/utils/slither_format/tests/test_constable_states.py b/slither/tools/slither_format/tests/test_constable_states.py similarity index 100% rename from utils/slither_format/tests/test_constable_states.py rename to slither/tools/slither_format/tests/test_constable_states.py diff --git a/utils/slither_format/tests/test_constant_function.py b/slither/tools/slither_format/tests/test_constant_function.py similarity index 100% rename from utils/slither_format/tests/test_constant_function.py rename to slither/tools/slither_format/tests/test_constant_function.py diff --git a/utils/slither_format/tests/test_data/const_state_variables.sol b/slither/tools/slither_format/tests/test_data/const_state_variables.sol similarity index 100% rename from utils/slither_format/tests/test_data/const_state_variables.sol rename to slither/tools/slither_format/tests/test_data/const_state_variables.sol diff --git a/utils/slither_format/tests/test_data/constant-0.5.1.sol b/slither/tools/slither_format/tests/test_data/constant-0.5.1.sol similarity index 100% rename from utils/slither_format/tests/test_data/constant-0.5.1.sol rename to slither/tools/slither_format/tests/test_data/constant-0.5.1.sol diff --git a/utils/slither_format/tests/test_data/constant.sol b/slither/tools/slither_format/tests/test_data/constant.sol similarity index 100% rename from utils/slither_format/tests/test_data/constant.sol rename to slither/tools/slither_format/tests/test_data/constant.sol diff --git a/utils/slither_format/tests/test_data/detector_combinations.sol b/slither/tools/slither_format/tests/test_data/detector_combinations.sol similarity index 100% rename from utils/slither_format/tests/test_data/detector_combinations.sol rename to slither/tools/slither_format/tests/test_data/detector_combinations.sol diff --git a/utils/slither_format/tests/test_data/external_function.sol b/slither/tools/slither_format/tests/test_data/external_function.sol similarity index 100% rename from utils/slither_format/tests/test_data/external_function.sol rename to slither/tools/slither_format/tests/test_data/external_function.sol diff --git a/utils/slither_format/tests/test_data/external_function_2.sol b/slither/tools/slither_format/tests/test_data/external_function_2.sol similarity index 100% rename from utils/slither_format/tests/test_data/external_function_2.sol rename to slither/tools/slither_format/tests/test_data/external_function_2.sol diff --git a/utils/slither_format/tests/test_data/external_function_import.sol b/slither/tools/slither_format/tests/test_data/external_function_import.sol similarity index 100% rename from utils/slither_format/tests/test_data/external_function_import.sol rename to slither/tools/slither_format/tests/test_data/external_function_import.sol diff --git a/utils/slither_format/tests/test_data/naming_convention.sol b/slither/tools/slither_format/tests/test_data/naming_convention.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention.sol rename to slither/tools/slither_format/tests/test_data/naming_convention.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_contract.sol b/slither/tools/slither_format/tests/test_data/naming_convention_contract.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_contract.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_contract.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_enum.sol b/slither/tools/slither_format/tests/test_data/naming_convention_enum.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_enum.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_enum.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_event.sol b/slither/tools/slither_format/tests/test_data/naming_convention_event.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_event.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_event.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_function.sol b/slither/tools/slither_format/tests/test_data/naming_convention_function.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_function.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_function.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_modifier.sol b/slither/tools/slither_format/tests/test_data/naming_convention_modifier.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_modifier.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_modifier.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_parameter.sol b/slither/tools/slither_format/tests/test_data/naming_convention_parameter.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_parameter.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_parameter.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_state_variable.sol b/slither/tools/slither_format/tests/test_data/naming_convention_state_variable.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_state_variable.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_state_variable.sol diff --git a/utils/slither_format/tests/test_data/naming_convention_structure.sol b/slither/tools/slither_format/tests/test_data/naming_convention_structure.sol similarity index 100% rename from utils/slither_format/tests/test_data/naming_convention_structure.sol rename to slither/tools/slither_format/tests/test_data/naming_convention_structure.sol diff --git a/utils/slither_format/tests/test_data/pragma.0.4.23.sol b/slither/tools/slither_format/tests/test_data/pragma.0.4.23.sol similarity index 100% rename from utils/slither_format/tests/test_data/pragma.0.4.23.sol rename to slither/tools/slither_format/tests/test_data/pragma.0.4.23.sol diff --git a/utils/slither_format/tests/test_data/pragma.0.4.24.sol b/slither/tools/slither_format/tests/test_data/pragma.0.4.24.sol similarity index 100% rename from utils/slither_format/tests/test_data/pragma.0.4.24.sol rename to slither/tools/slither_format/tests/test_data/pragma.0.4.24.sol diff --git a/utils/slither_format/tests/test_data/pragma.0.5.2.sol b/slither/tools/slither_format/tests/test_data/pragma.0.5.2.sol similarity index 100% rename from utils/slither_format/tests/test_data/pragma.0.5.2.sol rename to slither/tools/slither_format/tests/test_data/pragma.0.5.2.sol diff --git a/utils/slither_format/tests/test_data/pragma.0.5.4.sol b/slither/tools/slither_format/tests/test_data/pragma.0.5.4.sol similarity index 100% rename from utils/slither_format/tests/test_data/pragma.0.5.4.sol rename to slither/tools/slither_format/tests/test_data/pragma.0.5.4.sol diff --git a/utils/slither_format/tests/test_data/solc_version_incorrect1.sol b/slither/tools/slither_format/tests/test_data/solc_version_incorrect1.sol similarity index 100% rename from utils/slither_format/tests/test_data/solc_version_incorrect1.sol rename to slither/tools/slither_format/tests/test_data/solc_version_incorrect1.sol diff --git a/utils/slither_format/tests/test_data/solc_version_incorrect2.sol b/slither/tools/slither_format/tests/test_data/solc_version_incorrect2.sol similarity index 100% rename from utils/slither_format/tests/test_data/solc_version_incorrect2.sol rename to slither/tools/slither_format/tests/test_data/solc_version_incorrect2.sol diff --git a/utils/slither_format/tests/test_data/solc_version_incorrect3.sol b/slither/tools/slither_format/tests/test_data/solc_version_incorrect3.sol similarity index 100% rename from utils/slither_format/tests/test_data/solc_version_incorrect3.sol rename to slither/tools/slither_format/tests/test_data/solc_version_incorrect3.sol diff --git a/utils/slither_format/tests/test_data/solc_version_incorrect4.sol b/slither/tools/slither_format/tests/test_data/solc_version_incorrect4.sol similarity index 100% rename from utils/slither_format/tests/test_data/solc_version_incorrect4.sol rename to slither/tools/slither_format/tests/test_data/solc_version_incorrect4.sol diff --git a/utils/slither_format/tests/test_data/unicode.sol b/slither/tools/slither_format/tests/test_data/unicode.sol similarity index 100% rename from utils/slither_format/tests/test_data/unicode.sol rename to slither/tools/slither_format/tests/test_data/unicode.sol diff --git a/utils/slither_format/tests/test_data/unused_state.sol b/slither/tools/slither_format/tests/test_data/unused_state.sol similarity index 100% rename from utils/slither_format/tests/test_data/unused_state.sol rename to slither/tools/slither_format/tests/test_data/unused_state.sol diff --git a/utils/slither_format/tests/test_detector_combinations.py b/slither/tools/slither_format/tests/test_detector_combinations.py similarity index 100% rename from utils/slither_format/tests/test_detector_combinations.py rename to slither/tools/slither_format/tests/test_detector_combinations.py diff --git a/utils/slither_format/tests/test_external_function.py b/slither/tools/slither_format/tests/test_external_function.py similarity index 100% rename from utils/slither_format/tests/test_external_function.py rename to slither/tools/slither_format/tests/test_external_function.py diff --git a/utils/slither_format/tests/test_naming_convention.py b/slither/tools/slither_format/tests/test_naming_convention.py similarity index 100% rename from utils/slither_format/tests/test_naming_convention.py rename to slither/tools/slither_format/tests/test_naming_convention.py diff --git a/utils/slither_format/tests/test_pragma.py b/slither/tools/slither_format/tests/test_pragma.py similarity index 100% rename from utils/slither_format/tests/test_pragma.py rename to slither/tools/slither_format/tests/test_pragma.py diff --git a/utils/slither_format/tests/test_solc_version.py b/slither/tools/slither_format/tests/test_solc_version.py similarity index 100% rename from utils/slither_format/tests/test_solc_version.py rename to slither/tools/slither_format/tests/test_solc_version.py diff --git a/utils/slither_format/tests/test_unicode.py b/slither/tools/slither_format/tests/test_unicode.py similarity index 100% rename from utils/slither_format/tests/test_unicode.py rename to slither/tools/slither_format/tests/test_unicode.py diff --git a/utils/slither_format/tests/test_unused_state_vars.py b/slither/tools/slither_format/tests/test_unused_state_vars.py similarity index 100% rename from utils/slither_format/tests/test_unused_state_vars.py rename to slither/tools/slither_format/tests/test_unused_state_vars.py diff --git a/slither/tools/slither_format/utils/__init__.py b/slither/tools/slither_format/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/utils/slither_format/utils/patches.py b/slither/tools/slither_format/utils/patches.py similarity index 100% rename from utils/slither_format/utils/patches.py rename to slither/tools/slither_format/utils/patches.py diff --git a/slither/tools/upgradeability/__init__.py b/slither/tools/upgradeability/__init__.py new file mode 100644 index 000000000..e69de29bb 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 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/utils/command_line.py b/slither/utils/command_line.py index be1c566d1..bf9f0c696 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -12,6 +12,10 @@ from .colors import yellow, red logger = logging.getLogger("Slither") +DEFAULT_JSON_OUTPUT_TYPES = ["detectors"] +JSON_OUTPUT_TYPES = ["compilations", "console", "detectors", "list-detectors", "list-printers"] + + # Those are the flags shared by the command line and the config file defaults_flag_in_config = { 'detectors_to_run': 'all', @@ -24,6 +28,7 @@ defaults_flag_in_config = { 'exclude_medium': False, 'exclude_high': False, 'json': None, + 'json-types': ','.join(DEFAULT_JSON_OUTPUT_TYPES), 'disable_color': False, 'filter_paths': None, # debug command @@ -198,6 +203,7 @@ def output_detectors(detector_classes): idx = idx + 1 print(table) + def output_detectors_json(detector_classes): detectors_list = [] for detector in detector_classes: @@ -236,7 +242,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 = [] @@ -255,3 +261,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 + 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): 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/standard_libraries.py b/slither/utils/standard_libraries.py index c73a29b93..e138e6c35 100644 --- a/slither/utils/standard_libraries.py +++ b/slither/utils/standard_libraries.py @@ -13,6 +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-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): @@ -41,6 +59,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 ################################################################################### ################################################################################### @@ -56,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 ################################################################################### ################################################################################### @@ -104,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_aragonos(contract) + + # endregion ################################################################################### ################################################################################### @@ -191,3 +224,61 @@ def is_ds_group(contract): def is_dapphub_ds_group(contract): return _is_dappdhub_ds(contract, 'DSGroup') + +# endregion +################################################################################### +################################################################################### +# region Aragon +################################################################################### +################################################################################### + +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) 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): diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 48919d09a..ed53f9ef7 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -181,7 +181,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): @@ -280,5 +280,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/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", 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 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/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; + } } 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(){ + + } + +}