From 0ee1c64959e339387b25dfb83dc3fce6bff7e548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Tue, 8 Nov 2022 17:14:01 -0300 Subject: [PATCH 001/101] fail-on: rework feature This adds a new `fail_on` config option that can be changed with a mutually exclusive group of argument flags. It also decouples the exclude_* and fail_on flags, so you can do things like `fail_on: pedantic` while disabling optimization findings. Additionally, this adds some new code to detect the old-style config options, migrate their settings, and alert the user. Fixes #1458 --- slither/__main__.py | 78 +++++++++++++++++++---------------- slither/utils/command_line.py | 50 ++++++++++++++++++++-- 2 files changed, 88 insertions(+), 40 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 70357586e..a2402fb60 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -28,6 +28,8 @@ from slither.utils.output import output_to_json, output_to_zip, output_to_sarif, from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, set_colorization_enabled from slither.utils.command_line import ( + FailOnLevel, + migrate_config_options, output_detectors, output_results_to_markdown, output_detectors_json, @@ -220,22 +222,22 @@ def choose_detectors( detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) return detectors_to_run - if args.exclude_optimization and not args.fail_pedantic: + if args.exclude_optimization: detectors_to_run = [ d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION ] - if args.exclude_informational and not args.fail_pedantic: + if args.exclude_informational: detectors_to_run = [ d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL ] - if args.exclude_low and not args.fail_low: + if args.exclude_low: detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW] - if args.exclude_medium and not args.fail_medium: + if args.exclude_medium: detectors_to_run = [ d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM ] - if args.exclude_high and not args.fail_high: + if args.exclude_high: detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH] if args.detectors_to_exclude: detectors_to_run = [ @@ -401,41 +403,44 @@ def parse_args( default=defaults_flag_in_config["exclude_high"], ) - group_detector.add_argument( + fail_on_group = group_detector.add_mutually_exclusive_group() + fail_on_group.add_argument( "--fail-pedantic", - help="Return the number of findings in the exit code", - action="store_true", - default=defaults_flag_in_config["fail_pedantic"], + help="Fail if any findings are detected", + action="store_const", + dest="fail_on", + const=FailOnLevel.PEDANTIC, ) - - group_detector.add_argument( - "--no-fail-pedantic", - help="Do not return the number of findings in the exit code. Opposite of --fail-pedantic", - dest="fail_pedantic", - action="store_false", - required=False, - ) - - group_detector.add_argument( + fail_on_group.add_argument( "--fail-low", - help="Fail if low or greater impact finding is detected", - action="store_true", - default=defaults_flag_in_config["fail_low"], + help="Fail if any low or greater impact findings are detected", + action="store_const", + dest="fail_on", + const=FailOnLevel.LOW, ) - - group_detector.add_argument( + fail_on_group.add_argument( "--fail-medium", - help="Fail if medium or greater impact finding is detected", - action="store_true", - default=defaults_flag_in_config["fail_medium"], + help="Fail if any medium or greater impact findings are detected", + action="store_const", + dest="fail_on", + const=FailOnLevel.MEDIUM, ) - - group_detector.add_argument( + fail_on_group.add_argument( "--fail-high", - help="Fail if high impact finding is detected", - action="store_true", - default=defaults_flag_in_config["fail_high"], + help="Fail if any high impact findings are detected", + action="store_const", + dest="fail_on", + const=FailOnLevel.HIGH, + ) + fail_on_group.add_argument( + "--fail-none", + "--no-fail-pedantic", + help="Do not return the number of findings in the exit code", + action="store_const", + dest="fail_on", + const=FailOnLevel.NONE, ) + fail_on_group.set_defaults(fail_on=FailOnLevel.PEDANTIC) group_detector.add_argument( "--show-ignored-findings", @@ -910,17 +915,18 @@ def main_impl( stats = pstats.Stats(cp).sort_stats("cumtime") stats.print_stats() - if args.fail_high: + fail_on = FailOnLevel(args.fail_on) + if fail_on == FailOnLevel.HIGH: fail_on_detection = any(result["impact"] == "High" for result in results_detectors) - elif args.fail_medium: + elif fail_on == FailOnLevel.MEDIUM: fail_on_detection = any( result["impact"] in ["Medium", "High"] for result in results_detectors ) - elif args.fail_low: + elif fail_on == FailOnLevel.LOW: fail_on_detection = any( result["impact"] in ["Low", "Medium", "High"] for result in results_detectors ) - elif args.fail_pedantic: + elif fail_on == FailOnLevel.PEDANTIC: fail_on_detection = bool(results_detectors) else: fail_on_detection = False diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index c2fef5eca..a650960f5 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -1,4 +1,5 @@ import argparse +import enum import json import os import re @@ -27,6 +28,15 @@ JSON_OUTPUT_TYPES = [ "list-printers", ] + +class FailOnLevel(enum.Enum): + PEDANTIC = "pedantic" + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + NONE = "none" + + # Those are the flags shared by the command line and the config file defaults_flag_in_config = { "detectors_to_run": "all", @@ -38,10 +48,7 @@ defaults_flag_in_config = { "exclude_low": False, "exclude_medium": False, "exclude_high": False, - "fail_pedantic": True, - "fail_low": False, - "fail_medium": False, - "fail_high": False, + "fail_on": FailOnLevel.PEDANTIC.value, "json": None, "sarif": None, "json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES), @@ -57,6 +64,13 @@ defaults_flag_in_config = { **DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE, } +deprecated_flags = { + "fail_pedantic": True, + "fail_low": False, + "fail_medium": False, + "fail_high": False, +} + def read_config_file(args: argparse.Namespace) -> None: # No config file was provided as an argument @@ -73,6 +87,12 @@ def read_config_file(args: argparse.Namespace) -> None: with open(args.config_file, encoding="utf8") as f: config = json.load(f) for key, elem in config.items(): + if key in deprecated_flags: + logger.info( + yellow(f"{args.config_file} has a deprecated key: {key} : {elem}") + ) + migrate_config_options(args, key, elem) + continue if key not in defaults_flag_in_config: logger.info( yellow(f"{args.config_file} has an unknown key: {key} : {elem}") @@ -87,6 +107,28 @@ def read_config_file(args: argparse.Namespace) -> None: logger.error(yellow("Falling back to the default settings...")) +def migrate_config_options(args: argparse.Namespace, key: str, elem): + if key.startswith("fail_") and getattr(args, "fail_on") == defaults_flag_in_config["fail_on"]: + if key == "fail_pedantic": + pedantic_setting = elem + fail_on = pedantic_setting and FailOnLevel.PEDANTIC or FailOnLevel.NONE + setattr(args, "fail_on", fail_on) + logger.info( + "Migrating fail_pedantic: {} as fail_on: {}".format(pedantic_setting, fail_on.value) + ) + elif key == "fail_low" and elem == True: + logger.info("Migrating fail_low: true -> fail_on: low") + setattr(args, "fail_on", FailOnLevel.LOW) + elif key == "fail_medium" and elem == True: + logger.info("Migrating fail_medium: true -> fail_on: medium") + setattr(args, "fail_on", FailOnLevel.MEDIUM) + elif key == "fail_high" and elem == True: + logger.info("Migrating fail_high: true -> fail_on: high") + setattr(args, "fail_on", FailOnLevel.HIGH) + else: + logger.warn(yellow("Key {} was deprecated but no migration was provided".format(key))) + + def output_to_markdown( detector_classes: List[Type[AbstractDetector]], printer_classes: List[Type[AbstractPrinter]], From 0e2f085d82259e4761242dd298cb755e08fa80ea Mon Sep 17 00:00:00 2001 From: Simone Date: Thu, 23 Feb 2023 13:25:03 +0100 Subject: [PATCH 002/101] Support user defined operators --- slither/core/solidity_types/type_alias.py | 5 +- .../declarations/using_for_top_level.py | 43 +++++++++----- .../expressions/expression_parsing.py | 58 +++++++++++++++---- 3 files changed, 79 insertions(+), 27 deletions(-) diff --git a/slither/core/solidity_types/type_alias.py b/slither/core/solidity_types/type_alias.py index 5b9ea0a37..a9010c7d9 100644 --- a/slither/core/solidity_types/type_alias.py +++ b/slither/core/solidity_types/type_alias.py @@ -1,10 +1,11 @@ -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING, Tuple, Dict from slither.core.children.child_contract import ChildContract from slither.core.declarations.top_level import TopLevel from slither.core.solidity_types import Type, ElementaryType if TYPE_CHECKING: + from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations import Contract from slither.core.scope.scope import FileScope @@ -43,6 +44,8 @@ class TypeAliasTopLevel(TypeAlias, TopLevel): def __init__(self, underlying_type: Type, name: str, scope: "FileScope") -> None: super().__init__(underlying_type, name) self.file_scope: "FileScope" = scope + # operators redefined + self.operators: Dict[str, "FunctionTopLevel"] = {} def __str__(self) -> str: return self.name diff --git a/slither/solc_parsing/declarations/using_for_top_level.py b/slither/solc_parsing/declarations/using_for_top_level.py index b16fadc40..e20fb3bcf 100644 --- a/slither/solc_parsing/declarations/using_for_top_level.py +++ b/slither/solc_parsing/declarations/using_for_top_level.py @@ -55,22 +55,29 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- self._propagate_global(type_name) else: for f in self._functions: - full_name_split = f["function"]["name"].split(".") - if len(full_name_split) == 1: + # User defined operator + if "operator" in f: # Top level function - function_name: str = full_name_split[0] - self._analyze_top_level_function(function_name, type_name) - elif len(full_name_split) == 2: - # It can be a top level function behind an aliased import - # or a library function - first_part = full_name_split[0] - function_name = full_name_split[1] - self._check_aliased_import(first_part, function_name, type_name) + function_name: str = f["definition"]["name"] + operator: str = f["operator"] + self._analyze_operator(operator, function_name, type_name) else: - # MyImport.MyLib.a we don't care of the alias - library_name_str = full_name_split[1] - function_name = full_name_split[2] - self._analyze_library_function(library_name_str, function_name, type_name) + full_name_split = f["function"]["name"].split(".") + if len(full_name_split) == 1: + # Top level function + function_name: str = full_name_split[0] + self._analyze_top_level_function(function_name, type_name) + elif len(full_name_split) == 2: + # It can be a top level function behind an aliased import + # or a library function + first_part = full_name_split[0] + function_name = full_name_split[1] + self._check_aliased_import(first_part, function_name, type_name) + else: + # MyImport.MyLib.a we don't care of the alias + library_name_str = full_name_split[1] + function_name = full_name_split[2] + self._analyze_library_function(library_name_str, function_name, type_name) def _check_aliased_import( self, @@ -96,6 +103,14 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- self._propagate_global(type_name) break + def _analyze_operator( + self, operator: str, function_name: str, type_name: TypeAliasTopLevel + ) -> None: + for tl_function in self.compilation_unit.functions_top_level: + if tl_function.name == function_name: + type_name.operators[operator] = tl_function + break + def _analyze_library_function( self, library_name: str, diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index ea433a921..0727d2a16 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -1,6 +1,6 @@ import logging import re -from typing import Union, Dict, TYPE_CHECKING +from typing import Union, Dict, TYPE_CHECKING, List, Any import slither.core.expressions.type_conversion from slither.core.declarations.solidity_variables import ( @@ -236,6 +236,24 @@ if TYPE_CHECKING: pass +def _user_defined_op_call( + caller_context: CallerContextExpression, src, function_id: int, args: List[Any], type_call: str +) -> CallExpression: + var, was_created = find_variable(None, caller_context, function_id) + + if was_created: + var.set_offset(src, caller_context.compilation_unit) + + identifier = Identifier(var) + identifier.set_offset(src, caller_context.compilation_unit) + + var.references.append(identifier.source_mapping) + + call = CallExpression(identifier, args, type_call) + call.set_offset(src, caller_context.compilation_unit) + return call + + def parse_expression(expression: Dict, caller_context: CallerContextExpression) -> "Expression": # pylint: disable=too-many-nested-blocks,too-many-statements """ @@ -274,16 +292,24 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression) if name == "UnaryOperation": if is_compact_ast: attributes = expression - else: - attributes = expression["attributes"] - assert "prefix" in attributes - operation_type = UnaryOperationType.get_type(attributes["operator"], attributes["prefix"]) - - if is_compact_ast: expression = parse_expression(expression["subExpression"], caller_context) else: + attributes = expression["attributes"] assert len(expression["children"]) == 1 expression = parse_expression(expression["children"][0], caller_context) + assert "prefix" in attributes + + # Use of user defined operation + if "function" in attributes: + return _user_defined_op_call( + caller_context, + src, + attributes["function"], + [expression], + attributes["typeDescriptions"]["typeString"], + ) + + operation_type = UnaryOperationType.get_type(attributes["operator"], attributes["prefix"]) unary_op = UnaryOperation(expression, operation_type) unary_op.set_offset(src, caller_context.compilation_unit) return unary_op @@ -291,17 +317,25 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression) if name == "BinaryOperation": if is_compact_ast: attributes = expression - else: - attributes = expression["attributes"] - operation_type = BinaryOperationType.get_type(attributes["operator"]) - - if is_compact_ast: left_expression = parse_expression(expression["leftExpression"], caller_context) right_expression = parse_expression(expression["rightExpression"], caller_context) else: assert len(expression["children"]) == 2 + attributes = expression["attributes"] left_expression = parse_expression(expression["children"][0], caller_context) right_expression = parse_expression(expression["children"][1], caller_context) + + # Use of user defined operation + if "function" in attributes: + return _user_defined_op_call( + caller_context, + src, + attributes["function"], + [left_expression, right_expression], + attributes["typeDescriptions"]["typeString"], + ) + + operation_type = BinaryOperationType.get_type(attributes["operator"]) binary_op = BinaryOperation(left_expression, right_expression, operation_type) binary_op.set_offset(src, caller_context.compilation_unit) return binary_op From 5edc3c280e9683e42d8beff5cbe23e5f32529015 Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 10 Mar 2023 21:41:16 +0100 Subject: [PATCH 003/101] Add tests --- ...ed_operators-0.8.19.sol-0.8.19-compact.zip | Bin 0 -> 5844 bytes ...d_operators-0.8.19.sol-0.8.19-compact.json | 13 +++++ .../user_defined_operators-0.8.19.sol | 48 +++++++++++++++++ tests/test_ast_parsing.py | 1 + tests/test_features.py | 51 ++++++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 tests/ast-parsing/compile/user_defined_operators-0.8.19.sol-0.8.19-compact.zip create mode 100644 tests/ast-parsing/expected/user_defined_operators-0.8.19.sol-0.8.19-compact.json create mode 100644 tests/ast-parsing/user_defined_operators-0.8.19.sol diff --git a/tests/ast-parsing/compile/user_defined_operators-0.8.19.sol-0.8.19-compact.zip b/tests/ast-parsing/compile/user_defined_operators-0.8.19.sol-0.8.19-compact.zip new file mode 100644 index 0000000000000000000000000000000000000000..7159a1486165e875e77b28cb8132456dbea5a367 GIT binary patch literal 5844 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\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n", + "add_op(Int,Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "lib_call(Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "neg_usertype(Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: RETURN 2\n\"];\n}\n", + "neg_int(int256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "eq_op(Int,Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/user_defined_operators-0.8.19.sol b/tests/ast-parsing/user_defined_operators-0.8.19.sol new file mode 100644 index 000000000..e4df845fb --- /dev/null +++ b/tests/ast-parsing/user_defined_operators-0.8.19.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.8.19; + +type Int is int; +using {add as +, eq as ==, add, neg as -, Lib.f} for Int global; + +function add(Int a, Int b) pure returns (Int) { + return Int.wrap(Int.unwrap(a) + Int.unwrap(b)); +} + +function eq(Int a, Int b) pure returns (bool) { + return true; +} + +function neg(Int a) pure returns (Int) { + return a; +} + +library Lib { + function f(Int r) internal {} +} + +contract T { + function add_function_call(Int b, Int c) public returns(Int) { + Int res = add(b,c); + return res; + } + + function add_op(Int b, Int c) public returns(Int) { + return b + c; + } + + function lib_call(Int b) public { + return b.f(); + } + + function neg_usertype(Int b) public returns(Int) { + Int res = -b; + return res; + } + + function neg_int(int b) public returns(int) { + return -b; + } + + function eq_op(Int b, Int c) public returns(bool) { + return b == c; + } +} diff --git a/tests/test_ast_parsing.py b/tests/test_ast_parsing.py index c783f827f..945fde505 100644 --- a/tests/test_ast_parsing.py +++ b/tests/test_ast_parsing.py @@ -444,6 +444,7 @@ ALL_TESTS = [ Test("yul-top-level-0.8.0.sol", ["0.8.0"]), Test("complex_imports/import_aliases_issue_1319/test.sol", ["0.5.12"]), Test("yul-state-constant-access.sol", ["0.8.16"]), + Test("user_defined_operators-0.8.19.sol", ["0.8.19"]), ] # create the output folder if needed try: diff --git a/tests/test_features.py b/tests/test_features.py index d29a5eb6a..db0314b3f 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -160,3 +160,54 @@ def test_arithmetic_usage() -> None: assert { f.source_mapping.content_hash for f in unchecked_arithemtic_usage(slither.contracts[0]) } == {"2b4bc73cf59d486dd9043e840b5028b679354dd9", "e4ecd4d0fda7e762d29aceb8425f2c5d4d0bf962"} + + +def test_user_defined_operators() -> None: + solc_select.switch_global_version("0.8.19", always_install=True) + slither = Slither("./tests/ast-parsing/user_defined_operators-0.8.19.sol") + contract_t = slither.get_contract_from_name("T")[0] + add_function_call = contract_t.get_function_from_full_name("add_function_call(Int,Int)") + ok = False + for ir in add_function_call.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "add": + ok = True + if not ok: + assert False + + add_op = contract_t.get_function_from_full_name("add_op(Int,Int)") + ok = False + for ir in add_op.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "add": + ok = True + if not ok: + assert False + + lib_call = contract_t.get_function_from_full_name("lib_call(Int)") + ok = False + for ir in lib_call.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "f": + ok = True + if not ok: + assert False + + neg_usertype = contract_t.get_function_from_full_name("neg_usertype(Int)") + ok = False + for ir in neg_usertype.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "neg": + ok = True + if not ok: + assert False + + neg_int = contract_t.get_function_from_full_name("neg_int(int256)") + ok = True + for ir in neg_int.all_slithir_operations(): + if isinstance(ir, InternalCall): + ok = False + if not ok: + assert False + + eq_op = contract_t.get_function_from_full_name("eq_op(Int,Int)") + for ir in eq_op.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "eq": + return + assert False From 64abf06e0a8ef3e74fcdcc7c0956921dbc2cb4c4 Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 10 Mar 2023 21:53:32 +0100 Subject: [PATCH 004/101] Run pylint --- tests/test_features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_features.py b/tests/test_features.py index 297023b4c..c6b670d35 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -203,7 +203,7 @@ def test_using_for_global_collision() -> None: sl = Slither(compilation) _run_all_detectors(sl) - +# pylint: disable=too-many-branches def test_user_defined_operators() -> None: solc_select.switch_global_version("0.8.19", always_install=True) slither = Slither("./tests/ast-parsing/user_defined_operators-0.8.19.sol") From cc650f5683490342e38b92d6e681d44b07e706e5 Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 10 Mar 2023 22:04:50 +0100 Subject: [PATCH 005/101] Black --- tests/test_features.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_features.py b/tests/test_features.py index c6b670d35..748f1df9e 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -203,6 +203,7 @@ def test_using_for_global_collision() -> None: sl = Slither(compilation) _run_all_detectors(sl) + # pylint: disable=too-many-branches def test_user_defined_operators() -> None: solc_select.switch_global_version("0.8.19", always_install=True) From 072cb0b769b9938c62395131cba6f1a3113aa78f Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 29 Mar 2023 16:40:51 -0500 Subject: [PATCH 006/101] Find tainted functions/variables from external calls Given a list of functions from one contract, finds tainted functions/variables in other contracts --- slither/utils/upgradeability.py | 91 ++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 7b4e8493a..947c4652a 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple, List, Union +from typing import Optional, Tuple, List, Union, TypedDict from slither.core.declarations import ( Contract, Structure, @@ -19,10 +19,12 @@ from slither.core.variables.local_variable_init_from_tuple import LocalVariableI from slither.core.variables.state_variable import StateVariable from slither.analyses.data_dependency.data_dependency import get_dependencies from slither.core.variables.variable import Variable -from slither.core.expressions.literal import Literal -from slither.core.expressions.identifier import Identifier -from slither.core.expressions.call_expression import CallExpression -from slither.core.expressions.assignment_operation import AssignmentOperation +from slither.core.expressions import ( + Literal, + Identifier, + CallExpression, + AssignmentOperation, +) from slither.core.cfg.node import Node, NodeType from slither.slithir.operations import ( Operation, @@ -61,11 +63,23 @@ from slither.slithir.variables import ( from slither.tools.read_storage.read_storage import SlotInfo, SlitherReadStorage +class TaintedExternalContract(TypedDict): + contract: Contract + functions: List[Function] + variables: List[Variable] + + # pylint: disable=too-many-locals def compare( v1: Contract, v2: Contract ) -> Tuple[ - List[Variable], List[Variable], List[Variable], List[Function], List[Function], List[Function] + List[Variable], + List[Variable], + List[Variable], + List[Function], + List[Function], + List[Function], + List[TaintedExternalContract], ]: """ Compares two versions of a contract. Most useful for upgradeable (logic) contracts, @@ -159,6 +173,11 @@ def compare( ): tainted_variables.append(var) + # Find all external contracts and functions called by new/modified/tainted functions + tainted_contracts = tainted_external_contracts( + new_functions + modified_functions + tainted_functions + ) + return ( missing_vars_in_v2, new_variables, @@ -166,9 +185,69 @@ def compare( new_functions, modified_functions, tainted_functions, + tainted_contracts, ) +def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalContract]: + """ + Takes a list of functions from one contract, finds any calls in these to functions in external contracts, + and determines which variables and functions in the external contracts are tainted by these external calls. + Args: + funcs: a list of Function objects to search for external calls. + + Returns: + TaintedExternalContract(TypedDict) ( + contract: Contract, + functions: List[Function], + variables: List[Variable] + ) + """ + tainted_contracts = {} + + for func in funcs: + for contract, target in func.all_high_level_calls(): + if contract.name not in tainted_contracts: + tainted_contracts[contract.name] = TaintedExternalContract( + contract=contract, functions=[], variables=[] + ) + if ( + isinstance(target, Function) + and target not in funcs + and target not in tainted_contracts[contract.name]["functions"] + and not (target.is_constructor or target.is_fallback or target.is_receive) + ): + tainted_contracts[contract.name]["functions"].append(target) + for var in target.all_state_variables_written(): + if var not in tainted_contracts[contract.name]["variables"]: + tainted_contracts[contract.name]["variables"].append(var) + elif ( + isinstance(target, Variable) + and target not in tainted_contracts[contract.name]["variables"] + and not (target.is_constant or target.is_immutable) + ): + tainted_contracts[contract.name]["variables"].append(target) + tainted_contracts = { + item + for item in tainted_contracts.items() + if len(item[1]["variables"]) > 0 and len(item[1]["functions"]) > 0 + } + for c in tainted_contracts.items(): + contract = c[1]["contract"] + variables = c[1]["variables"] + for var in variables: + read_write = set( + contract.get_functions_reading_from_variable(var) + + contract.get_functions_writing_to_variable(var) + ) + for f in read_write: + if f not in tainted_contracts[contract.name]["functions"] and not ( + f.is_constructor or f.is_fallback or f.is_receive + ): + tainted_contracts[contract.name]["functions"].append(f) + return list(tainted_contracts.values()) + + def get_missing_vars(v1: Contract, v2: Contract) -> List[StateVariable]: """ Gets all non-constant/immutable StateVariables that appear in v1 but not v2 From 4b6dd02ac8072b7e8ff0f9401c29c808c27d6499 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 30 Mar 2023 16:06:38 -0500 Subject: [PATCH 007/101] Find contracts tainted by inheriting a tainted contract and functions that call tainted functions --- slither/utils/upgradeability.py | 38 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 947c4652a..1f5ef380c 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -204,6 +204,7 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon ) """ tainted_contracts = {} + tainted_list = [] for func in funcs: for contract, target in func.all_high_level_calls(): @@ -227,12 +228,10 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon and not (target.is_constant or target.is_immutable) ): tainted_contracts[contract.name]["variables"].append(target) - tainted_contracts = { - item - for item in tainted_contracts.items() - if len(item[1]["variables"]) > 0 and len(item[1]["functions"]) > 0 - } for c in tainted_contracts.items(): + if len(c[1]["functions"]) == 0 and len(c[1]["variables"]) == 0: + continue + tainted_list.append(c[1]) contract = c[1]["contract"] variables = c[1]["variables"] for var in variables: @@ -245,7 +244,34 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon f.is_constructor or f.is_fallback or f.is_receive ): tainted_contracts[contract.name]["functions"].append(f) - return list(tainted_contracts.values()) + return tainted_list + + +def tainted_inheriting_contracts( + tainted_contracts: List[TaintedExternalContract], + contracts: List[Contract] = None +) -> List[TaintedExternalContract]: + for tainted in tainted_contracts: + contract = tainted['contract'] + if contracts is None: + contracts = contract.compilation_unit.contracts + for c in contracts: + inheritance = [i.name for i in c.inheritance] + if contract.name in inheritance and c.name not in tainted_contracts: + new_taint = TaintedExternalContract( + contract=c, functions=[], variables=[] + ) + for f in c.functions_declared: + internal_calls = f.all_internal_calls() + if ( + any(str(call) == str(t) for t in tainted['functions'] for call in internal_calls) + or any(str(var) == str(t) for t in tainted['variables'] + for var in f.all_state_variables_read() + f.all_state_variables_written()) + ): + new_taint['functions'].append(f) + if len(new_taint['functions']) > 0: + tainted_contracts.append(new_taint) + return tainted_contracts def get_missing_vars(v1: Contract, v2: Contract) -> List[StateVariable]: From 0deff18d01e73783072ba7483efe713a2ad7f57c Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 30 Mar 2023 16:16:23 -0500 Subject: [PATCH 008/101] Add docstring to `tainted_inheriting_contracts` --- slither/utils/upgradeability.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 1f5ef380c..8cf7595cf 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -251,6 +251,18 @@ def tainted_inheriting_contracts( tainted_contracts: List[TaintedExternalContract], contracts: List[Contract] = None ) -> List[TaintedExternalContract]: + """ + Takes a list of TaintedExternalContract obtained from tainted_external_contracts, and finds any contracts which + inherit a tainted contract, as well as any functions that call tainted functions or read tainted variables in + the inherited contract. + Args: + tainted_contracts: the list obtained from `tainted_external_contracts` or `compare`. + contracts: (optional) the list of contracts to check for inheritance. If not provided, defaults to + `contract.compilation_unit.contracts` for each contract in tainted_contracts. + + Returns: + An updated list of TaintedExternalContract, including all from the input list. + """ for tainted in tainted_contracts: contract = tainted['contract'] if contracts is None: From 5967958c71e58cc3fcd7cacecf162f8fb0916385 Mon Sep 17 00:00:00 2001 From: webthethird Date: Thu, 30 Mar 2023 16:18:27 -0500 Subject: [PATCH 009/101] Black --- slither/utils/upgradeability.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 8cf7595cf..2fa20f0f4 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -248,8 +248,7 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon def tainted_inheriting_contracts( - tainted_contracts: List[TaintedExternalContract], - contracts: List[Contract] = None + tainted_contracts: List[TaintedExternalContract], contracts: List[Contract] = None ) -> List[TaintedExternalContract]: """ Takes a list of TaintedExternalContract obtained from tainted_external_contracts, and finds any contracts which @@ -264,24 +263,24 @@ def tainted_inheriting_contracts( An updated list of TaintedExternalContract, including all from the input list. """ for tainted in tainted_contracts: - contract = tainted['contract'] + contract = tainted["contract"] if contracts is None: contracts = contract.compilation_unit.contracts for c in contracts: inheritance = [i.name for i in c.inheritance] if contract.name in inheritance and c.name not in tainted_contracts: - new_taint = TaintedExternalContract( - contract=c, functions=[], variables=[] - ) + new_taint = TaintedExternalContract(contract=c, functions=[], variables=[]) for f in c.functions_declared: internal_calls = f.all_internal_calls() - if ( - any(str(call) == str(t) for t in tainted['functions'] for call in internal_calls) - or any(str(var) == str(t) for t in tainted['variables'] - for var in f.all_state_variables_read() + f.all_state_variables_written()) + if any( + str(call) == str(t) for t in tainted["functions"] for call in internal_calls + ) or any( + str(var) == str(t) + for t in tainted["variables"] + for var in f.all_state_variables_read() + f.all_state_variables_written() ): - new_taint['functions'].append(f) - if len(new_taint['functions']) > 0: + new_taint["functions"].append(f) + if len(new_taint["functions"]) > 0: tainted_contracts.append(new_taint) return tainted_contracts From 675dbea4334386d2a343652483296ae8c30c08a1 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 31 Mar 2023 09:32:08 -0500 Subject: [PATCH 010/101] Get tainted variables in `tainted_inheriting_contracts` in addition to tainted functions --- slither/utils/upgradeability.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 2fa20f0f4..805da9905 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -280,7 +280,20 @@ def tainted_inheriting_contracts( for var in f.all_state_variables_read() + f.all_state_variables_written() ): new_taint["functions"].append(f) + for var in f.all_state_variables_read() + f.all_state_variables_written(): + if not (var in tainted["variables"] or var in new_taint["variables"]): + new_taint["variables"].append(var) if len(new_taint["functions"]) > 0: + for var in new_taint["variables"]: + read_write = set( + contract.get_functions_reading_from_variable(var) + + contract.get_functions_writing_to_variable(var) + ) + for f in read_write: + if f not in tainted["functions"] + new_taint["functions"] and not ( + f.is_constructor or f.is_fallback or f.is_receive + ): + new_taint["functions"].append(f) tainted_contracts.append(new_taint) return tainted_contracts From d88bba49856fe6a823aaf7855916751e2478b5b2 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 31 Mar 2023 09:38:25 -0500 Subject: [PATCH 011/101] Fix too many values to unpack in test_upgradeability_util.py --- tests/unit/utils/test_upgradeability_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_upgradeability_util.py b/tests/unit/utils/test_upgradeability_util.py index 7d6fb82da..a291d5680 100644 --- a/tests/unit/utils/test_upgradeability_util.py +++ b/tests/unit/utils/test_upgradeability_util.py @@ -22,7 +22,7 @@ def test_upgrades_compare() -> None: sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.8.2.sol")) v1 = sl.get_contract_from_name("ContractV1")[0] v2 = sl.get_contract_from_name("ContractV2")[0] - missing_vars, new_vars, tainted_vars, new_funcs, modified_funcs, tainted_funcs = compare(v1, v2) + missing_vars, new_vars, tainted_vars, new_funcs, modified_funcs, tainted_funcs, tainted_contracts = compare(v1, v2) assert len(missing_vars) == 0 assert new_vars == [v2.get_state_variable_from_name("stateC")] assert tainted_vars == [ From cba0dc95111b85d0896e870531dc86255c9fd923 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 31 Mar 2023 10:13:07 -0500 Subject: [PATCH 012/101] Add python types --- slither/utils/upgradeability.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 805da9905..0bf01b373 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -203,8 +203,8 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon variables: List[Variable] ) """ - tainted_contracts = {} - tainted_list = [] + tainted_contracts: dict[str, TaintedExternalContract] = {} + tainted_list: list[TaintedExternalContract] = [] for func in funcs: for contract, target in func.all_high_level_calls(): From 386c3e14a36aa8007d0b911e46d622c89abe3f2e Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 31 Mar 2023 10:31:43 -0500 Subject: [PATCH 013/101] Pylint and black --- slither/utils/upgradeability.py | 56 ++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 0bf01b373..1c2869da6 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -266,35 +266,39 @@ def tainted_inheriting_contracts( contract = tainted["contract"] if contracts is None: contracts = contract.compilation_unit.contracts + contracts = [ + c + for c in contracts + if c.name not in tainted_contracts and c.name in [i.name for i in c.inheritance] + ] for c in contracts: - inheritance = [i.name for i in c.inheritance] - if contract.name in inheritance and c.name not in tainted_contracts: - new_taint = TaintedExternalContract(contract=c, functions=[], variables=[]) - for f in c.functions_declared: - internal_calls = f.all_internal_calls() - if any( - str(call) == str(t) for t in tainted["functions"] for call in internal_calls - ) or any( - str(var) == str(t) - for t in tainted["variables"] - for var in f.all_state_variables_read() + f.all_state_variables_written() + new_taint = TaintedExternalContract(contract=c, functions=[], variables=[]) + for f in c.functions_declared: + internal_calls = f.all_internal_calls() + if any( + str(call) == str(t) for t in tainted["functions"] for call in internal_calls + ) or any( + str(var) == str(t) + for t in tainted["variables"] + for var in f.all_state_variables_read() + f.all_state_variables_written() + ): + new_taint["functions"].append(f) + for f in new_taint["functions"]: + for var in f.all_state_variables_read() + f.all_state_variables_written(): + if not (var in tainted["variables"] or var in new_taint["variables"]): + new_taint["variables"].append(var) + for var in new_taint["variables"]: + read_write = set( + contract.get_functions_reading_from_variable(var) + + contract.get_functions_writing_to_variable(var) + ) + for f in read_write: + if f not in tainted["functions"] + new_taint["functions"] and not ( + f.is_constructor or f.is_fallback or f.is_receive ): new_taint["functions"].append(f) - for var in f.all_state_variables_read() + f.all_state_variables_written(): - if not (var in tainted["variables"] or var in new_taint["variables"]): - new_taint["variables"].append(var) - if len(new_taint["functions"]) > 0: - for var in new_taint["variables"]: - read_write = set( - contract.get_functions_reading_from_variable(var) - + contract.get_functions_writing_to_variable(var) - ) - for f in read_write: - if f not in tainted["functions"] + new_taint["functions"] and not ( - f.is_constructor or f.is_fallback or f.is_receive - ): - new_taint["functions"].append(f) - tainted_contracts.append(new_taint) + if len(new_taint["functions"]) > 0: + tainted_contracts.append(new_taint) return tainted_contracts From 20f5825fb6bed64d15a2988c8423ebb226a3c143 Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 31 Mar 2023 11:37:05 -0500 Subject: [PATCH 014/101] Pylint and black --- tests/unit/utils/test_upgradeability_util.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/unit/utils/test_upgradeability_util.py b/tests/unit/utils/test_upgradeability_util.py index a291d5680..8dfbccc61 100644 --- a/tests/unit/utils/test_upgradeability_util.py +++ b/tests/unit/utils/test_upgradeability_util.py @@ -22,8 +22,16 @@ def test_upgrades_compare() -> None: sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.8.2.sol")) v1 = sl.get_contract_from_name("ContractV1")[0] v2 = sl.get_contract_from_name("ContractV2")[0] - missing_vars, new_vars, tainted_vars, new_funcs, modified_funcs, tainted_funcs, tainted_contracts = compare(v1, v2) - assert len(missing_vars) == 0 + ( + missing_vars, + new_vars, + tainted_vars, + new_funcs, + modified_funcs, + tainted_funcs, + tainted_contracts, + ) = compare(v1, v2) + assert len(missing_vars) == len(tainted_contracts) == 0 assert new_vars == [v2.get_state_variable_from_name("stateC")] assert tainted_vars == [ v2.get_state_variable_from_name("stateB"), From f585d2bb862a8e5a3046c1df3576cbdacacbaa1d Mon Sep 17 00:00:00 2001 From: webthethird Date: Fri, 31 Mar 2023 14:06:13 -0500 Subject: [PATCH 015/101] Add TODO --- slither/utils/upgradeability.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 1c2869da6..1e5b20a69 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -17,6 +17,7 @@ from slither.core.solidity_types import ( from slither.core.variables.local_variable import LocalVariable from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple from slither.core.variables.state_variable import StateVariable +from slither.slithir.variables import TemporaryVariable from slither.analyses.data_dependency.data_dependency import get_dependencies from slither.core.variables.variable import Variable from slither.core.expressions import ( @@ -208,6 +209,8 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon for func in funcs: for contract, target in func.all_high_level_calls(): + if contract.is_library: + continue if contract.name not in tainted_contracts: tainted_contracts[contract.name] = TaintedExternalContract( contract=contract, functions=[], variables=[] @@ -406,7 +409,7 @@ def encode_ir_for_compare(ir: Operation) -> str: if isinstance(ir, Assignment): return f"({encode_var_for_compare(ir.lvalue)}):=({encode_var_for_compare(ir.rvalue)})" if isinstance(ir, Index): - return f"index({ntype(ir.index_type)})" + return f"index({ntype(ir.variable_right.type)})" if isinstance(ir, Member): return "member" # .format(ntype(ir._type)) if isinstance(ir, Length): @@ -531,6 +534,7 @@ def get_proxy_implementation_var(proxy: Contract) -> Optional[Variable]: try: delegate = next(var for var in dependencies if isinstance(var, StateVariable)) except StopIteration: + # TODO: Handle cases where get_dependencies doesn't return any state variables. return delegate return delegate From 2b330a198f71015fcd67e15401675ead04e9f288 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 3 Apr 2023 14:01:44 -0500 Subject: [PATCH 016/101] Tweak how tainted variables are handled --- slither/utils/upgradeability.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 1e5b20a69..35921c843 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -129,7 +129,7 @@ def compare( new_modified_functions.append(function) new_functions.append(function) new_modified_function_vars += ( - function.state_variables_read + function.state_variables_written + function.all_state_variables_written() ) elif not function.is_constructor_variables and is_function_modified( orig_function, function @@ -137,7 +137,7 @@ def compare( new_modified_functions.append(function) modified_functions.append(function) new_modified_function_vars += ( - function.state_variables_read + function.state_variables_written + function.all_state_variables_written() ) # Find all unmodified functions that call a modified function or read/write the @@ -155,7 +155,7 @@ def compare( tainted_vars = [ var for var in set(new_modified_function_vars) - if var in function.variables_read_or_written + if var in function.all_state_variables_read() + function.all_state_variables_written() and not var.is_constant and not var.is_immutable ] @@ -166,7 +166,8 @@ def compare( for var in order_vars2: read_by = v2.get_functions_reading_from_variable(var) written_by = v2.get_functions_writing_to_variable(var) - if v1.get_state_variable_from_name(var.name) is None: + # if v1.get_state_variable_from_name(var.name) is None: + if next((v for v in v1.state_variables_ordered if v.name == var.name), None) is None: new_variables.append(var) elif any( func in read_by or func in written_by From da045d6db1d25767ae9658ace6a40e94588da074 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 12:08:27 -0500 Subject: [PATCH 017/101] Make TaintedExternalContract a regular class and add TaintedFunction and TaintedVariable classes --- slither/utils/upgradeability.py | 182 +++++++++++++++++++++++--------- 1 file changed, 135 insertions(+), 47 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 35921c843..7316b480d 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple, List, Union, TypedDict +from typing import Optional, Tuple, List, Union from slither.core.declarations import ( Contract, Structure, @@ -17,7 +17,6 @@ from slither.core.solidity_types import ( from slither.core.variables.local_variable import LocalVariable from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple from slither.core.variables.state_variable import StateVariable -from slither.slithir.variables import TemporaryVariable from slither.analyses.data_dependency.data_dependency import get_dependencies from slither.core.variables.variable import Variable from slither.core.expressions import ( @@ -64,10 +63,63 @@ from slither.slithir.variables import ( from slither.tools.read_storage.read_storage import SlotInfo, SlitherReadStorage -class TaintedExternalContract(TypedDict): - contract: Contract - functions: List[Function] - variables: List[Variable] +class TaintedFunction: + def __init__(self, f: "Function") -> None: + self._function: Function = f + self._tainted_by: List[Union[Function, StateVariable]] = [] + + @property + def function(self) -> Function: + return self._function + + @property + def tainted_by(self) -> List[Union[Function, StateVariable]]: + return self._tainted_by + + def add_tainted_by(self, f: Union[Function, StateVariable]): + self._tainted_by.append(f) + + +class TaintedVariable: + def __init__(self, v: "StateVariable") -> None: + self._variable: StateVariable = v + self._tainted_by: List[Function] = [] + + @property + def variable(self) -> StateVariable: + return self._variable + + @property + def tainted_by(self) -> List[Function]: + return self._tainted_by + + def add_tainted_by(self, f: Function): + self._tainted_by.append(f) + + +class TaintedExternalContract: + def __init__(self, contract: "Contract") -> None: + self._contract: Contract = contract + self._tainted_functions: List[TaintedFunction] = [] + self._tainted_variables: List[TaintedVariable] = [] + + @property + def contract(self) -> Contract: + return self._contract + + @property + def tainted_functions(self) -> List[TaintedFunction]: + return self._tainted_functions + + def add_tainted_function(self, f: TaintedFunction): + self._tainted_functions.append(f) + + @property + def tainted_variables(self) -> List[TaintedVariable]: + return self._tainted_variables + + def add_tainted_variable(self, v: TaintedVariable): + self._tainted_variables.append(v) # pylint: disable=too-many-locals @@ -128,17 +180,13 @@ def compare( if sig not in func_sigs1: new_modified_functions.append(function) new_functions.append(function) - new_modified_function_vars += ( - function.all_state_variables_written() - ) + new_modified_function_vars += function.all_state_variables_written() elif not function.is_constructor_variables and is_function_modified( orig_function, function ): new_modified_functions.append(function) modified_functions.append(function) - new_modified_function_vars += ( - function.all_state_variables_written() - ) + new_modified_function_vars += function.all_state_variables_written() # Find all unmodified functions that call a modified function or read/write the # same state variable(s) as a new/modified function, i.e., tainted functions @@ -213,41 +261,52 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon if contract.is_library: continue if contract.name not in tainted_contracts: - tainted_contracts[contract.name] = TaintedExternalContract( - contract=contract, functions=[], variables=[] - ) + tainted_contracts[contract.name] = TaintedExternalContract(contract) if ( isinstance(target, Function) and target not in funcs - and target not in tainted_contracts[contract.name]["functions"] + and target + not in (f.function for f in tainted_contracts[contract.name].tainted_functions) and not (target.is_constructor or target.is_fallback or target.is_receive) ): - tainted_contracts[contract.name]["functions"].append(target) + tainted_function = TaintedFunction(target) + tainted_function.add_tainted_by(func) + tainted_contracts[contract.name].add_tainted_function(tainted_function) for var in target.all_state_variables_written(): - if var not in tainted_contracts[contract.name]["variables"]: - tainted_contracts[contract.name]["variables"].append(var) + if var not in ( + v.variable for v in tainted_contracts[contract.name].tainted_variables + ): + tainted_var = TaintedVariable(var) + tainted_var.add_tainted_by(target) + tainted_contracts[contract.name].add_tainted_variable(tainted_var) elif ( - isinstance(target, Variable) - and target not in tainted_contracts[contract.name]["variables"] + isinstance(target, StateVariable) + and target + not in (v.variable for v in tainted_contracts[contract.name].tainted_variables) and not (target.is_constant or target.is_immutable) ): + tainted_var = TaintedVariable(target) + tainted_var.add_tainted_by(func) tainted_contracts[contract.name]["variables"].append(target) - for c in tainted_contracts.items(): - if len(c[1]["functions"]) == 0 and len(c[1]["variables"]) == 0: - continue - tainted_list.append(c[1]) - contract = c[1]["contract"] - variables = c[1]["variables"] + for c in tainted_contracts.values(): + # if len(c.tainted_functions) == 0 and len(c.tainted_variables) == 0: + # continue + tainted_list.append(c) + contract = c.contract + variables = c.tainted_variables for var in variables: + var = var.variable read_write = set( contract.get_functions_reading_from_variable(var) + contract.get_functions_writing_to_variable(var) ) for f in read_write: - if f not in tainted_contracts[contract.name]["functions"] and not ( - f.is_constructor or f.is_fallback or f.is_receive - ): - tainted_contracts[contract.name]["functions"].append(f) + if f not in ( + t.function for t in tainted_contracts[contract.name].tainted_functions + ) and not (f.is_constructor or f.is_fallback or f.is_receive): + tainted_func = TaintedFunction(f) + tainted_func.add_tainted_by(var) + c.add_tainted_function(tainted_func) return tainted_list @@ -267,41 +326,70 @@ def tainted_inheriting_contracts( An updated list of TaintedExternalContract, including all from the input list. """ for tainted in tainted_contracts: - contract = tainted["contract"] + contract = tainted.contract if contracts is None: contracts = contract.compilation_unit.contracts contracts = [ c for c in contracts - if c.name not in tainted_contracts and c.name in [i.name for i in c.inheritance] + if c.name not in [t.contract.name for t in tainted_contracts] + and c.name in [i.name for i in c.inheritance] ] for c in contracts: - new_taint = TaintedExternalContract(contract=c, functions=[], variables=[]) + new_taint = TaintedExternalContract(c) for f in c.functions_declared: internal_calls = f.all_internal_calls() if any( - str(call) == str(t) for t in tainted["functions"] for call in internal_calls + call == t.function for t in tainted.tainted_functions for call in internal_calls ) or any( - str(var) == str(t) - for t in tainted["variables"] + var == t.variable + for t in tainted.tainted_variables for var in f.all_state_variables_read() + f.all_state_variables_written() ): - new_taint["functions"].append(f) - for f in new_taint["functions"]: + tainted_func = TaintedFunction(f) + tainted_by = next( + ( + t.function + for t in tainted.tainted_functions + for call in internal_calls + if str(call) == str(t.function) + ), + next( + ( + t.variable + for t in tainted.tainted_variables + for var in f.all_state_variables_read() + + f.all_state_variables_written() + if var == t.variable + ), + None, + ), + ) + tainted_func.add_tainted_by(tainted_by) + new_taint.add_tainted_function(tainted_func) + for f in new_taint.tainted_functions: + f = f.function for var in f.all_state_variables_read() + f.all_state_variables_written(): - if not (var in tainted["variables"] or var in new_taint["variables"]): - new_taint["variables"].append(var) - for var in new_taint["variables"]: + if var not in ( + v.variable for v in tainted.tainted_variables + new_taint.tainted_variables + ): + tainted_var = TaintedVariable(var) + tainted_var.add_tainted_by(f) + new_taint.add_tainted_variable(tainted_var) + for var in new_taint.tainted_variables: + var = var.variable read_write = set( contract.get_functions_reading_from_variable(var) + contract.get_functions_writing_to_variable(var) ) for f in read_write: - if f not in tainted["functions"] + new_taint["functions"] and not ( - f.is_constructor or f.is_fallback or f.is_receive - ): - new_taint["functions"].append(f) - if len(new_taint["functions"]) > 0: + if f not in ( + t.function for t in tainted.tainted_functions + new_taint.tainted_functions + ) and not (f.is_constructor or f.is_fallback or f.is_receive): + tainted_func = TaintedFunction(f) + tainted_func.add_tainted_by(var) + new_taint.add_tainted_function(tainted_func) + if len(new_taint.tainted_functions) > 0: tainted_contracts.append(new_taint) return tainted_contracts From d1b34b64e70da0ce3904408664a91dc02f206d53 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 12:09:46 -0500 Subject: [PATCH 018/101] Avoid string comparison --- slither/utils/upgradeability.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 7316b480d..df782ef6f 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -352,7 +352,7 @@ def tainted_inheriting_contracts( t.function for t in tainted.tainted_functions for call in internal_calls - if str(call) == str(t.function) + if call == t.function ), next( ( From 85c22f491e56f7aaa15d69606000e89bc6c0271b Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 12:22:30 -0500 Subject: [PATCH 019/101] Avoid re-defining `contracts` in `tainted_inheriting_contracts` --- slither/utils/upgradeability.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index df782ef6f..3be47676a 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -327,15 +327,16 @@ def tainted_inheriting_contracts( """ for tainted in tainted_contracts: contract = tainted.contract + check_contracts = contracts if contracts is None: - contracts = contract.compilation_unit.contracts - contracts = [ + check_contracts = contract.compilation_unit.contracts + check_contracts = [ c - for c in contracts + for c in check_contracts if c.name not in [t.contract.name for t in tainted_contracts] and c.name in [i.name for i in c.inheritance] ] - for c in contracts: + for c in check_contracts: new_taint = TaintedExternalContract(c) for f in c.functions_declared: internal_calls = f.all_internal_calls() From 38acd93bd2f03d2885343847e534709736343b48 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 12:31:02 -0500 Subject: [PATCH 020/101] Add inline comments --- slither/utils/upgradeability.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 3be47676a..568cb7195 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -247,10 +247,10 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon funcs: a list of Function objects to search for external calls. Returns: - TaintedExternalContract(TypedDict) ( + TaintedExternalContract() ( contract: Contract, - functions: List[Function], - variables: List[Variable] + tainted_functions: List[TaintedFunction], + tainted_variables: List[TaintedVariable] ) """ tainted_contracts: dict[str, TaintedExternalContract] = {} @@ -259,8 +259,10 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon for func in funcs: for contract, target in func.all_high_level_calls(): if contract.is_library: + # Not interested in library calls continue if contract.name not in tainted_contracts: + # A contract may be tainted by multiple function calls - only make one TaintedExternalContract object tainted_contracts[contract.name] = TaintedExternalContract(contract) if ( isinstance(target, Function) @@ -269,10 +271,12 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon not in (f.function for f in tainted_contracts[contract.name].tainted_functions) and not (target.is_constructor or target.is_fallback or target.is_receive) ): + # Found a high-level call to a new tainted function tainted_function = TaintedFunction(target) tainted_function.add_tainted_by(func) tainted_contracts[contract.name].add_tainted_function(tainted_function) for var in target.all_state_variables_written(): + # Consider as tainted all variables written by the tainted function if var not in ( v.variable for v in tainted_contracts[contract.name].tainted_variables ): @@ -285,16 +289,16 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon not in (v.variable for v in tainted_contracts[contract.name].tainted_variables) and not (target.is_constant or target.is_immutable) ): + # Found a new high-level call to a public state variable getter tainted_var = TaintedVariable(target) tainted_var.add_tainted_by(func) tainted_contracts[contract.name]["variables"].append(target) for c in tainted_contracts.values(): - # if len(c.tainted_functions) == 0 and len(c.tainted_variables) == 0: - # continue tainted_list.append(c) contract = c.contract variables = c.tainted_variables for var in variables: + # For each tainted variable, consider as tainted any function that reads or writes to it var = var.variable read_write = set( contract.get_functions_reading_from_variable(var) @@ -330,6 +334,7 @@ def tainted_inheriting_contracts( check_contracts = contracts if contracts is None: check_contracts = contract.compilation_unit.contracts + # We are only interested in checking contracts that inherit a tainted contract check_contracts = [ c for c in check_contracts @@ -339,6 +344,7 @@ def tainted_inheriting_contracts( for c in check_contracts: new_taint = TaintedExternalContract(c) for f in c.functions_declared: + # Search for functions that call an inherited tainted function or access an inherited tainted variable internal_calls = f.all_internal_calls() if any( call == t.function for t in tainted.tainted_functions for call in internal_calls @@ -348,6 +354,7 @@ def tainted_inheriting_contracts( for var in f.all_state_variables_read() + f.all_state_variables_written() ): tainted_func = TaintedFunction(f) + # Given that at least one of the `any` conditions was met, find which one is the taint source tainted_by = next( ( t.function @@ -369,8 +376,9 @@ def tainted_inheriting_contracts( tainted_func.add_tainted_by(tainted_by) new_taint.add_tainted_function(tainted_func) for f in new_taint.tainted_functions: + # For each newly found tainted function, consider as tainted any variable it writes to f = f.function - for var in f.all_state_variables_read() + f.all_state_variables_written(): + for var in f.all_state_variables_written(): if var not in ( v.variable for v in tainted.tainted_variables + new_taint.tainted_variables ): @@ -378,6 +386,7 @@ def tainted_inheriting_contracts( tainted_var.add_tainted_by(f) new_taint.add_tainted_variable(tainted_var) for var in new_taint.tainted_variables: + # For each newly found tainted variable, consider as tainted any function that reads or writes to it var = var.variable read_write = set( contract.get_functions_reading_from_variable(var) From 98a5cf012d1eff1896ac28017c7eabbe5e696d0d Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 12:33:58 -0500 Subject: [PATCH 021/101] Use canonical_name in comparisons --- slither/utils/upgradeability.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 568cb7195..6351eb6cb 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -347,9 +347,11 @@ def tainted_inheriting_contracts( # Search for functions that call an inherited tainted function or access an inherited tainted variable internal_calls = f.all_internal_calls() if any( - call == t.function for t in tainted.tainted_functions for call in internal_calls + call.canonical_name == t.function.canonical_name + for t in tainted.tainted_functions + for call in internal_calls ) or any( - var == t.variable + var.canonical_name == t.variable.canonical_name for t in tainted.tainted_variables for var in f.all_state_variables_read() + f.all_state_variables_written() ): @@ -360,7 +362,7 @@ def tainted_inheriting_contracts( t.function for t in tainted.tainted_functions for call in internal_calls - if call == t.function + if call.canonical_name == t.function.canonical_name ), next( ( @@ -368,7 +370,7 @@ def tainted_inheriting_contracts( for t in tainted.tainted_variables for var in f.all_state_variables_read() + f.all_state_variables_written() - if var == t.variable + if var.canonical_name == t.variable.canonical_name ), None, ), From 1e7397965a523fd14eb4a270d4634e037acd3c0a Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 12:50:41 -0500 Subject: [PATCH 022/101] Fix expected tainted funcs, since we changed what's considered tainted --- tests/unit/utils/test_upgradeability_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/utils/test_upgradeability_util.py b/tests/unit/utils/test_upgradeability_util.py index 8dfbccc61..88adcf00f 100644 --- a/tests/unit/utils/test_upgradeability_util.py +++ b/tests/unit/utils/test_upgradeability_util.py @@ -40,7 +40,6 @@ def test_upgrades_compare() -> None: assert new_funcs == [v2.get_function_from_signature("i()")] assert modified_funcs == [v2.get_function_from_signature("checkB()")] assert tainted_funcs == [ - v2.get_function_from_signature("g(uint256)"), v2.get_function_from_signature("h()"), ] From 178960f655d35dfde99d1c1a7e8e2b5521dfbfd1 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 15:09:54 -0500 Subject: [PATCH 023/101] Simplify by removing `TaintedFunction` and `TaintedVariable` classes --- slither/utils/upgradeability.py | 120 +++++++------------------------- 1 file changed, 25 insertions(+), 95 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 6351eb6cb..2a8347ddb 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -63,62 +63,28 @@ from slither.slithir.variables import ( from slither.tools.read_storage.read_storage import SlotInfo, SlitherReadStorage -class TaintedFunction: - def __init__(self, f: "Function") -> None: - self._function: Function = f - self._tainted_by: List[Union[Function, StateVariable]] = [] - - @property - def function(self) -> Function: - return self._function - - @property - def tainted_by(self) -> List[Union[Function, StateVariable]]: - return self._tainted_by - - def add_tainted_by(self, f: Union[Function, StateVariable]): - self._tainted_by.append(f) - - -class TaintedVariable: - def __init__(self, v: "StateVariable") -> None: - self._variable: StateVariable = v - self._tainted_by: List[Function] = [] - - @property - def variable(self) -> StateVariable: - return self._variable - - @property - def tainted_by(self) -> List[Function]: - return self._tainted_by - - def add_tainted_by(self, f: Function): - self._tainted_by.append(f) - - class TaintedExternalContract: def __init__(self, contract: "Contract") -> None: self._contract: Contract = contract - self._tainted_functions: List[TaintedFunction] = [] - self._tainted_variables: List[TaintedVariable] = [] + self._tainted_functions: List[Function] = [] + self._tainted_variables: List[Variable] = [] @property def contract(self) -> Contract: return self._contract @property - def tainted_functions(self) -> List[TaintedFunction]: + def tainted_functions(self) -> List[Function]: return self._tainted_functions - def add_tainted_function(self, f: TaintedFunction): + def add_tainted_function(self, f: Function): self._tainted_functions.append(f) @property - def tainted_variables(self) -> List[TaintedVariable]: + def tainted_variables(self) -> List[Variable]: return self._tainted_variables - def add_tainted_variable(self, v: TaintedVariable): + def add_tainted_variable(self, v: Variable): self._tainted_variables.append(v) @@ -268,49 +234,40 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon isinstance(target, Function) and target not in funcs and target - not in (f.function for f in tainted_contracts[contract.name].tainted_functions) + not in (f for f in tainted_contracts[contract.name].tainted_functions) and not (target.is_constructor or target.is_fallback or target.is_receive) ): # Found a high-level call to a new tainted function - tainted_function = TaintedFunction(target) - tainted_function.add_tainted_by(func) - tainted_contracts[contract.name].add_tainted_function(tainted_function) + tainted_contracts[contract.name].add_tainted_function(target) for var in target.all_state_variables_written(): # Consider as tainted all variables written by the tainted function if var not in ( - v.variable for v in tainted_contracts[contract.name].tainted_variables + v for v in tainted_contracts[contract.name].tainted_variables ): - tainted_var = TaintedVariable(var) - tainted_var.add_tainted_by(target) - tainted_contracts[contract.name].add_tainted_variable(tainted_var) + tainted_contracts[contract.name].add_tainted_variable(var) elif ( isinstance(target, StateVariable) - and target - not in (v.variable for v in tainted_contracts[contract.name].tainted_variables) + and target not in (v for v in tainted_contracts[contract.name].tainted_variables) and not (target.is_constant or target.is_immutable) ): # Found a new high-level call to a public state variable getter - tainted_var = TaintedVariable(target) - tainted_var.add_tainted_by(func) - tainted_contracts[contract.name]["variables"].append(target) + tainted_contracts[contract.name].add_tainted_variable(target) for c in tainted_contracts.values(): tainted_list.append(c) contract = c.contract variables = c.tainted_variables for var in variables: # For each tainted variable, consider as tainted any function that reads or writes to it - var = var.variable read_write = set( contract.get_functions_reading_from_variable(var) + contract.get_functions_writing_to_variable(var) ) for f in read_write: - if f not in ( - t.function for t in tainted_contracts[contract.name].tainted_functions - ) and not (f.is_constructor or f.is_fallback or f.is_receive): - tainted_func = TaintedFunction(f) - tainted_func.add_tainted_by(var) - c.add_tainted_function(tainted_func) + if ( + f not in tainted_contracts[contract.name].tainted_functions + and not (f.is_constructor or f.is_fallback or f.is_receive) + ): + c.add_tainted_function(f) return tainted_list @@ -339,7 +296,7 @@ def tainted_inheriting_contracts( c for c in check_contracts if c.name not in [t.contract.name for t in tainted_contracts] - and c.name in [i.name for i in c.inheritance] + and contract.name in [i.name for i in c.inheritance] ] for c in check_contracts: new_taint = TaintedExternalContract(c) @@ -347,60 +304,33 @@ def tainted_inheriting_contracts( # Search for functions that call an inherited tainted function or access an inherited tainted variable internal_calls = f.all_internal_calls() if any( - call.canonical_name == t.function.canonical_name + call.canonical_name == t.canonical_name for t in tainted.tainted_functions for call in internal_calls ) or any( - var.canonical_name == t.variable.canonical_name + var.canonical_name == t.canonical_name for t in tainted.tainted_variables for var in f.all_state_variables_read() + f.all_state_variables_written() ): - tainted_func = TaintedFunction(f) - # Given that at least one of the `any` conditions was met, find which one is the taint source - tainted_by = next( - ( - t.function - for t in tainted.tainted_functions - for call in internal_calls - if call.canonical_name == t.function.canonical_name - ), - next( - ( - t.variable - for t in tainted.tainted_variables - for var in f.all_state_variables_read() - + f.all_state_variables_written() - if var.canonical_name == t.variable.canonical_name - ), - None, - ), - ) - tainted_func.add_tainted_by(tainted_by) - new_taint.add_tainted_function(tainted_func) + new_taint.add_tainted_function(f) for f in new_taint.tainted_functions: # For each newly found tainted function, consider as tainted any variable it writes to - f = f.function for var in f.all_state_variables_written(): if var not in ( - v.variable for v in tainted.tainted_variables + new_taint.tainted_variables + v for v in tainted.tainted_variables + new_taint.tainted_variables ): - tainted_var = TaintedVariable(var) - tainted_var.add_tainted_by(f) - new_taint.add_tainted_variable(tainted_var) + new_taint.add_tainted_variable(var) for var in new_taint.tainted_variables: # For each newly found tainted variable, consider as tainted any function that reads or writes to it - var = var.variable read_write = set( contract.get_functions_reading_from_variable(var) + contract.get_functions_writing_to_variable(var) ) for f in read_write: if f not in ( - t.function for t in tainted.tainted_functions + new_taint.tainted_functions + t for t in tainted.tainted_functions + new_taint.tainted_functions ) and not (f.is_constructor or f.is_fallback or f.is_receive): - tainted_func = TaintedFunction(f) - tainted_func.add_tainted_by(var) - new_taint.add_tainted_function(tainted_func) + new_taint.add_tainted_function(f) if len(new_taint.tainted_functions) > 0: tainted_contracts.append(new_taint) return tainted_contracts From 3bcefac1c87d9b4fbead106ca25f8ed115b720f7 Mon Sep 17 00:00:00 2001 From: webthethird Date: Tue, 4 Apr 2023 15:10:19 -0500 Subject: [PATCH 024/101] Black --- slither/utils/upgradeability.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 2a8347ddb..29edfec0f 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -233,17 +233,14 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon if ( isinstance(target, Function) and target not in funcs - and target - not in (f for f in tainted_contracts[contract.name].tainted_functions) + and target not in (f for f in tainted_contracts[contract.name].tainted_functions) and not (target.is_constructor or target.is_fallback or target.is_receive) ): # Found a high-level call to a new tainted function tainted_contracts[contract.name].add_tainted_function(target) for var in target.all_state_variables_written(): # Consider as tainted all variables written by the tainted function - if var not in ( - v for v in tainted_contracts[contract.name].tainted_variables - ): + if var not in (v for v in tainted_contracts[contract.name].tainted_variables): tainted_contracts[contract.name].add_tainted_variable(var) elif ( isinstance(target, StateVariable) @@ -263,9 +260,8 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon + contract.get_functions_writing_to_variable(var) ) for f in read_write: - if ( - f not in tainted_contracts[contract.name].tainted_functions - and not (f.is_constructor or f.is_fallback or f.is_receive) + if f not in tainted_contracts[contract.name].tainted_functions and not ( + f.is_constructor or f.is_fallback or f.is_receive ): c.add_tainted_function(f) return tainted_list From ae7f0b2f052afbbde1684d988d636936105e84fe Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 10 Apr 2023 11:47:56 -0500 Subject: [PATCH 025/101] Only check internal calls to Functions --- slither/utils/upgradeability.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 29edfec0f..1bff3c153 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -298,7 +298,7 @@ def tainted_inheriting_contracts( new_taint = TaintedExternalContract(c) for f in c.functions_declared: # Search for functions that call an inherited tainted function or access an inherited tainted variable - internal_calls = f.all_internal_calls() + internal_calls = [c for c in f.all_internal_calls() if isinstance(c, Function)] if any( call.canonical_name == t.canonical_name for t in tainted.tainted_functions From ca82da060358664b79b197d1446209cde584590c Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 12:32:02 -0500 Subject: [PATCH 026/101] Update compare docstring --- slither/utils/upgradeability.py | 1 + 1 file changed, 1 insertion(+) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 1bff3c153..b6915b4f2 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -115,6 +115,7 @@ def compare( new-functions: list[Function], modified-functions: list[Function], tainted-functions: list[Function] + tainted-contracts: list[TaintedExternalContract] """ order_vars1 = [ From 853051ebf473fa77dfb1029170c5f034ad02ea99 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 13:53:05 -0500 Subject: [PATCH 027/101] Reduce tainted variables to only written --- slither/utils/upgradeability.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index b6915b4f2..516855130 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -177,15 +177,13 @@ def compare( if len(modified_calls) > 0 or len(tainted_vars) > 0: tainted_functions.append(function) - # Find all new or tainted variables, i.e., variables that are read or written by a new/modified/tainted function + # Find all new or tainted variables, i.e., variables that are written by a new/modified/tainted function for var in order_vars2: - read_by = v2.get_functions_reading_from_variable(var) written_by = v2.get_functions_writing_to_variable(var) - # if v1.get_state_variable_from_name(var.name) is None: if next((v for v in v1.state_variables_ordered if v.name == var.name), None) is None: new_variables.append(var) elif any( - func in read_by or func in written_by + func in written_by for func in new_modified_functions + tainted_functions ): tainted_variables.append(var) From 823337e45db37cc6de39c452b2898ba0b1748948 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 14:07:34 -0500 Subject: [PATCH 028/101] Update test --- tests/unit/utils/test_upgradeability_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/utils/test_upgradeability_util.py b/tests/unit/utils/test_upgradeability_util.py index 88adcf00f..7a2931d52 100644 --- a/tests/unit/utils/test_upgradeability_util.py +++ b/tests/unit/utils/test_upgradeability_util.py @@ -34,7 +34,6 @@ def test_upgrades_compare() -> None: assert len(missing_vars) == len(tainted_contracts) == 0 assert new_vars == [v2.get_state_variable_from_name("stateC")] assert tainted_vars == [ - v2.get_state_variable_from_name("stateB"), v2.get_state_variable_from_name("bug"), ] assert new_funcs == [v2.get_function_from_signature("i()")] From 6ccc8cfee763448aa15c4ba10aaf093420479553 Mon Sep 17 00:00:00 2001 From: webthethird Date: Mon, 17 Apr 2023 14:07:42 -0500 Subject: [PATCH 029/101] Black --- slither/utils/upgradeability.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 516855130..5be745bd6 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -182,10 +182,7 @@ def compare( written_by = v2.get_functions_writing_to_variable(var) if next((v for v in v1.state_variables_ordered if v.name == var.name), None) is None: new_variables.append(var) - elif any( - func in written_by - for func in new_modified_functions + tainted_functions - ): + elif any(func in written_by for func in new_modified_functions + tainted_functions): tainted_variables.append(var) # Find all external contracts and functions called by new/modified/tainted functions From ef2eadc1c412222f815fee2326b51b17062b3019 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 19 Apr 2023 15:24:42 -0500 Subject: [PATCH 030/101] Update test_upgrades_compare to test cross-contract taint --- .../upgradeability_util/src/ContractV2.sol | 5 + .../upgradeability_util/src/ERC20.sol | 376 ++++++++++++++++++ tests/unit/utils/test_upgradeability_util.py | 20 +- 3 files changed, 399 insertions(+), 2 deletions(-) create mode 100644 tests/unit/utils/test_data/upgradeability_util/src/ERC20.sol diff --git a/tests/unit/utils/test_data/upgradeability_util/src/ContractV2.sol b/tests/unit/utils/test_data/upgradeability_util/src/ContractV2.sol index 9b102f3e9..9c508caf3 100644 --- a/tests/unit/utils/test_data/upgradeability_util/src/ContractV2.sol +++ b/tests/unit/utils/test_data/upgradeability_util/src/ContractV2.sol @@ -1,6 +1,7 @@ pragma solidity ^0.8.2; import "./ProxyStorage.sol"; +import "./ERC20.sol"; contract ContractV2 is ProxyStorage { uint private stateA = 0; @@ -38,4 +39,8 @@ contract ContractV2 is ProxyStorage { function checkB() internal returns (bool) { return stateB == 32; } + + function erc20Transfer(address erc20, address to, uint256 amount) public returns (bool) { + return ERC20(erc20).transfer(to, amount); + } } diff --git a/tests/unit/utils/test_data/upgradeability_util/src/ERC20.sol b/tests/unit/utils/test_data/upgradeability_util/src/ERC20.sol new file mode 100644 index 000000000..6c7801581 --- /dev/null +++ b/tests/unit/utils/test_data/upgradeability_util/src/ERC20.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual returns (bool) { + address owner = msg.sender; + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual returns (bool) { + address owner = msg.sender; + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + address spender = msg.sender; + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = msg.sender; + _approve(owner, spender, allowance(owner, spender) + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + address owner = msg.sender; + uint256 currentAllowance = allowance(owner, spender); + require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + _balances[to] += amount; + } + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. + _balances[account] += amount; + } + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + // Overflow not possible: amount <= accountBalance <= totalSupply. + _totalSupply -= amount; + } + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + } + + /** + * @dev Updates `owner` s allowance for `spender` based on spent `amount`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} diff --git a/tests/unit/utils/test_upgradeability_util.py b/tests/unit/utils/test_upgradeability_util.py index 7a2931d52..e367a3eed 100644 --- a/tests/unit/utils/test_upgradeability_util.py +++ b/tests/unit/utils/test_upgradeability_util.py @@ -31,16 +31,32 @@ def test_upgrades_compare() -> None: tainted_funcs, tainted_contracts, ) = compare(v1, v2) - assert len(missing_vars) == len(tainted_contracts) == 0 + assert len(missing_vars) == 0 assert new_vars == [v2.get_state_variable_from_name("stateC")] assert tainted_vars == [ v2.get_state_variable_from_name("bug"), ] - assert new_funcs == [v2.get_function_from_signature("i()")] + assert new_funcs == [ + v2.get_function_from_signature("i()"), + v2.get_function_from_signature("erc20Transfer(address,address,uint256)"), + ] assert modified_funcs == [v2.get_function_from_signature("checkB()")] assert tainted_funcs == [ v2.get_function_from_signature("h()"), ] + erc20 = sl.get_contract_from_name("ERC20")[0] + assert len(tainted_contracts) == 1 + assert tainted_contracts[0].contract == erc20 + assert set(tainted_contracts[0].tainted_functions) == { + erc20.get_function_from_signature("transfer(address,uint256)"), + erc20.get_function_from_signature("_transfer(address,address,uint256)"), + erc20.get_function_from_signature("_burn(address,uint256)"), + erc20.get_function_from_signature("balanceOf(address)"), + erc20.get_function_from_signature("_mint(address,uint256)"), + } + assert tainted_contracts[0].tainted_variables == [ + erc20.get_state_variable_from_name("_balances") + ] def test_upgrades_implementation_var() -> None: From 7a2fc5cd9132eac8035aeaec9c6fe6d3958b196a Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 17 Mar 2023 18:24:47 -0500 Subject: [PATCH 031/101] remove new array depth and parse length upfront --- slither/core/expressions/new_array.py | 25 ++++++----------- slither/slithir/convert.py | 2 +- slither/slithir/operations/init_array.py | 2 +- slither/slithir/operations/new_array.py | 18 +++++-------- .../slithir/tmp_operations/tmp_new_array.py | 8 +----- slither/slithir/utils/ssa.py | 3 +-- .../expressions/expression_parsing.py | 27 ++++--------------- .../visitors/expression/expression_printer.py | 3 +-- .../visitors/slithir/expression_to_slithir.py | 2 +- tests/unit/slithir/test_ssa_generation.py | 19 +++++++++++++ 10 files changed, 45 insertions(+), 64 deletions(-) diff --git a/slither/core/expressions/new_array.py b/slither/core/expressions/new_array.py index 162b48f1e..08ccf24c0 100644 --- a/slither/core/expressions/new_array.py +++ b/slither/core/expressions/new_array.py @@ -2,31 +2,22 @@ from typing import Union, TYPE_CHECKING from slither.core.expressions.expression import Expression -from slither.core.solidity_types.type import Type if TYPE_CHECKING: - from slither.core.solidity_types.elementary_type import ElementaryType - from slither.core.solidity_types.type_alias import TypeAliasTopLevel + from slither.core.solidity_types.array_type import ArrayType class NewArray(Expression): - - # note: dont conserve the size of the array if provided - def __init__( - self, depth: int, array_type: Union["TypeAliasTopLevel", "ElementaryType"] - ) -> None: + def __init__(self, array_type: "ArrayType") -> None: super().__init__() - assert isinstance(array_type, Type) - self._depth: int = depth - self._array_type: Type = array_type + from slither.core.solidity_types.array_type import ArrayType - @property - def array_type(self) -> Type: - return self._array_type + assert isinstance(array_type, ArrayType) + self._array_type = array_type @property - def depth(self) -> int: - return self._depth + def array_type(self) -> "ArrayType": + return self._array_type def __str__(self): - return "new " + str(self._array_type) + "[]" * self._depth + return "new " + str(self._array_type) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 3cfddf5e6..3161addd7 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1077,7 +1077,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call, return op if isinstance(ins.ori, TmpNewArray): - n = NewArray(ins.ori.depth, ins.ori.array_type, ins.lvalue) + n = NewArray(ins.ori.array_type, ins.lvalue) n.set_expression(ins.expression) return n diff --git a/slither/slithir/operations/init_array.py b/slither/slithir/operations/init_array.py index 4f6b2f9fa..e0754c770 100644 --- a/slither/slithir/operations/init_array.py +++ b/slither/slithir/operations/init_array.py @@ -41,7 +41,7 @@ class InitArray(OperationWithLValue): def convert(elem): if isinstance(elem, (list,)): return str([convert(x) for x in elem]) - return str(elem) + return f"{elem}({elem.type})" init_values = convert(self.init_values) return f"{self.lvalue}({self.lvalue.type}) = {init_values}" diff --git a/slither/slithir/operations/new_array.py b/slither/slithir/operations/new_array.py index 8dad8532f..b023a04a5 100644 --- a/slither/slithir/operations/new_array.py +++ b/slither/slithir/operations/new_array.py @@ -4,7 +4,7 @@ from slither.slithir.operations.call import Call from slither.core.solidity_types.type import Type if TYPE_CHECKING: - from slither.core.solidity_types.type_alias import TypeAliasTopLevel + from slither.core.solidity_types.type_alias import ArrayType from slither.slithir.variables.constant import Constant from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA @@ -13,29 +13,25 @@ if TYPE_CHECKING: class NewArray(Call, OperationWithLValue): def __init__( self, - depth: int, - array_type: "TypeAliasTopLevel", + array_type: "ArrayType", lvalue: Union["TemporaryVariableSSA", "TemporaryVariable"], ) -> None: super().__init__() - assert isinstance(array_type, Type) - self._depth = depth + from slither.core.solidity_types.array_type import ArrayType + + assert isinstance(array_type, ArrayType) self._array_type = array_type self._lvalue = lvalue @property - def array_type(self) -> "TypeAliasTopLevel": + def array_type(self) -> "ArrayType": return self._array_type @property def read(self) -> List["Constant"]: return self._unroll(self.arguments) - @property - def depth(self) -> int: - return self._depth - def __str__(self): args = [str(a) for a in self.arguments] - return f"{self.lvalue} = new {self.array_type}{'[]' * self.depth}({','.join(args)})" + return f"{self.lvalue} = new {self.array_type}({','.join(args)})" diff --git a/slither/slithir/tmp_operations/tmp_new_array.py b/slither/slithir/tmp_operations/tmp_new_array.py index efbdb6242..04acb4b9e 100644 --- a/slither/slithir/tmp_operations/tmp_new_array.py +++ b/slither/slithir/tmp_operations/tmp_new_array.py @@ -6,13 +6,11 @@ from slither.slithir.variables.temporary import TemporaryVariable class TmpNewArray(OperationWithLValue): def __init__( self, - depth: int, array_type: Type, lvalue: TemporaryVariable, ) -> None: super().__init__() assert isinstance(array_type, Type) - self._depth = depth self._array_type = array_type self._lvalue = lvalue @@ -24,9 +22,5 @@ class TmpNewArray(OperationWithLValue): def read(self): return [] - @property - def depth(self) -> int: - return self._depth - def __str__(self): - return f"{self.lvalue} = new {self.array_type}{'[]' * self._depth}" + return f"{self.lvalue} = new {self.array_type}" diff --git a/slither/slithir/utils/ssa.py b/slither/slithir/utils/ssa.py index 9a180d14f..4c958798b 100644 --- a/slither/slithir/utils/ssa.py +++ b/slither/slithir/utils/ssa.py @@ -789,10 +789,9 @@ def copy_ir(ir: Operation, *instances) -> Operation: variable_right = get_variable(ir, lambda x: x.variable_right, *instances) return Member(variable_left, variable_right, lvalue) if isinstance(ir, NewArray): - depth = ir.depth array_type = ir.array_type lvalue = get_variable(ir, lambda x: x.lvalue, *instances) - new_ir = NewArray(depth, array_type, lvalue) + new_ir = NewArray(array_type, lvalue) new_ir.arguments = get_rec_values(ir, lambda x: x.arguments, *instances) return new_ir if isinstance(ir, NewElementaryType): diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index d0dc4c7e0..c74caf94f 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -560,6 +560,7 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression) if type_name[caller_context.get_key()] == "ArrayTypeName": depth = 0 + array_type = parse_type(type_name, caller_context) while type_name[caller_context.get_key()] == "ArrayTypeName": # Note: dont conserve the size of the array if provided # We compute it directly @@ -567,29 +568,11 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression) type_name = type_name["baseType"] else: type_name = type_name["children"][0] + if depth > 0: + # nested array + array_type = ArrayType(parse_type(type_name, caller_context), array_type.length) depth += 1 - if type_name[caller_context.get_key()] == "ElementaryTypeName": - if is_compact_ast: - array_type = ElementaryType(type_name["name"]) - else: - array_type = ElementaryType(type_name["attributes"]["name"]) - elif type_name[caller_context.get_key()] == "UserDefinedTypeName": - if is_compact_ast: - if "name" not in type_name: - name_type = type_name["pathNode"]["name"] - else: - name_type = type_name["name"] - - array_type = parse_type(UnknownType(name_type), caller_context) - else: - array_type = parse_type( - UnknownType(type_name["attributes"]["name"]), caller_context - ) - elif type_name[caller_context.get_key()] == "FunctionTypeName": - array_type = parse_type(type_name, caller_context) - else: - raise ParsingError(f"Incorrect type array {type_name}") - array = NewArray(depth, array_type) + array = NewArray(array_type) array.set_offset(src, caller_context.compilation_unit) return array diff --git a/slither/visitors/expression/expression_printer.py b/slither/visitors/expression/expression_printer.py index 601627c02..61af4f1c9 100644 --- a/slither/visitors/expression/expression_printer.py +++ b/slither/visitors/expression/expression_printer.py @@ -76,8 +76,7 @@ class ExpressionPrinter(ExpressionVisitor): def _post_new_array(self, expression: expressions.NewArray) -> None: array = str(expression.array_type) - depth = expression.depth - val = f"new {array}{'[]' * depth}" + val = f"new {array}" set_val(expression, val) def _post_new_contract(self, expression: expressions.NewContract) -> None: diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 90905be4e..bb1c62200 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -528,7 +528,7 @@ class ExpressionToSlithIR(ExpressionVisitor): def _post_new_array(self, expression: NewArray) -> None: val = TemporaryVariable(self._node) - operation = TmpNewArray(expression.depth, expression.array_type, val) + operation = TmpNewArray(expression.array_type, val) operation.set_expression(expression) self._result.append(operation) set_val(expression, val) diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 4e26aa54d..46147f8df 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -34,6 +34,8 @@ from slither.slithir.variables import ( TemporaryVariableSSA, ) +from slither.core.solidity_types import ArrayType + # Directory of currently executing script. Will be used as basis for temporary file names. SCRIPT_DIR = pathlib.Path(getsourcefile(lambda: 0)).parent # type:ignore @@ -1048,3 +1050,20 @@ def test_issue_1748(slither_from_source): operations = f.slithir_operations assign_op = operations[0] assert isinstance(assign_op, InitArray) + + +def test_issue_1776(): + source = """ + contract Contract { + function foo() public returns (uint) { + uint[] memory arr = new uint[](2); + return 0; + } + } + """ + with slither_from_source(source) as slither: + c = slither.get_contract_from_name("Contract")[0] + f = c.functions[0] + operations = f.slithir_operations + assign_op = operations[0] + assert isinstance(assign_op.lvalue.type, ArrayType) From 8f9fe80c14fc9e764529886179ffcad4b5df71b8 Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Mon, 20 Mar 2023 20:27:15 -0400 Subject: [PATCH 032/101] update test to check nested array length --- tests/unit/slithir/test_ssa_generation.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 46147f8df..624770740 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -1056,7 +1056,7 @@ def test_issue_1776(): source = """ contract Contract { function foo() public returns (uint) { - uint[] memory arr = new uint[](2); + uint[5][10][] memory arr = new uint[5][10][](2); return 0; } } @@ -1065,5 +1065,13 @@ def test_issue_1776(): c = slither.get_contract_from_name("Contract")[0] f = c.functions[0] operations = f.slithir_operations - assign_op = operations[0] - assert isinstance(assign_op.lvalue.type, ArrayType) + new_op = operations[0] + lvalue = new_op.lvalue + lvalue_type = lvalue.type + assert isinstance(lvalue_type, ArrayType) + lvalue_type1 = lvalue_type.type + assert isinstance(lvalue_type1, ArrayType) + assert lvalue_type1.length_value.value == "10" + lvalue_type2 = lvalue_type1.type + assert isinstance(lvalue_type2, ArrayType) + assert lvalue_type2.length_value.value == "5" From aadee32b95ba522d670e3fc25304c939b827cbfa Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Mon, 20 Mar 2023 20:27:57 -0400 Subject: [PATCH 033/101] Fix incorrect array_type in NewArray --- slither/core/expressions/new_array.py | 3 +-- .../solc_parsing/expressions/expression_parsing.py | 13 +------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/slither/core/expressions/new_array.py b/slither/core/expressions/new_array.py index 08ccf24c0..05d50f168 100644 --- a/slither/core/expressions/new_array.py +++ b/slither/core/expressions/new_array.py @@ -1,5 +1,4 @@ -from typing import Union, TYPE_CHECKING - +from typing import TYPE_CHECKING from slither.core.expressions.expression import Expression diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index c74caf94f..945a60b8f 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -559,19 +559,8 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression) type_name = children[0] if type_name[caller_context.get_key()] == "ArrayTypeName": - depth = 0 array_type = parse_type(type_name, caller_context) - while type_name[caller_context.get_key()] == "ArrayTypeName": - # Note: dont conserve the size of the array if provided - # We compute it directly - if is_compact_ast: - type_name = type_name["baseType"] - else: - type_name = type_name["children"][0] - if depth > 0: - # nested array - array_type = ArrayType(parse_type(type_name, caller_context), array_type.length) - depth += 1 + assert isinstance(array_type, ArrayType) array = NewArray(array_type) array.set_offset(src, caller_context.compilation_unit) return array From 73f5ad3c93c13477f7141f30cf13ba223529e210 Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Mon, 20 Mar 2023 20:42:29 -0400 Subject: [PATCH 034/101] Fix pylint issues --- slither/core/expressions/new_array.py | 1 + slither/slithir/operations/new_array.py | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/slither/core/expressions/new_array.py b/slither/core/expressions/new_array.py index 05d50f168..b1d407793 100644 --- a/slither/core/expressions/new_array.py +++ b/slither/core/expressions/new_array.py @@ -9,6 +9,7 @@ if TYPE_CHECKING: class NewArray(Expression): def __init__(self, array_type: "ArrayType") -> None: super().__init__() + # pylint: disable=import-outside-toplevel from slither.core.solidity_types.array_type import ArrayType assert isinstance(array_type, ArrayType) diff --git a/slither/slithir/operations/new_array.py b/slither/slithir/operations/new_array.py index b023a04a5..2d032ccd8 100644 --- a/slither/slithir/operations/new_array.py +++ b/slither/slithir/operations/new_array.py @@ -1,10 +1,10 @@ from typing import List, Union, TYPE_CHECKING -from slither.slithir.operations.lvalue import OperationWithLValue + +from slither.core.solidity_types.array_type import ArrayType from slither.slithir.operations.call import Call -from slither.core.solidity_types.type import Type +from slither.slithir.operations.lvalue import OperationWithLValue if TYPE_CHECKING: - from slither.core.solidity_types.type_alias import ArrayType from slither.slithir.variables.constant import Constant from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA @@ -17,8 +17,6 @@ class NewArray(Call, OperationWithLValue): lvalue: Union["TemporaryVariableSSA", "TemporaryVariable"], ) -> None: super().__init__() - from slither.core.solidity_types.array_type import ArrayType - assert isinstance(array_type, ArrayType) self._array_type = array_type From 063418da60658f8595a058cb972e0d99f795029e Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Sun, 2 Apr 2023 18:25:16 -0400 Subject: [PATCH 035/101] Assert array dynamic or static --- tests/unit/slithir/test_ssa_generation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 624770740..dc5113617 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -1068,10 +1068,13 @@ def test_issue_1776(): new_op = operations[0] lvalue = new_op.lvalue lvalue_type = lvalue.type + assert lvalue_type.is_dynamic assert isinstance(lvalue_type, ArrayType) lvalue_type1 = lvalue_type.type + assert not lvalue_type1.is_dynamic assert isinstance(lvalue_type1, ArrayType) assert lvalue_type1.length_value.value == "10" lvalue_type2 = lvalue_type1.type + assert not lvalue_type2.is_dynamic assert isinstance(lvalue_type2, ArrayType) assert lvalue_type2.length_value.value == "5" From c8e6c4a6a84bc9334e926730710c3441dbf8e496 Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Sun, 2 Apr 2023 18:30:48 -0400 Subject: [PATCH 036/101] Reorder assertions --- tests/unit/slithir/test_ssa_generation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index dc5113617..7f2f042cb 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -1068,13 +1068,13 @@ def test_issue_1776(): new_op = operations[0] lvalue = new_op.lvalue lvalue_type = lvalue.type - assert lvalue_type.is_dynamic assert isinstance(lvalue_type, ArrayType) + assert lvalue_type.is_dynamic lvalue_type1 = lvalue_type.type - assert not lvalue_type1.is_dynamic assert isinstance(lvalue_type1, ArrayType) + assert not lvalue_type1.is_dynamic assert lvalue_type1.length_value.value == "10" lvalue_type2 = lvalue_type1.type - assert not lvalue_type2.is_dynamic assert isinstance(lvalue_type2, ArrayType) + assert not lvalue_type2.is_dynamic assert lvalue_type2.length_value.value == "5" From 0c803a1265afc7a787dac05355ff1e60df0068ba Mon Sep 17 00:00:00 2001 From: William Aaron Cheung Date: Fri, 21 Apr 2023 13:49:27 -0400 Subject: [PATCH 037/101] fix broken test --- tests/unit/slithir/test_ssa_generation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 7f2f042cb..d263d1453 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -1052,7 +1052,7 @@ def test_issue_1748(slither_from_source): assert isinstance(assign_op, InitArray) -def test_issue_1776(): +def test_issue_1776(slither_from_source): source = """ contract Contract { function foo() public returns (uint) { From b26f8da1248e7f57119e76f5dc89014cd46272e8 Mon Sep 17 00:00:00 2001 From: Simone Date: Mon, 24 Apr 2023 21:23:08 +0200 Subject: [PATCH 038/101] Improve try-catch parsing --- slither/solc_parsing/declarations/function.py | 76 +++++++++++++++++-- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 7438a7bb0..ab5d289a8 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -660,6 +660,55 @@ class FunctionSolc(CallerContextExpression): link_underlying_nodes(node_condition, node_endDoWhile) return node_endDoWhile + # pylint: disable=no-self-use + def _construct_try_expression(self, externalCall: Dict, parameters_list: Dict) -> Dict: + # if the parameters are more than 1 we make the leftHandSide of the Assignment node + # a TupleExpression otherwise an Identifier + + ret: Dict = {"nodeType": "Assignment", "operator": "=", "src": parameters_list["src"]} + + parameters = parameters_list.get("parameters", None) + + # if the name is "" it means the return variable is not used + if len(parameters) == 1: + if parameters[0]["name"] != "": + ret["typeDescriptions"] = { + "typeString": parameters[0]["typeName"]["typeDescriptions"]["typeString"] + } + leftHandSide = { + "name": parameters[0]["name"], + "nodeType": "Identifier", + "src": parameters[0]["src"], + "typeDescriptions": parameters[0]["typeDescriptions"], + } + else: + # we don't need an Assignment so we return only the external call + return externalCall + else: + ret["typeDescriptions"] = {"typeString": "tuple()"} + leftHandSide = { + "components": [], + "nodeType": "TupleExpression", + "src": parameters_list["src"], + } + + for p in parameters: + if p["name"] == "": + continue + + ident = { + "name": p["name"], + "nodeType": "Identifier", + "src": p["src"], + "typeDescriptions": p["typeDescriptions"], + } + leftHandSide["components"].append(ident) + + ret["leftHandSide"] = leftHandSide + ret["rightHandSide"] = externalCall + + return ret + def _parse_try_catch(self, statement: Dict, node: NodeSolc) -> NodeSolc: externalCall = statement.get("externalCall", None) @@ -669,15 +718,27 @@ class FunctionSolc(CallerContextExpression): node.underlying_node.scope.is_checked, False, node.underlying_node.scope ) new_node = self._new_node(NodeType.TRY, statement["src"], catch_scope) - new_node.add_unparsed_expression(externalCall) + clauses = statement.get("clauses", []) + # the first clause is the try scope + returned_variables = clauses[0].get("parameters", None) + constructed_try_expression = self._construct_try_expression( + externalCall, returned_variables + ) + new_node.add_unparsed_expression(constructed_try_expression) link_underlying_nodes(node, new_node) node = new_node - for clause in statement.get("clauses", []): - self._parse_catch(clause, node) + for index, clause in enumerate(clauses): + # clauses after the first one are related to catch cases + # we set the parameters (e.g. data in this case. catch(string memory data) ...) + # to be initialized so they are not reported by the uninitialized-local-variables detector + if index >= 1: + self._parse_catch(clause, node, True) + else: + self._parse_catch(clause, node, False) return node - def _parse_catch(self, statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_catch(self, statement: Dict, node: NodeSolc, var_initialized: bool) -> NodeSolc: block = statement.get("block", None) if block is None: @@ -695,7 +756,7 @@ class FunctionSolc(CallerContextExpression): if params: for param in params.get("parameters", []): assert param[self.get_key()] == "VariableDeclaration" - self._add_param(param) + self._add_param(param, var_initialized) return self._parse_statement(block, try_node, try_scope) @@ -1161,7 +1222,7 @@ class FunctionSolc(CallerContextExpression): visited.add(son) self._fix_catch(son, end_node, visited) - def _add_param(self, param: Dict) -> LocalVariableSolc: + def _add_param(self, param: Dict, initialized: bool = False) -> LocalVariableSolc: local_var = LocalVariable() local_var.set_function(self._function) @@ -1171,6 +1232,9 @@ class FunctionSolc(CallerContextExpression): local_var_parser.analyze(self) + if initialized: + local_var.initialized = True + # see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location if local_var.location == "default": local_var.set_location("memory") From d7b0d612c64eefc4bce33cdc4b09c6c740837a94 Mon Sep 17 00:00:00 2001 From: Simone Date: Thu, 27 Apr 2023 10:53:20 +0200 Subject: [PATCH 039/101] Improve handling of multiple return variables --- slither/solc_parsing/declarations/function.py | 41 ++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index ab5d289a8..7ac67a02d 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -672,6 +672,7 @@ class FunctionSolc(CallerContextExpression): # if the name is "" it means the return variable is not used if len(parameters) == 1: if parameters[0]["name"] != "": + self._add_param(parameters[0]) ret["typeDescriptions"] = { "typeString": parameters[0]["typeName"]["typeDescriptions"]["typeString"] } @@ -692,10 +693,17 @@ class FunctionSolc(CallerContextExpression): "src": parameters_list["src"], } - for p in parameters: + for i, p in enumerate(parameters): if p["name"] == "": continue + new_statement = { + "nodeType": "VariableDefinitionStatement", + "src": p["src"], + "declarations": [p], + } + self._add_param_init_tuple(new_statement, i) + ident = { "name": p["name"], "nodeType": "Identifier", @@ -735,10 +743,11 @@ class FunctionSolc(CallerContextExpression): if index >= 1: self._parse_catch(clause, node, True) else: + # the parameters for the try scope were already added in _construct_try_expression self._parse_catch(clause, node, False) return node - def _parse_catch(self, statement: Dict, node: NodeSolc, var_initialized: bool) -> NodeSolc: + def _parse_catch(self, statement: Dict, node: NodeSolc, add_param: bool) -> NodeSolc: block = statement.get("block", None) if block is None: @@ -748,15 +757,16 @@ class FunctionSolc(CallerContextExpression): try_node = self._new_node(NodeType.CATCH, statement["src"], try_scope) link_underlying_nodes(node, try_node) - if self.is_compact_ast: - params = statement.get("parameters", None) - else: - params = statement[self.get_children("children")] + if add_param: + if self.is_compact_ast: + params = statement.get("parameters", None) + else: + params = statement[self.get_children("children")] - if params: - for param in params.get("parameters", []): - assert param[self.get_key()] == "VariableDeclaration" - self._add_param(param, var_initialized) + if params: + for param in params.get("parameters", []): + assert param[self.get_key()] == "VariableDeclaration" + self._add_param(param, True) return self._parse_statement(block, try_node, try_scope) @@ -1242,6 +1252,17 @@ class FunctionSolc(CallerContextExpression): self._add_local_variable(local_var_parser) return local_var_parser + def _add_param_init_tuple(self, statement: Dict, index: int) -> LocalVariableInitFromTupleSolc: + + local_var = LocalVariableInitFromTuple() + local_var.set_function(self._function) + local_var.set_offset(statement["src"], self._function.compilation_unit) + + local_var_parser = LocalVariableInitFromTupleSolc(local_var, statement, index) + + self._add_local_variable(local_var_parser) + return local_var_parser + def _parse_params(self, params: Dict): assert params[self.get_key()] == "ParameterList" From 50876cf368bca53cada09234ee1096b2991af3d7 Mon Sep 17 00:00:00 2001 From: Simone Date: Thu, 27 Apr 2023 11:13:01 +0200 Subject: [PATCH 040/101] Handle when there aren't return variables --- slither/solc_parsing/declarations/function.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 7ac67a02d..57d7784e5 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -665,6 +665,11 @@ class FunctionSolc(CallerContextExpression): # if the parameters are more than 1 we make the leftHandSide of the Assignment node # a TupleExpression otherwise an Identifier + # case when there isn't returns(...) + # e.g. external call that doesn't have any return variable + if not parameters_list: + return externalCall + ret: Dict = {"nodeType": "Assignment", "operator": "=", "src": parameters_list["src"]} parameters = parameters_list.get("parameters", None) From 6eb296cdf71ebd48ccab7e24ca99ba89a3b2c171 Mon Sep 17 00:00:00 2001 From: Simone Date: Thu, 27 Apr 2023 11:23:03 +0200 Subject: [PATCH 041/101] Test catch variables not detected as uninitialized --- ...11_uninitialized_local_variable_sol__0.txt | 2 +- ..._6_uninitialized_local_variable_sol__0.txt | 2 +- .../0.6.11/uninitialized_local_variable.sol | 14 +++++++++++++- ...ninitialized_local_variable.sol-0.6.11.zip | Bin 1824 -> 2712 bytes .../0.7.6/uninitialized_local_variable.sol | 14 +++++++++++++- ...uninitialized_local_variable.sol-0.7.6.zip | Bin 1762 -> 2649 bytes 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_6_11_uninitialized_local_variable_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_6_11_uninitialized_local_variable_sol__0.txt index 8e5dc65e8..7e5fa9559 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_6_11_uninitialized_local_variable_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_6_11_uninitialized_local_variable_sol__0.txt @@ -1,2 +1,2 @@ -Uninitialized.func().uint_not_init (tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol#4) is a local variable never initialized +Uninitialized.func().uint_not_init (tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol#8) is a local variable never initialized diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_7_6_uninitialized_local_variable_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_7_6_uninitialized_local_variable_sol__0.txt index 495859ec1..7bf1564d7 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_7_6_uninitialized_local_variable_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_UninitializedLocalVars_0_7_6_uninitialized_local_variable_sol__0.txt @@ -1,2 +1,2 @@ -Uninitialized.func().uint_not_init (tests/e2e/detectors/test_data/uninitialized-local/0.7.6/uninitialized_local_variable.sol#4) is a local variable never initialized +Uninitialized.func().uint_not_init (tests/e2e/detectors/test_data/uninitialized-local/0.7.6/uninitialized_local_variable.sol#8) is a local variable never initialized diff --git a/tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol b/tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol index d28eef957..00a4fbc86 100644 --- a/tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol +++ b/tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol @@ -1,9 +1,21 @@ +interface I { + function a() external; +} + contract Uninitialized{ function func() external returns(uint){ uint uint_not_init; uint uint_init = 1; return uint_not_init + uint_init; - } + } + + function func_try_catch(I i) external returns(uint) { + try i.a() { + return 1; + } catch (bytes memory data) { + data; + } + } } diff --git a/tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol-0.6.11.zip b/tests/e2e/detectors/test_data/uninitialized-local/0.6.11/uninitialized_local_variable.sol-0.6.11.zip index ca7ea0eb308fe7b52d4395dd5171d8bdbf5d44f2..d5b1203065287433605f4fdd799d6e32862f7048 100644 GIT binary patch delta 2557 zcmV@KL7#%4gfV;n^xf(0*uZH003G%kr-ru^+P_6+Z$mK{=)<4#k(GbXB?s{~H=a zs3iZSL1y6a%c)g=c8>~Gzo559b-?F@wye`NK)+y_mW4~=J%l}F&*_)3Aiw|>(5`1O z>!tfUafTu?!!Sn&5*^ygdzy;8H7BLD=W~5~y=n%^pC9f{{R3Z~-k2`0P=BWZarpw# z!zcOOX?_oKPLE`8q-&F=(Jlr8F_nz>CnbC5Gm$7Q_roxMvgz4;#M+XVKe_~?6~*)G z26CcKqL-st$F@hZVKEU8XrU)6Ihxs4zJ|Em2x!1)dSG=3twk5gJai^t9(CeW_8Nt`A$c4AX z!L9X_Ei*B|sO$6_X_rU+nB?ES!a)PZsLAabL*!3?g6Xf4bHyJa?PCv%`vycUDY(-! z(1EBBrk2I@e{v8*{XplTytWpbb+nv)nnL`K@Z17&f>E5R7N2YoDRSeU809V#IR@R_ zic$9uusQfhSOrMN;HFQU%y9@8S^ZHaHu{)hz|;nC&5gnj(QP<)`7X4lnR`ju=`Q4u3K1Wif)?Ka-kMexshf9 zo|cCbj)BxSi}FzHAzbngE*`UQ*x6MFfRh&zUvT1wVpT6}w0J?!pnuv%brbs$4QSx@ z^~sgRB^)CGQS0wj7E<6jL3U9%_&#*$&3}t+=ew3U|Mx&JierJAqw<{7u-apqYiOE( z-zlLtp15QYtvku0Ww6;%&BS!R0g0cX1&(N0+>EW&lgFcjut4xd!L<*l7Qi8fZTD}7 zO4LFUsg=p}vrrLMC`<1sqnZqfKyTH3f_(E}b=Vuk`JAzHWeT6j4A`#x=WsKmf8Lti zE+~@@U?iNQ3|SkM-){^WDb8D$!-`^m+rB2>D%)v~@S=TU@iQ+XT4ML38l;YRYyB}N z)pnN5!ZiU!^+0SdDQCN1J%lG~E@pm7Dj-+2zo874m_9oRJ37@NXB_0~TeQckdaoJH zc%*PI`?>eI2s7IEM*okcu6-1!NPzs=szbpU*%p~M{#_`{1>mHcZ>{N|4YK8bHOQ1r z1*RD(^jtM#+E8cM(giRmH#fmWaA3|fTx=-mvz(p6Hj41(m z`Fi7Z7^f_UwyCVvVrH7Ox@Iw|k%L@9JP36;H@7QAO3o&PSPh9yF{Ri-#hFfUY)dH1 zY{}VdqCLpLl)@)|sWai~nP&TcwrPui#wai}z7PTw;YKt(Eg_k*asE`b&IwI*(N($G zev;cB&FF*F=_F*{g5+Xe2J_;*!0sMGuH=#TZgri!ozdimHsM?<7rHZ`5^Fa2?xnnmj&&?6NQh^fI!`rUiowF`O)^t@ z9yYZrq-T-hXf?Mw)OykA-2M#FH2H8GTY@Dh;EYI#q}{N|(0TW!bu8SFuCUWl<&hZS zmydxk6jM}(^#CP>B1x%#Q@RzR&(W#9i{!N*(|8Bq{^ba1v0hF#^1IZhn%d=V=Jod; z=ysp64{GpWdTGPuNSO!xoKMsvxsE^xm6;pV#-Q!BrmDqoT9epazvXIW9a^rSE6Atly@@SD{>-5@~R6pqnYrya(B1@MoMev(wPxvh3k_Y8&U zq&@x9n#SVrNyCdxof+B2U#Y0&KMNQqjz+oscN+ui{G=dpwtUt8Z*b48By3_d$xkIZIr>QuZDK#F#CvlL{n4{_9kk074aWKdfm%;!{sGY4hBpz&4`t#nn>@OEh6H6 zieA|hM+vh57e_zswlLxG^i!HjJT08L=x-oV*y(#|g^ClBWH}{Zh{^w%gS7NI3?|{` zgpW_Y T2><|EJd?r(SOy>p00000-=q6_ delta 1662 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 I2><{907VzOmjD0& delta 1607 zcmV-N2Dtgz6ygmVP)h>@KL7#%4gfoicUE;nYX2Ap002T9001|}t(aS6;oO)2cgKrt@CvFD zq-twonro_@{&p*CwbB5MY;H;&9AR62-ZWz<@VE4AZ4Qa(U0<$njRn~ymyLt`Xc7Yr zPW;C;9N^_wQV658`?crE5J&a2g{MGF(MSyx_#E^>kW!?o1Q6FJyPm z1ZMR&cXfcTHweG@NUjJ2%n)`xOWk|IzI5u{Tw9D~q22nnKNm9|r>K8&egVk>2h;JI zT2npyo2Ro6mNX&c2+u*bcw2HdQ8*Gnc8rTxPW_eD+U=R%<-2-V?P_(GL(*ZiU5b@4 zEKfrm01iBZ1k}ckgyH)E))5Fjf3^i1Xb> zGXd7wA=Mf~r>fchP|&Nie_Oxw*6f}Z!PwG|R=QQLE9AW1YGWd(g*pr}!&))ti{$^2 zNVy{4J)sTsn%``g+bO$mlb2QibhQLJtY4Pokoor5^_zMQg(H8kM|M}m+^hc_!pxT> zs2R62g)o|hlJ72*sYp?jCpZm`eapu-o+YR7X4;BvQZ6p%JkB7@OIccF!2Q(dAu5jt z#(XDCsqAmuvcbt!8)m%UF)W3R*TQ~eGp#%PInmo?Emw^;q z6t1IH@OQHQ(bK!r8iv=hwVOn`xlWE_V+Z4M)UHEaVdhxRMJCQnMwNsx*9 zw1`WgZ6bfGoBoJpVOBe85ymLKcYYlhzW@UjyR1^-xx%myN*BQ}k~ce3^S9!+B+$yq zH>RUV+MM5VxrBk46l0*)$85C|8u*?FkvO-wBM%vD92uxyuj288k1ofGQ9s#)AYzW! z!T5h-y;74pRnmBp5MN}CX9t2L9j+2Av36@{m4{DkT28j|e}U@R=ERYL{F=ShmxM=> z?ERla+q06O0Po|^-jXFfX(1+A3cgq+8%s8h`E$w`uu}L2(8=at?JGhJ96P{geOTI2 z1P16WkR6sq>smlds2Tz8(lY=1Pr7;IBzu2fe}w24sKLD#@QQq{AVt}o{}H=KZt+9 z+?xVqzK?Ny3vtzTOafoqzCXGI+t**{GDVvmO$07sT-@l;Qv=AdEk@oNbFHhkTrepe z`NCLkxeJrafMpvdBW?lvE50B}1-l^%Nm&p`Ep%&DVa|q(*RM_|u##Crc}ZQv6@)9c zFoVs2zDZa{F^U!c-CK~-P)h* Date: Sat, 22 Apr 2023 15:34:31 -0600 Subject: [PATCH 042/101] Catch AssertionError and log context and raise again while parsing partial fix of #1853 Opened PR per request in #1854 AssertionError with stack trace can occur while parsing contracts that provides no context to what was parsed that threw the error as described in #1853. This change catches this error, logs additional context, and then throws the error again. An example of the logging from code I am working on: ``` $ slither . ... Compiler run successful ERROR:SlitherSolcParsing: Failed to parse MyERC20Controller.constructor (contracts/MyERC20Controller.sol#19-25): (tokenX,tokenY) = IMyFactoryCallback(msg.sender).tokens() (success,data) = msg.sender.delegatecall(abi.encodeWithSelector(IMyFactoryCallback.newTokens.selector,tokenX,tokenY)) require(bool,string)(success,Factory new tokens failed) _tokens = abi.decode(data,(IMyERC20[6])) Traceback (most recent call last): File "~/slither/slither/__main__.py", line 810, in main_impl ) = process_all(filename, args, detector_classes, printer_classes) File "~/slither/slither/__main__.py", line 101, in process_all ... ``` Logging should make it easier for further issues from users to be easily understood and if necessary included in a bug report. --- slither/solc_parsing/slither_compilation_unit_solc.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index cb94a7d9e..fa38adc0e 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -742,6 +742,13 @@ Please rename it, this name is reserved for Slither's internals""" self._underlying_contract_to_parser[contract].log_incorrect_parsing( f"Impossible to generate IR for {contract.name}.{func.name} ({func.source_mapping}):\n {e}" ) + except AssertionError as e: + func_expressions = "\n".join([f'\t{ex}' for ex in func.expressions]) + logger.error( + f"\nFailed to parse {contract.name}.{func.name} ({func.source_mapping}):\n " + f"{func_expressions}") + raise e + contract.convert_expression_to_slithir_ssa() From a4589b07696cc2bcb503dfd87454223caf735ec6 Mon Sep 17 00:00:00 2001 From: Will Fey Date: Thu, 27 Apr 2023 21:30:36 -0600 Subject: [PATCH 043/101] ran black to fix check --- slither/solc_parsing/slither_compilation_unit_solc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index fa38adc0e..aa4f88890 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -743,13 +743,13 @@ Please rename it, this name is reserved for Slither's internals""" f"Impossible to generate IR for {contract.name}.{func.name} ({func.source_mapping}):\n {e}" ) except AssertionError as e: - func_expressions = "\n".join([f'\t{ex}' for ex in func.expressions]) + func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions]) logger.error( f"\nFailed to parse {contract.name}.{func.name} ({func.source_mapping}):\n " - f"{func_expressions}") + f"{func_expressions}" + ) raise e - contract.convert_expression_to_slithir_ssa() for func in self._compilation_unit.functions_top_level: From bcf570ebb6a333a5f6bc1b59a53211788264e77d Mon Sep 17 00:00:00 2001 From: devtooligan Date: Tue, 2 May 2023 15:51:04 -0700 Subject: [PATCH 044/101] feat: loc printer --- slither/printers/all_printers.py | 1 + slither/printers/summary/human_summary.py | 79 ++++++-------- slither/printers/summary/loc.py | 127 ++++++++++++++++++++++ slither/utils/myprettytable.py | 39 +++++++ 4 files changed, 201 insertions(+), 45 deletions(-) create mode 100644 slither/printers/summary/loc.py diff --git a/slither/printers/all_printers.py b/slither/printers/all_printers.py index 6dc8dddbd..3dd64da3e 100644 --- a/slither/printers/all_printers.py +++ b/slither/printers/all_printers.py @@ -1,6 +1,7 @@ # pylint: disable=unused-import,relative-beyond-top-level from .summary.function import FunctionSummary from .summary.contract import ContractSummary +from .summary.loc import Loc from .inheritance.inheritance import PrinterInheritance from .inheritance.inheritance_graph import PrinterInheritanceGraph from .call.call_graph import PrinterCallGraph diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py index 9eacb97c6..157b8228a 100644 --- a/slither/printers/summary/human_summary.py +++ b/slither/printers/summary/human_summary.py @@ -2,12 +2,12 @@ Module printing summary of the contract """ import logging -from pathlib import Path from typing import Tuple, List, Dict from slither.core.declarations import SolidityFunction, Function from slither.core.variables.state_variable import StateVariable from slither.printers.abstract_printer import AbstractPrinter +from slither.printers.summary.loc import compute_loc_metrics from slither.slithir.operations import ( LowLevelCall, HighLevelCall, @@ -21,7 +21,6 @@ from slither.utils.colors import green, red, yellow from slither.utils.myprettytable import MyPrettyTable from slither.utils.standard_libraries import is_standard_library from slither.core.cfg.node import NodeType -from slither.utils.tests_pattern import is_test_file class PrinterHumanSummary(AbstractPrinter): @@ -32,7 +31,6 @@ class PrinterHumanSummary(AbstractPrinter): @staticmethod def _get_summary_erc20(contract): - functions_name = [f.name for f in contract.functions] state_variables = [v.name for v in contract.state_variables] @@ -165,27 +163,6 @@ class PrinterHumanSummary(AbstractPrinter): def _number_functions(contract): return len(contract.functions) - def _lines_number(self): - if not self.slither.source_code: - return None - total_dep_lines = 0 - total_lines = 0 - total_tests_lines = 0 - - for filename, source_code in self.slither.source_code.items(): - lines = len(source_code.splitlines()) - is_dep = False - if self.slither.crytic_compile: - is_dep = self.slither.crytic_compile.is_dependency(filename) - if is_dep: - total_dep_lines += lines - else: - if is_test_file(Path(filename)): - total_tests_lines += lines - else: - total_lines += lines - return total_lines, total_dep_lines, total_tests_lines - def _get_number_of_assembly_lines(self): total_asm_lines = 0 for contract in self.contracts: @@ -226,7 +203,6 @@ class PrinterHumanSummary(AbstractPrinter): return list(set(ercs)) def _get_features(self, contract): # pylint: disable=too-many-branches - has_payable = False can_send_eth = False can_selfdestruct = False @@ -291,6 +267,36 @@ class PrinterHumanSummary(AbstractPrinter): "Proxy": contract.is_upgradeable_proxy, } + def _get_contracts(self, txt): + ( + number_contracts, + number_contracts_deps, + number_contracts_tests, + ) = self._number_contracts() + txt += f"Total number of contracts in source files: {number_contracts}\n" + if number_contracts_deps > 0: + txt += f"Number of contracts in dependencies: {number_contracts_deps}\n" + if number_contracts_tests > 0: + txt += f"Number of contracts in tests : {number_contracts_tests}\n" + return txt + + def _get_number_lines(self, txt, results): + lines_dict = compute_loc_metrics(self.slither) + txt += "Source lines of code (SLOC) in source files: " + txt += f"{lines_dict['src']['sloc']}\n" + if lines_dict["dep"]["sloc"] > 0: + txt += "Source lines of code (SLOC) in dependencies: " + txt += f"{lines_dict['dep']['sloc']}\n" + if lines_dict["test"]["sloc"] > 0: + txt += "Source lines of code (SLOC) in tests : " + txt += f"{lines_dict['test']['sloc']}\n" + results["number_lines"] = lines_dict["src"]["sloc"] + results["number_lines__dependencies"] = lines_dict["dep"]["sloc"] + total_asm_lines = self._get_number_of_assembly_lines() + txt += f"Number of assembly lines: {total_asm_lines}\n" + results["number_lines_assembly"] = total_asm_lines + return txt, results + def output(self, _filename): # pylint: disable=too-many-locals,too-many-statements """ _filename is not used @@ -311,24 +317,8 @@ class PrinterHumanSummary(AbstractPrinter): "number_findings": {}, "detectors": [], } - - lines_number = self._lines_number() - if lines_number: - total_lines, total_dep_lines, total_tests_lines = lines_number - txt += f"Number of lines: {total_lines} (+ {total_dep_lines} in dependencies, + {total_tests_lines} in tests)\n" - results["number_lines"] = total_lines - results["number_lines__dependencies"] = total_dep_lines - total_asm_lines = self._get_number_of_assembly_lines() - txt += f"Number of assembly lines: {total_asm_lines}\n" - results["number_lines_assembly"] = total_asm_lines - - ( - number_contracts, - number_contracts_deps, - number_contracts_tests, - ) = self._number_contracts() - txt += f"Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n" - + txt = self._get_contracts(txt) + txt, results = self._get_number_lines(txt, results) ( txt_detectors, detectors_results, @@ -352,7 +342,7 @@ class PrinterHumanSummary(AbstractPrinter): libs = self._standard_libraries() if libs: txt += f'\nUse: {", ".join(libs)}\n' - results["standard_libraries"] = [str(l) for l in libs] + results["standard_libraries"] = [str(lib) for lib in libs] ercs = self._ercs() if ercs: @@ -363,7 +353,6 @@ class PrinterHumanSummary(AbstractPrinter): ["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"] ) for contract in self.slither.contracts_derived: - if contract.is_from_dependency() or contract.is_test: continue diff --git a/slither/printers/summary/loc.py b/slither/printers/summary/loc.py new file mode 100644 index 000000000..4ed9aa6ab --- /dev/null +++ b/slither/printers/summary/loc.py @@ -0,0 +1,127 @@ +""" + Lines of Code (LOC) printer + + Definitions: + cloc: comment lines of code containing only comments + sloc: source lines of code with no whitespace or comments + loc: all lines of code including whitespace and comments + src: source files (excluding tests and dependencies) + dep: dependency files + test: test files +""" +from pathlib import Path +from slither.printers.abstract_printer import AbstractPrinter +from slither.utils.myprettytable import transpose, make_pretty_table +from slither.utils.tests_pattern import is_test_file + + +def count_lines(contract_lines: list) -> tuple: + """Function to count and classify the lines of code in a contract. + Args: + contract_lines: list(str) representing the lines of a contract. + Returns: + tuple(int, int, int) representing (cloc, sloc, loc) + """ + multiline_comment = False + cloc = 0 + sloc = 0 + loc = 0 + + for line in contract_lines: + loc += 1 + stripped_line = line.strip() + if not multiline_comment: + if stripped_line.startswith("//"): + cloc += 1 + elif "/*" in stripped_line: + # Account for case where /* is followed by */ on the same line. + # If it is, then multiline_comment does not need to be set to True + start_idx = stripped_line.find("/*") + end_idx = stripped_line.find("*/", start_idx + 2) + if end_idx == -1: + multiline_comment = True + cloc += 1 + elif stripped_line: + sloc += 1 + else: + cloc += 1 + if "*/" in stripped_line: + multiline_comment = False + + return cloc, sloc, loc + + +def _update_lines_dict(file_type: str, lines: list, lines_dict: dict) -> dict: + """An internal function used to update (mutate in place) the lines_dict. + Args: + file_type: str indicating "src" (source files), "dep" (dependency files), or "test" tests. + lines: list(str) representing the lines of a contract. + lines_dict: dict to be updated with this shape: + { + "src" : {"loc": 30, "sloc": 20, "cloc": 5}, # code in source files + "dep" : {"loc": 50, "sloc": 30, "cloc": 10}, # code in dependencies + "test": {"loc": 80, "sloc": 60, "cloc": 10}, # code in tests + } + Returns: + an updated lines_dict + """ + cloc, sloc, loc = count_lines(lines) + lines_dict[file_type]["loc"] += loc + lines_dict[file_type]["cloc"] += cloc + lines_dict[file_type]["sloc"] += sloc + return lines_dict + + +def compute_loc_metrics(slither) -> dict: + """Used to compute the lines of code metrics for a Slither object. + Args: + slither: A Slither object + Returns: + A new dict with the following shape: + { + "src" : {"loc": 30, "sloc": 20, "cloc": 5}, # code in source files + "dep" : {"loc": 50, "sloc": 30, "cloc": 10}, # code in dependencies + "test": {"loc": 80, "sloc": 60, "cloc": 10}, # code in tests + } + """ + + lines_dict = { + "src": {"loc": 0, "sloc": 0, "cloc": 0}, + "dep": {"loc": 0, "sloc": 0, "cloc": 0}, + "test": {"loc": 0, "sloc": 0, "cloc": 0}, + } + + if not slither.source_code: + return lines_dict + + for filename, source_code in slither.source_code.items(): + current_lines = source_code.splitlines() + is_dep = False + if slither.crytic_compile: + is_dep = slither.crytic_compile.is_dependency(filename) + file_type = "dep" if is_dep else "test" if is_test_file(Path(filename)) else "src" + lines_dict = _update_lines_dict(file_type, current_lines, lines_dict) + return lines_dict + + +class Loc(AbstractPrinter): + ARGUMENT = "loc" + HELP = """Count the total number lines of code (LOC), source lines of code (SLOC), \ + and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), \ + and test files (TEST).""" + + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#loc" + + def output(self, _filename): + # compute loc metrics + lines_dict = compute_loc_metrics(self.slither) + + # prepare the table + headers = [""] + list(lines_dict.keys()) + report_dict = transpose(lines_dict) + table = make_pretty_table(headers, report_dict) + txt = "Lines of Code \n" + str(table) + self.info(txt) + res = self.generate_output(txt) + res.add_pretty_table(table, "Code Lines") + return res diff --git a/slither/utils/myprettytable.py b/slither/utils/myprettytable.py index af10a6ff2..efdb96504 100644 --- a/slither/utils/myprettytable.py +++ b/slither/utils/myprettytable.py @@ -22,3 +22,42 @@ class MyPrettyTable: def __str__(self) -> str: return str(self.to_pretty_table()) + + +# **Dict to MyPrettyTable utility functions** + + +# Converts a dict to a MyPrettyTable. Dict keys are the row headers. +# @param headers str[] of column names +# @param body dict of row headers with a dict of the values +# @param totals bool optional add Totals row +def make_pretty_table(headers: list, body: dict, totals: bool = False) -> MyPrettyTable: + table = MyPrettyTable(headers) + for row in body: + table_row = [row] + [body[row][key] for key in headers[1:]] + table.add_row(table_row) + if totals: + table.add_row(["Total"] + [sum([body[row][key] for row in body]) for key in headers[1:]]) + return table + + +# takes a dict of dicts and returns a dict of dicts with the keys transposed +# example: +# in: +# { +# "dep": {"loc": 0, "sloc": 0, "cloc": 0}, +# "test": {"loc": 0, "sloc": 0, "cloc": 0}, +# "src": {"loc": 0, "sloc": 0, "cloc": 0}, +# } +# out: +# { +# 'loc': {'dep': 0, 'test': 0, 'src': 0}, +# 'sloc': {'dep': 0, 'test': 0, 'src': 0}, +# 'cloc': {'dep': 0, 'test': 0, 'src': 0}, +# } +def transpose(table): + any_key = list(table.keys())[0] + return { + inner_key: {outer_key: table[outer_key][inner_key] for outer_key in table} + for inner_key in table[any_key] + } From 4f676309902a9e144bd75af1fa1db0770f0b9ec2 Mon Sep 17 00:00:00 2001 From: bart1e Date: Thu, 9 Feb 2023 19:39:58 +0100 Subject: [PATCH 045/101] Detector for incorrect using-for at the top level added --- slither/detectors/all_detectors.py | 1 + .../statements/incorrect_using_for.py | 200 ++++++++++++++++++ .../0.8.17/IncorrectUsingForTopLevel.sol | 94 ++++++++ ...TopLevel.sol.0.8.17.IncorrectUsingFor.json | 124 +++++++++++ tests/e2e/detectors/test_detectors.py | 5 + 5 files changed, 424 insertions(+) create mode 100644 slither/detectors/statements/incorrect_using_for.py create mode 100644 tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol create mode 100644 tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 9722b8793..21374a38b 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -89,3 +89,4 @@ from .functions.protected_variable import ProtectedVariables from .functions.permit_domain_signature_collision import DomainSeparatorCollision from .functions.codex import Codex from .functions.cyclomatic_complexity import CyclomaticComplexity +from .statements.incorrect_using_for import IncorrectUsingFor \ No newline at end of file diff --git a/slither/detectors/statements/incorrect_using_for.py b/slither/detectors/statements/incorrect_using_for.py new file mode 100644 index 000000000..7cc18deaf --- /dev/null +++ b/slither/detectors/statements/incorrect_using_for.py @@ -0,0 +1,200 @@ +from slither.core.declarations import Contract, Structure, Enum +from slither.core.declarations.using_for_top_level import UsingForTopLevel +from slither.core.solidity_types import ( + UserDefinedType, + Type, + ElementaryType, + TypeAlias, + MappingType, + ArrayType, +) +from slither.core.solidity_types.elementary_type import Uint, Int, Byte +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification + + +class IncorrectUsingFor(AbstractDetector): + """ + Detector for incorrect using-for statement usage. + """ + + ARGUMENT = "incorrect-using-for" + HELP = "Detects using-for statement usage when no function from a given library matches a given type" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage" + + WIKI_TITLE = "Incorrect usage of using-for statement" + WIKI_DESCRIPTION = "In Solidity, it is possible to use libraries for certain types, by the `using-for` statement " \ + "(`using for `). However, the Solidity compiler doesn't check whether a given " \ + "library has at least one function matching a given type. If it doesn't, such a statement has " \ + "no effect and may be confusing. " + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ + ```solidity + library L { + function f(bool) public pure {} + } + + using L for uint; + ``` + Such a code will compile despite the fact that `L` has no function with `uint` as its first argument.""" + # endregion wiki_exploit_scenario + WIKI_RECOMMENDATION = "Make sure that the libraries used in `using-for` statements have at least one function " \ + "matching a type used in these statements. " + + @staticmethod + def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool: + """ + Returns True if contract1 may be implicitly converted to contract2. + """ + return contract1 == contract2 or contract2 in contract1.inheritance + + @staticmethod + def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both uints. + """ + assert type1.type in Uint and type2.type in Uint + + return type1.size <= type2.size + + @staticmethod + def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both ints. + """ + assert type1.type in Int and type2.type in Int + + return type1.size <= type2.size + + @staticmethod + def _implicitly_convertible_to_for_addresses( + type1: ElementaryType, type2: ElementaryType + ) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both addresses. + """ + assert type1.type == "address" and type2.type == "address" + # payable attribute to be implemented; for now, always return True + return True + + @staticmethod + def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both bytes. + """ + assert type1.type in Byte and type2.type in Byte + + return type1.size <= type2.size + + @staticmethod + def _implicitly_convertible_to_for_elementary_types( + type1: ElementaryType, type2: ElementaryType + ) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + if type1.type == "bool" and type2.type == "bool": + return True + if type1.type == "string" and type2.type == "string": + return True + if type1.type == "bytes" and type2.type == "bytes": + return True + if type1.type == "address" and type2.type == "address": + return IncorrectUsingFor._implicitly_convertible_to_for_addresses(type1, type2) + if type1.type in Uint and type2.type in Uint: + return IncorrectUsingFor._implicitly_convertible_to_for_uints(type1, type2) + if type1.type in Int and type2.type in Int: + return IncorrectUsingFor._implicitly_convertible_to_for_ints(type1, type2) + if ( + type1.type != "bytes" + and type2.type != "bytes" + and type1.type in Byte + and type2.type in Byte + ): + return IncorrectUsingFor._implicitly_convertible_to_for_bytes(type1, type2) + return False + + @staticmethod + def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + return type1.type_from == type2.type_from and type1.type_to == type2.type_to + + @staticmethod + def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + return IncorrectUsingFor._implicitly_convertible_to(type1.type, type2.type) + + @staticmethod + def _implicitly_convertible_to(type1: Type, type2: Type) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias): + if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias): + return type1.type == type2.type + return False + + if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType): + if isinstance(type1.type, Contract) and isinstance(type2.type, Contract): + return IncorrectUsingFor._implicitly_convertible_to_for_contracts( + type1.type, type2.type + ) + + if isinstance(type1.type, Structure) and isinstance(type2.type, Structure): + return type1.type.canonical_name == type2.type.canonical_name + + if isinstance(type1.type, Enum) and isinstance(type2.type, Enum): + return type1.type.canonical_name == type2.type.canonical_name + + if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType): + return IncorrectUsingFor._implicitly_convertible_to_for_elementary_types(type1, type2) + + if isinstance(type1, MappingType) and isinstance(type2, MappingType): + return IncorrectUsingFor._implicitly_convertible_to_for_mappings(type1, type2) + + if isinstance(type1, ArrayType) and isinstance(type2, ArrayType): + return IncorrectUsingFor._implicitly_convertible_to_for_arrays(type1, type2) + + return False + + @staticmethod + def _is_correctly_used(type_: Type, library: Contract) -> bool: + """ + Checks if a `using library for type_` statement is used correctly (that is, does library contain any function + with type_ as the first argument). + """ + for f in library.functions: + if len(f.parameters) == 0: + continue + if not IncorrectUsingFor._implicitly_convertible_to(type_, f.parameters[0].type): + continue + return True + return False + + def _append_result(self, results: list, uf: UsingForTopLevel, type_: Type, library: Contract): + info = f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in " \ + f"{library}.\n" + res = self.generate_result(info) + results.append(res) + + def _detect(self): + results = [] + + for uf in self.compilation_unit.using_for_top_level: + # UsingForTopLevel.using_for is a dict with a single entry, which is mapped to a list of functions/libraries + # the following code extracts the type from using-for and skips using-for statements with functions + type_ = list(uf.using_for.keys())[0] + for lib_or_fcn in uf.using_for[type_]: + # checking for using-for with functions is already performed by the compiler; we only consider libraries + if isinstance(lib_or_fcn, UserDefinedType): + if not self._is_correctly_used(type_, lib_or_fcn.type): + self._append_result(results, uf, type_, lib_or_fcn.type) + + return results diff --git a/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol b/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol new file mode 100644 index 000000000..5b173e7b5 --- /dev/null +++ b/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol @@ -0,0 +1,94 @@ +pragma solidity 0.8.17; + +struct S1 +{ + uint __; +} + +struct S2 +{ + uint128 __; +} + +enum E1 +{ + A, + B +} + +enum E2 +{ + A, + B +} + +contract C0 +{ + +} + +contract C1 is C0 +{ + +} + +contract C2 is C1 +{ + +} + +contract C3 +{ + +} + +type custom_uint is uint248; +type custom_int is int248; + +library L +{ + function f0(C0) public pure {} + function f1(bool) public pure {} + function f2(string memory) public pure {} + function f3(bytes memory) public pure {} + function f4(uint248) public pure {} + function f5(int248) public pure {} + function f6(address) public pure {} + function f7(bytes17) public pure {} + function f8(S1 memory) public pure {} + function f9(E1) public pure {} + function f10(mapping(int => uint) storage) public pure {} + function f11(string[] memory) public pure {} + function f12(bytes[][][] memory) public pure {} + function f13(custom_uint) public pure {} +} + +// the following statements are correct +using L for C2; +using L for bool; +using L for string; +using L for bytes; +using L for uint240; +using L for int16; +using L for address; +using L for bytes16; +using L for S1; +using L for E1; +using L for mapping(int => uint); +using L for string[]; +using L for bytes[][][]; +using L for custom_uint; + +// the following statements are incorrect +using L for C3; +using L for bytes17[]; +using L for uint; +using L for int; +using L for bytes18; +using L for S2; +using L for E2; +using L for mapping(int => uint128); +using L for mapping(int128 => uint); +using L for string[][]; +using L for bytes[][]; +using L for custom_int; diff --git a/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json b/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json new file mode 100644 index 000000000..9321baa9c --- /dev/null +++ b/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json @@ -0,0 +1,124 @@ +[ + [ + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L.\n", + "first_markdown_element": "", + "id": "000067f9a866a9e43f9c82b9b70b3a7657545318fb4d3334b39f03e47b63e50a", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L.\n", + "first_markdown_element": "", + "id": "09b0f0c08a3f0fe4ee75eff34f9c4718a5f729814eda9fa21c2ba30a01e1de0e", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L.\n", + "first_markdown_element": "", + "id": "1a051922964f790736e90754dfebf381c6e078f957ab8e7eeedf9d79ef9d56e5", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L.\n", + "first_markdown_element": "", + "id": "3b1d94dc870c0f03b4f43b34b6278adf6669b9e30e245ae21247d3839445366c", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L.\n", + "first_markdown_element": "", + "id": "6d993d086c6d6fb8cd005acd124d7d165b38e85cc23d781a8c99d9efd0d2c25a", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L.\n", + "first_markdown_element": "", + "id": "6e785ac39c18c840fc9f46fc066670776551a98bfd8d2030ee523a0c55bf5eef", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L.\n", + "first_markdown_element": "", + "id": "90efff9103e8469dcd2d251f27bea0ab6875f0f511fe8aedc093532cb4a52aa5", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L.\n", + "first_markdown_element": "", + "id": "a36393040b3e588bbfb6ff945333997a967bc50e1f9d703be020a1786514fb12", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L.\n", + "first_markdown_element": "", + "id": "c8be02cb293bf4e4b5bbe3def377356d8761ed34c2bc3259f7073e7840eec6c6", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L.\n", + "first_markdown_element": "", + "id": "e6abc32f0e370622f8a17cbc5eb94340b78931210ef47269e39e683cc03a34d3", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L.\n", + "first_markdown_element": "", + "id": "eec4404d65776c62a3053ef34081ca8cfdc4c32c571e543c337c5af1a0c11c9c", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L.\n", + "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L.\n", + "first_markdown_element": "", + "id": "f5cb90eb9284eb3935980052fc2ad410058acafa4478ebd3109cc9520510095c", + "check": "incorrect-using-for", + "impact": "Informational", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index e6b87d530..2088ac214 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1639,6 +1639,11 @@ ALL_TEST_OBJECTS = [ "LowCyclomaticComplexity.sol", "0.8.16", ), + Test( + all_detectors.IncorrectUsingFor, + "IncorrectUsingForTopLevel.sol", + "0.8.17", + ), ] GENERIC_PATH = "/GENERIC_PATH" From 5a450a07e04626f590ac303aba47db72cfea9c3b Mon Sep 17 00:00:00 2001 From: bart1e Date: Thu, 9 Feb 2023 20:03:36 +0100 Subject: [PATCH 046/101] Black run --- .../statements/incorrect_using_for.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/slither/detectors/statements/incorrect_using_for.py b/slither/detectors/statements/incorrect_using_for.py index 7cc18deaf..4df0701a2 100644 --- a/slither/detectors/statements/incorrect_using_for.py +++ b/slither/detectors/statements/incorrect_using_for.py @@ -25,10 +25,12 @@ class IncorrectUsingFor(AbstractDetector): WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage" WIKI_TITLE = "Incorrect usage of using-for statement" - WIKI_DESCRIPTION = "In Solidity, it is possible to use libraries for certain types, by the `using-for` statement " \ - "(`using for `). However, the Solidity compiler doesn't check whether a given " \ - "library has at least one function matching a given type. If it doesn't, such a statement has " \ - "no effect and may be confusing. " + WIKI_DESCRIPTION = ( + "In Solidity, it is possible to use libraries for certain types, by the `using-for` statement " + "(`using for `). However, the Solidity compiler doesn't check whether a given " + "library has at least one function matching a given type. If it doesn't, such a statement has " + "no effect and may be confusing. " + ) # region wiki_exploit_scenario WIKI_EXPLOIT_SCENARIO = """ @@ -41,8 +43,10 @@ class IncorrectUsingFor(AbstractDetector): ``` Such a code will compile despite the fact that `L` has no function with `uint` as its first argument.""" # endregion wiki_exploit_scenario - WIKI_RECOMMENDATION = "Make sure that the libraries used in `using-for` statements have at least one function " \ - "matching a type used in these statements. " + WIKI_RECOMMENDATION = ( + "Make sure that the libraries used in `using-for` statements have at least one function " + "matching a type used in these statements. " + ) @staticmethod def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool: @@ -179,8 +183,10 @@ class IncorrectUsingFor(AbstractDetector): return False def _append_result(self, results: list, uf: UsingForTopLevel, type_: Type, library: Contract): - info = f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in " \ - f"{library}.\n" + info = ( + f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in " + f"{library}.\n" + ) res = self.generate_result(info) results.append(res) From 32d4bba9efaaffa72b36bca448e0fce2c01b0abd Mon Sep 17 00:00:00 2001 From: bart1e Date: Wed, 3 May 2023 18:01:22 +0200 Subject: [PATCH 047/101] Tests updated --- slither/detectors/all_detectors.py | 2 +- ...TopLevel.sol.0.8.17.IncorrectUsingFor.json | 124 ------------------ ..._8_17_IncorrectUsingForTopLevel_sol__0.txt | 24 ++++ .../0.8.17/IncorrectUsingForTopLevel.sol | 0 .../IncorrectUsingForTopLevel.sol-0.8.17.zip | Bin 0 -> 10081 bytes 5 files changed, 25 insertions(+), 125 deletions(-) delete mode 100644 tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt rename tests/{detectors => e2e/detectors/test_data}/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol (100%) create mode 100644 tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol-0.8.17.zip diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 21374a38b..324c97052 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -89,4 +89,4 @@ from .functions.protected_variable import ProtectedVariables from .functions.permit_domain_signature_collision import DomainSeparatorCollision from .functions.codex import Codex from .functions.cyclomatic_complexity import CyclomaticComplexity -from .statements.incorrect_using_for import IncorrectUsingFor \ No newline at end of file +from .statements.incorrect_using_for import IncorrectUsingFor diff --git a/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json b/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json deleted file mode 100644 index 9321baa9c..000000000 --- a/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol.0.8.17.IncorrectUsingFor.json +++ /dev/null @@ -1,124 +0,0 @@ -[ - [ - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L.\n", - "first_markdown_element": "", - "id": "000067f9a866a9e43f9c82b9b70b3a7657545318fb4d3334b39f03e47b63e50a", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L.\n", - "first_markdown_element": "", - "id": "09b0f0c08a3f0fe4ee75eff34f9c4718a5f729814eda9fa21c2ba30a01e1de0e", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L.\n", - "first_markdown_element": "", - "id": "1a051922964f790736e90754dfebf381c6e078f957ab8e7eeedf9d79ef9d56e5", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L.\n", - "first_markdown_element": "", - "id": "3b1d94dc870c0f03b4f43b34b6278adf6669b9e30e245ae21247d3839445366c", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L.\n", - "first_markdown_element": "", - "id": "6d993d086c6d6fb8cd005acd124d7d165b38e85cc23d781a8c99d9efd0d2c25a", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L.\n", - "first_markdown_element": "", - "id": "6e785ac39c18c840fc9f46fc066670776551a98bfd8d2030ee523a0c55bf5eef", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L.\n", - "first_markdown_element": "", - "id": "90efff9103e8469dcd2d251f27bea0ab6875f0f511fe8aedc093532cb4a52aa5", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L.\n", - "first_markdown_element": "", - "id": "a36393040b3e588bbfb6ff945333997a967bc50e1f9d703be020a1786514fb12", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L.\n", - "first_markdown_element": "", - "id": "c8be02cb293bf4e4b5bbe3def377356d8761ed34c2bc3259f7073e7840eec6c6", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L.\n", - "first_markdown_element": "", - "id": "e6abc32f0e370622f8a17cbc5eb94340b78931210ef47269e39e683cc03a34d3", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L.\n", - "first_markdown_element": "", - "id": "eec4404d65776c62a3053ef34081ca8cfdc4c32c571e543c337c5af1a0c11c9c", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - }, - { - "elements": [], - "description": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L.\n", - "markdown": "using-for statement at tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L.\n", - "first_markdown_element": "", - "id": "f5cb90eb9284eb3935980052fc2ad410058acafa4478ebd3109cc9520510095c", - "check": "incorrect-using-for", - "impact": "Informational", - "confidence": "High" - } - ] -] \ No newline at end of file diff --git a/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt new file mode 100644 index 000000000..4a85bca5f --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt @@ -0,0 +1,24 @@ +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L. + +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L. + diff --git a/tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol b/tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol similarity index 100% rename from tests/detectors/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol rename to tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol diff --git a/tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol-0.8.17.zip b/tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol-0.8.17.zip new file mode 100644 index 0000000000000000000000000000000000000000..9afc56d39e442ef3a41f3541db7a05857656332c GIT binary patch literal 10081 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 Date: Sun, 26 Feb 2023 17:39:01 +0100 Subject: [PATCH 048/101] Detector for array length caching added --- slither/detectors/all_detectors.py | 1 + .../operations/cache_array_length.py | 215 ++++++++++++++++++ .../0.8.17/CacheArrayLength.sol | 127 +++++++++++ ...rayLength.sol.0.8.17.CacheArrayLength.json | 74 ++++++ tests/e2e/detectors/test_detectors.py | 5 + 5 files changed, 422 insertions(+) create mode 100644 slither/detectors/operations/cache_array_length.py create mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol create mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 9722b8793..6a1ec6739 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -89,3 +89,4 @@ from .functions.protected_variable import ProtectedVariables from .functions.permit_domain_signature_collision import DomainSeparatorCollision from .functions.codex import Codex from .functions.cyclomatic_complexity import CyclomaticComplexity +from .operations.cache_array_length import CacheArrayLength diff --git a/slither/detectors/operations/cache_array_length.py b/slither/detectors/operations/cache_array_length.py new file mode 100644 index 000000000..8c1112d2d --- /dev/null +++ b/slither/detectors/operations/cache_array_length.py @@ -0,0 +1,215 @@ +from typing import List, Set + +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations import Function +from slither.core.expressions import BinaryOperation, Identifier, MemberAccess, UnaryOperation +from slither.core.solidity_types import ArrayType +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.variables import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations import Length, Delete + + +class CacheArrayLength(AbstractDetector): + """ + Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it. + """ + + ARGUMENT = "cache-array-length" + HELP = ( + "Detects `for` loops that use `length` member of some storage array in their loop condition and don't " + "modify it. " + ) + IMPACT = DetectorClassification.OPTIMIZATION + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length" + + WIKI_TITLE = "Cache array length" + WIKI_DESCRIPTION = ( + "Detects `for` loops that use `length` member of some storage array in their loop condition " + "and don't modify it. " + ) + WIKI_EXPLOIT_SCENARIO = """ +```solidity +contract C +{ + uint[] array; + + function f() public + { + for (uint i = 0; i < array.length; i++) + { + // code that does not modify length of `array` + } + } +} +``` +Since the `for` loop in `f` doesn't modify `array.length`, it is more gas efficient to cache it in some local variable and use that variable instead, like in the following example: + +```solidity +contract C +{ + uint[] array; + + function f() public + { + uint array_length = array.length; + for (uint i = 0; i < array_length; i++) + { + // code that does not modify length of `array` + } + } +} +``` + """ + WIKI_RECOMMENDATION = ( + "Cache the lengths of storage arrays if they are used and not modified in `for` loops." + ) + + @staticmethod + def _is_identifier_member_access_comparison(exp: BinaryOperation) -> bool: + """ + Checks whether a BinaryOperation `exp` is an operation on Identifier and MemberAccess. + """ + return ( + isinstance(exp.expression_left, Identifier) + and isinstance(exp.expression_right, MemberAccess) + ) or ( + isinstance(exp.expression_left, MemberAccess) + and isinstance(exp.expression_right, Identifier) + ) + + @staticmethod + def _extract_array_from_length_member_access(exp: MemberAccess) -> [StateVariable | None]: + """ + Given a member access `exp`, it returns state array which `length` member is accessed through `exp`. + If array is not a state array or its `length` member is not referenced, it returns `None`. + """ + if exp.member_name != "length": + return None + if not isinstance(exp.expression, Identifier): + return None + if not isinstance(exp.expression.value, StateVariable): + return None + if not isinstance(exp.expression.value.type, ArrayType): + return None + return exp.expression.value + + @staticmethod + def _is_loop_referencing_array_length( + node: Node, visited: Set[Node], array: StateVariable, depth: int + ) -> True: + """ + For a given loop, checks if it references `array.length` at some point. + Will also return True if `array.length` is referenced but not changed. + This may potentially generate false negatives in the detector, but it was done this way because: + - situations when array `length` is referenced but not modified in loop are rare + - checking if `array.length` is indeed modified would require much more work + """ + visited.add(node) + if node.type == NodeType.STARTLOOP: + depth += 1 + if node.type == NodeType.ENDLOOP: + depth -= 1 + if depth == 0: + return False + + # Array length may change in the following situations: + # - when `push` is called + # - when `pop` is called + # - when `delete` is called on the entire array + if node.type == NodeType.EXPRESSION: + for op in node.irs: + if isinstance(op, Length) and op.value == array: + # op accesses array.length, not necessarily modifying it + return True + if isinstance(op, Delete): + # take into account only delete entire array, since delete array[i] doesn't change `array.length` + if ( + isinstance(op.expression, UnaryOperation) + and isinstance(op.expression.expression, Identifier) + and op.expression.expression.value == array + ): + return True + + for son in node.sons: + if son not in visited: + if CacheArrayLength._is_loop_referencing_array_length(son, visited, array, depth): + return True + return False + + @staticmethod + def _handle_loops(nodes: List[Node], non_optimal_array_len_usages: List[SourceMapping]) -> None: + """ + For each loop, checks if it has a comparison with `length` array member and, if it has, checks whether that + array size could potentially change in that loop. + If it cannot, the loop condition is added to `non_optimal_array_len_usages`. + There may be some false negatives here - see docs for `_is_loop_referencing_array_length` for more information. + """ + for node in nodes: + if node.type == NodeType.STARTLOOP: + if_node = node.sons[0] + if if_node.type != NodeType.IFLOOP: + continue + if not isinstance(if_node.expression, BinaryOperation): + continue + exp: BinaryOperation = if_node.expression + if not CacheArrayLength._is_identifier_member_access_comparison(exp): + continue + array: StateVariable + if isinstance(exp.expression_right, MemberAccess): + array = CacheArrayLength._extract_array_from_length_member_access( + exp.expression_right + ) + else: # isinstance(exp.expression_left, MemberAccess) == True + array = CacheArrayLength._extract_array_from_length_member_access( + exp.expression_left + ) + if array is None: + continue + + visited: Set[Node] = set() + if not CacheArrayLength._is_loop_referencing_array_length( + if_node, visited, array, 1 + ): + non_optimal_array_len_usages.append(if_node.expression) + + @staticmethod + def _get_non_optimal_array_len_usages_for_function(f: Function) -> List[SourceMapping]: + """ + Finds non-optimal usages of array length in loop conditions in a given function. + """ + non_optimal_array_len_usages: List[SourceMapping] = [] + CacheArrayLength._handle_loops(f.nodes, non_optimal_array_len_usages) + + return non_optimal_array_len_usages + + @staticmethod + def _get_non_optimal_array_len_usages(functions: List[Function]) -> List[SourceMapping]: + """ + Finds non-optimal usages of array length in loop conditions in given functions. + """ + non_optimal_array_len_usages: List[SourceMapping] = [] + + for f in functions: + non_optimal_array_len_usages += ( + CacheArrayLength._get_non_optimal_array_len_usages_for_function(f) + ) + + return non_optimal_array_len_usages + + def _detect(self): + results = [] + + non_optimal_array_len_usages = CacheArrayLength._get_non_optimal_array_len_usages( + self.compilation_unit.functions + ) + for usage in non_optimal_array_len_usages: + info = [ + f"Loop condition at {usage.source_mapping} should use cached array length instead of referencing " + f"`length` member of the storage array.\n " + ] + res = self.generate_result(info) + results.append(res) + return results diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol new file mode 100644 index 000000000..79858d182 --- /dev/null +++ b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol @@ -0,0 +1,127 @@ +pragma solidity 0.8.17; + +contract CacheArrayLength +{ + struct S + { + uint s; + } + + S[] array; + S[] array2; + + function f() public + { + // array accessed but length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array[i] = S(0); + } + + // array.length doesn't change, but array.length not used in loop condition + for (uint i = array.length; i >= 0; i--) + { + + } + + // array.length changes in the inner loop + for (uint i = 0; i < array.length; i++) + { + for (uint j = i; j < 2 * i; j++) + array.push(S(j)); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + array.pop(); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + delete array; + } + + // array.length doesn't change despite using delete + for (uint i = 0; i < array.length; i++) // warning should appear + { + delete array[i]; + } + + // array.length changes; push used in more complex expression + for (uint i = 0; i < array.length; i++) + { + array.push() = S(i); + } + + // array.length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array2.pop(); + array2.push(); + array2.push(S(i)); + delete array2; + delete array[0]; + } + + // array.length changes; array2.length doesn't change + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + array.pop(); + } + } + } + + // array.length doesn't change; array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) + { + array2.pop(); + } + } + } + + // none of array.length and array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + + } + } + } + + S[] memory array3; + + // array3 not modified, but it's not a storage array + for (uint i = 0; i < array3.length; i++) + { + + } + } +} \ No newline at end of file diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json new file mode 100644 index 000000000..b2050508f --- /dev/null +++ b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json @@ -0,0 +1,74 @@ +[ + [ + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "66f0059f2e2608b9e532ab9f6a473aa9128f1e9f02fe16fcb960b4ae4970b2cb", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "8463e2cd88985588ad2426476df033957fb4f8969117b7649f1165244b86d2e0", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "961db8345eea46c3814eb2c6f6fc6d1506a2feaa85630349266cff940f33a7ab", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "aea0a11159dec6d660f39a09aa44b56ad0effb7ced7183552b21b69f0f62e0c7", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "d296a0fd4cbe7b9a2db11b3d08b2db08a53414326a91eeda1f78853c4d4c5714", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "db2bfdc710cfa1ea5339e1f14b1a5b80735c50c37522f983a220b579df34c5d0", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + }, + { + "elements": [], + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", + "first_markdown_element": "", + "id": "f5085e5d1ddd69d59f60443b7e89ea5f7301ba6a0a42786624b783389474aaf7", + "check": "cache-array-length", + "impact": "Optimization", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index e6b87d530..5a9180256 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1639,6 +1639,11 @@ ALL_TEST_OBJECTS = [ "LowCyclomaticComplexity.sol", "0.8.16", ), + Test( + all_detectors.CacheArrayLength, + "CacheArrayLength.sol", + "0.8.17", + ), ] GENERIC_PATH = "/GENERIC_PATH" From 71fef2c437ca97bc7e303f6eadc615252ef78726 Mon Sep 17 00:00:00 2001 From: bart1e Date: Sun, 26 Feb 2023 18:11:34 +0100 Subject: [PATCH 049/101] 'unsupported operand type' error fix --- .../operations/cache_array_length.py | 2 +- ...rayLength.sol.0.8.17.CacheArrayLength.json | 42 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/slither/detectors/operations/cache_array_length.py b/slither/detectors/operations/cache_array_length.py index 8c1112d2d..1f8111bdb 100644 --- a/slither/detectors/operations/cache_array_length.py +++ b/slither/detectors/operations/cache_array_length.py @@ -81,7 +81,7 @@ contract C ) @staticmethod - def _extract_array_from_length_member_access(exp: MemberAccess) -> [StateVariable | None]: + def _extract_array_from_length_member_access(exp: MemberAccess) -> StateVariable: """ Given a member access `exp`, it returns state array which `length` member is accessed through `exp`. If array is not a state array or its `length` member is not referenced, it returns `None`. diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json index b2050508f..3323ff479 100644 --- a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json +++ b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json @@ -2,70 +2,70 @@ [ { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "66f0059f2e2608b9e532ab9f6a473aa9128f1e9f02fe16fcb960b4ae4970b2cb", + "id": "2ff6144814e406cadadd58712f5a7a8ef6ee169da06660d590e7bee37759fc98", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "8463e2cd88985588ad2426476df033957fb4f8969117b7649f1165244b86d2e0", + "id": "48a6388cf2193fdd780ea86cb3e588dfd3276182e209f3f2807d9927a5ba25bc", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "961db8345eea46c3814eb2c6f6fc6d1506a2feaa85630349266cff940f33a7ab", + "id": "562b7ae618977ea1d0a232d8af5edd81ce404b6844e864d0c4ab162a88142c71", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "aea0a11159dec6d660f39a09aa44b56ad0effb7ced7183552b21b69f0f62e0c7", + "id": "8f1aa2da0763f65179e90a2b96d7feb68c99aab60f227a5da8dad2f12069f047", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "d296a0fd4cbe7b9a2db11b3d08b2db08a53414326a91eeda1f78853c4d4c5714", + "id": "9c988bc3f6748fadb8527c9eea53e3d06f5f4e7b2a0c7c70993c4af758bbba47", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "db2bfdc710cfa1ea5339e1f14b1a5b80735c50c37522f983a220b579df34c5d0", + "id": "a1c39b4ae47535f3354d0bf26ea67d3b524efb94a3bdb2f503bda245471ee451", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" }, { "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n", + "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", + "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", "first_markdown_element": "", - "id": "f5085e5d1ddd69d59f60443b7e89ea5f7301ba6a0a42786624b783389474aaf7", + "id": "c57b54ebb07e5114ccb8c124fa3971b9385d5252264f2bf2acbe131b3a986aa8", "check": "cache-array-length", "impact": "Optimization", "confidence": "High" From d1804f3d01edb309f9632ac0ba67e0ca7e8fdce3 Mon Sep 17 00:00:00 2001 From: bart1e Date: Wed, 3 May 2023 19:20:05 +0200 Subject: [PATCH 050/101] Tests updated --- ...yLength_0_8_17_CacheArrayLength_sol__0.txt | 14 ++ .../0.8.17/CacheArrayLength.sol | 127 ++++++++++++++++++ .../0.8.17/CacheArrayLength.sol-0.8.17.zip | Bin 0 -> 7589 bytes 3 files changed, 141 insertions(+) create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol create mode 100644 tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip diff --git a/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt new file mode 100644 index 000000000..a0ba35740 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt @@ -0,0 +1,14 @@ +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array. + diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol new file mode 100644 index 000000000..79858d182 --- /dev/null +++ b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol @@ -0,0 +1,127 @@ +pragma solidity 0.8.17; + +contract CacheArrayLength +{ + struct S + { + uint s; + } + + S[] array; + S[] array2; + + function f() public + { + // array accessed but length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array[i] = S(0); + } + + // array.length doesn't change, but array.length not used in loop condition + for (uint i = array.length; i >= 0; i--) + { + + } + + // array.length changes in the inner loop + for (uint i = 0; i < array.length; i++) + { + for (uint j = i; j < 2 * i; j++) + array.push(S(j)); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + array.pop(); + } + + // array.length changes + for (uint i = 0; i < array.length; i++) + { + delete array; + } + + // array.length doesn't change despite using delete + for (uint i = 0; i < array.length; i++) // warning should appear + { + delete array[i]; + } + + // array.length changes; push used in more complex expression + for (uint i = 0; i < array.length; i++) + { + array.push() = S(i); + } + + // array.length doesn't change + for (uint i = 0; i < array.length; i++) // warning should appear + { + array2.pop(); + array2.push(); + array2.push(S(i)); + delete array2; + delete array[0]; + } + + // array.length changes; array2.length doesn't change + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + array.pop(); + } + } + } + + // array.length doesn't change; array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) + { + array2.pop(); + } + } + } + + // none of array.length and array2.length changes + for (uint i = 0; i < 7; i++) + { + for (uint j = i; j < array.length; j++) // warning should appear + { + for (uint k = 0; k < j; k++) + { + + } + + for (uint k = 0; k < array2.length; k++) // warning should appear + { + + } + } + } + + S[] memory array3; + + // array3 not modified, but it's not a storage array + for (uint i = 0; i < array3.length; i++) + { + + } + } +} \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip new file mode 100644 index 0000000000000000000000000000000000000000..ab3d813718b45d438f91556934cc243e900beb57 GIT binary patch literal 7589 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 literal 0 HcmV?d00001 From 9cc616e4cc539a6479673d62565625d446532b0e Mon Sep 17 00:00:00 2001 From: bart1e Date: Wed, 3 May 2023 19:21:58 +0200 Subject: [PATCH 051/101] Old tests removed --- .../0.8.17/CacheArrayLength.sol | 127 ------------------ ...rayLength.sol.0.8.17.CacheArrayLength.json | 74 ---------- 2 files changed, 201 deletions(-) delete mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol delete mode 100644 tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol deleted file mode 100644 index 79858d182..000000000 --- a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol +++ /dev/null @@ -1,127 +0,0 @@ -pragma solidity 0.8.17; - -contract CacheArrayLength -{ - struct S - { - uint s; - } - - S[] array; - S[] array2; - - function f() public - { - // array accessed but length doesn't change - for (uint i = 0; i < array.length; i++) // warning should appear - { - array[i] = S(0); - } - - // array.length doesn't change, but array.length not used in loop condition - for (uint i = array.length; i >= 0; i--) - { - - } - - // array.length changes in the inner loop - for (uint i = 0; i < array.length; i++) - { - for (uint j = i; j < 2 * i; j++) - array.push(S(j)); - } - - // array.length changes - for (uint i = 0; i < array.length; i++) - { - array.pop(); - } - - // array.length changes - for (uint i = 0; i < array.length; i++) - { - delete array; - } - - // array.length doesn't change despite using delete - for (uint i = 0; i < array.length; i++) // warning should appear - { - delete array[i]; - } - - // array.length changes; push used in more complex expression - for (uint i = 0; i < array.length; i++) - { - array.push() = S(i); - } - - // array.length doesn't change - for (uint i = 0; i < array.length; i++) // warning should appear - { - array2.pop(); - array2.push(); - array2.push(S(i)); - delete array2; - delete array[0]; - } - - // array.length changes; array2.length doesn't change - for (uint i = 0; i < 7; i++) - { - for (uint j = i; j < array.length; j++) - { - for (uint k = 0; k < j; k++) - { - - } - - for (uint k = 0; k < array2.length; k++) // warning should appear - { - array.pop(); - } - } - } - - // array.length doesn't change; array2.length changes - for (uint i = 0; i < 7; i++) - { - for (uint j = i; j < array.length; j++) // warning should appear - { - for (uint k = 0; k < j; k++) - { - - } - - for (uint k = 0; k < array2.length; k++) - { - array2.pop(); - } - } - } - - // none of array.length and array2.length changes - for (uint i = 0; i < 7; i++) - { - for (uint j = i; j < array.length; j++) // warning should appear - { - for (uint k = 0; k < j; k++) - { - - } - - for (uint k = 0; k < array2.length; k++) // warning should appear - { - - } - } - } - - S[] memory array3; - - // array3 not modified, but it's not a storage array - for (uint i = 0; i < array3.length; i++) - { - - } - } -} \ No newline at end of file diff --git a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json b/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json deleted file mode 100644 index 3323ff479..000000000 --- a/tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol.0.8.17.CacheArrayLength.json +++ /dev/null @@ -1,74 +0,0 @@ -[ - [ - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "2ff6144814e406cadadd58712f5a7a8ef6ee169da06660d590e7bee37759fc98", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "48a6388cf2193fdd780ea86cb3e588dfd3276182e209f3f2807d9927a5ba25bc", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "562b7ae618977ea1d0a232d8af5edd81ce404b6844e864d0c4ab162a88142c71", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "8f1aa2da0763f65179e90a2b96d7feb68c99aab60f227a5da8dad2f12069f047", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "9c988bc3f6748fadb8527c9eea53e3d06f5f4e7b2a0c7c70993c4af758bbba47", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "a1c39b4ae47535f3354d0bf26ea67d3b524efb94a3bdb2f503bda245471ee451", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - }, - { - "elements": [], - "description": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", - "markdown": "Loop condition at tests/detectors/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array.\n ", - "first_markdown_element": "", - "id": "c57b54ebb07e5114ccb8c124fa3971b9385d5252264f2bf2acbe131b3a986aa8", - "check": "cache-array-length", - "impact": "Optimization", - "confidence": "High" - } - ] -] \ No newline at end of file From 2da5ee4caa89d57af91ec6c3c391151fde9aee96 Mon Sep 17 00:00:00 2001 From: Simone Date: Wed, 10 May 2023 16:34:05 +0200 Subject: [PATCH 052/101] Fix multiple try catch with same return var name --- slither/solc_parsing/declarations/function.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 57d7784e5..ed1fbcd7f 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -685,6 +685,7 @@ class FunctionSolc(CallerContextExpression): "name": parameters[0]["name"], "nodeType": "Identifier", "src": parameters[0]["src"], + "referencedDeclaration": parameters[0]["id"], "typeDescriptions": parameters[0]["typeDescriptions"], } else: @@ -713,6 +714,7 @@ class FunctionSolc(CallerContextExpression): "name": p["name"], "nodeType": "Identifier", "src": p["src"], + "referencedDeclaration": p["id"], "typeDescriptions": p["typeDescriptions"], } leftHandSide["components"].append(ident) From 2cdc5448acd2f2d4bcc3f78b5c71ef6695eb2472 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 10 May 2023 11:09:22 -0500 Subject: [PATCH 053/101] Make cross-contract taint optional in `compare` --- slither/utils/upgradeability.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 5be745bd6..f90dc1376 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -90,7 +90,7 @@ class TaintedExternalContract: # pylint: disable=too-many-locals def compare( - v1: Contract, v2: Contract + v1: Contract, v2: Contract, include_external: bool = False ) -> Tuple[ List[Variable], List[Variable], @@ -185,10 +185,12 @@ def compare( elif any(func in written_by for func in new_modified_functions + tainted_functions): tainted_variables.append(var) - # Find all external contracts and functions called by new/modified/tainted functions - tainted_contracts = tainted_external_contracts( - new_functions + modified_functions + tainted_functions - ) + tainted_contracts = [] + if include_external: + # Find all external contracts and functions called by new/modified/tainted functions + tainted_contracts = tainted_external_contracts( + new_functions + modified_functions + tainted_functions + ) return ( missing_vars_in_v2, From a15ab76027f1d194fdf13c196efd1c9fda9268c6 Mon Sep 17 00:00:00 2001 From: Simone Date: Wed, 10 May 2023 18:11:20 +0200 Subject: [PATCH 054/101] Catch Exception in more places --- .../slither_compilation_unit_solc.py | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index aa4f88890..f4258cd41 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -742,19 +742,46 @@ Please rename it, this name is reserved for Slither's internals""" self._underlying_contract_to_parser[contract].log_incorrect_parsing( f"Impossible to generate IR for {contract.name}.{func.name} ({func.source_mapping}):\n {e}" ) - except AssertionError as e: + except Exception as e: func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions]) logger.error( - f"\nFailed to parse {contract.name}.{func.name} ({func.source_mapping}):\n " + f"\nFailed to generate IR for {contract.name}.{func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{contract.name}.{func.name} ({func.source_mapping}):\n " f"{func_expressions}" ) raise e - - contract.convert_expression_to_slithir_ssa() + try: + contract.convert_expression_to_slithir_ssa() + except Exception as e: + logger.error( + f"\nFailed to convert IR to SSA for {contract.name} contract. Please open an issue https://github.com/crytic/slither/issues.\n " + ) + raise e for func in self._compilation_unit.functions_top_level: - func.generate_slithir_and_analyze() - func.generate_slithir_ssa({}) + try: + func.generate_slithir_and_analyze() + except AttributeError as e: + logger.error( + f"Impossible to generate IR for top level function {func.name} ({func.source_mapping}):\n {e}" + ) + except Exception as e: + func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions]) + logger.error( + f"\nFailed to generate IR for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n " + f"{func_expressions}" + ) + raise e + + try: + func.generate_slithir_ssa({}) + except Exception as e: + func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions]) + logger.error( + f"\nFailed to convert IR to SSA for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n " + f"{func_expressions}" + ) + raise e + self._compilation_unit.propagate_function_calls() for contract in self._compilation_unit.contracts: contract.fix_phi() From 10641851e738d7f3f27b0a851612c0b24a7efb9c Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 10 May 2023 11:53:06 -0500 Subject: [PATCH 055/101] Update test --- tests/unit/utils/test_upgradeability_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_upgradeability_util.py b/tests/unit/utils/test_upgradeability_util.py index e367a3eed..86ae0d675 100644 --- a/tests/unit/utils/test_upgradeability_util.py +++ b/tests/unit/utils/test_upgradeability_util.py @@ -30,7 +30,7 @@ def test_upgrades_compare() -> None: modified_funcs, tainted_funcs, tainted_contracts, - ) = compare(v1, v2) + ) = compare(v1, v2, include_external=True) assert len(missing_vars) == 0 assert new_vars == [v2.get_state_variable_from_name("stateC")] assert tainted_vars == [ From af7279a6bf5bba57d8a48cdbc78d79551adf6f18 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 10 May 2023 11:54:09 -0500 Subject: [PATCH 056/101] Update docstring --- slither/utils/upgradeability.py | 1 + 1 file changed, 1 insertion(+) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index f90dc1376..37c5301e2 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -107,6 +107,7 @@ def compare( Args: v1: Original version of (upgradeable) contract v2: Updated version of (upgradeable) contract + include_external: Optional flag to enable cross-contract external taint analysis Returns: missing-vars-in-v2: list[Variable], From 8408788e8e3ce74c9d34d7bf94bc200067b33f27 Mon Sep 17 00:00:00 2001 From: devtooligan Date: Thu, 11 May 2023 10:59:09 -0700 Subject: [PATCH 057/101] chore: add to scripts/ci_test_printers.sh --- scripts/ci_test_printers.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci_test_printers.sh b/scripts/ci_test_printers.sh index 61994b337..d13932b81 100755 --- a/scripts/ci_test_printers.sh +++ b/scripts/ci_test_printers.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -### Test printer +### Test printer cd tests/e2e/solc_parsing/test_data/compile/ || exit # Do not test the evm printer,as it needs a refactoring -ALL_PRINTERS="cfg,constructor-calls,contract-summary,data-dependency,echidna,function-id,function-summary,modifiers,call-graph,human-summary,inheritance,inheritance-graph,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration" +ALL_PRINTERS="cfg,constructor-calls,contract-summary,data-dependency,echidna,function-id,function-summary,modifiers,call-graph,human-summary,inheritance,inheritance-graph,loc,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration" # Only test 0.5.17 to limit test time for file in *0.5.17-compact.zip; do From 0b7257209dd09b1d877ec40870dd55eaf8ab10d1 Mon Sep 17 00:00:00 2001 From: bart1e Date: Mon, 15 May 2023 21:01:04 +0200 Subject: [PATCH 058/101] External calls handled + output printing changed --- .../operations/cache_array_length.py | 16 +++++-- ...yLength_0_8_17_CacheArrayLength_sol__0.txt | 18 ++++--- .../0.8.17/CacheArrayLength.sol | 44 ++++++++++++++++++ .../0.8.17/CacheArrayLength.sol-0.8.17.zip | Bin 7589 -> 8898 bytes 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/slither/detectors/operations/cache_array_length.py b/slither/detectors/operations/cache_array_length.py index 1f8111bdb..da73d3fd5 100644 --- a/slither/detectors/operations/cache_array_length.py +++ b/slither/detectors/operations/cache_array_length.py @@ -7,7 +7,7 @@ from slither.core.solidity_types import ArrayType from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables import StateVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.slithir.operations import Length, Delete +from slither.slithir.operations import Length, Delete, HighLevelCall class CacheArrayLength(AbstractDetector): @@ -119,8 +119,10 @@ contract C # - when `push` is called # - when `pop` is called # - when `delete` is called on the entire array + # - when external function call is made (instructions from internal function calls are already in + # `node.all_slithir_operations()`, so we don't need to handle internal calls separately) if node.type == NodeType.EXPRESSION: - for op in node.irs: + for op in node.all_slithir_operations(): if isinstance(op, Length) and op.value == array: # op accesses array.length, not necessarily modifying it return True @@ -132,6 +134,8 @@ contract C and op.expression.expression.value == array ): return True + if isinstance(op, HighLevelCall) and not op.function.view and not op.function.pure: + return True for son in node.sons: if son not in visited: @@ -173,7 +177,7 @@ contract C if not CacheArrayLength._is_loop_referencing_array_length( if_node, visited, array, 1 ): - non_optimal_array_len_usages.append(if_node.expression) + non_optimal_array_len_usages.append(if_node) @staticmethod def _get_non_optimal_array_len_usages_for_function(f: Function) -> List[SourceMapping]: @@ -207,8 +211,10 @@ contract C ) for usage in non_optimal_array_len_usages: info = [ - f"Loop condition at {usage.source_mapping} should use cached array length instead of referencing " - f"`length` member of the storage array.\n " + "Loop condition at ", + usage, + " should use cached array length instead of referencing `length` member " + "of the storage array.\n ", ] res = self.generate_result(info) results.append(res) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt index a0ba35740..63d4b883c 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt @@ -1,14 +1,18 @@ -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#47 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#36) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#78 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i_scope_22 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#166) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#16 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at j_scope_11 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#108) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#88 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i_scope_6 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#79) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#105 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at i_scope_21 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#160) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#112 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at k_scope_9 < array2.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#98) should use cached array length instead of referencing `length` member of the storage array. -Loop condition at tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#59 should use cached array length instead of referencing `length` member of the storage array. +Loop condition at k_scope_17 < array2.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#132) should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at j_scope_15 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#125) should use cached array length instead of referencing `length` member of the storage array. + +Loop condition at i_scope_4 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#67) should use cached array length instead of referencing `length` member of the storage array. diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol index 79858d182..704d6bed9 100644 --- a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol +++ b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol @@ -10,6 +10,26 @@ contract CacheArrayLength S[] array; S[] array2; + function h() external + { + + } + + function g() internal + { + this.h(); + } + + function h_view() external view + { + + } + + function g_view() internal view + { + this.h_view(); + } + function f() public { // array accessed but length doesn't change @@ -123,5 +143,29 @@ contract CacheArrayLength { } + + // array not modified, but it may potentially change in an internal function call + for (uint i = 0; i < array.length; i++) + { + g(); + } + + // array not modified, but it may potentially change in an external function call + for (uint i = 0; i < array.length; i++) + { + this.h(); + } + + // array not modified and it cannot be changed in a function call since g_view is a view function + for (uint i = 0; i < array.length; i++) // warning should appear + { + g_view(); + } + + // array not modified and it cannot be changed in a function call since h_view is a view function + for (uint i = 0; i < array.length; i++) // warning should appear + { + this.h_view(); + } } } \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip b/tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip index ab3d813718b45d438f91556934cc243e900beb57..dafdfc43167f9eb622fed98bdc69e91d02f89dfa 100644 GIT binary patch delta 8644 zcmV;#Av@lsJHka6P)h>@KL7#%4gfHxuU4LDj71P4005BSu^0pae-k2Z4bNMgY$EpW zB#8-h0PCa2>8Snp{hfhNYmqL`Rtr`Z8_$uiz=Ex&=kMUUoDAZ!xXWY?eyets0%9mF z3J!+FTTPk7*21!8>HEBOKN}aBxbpA_wW$xi9YlQKg`~ozYSni;SnSlx$B~>YbY0O; z-SKre@XlVJ(uuuOe~|<}>>~T6vNeEf+4ygQW?|^2D_?KguArT$O7l5X_Rhi@JZdc~ z2cFX4%8(%0^x{$Rk2YbfL2~z_xu5{CTz}-aaT>Dy*eku>Dbia~GeQa-3Ojvy-|HO? zjK~+-qK@Wy&6;V))E2hN9mWYY{Rbx(Oyhgp(`U|*d!toqe=8@Zmv2W((O*c0e<`Iga@?ZQJm zSk$k%?E}$W;4D`kLL#rhdP6;$Cy*-C?v2n&q@8wz5H@40#HVwNc)@R@caSja%VKh) zO;y9Q_EF-ffHY!w7fwzM{P81pK*{24VV?5|=Bb8Pf1)Ov*t#=YkuNtSPho57{HVi1 z&J`uHlQgky%8oDO+z;(s`O(v83?_D4)YS!{_yb`##eVmjlDkB{VfHg29nAf3;4q%G zH>}F!zbg>`%6%6n6h=>7$8Wb2$ycc1aZ8+DGMRIE*2?VN35!ll4S0uxhQxN`XlHX` z1;$bYe~?cE3rA(sU(Ip+&{^a>p`M_I$*<(hb3!5j2lR46tk3)VmFZ|PrS zrrhTu^I5&>KOW_f)SxP2eel*qqg;Wv{L?tqsYQi_GMRrnrH_CP6DX0{YVQpn{{HIb z3Vik!b1&*NxU!xwT3)Drgg7OT{9W%AETi%Oe}Y51Hfv0Z2gmbS>a5wOJ^;nmWyXbN zd1Z|+3`ti6AVj+VhvR3h!*+CEU`zhgM`@m*Z{UdceQ75P>N(|_un09FO~O~tX{5VD z=OA6y%|q{z>*2+V^946X;g=zZnB_9brgd(PDU;Sxaz0;9LYE3`@~D+U2d*hib?#g4-SsrTT%~b~8?7E4f8C>DqRQak3mJw{h>!n^9S9l*2hGt_FC!^M z*VU${EpQ0mQencatE6kk2CnALR*nCA>pJrVK6Ou)4vVTLZJ%^Lf8)2{*npMqAu@f9 zZjH3JeM;If^L(uRAra9n>Q=@vT0Q*fFuwnO6@P?kjAOvmEAf3wMp zSDDf1u4Ku}9hd$l*S;F`Q6Q|LrcQSZe>-v_l#a_; zcV5IilhbC}f+{bO>I%(k_H$~TbRHF*zZ9*zmJ?kddX#yMKFj)_k#GT1EO+ozRLQP; zVKCD}|D(pF42#SubS}lOY1AUCW3M7!gk(*U<@H1Z=7|}0XH3&IW^q-ju0^;7#2RY5 zC*T}|uU}(v@4+e^E$rU3c{8XAh*9!`eH@JLt;E;VIT_&y(tL24LIu^c;7=N}d` zGz_VMX79|6bQ0AyfJIDG$2;_IQ|WgNfPq!p1`YsZY<@G~dor){Qcq#Xrn0fpB4uiS zK0M8eSiM81mK^X70K{V*zdu0SOsqXEkaK%%x|v$)_tYYf5yZ(Re*ju^j~hnnucy|l zbQ+#NN4Ss5=yIGT_;=$f3GMa;D00pVLOn3Tus@FkWgOIx0xtiC_HaS6^rLTCY85&n z`yN#`X(S-1NZK3zcT7;>2DmSMasIRDCj(R9?*qnUlW`*Na@D|_=)?F5W`8nDK^~-A zwYmQG<3`($(cY$=f56}Dr#)I3L~{EMaR?D!^2F4VO$Dr->&lV2TPb8Kg;GeZJ!XWk zCz_J>b^%fAVMG)h=@G#^*wwp6gUtFDC0JXiAaG_>U5opkp7^GFMgQ#K`YD_emDPw|3mO-KW2of@DV% zz}5eyk&7e7iiO>GioV@q$Zc3uQItrJ9bczdB1xcRag@6uy z`t*24@W9)q80q(FZMzpEiHwuT#moeV^w21D7&o`ge-H&GAC3#agomtFbOABI8OADx zD4c6E$(%vL6qt9?ee70>7?QNw(|!_uE$WJR{Txqlpi5pfF%@k$xJ{4GV%ALr8xKWw z45F@Fvk+5Z@=ax-n<#la>aZf_c2m^xje#PIn)J|bCcy=18WL*U7cNG+G|1nthjDHz z&H@Toe@izU1z<6tj+ybjBmlKP5mvvWC4H!)$hj^GDxK#cVkZ%Y||6EaGS|!m~=`jFdX+X8gjKJNxo`|&Lf6gFaUSc zGA!mfB*4JA%D`{6k3g&z#(9WAtssrp{d7%De?hpo4I8QaPC#91!1{gqUWX6J+Jm+0 zg(sN{0$uNC4obl{ubNVcd1$$Q)bVW_EM<%Sik1eXe(w$k2*c^^qBba)*j z%7DoUNY`R~8;fAXW10l*(sM7yJcQU#X?_GH?dg^`!I+{x^qW742fI=ZJG3B7%l3hn zeReL zAhtj^)Prv1++iMAh9r+%DUceV6C|TsL49uS%G;Yq8PKApiK~auN`Np|X{+$hYy#qL zvkwryx1+QUV`X(k3IF+BEZ2$R*=7ZIe|U)#btl~XDh%GHWinLRvOBu}rd-&}=R|*$ z+ybv(Oy&fVG}se@Z@~t}P~|}4?mB&CHU3z5VB3k^_^+BCp|n3t+pv44-aoe2{k6~) zQzxRK^Slk)F^U=T$JJrv{;LUQcMeeUuPm>3<>pq-AM=%PN@Zh>Ymp`K5{qmke~RVz zC&W(vR~Huu*NK%$UU~i$aFe;qC90KrFz2?nY?0sz1W@mG>j+CnWPQ7Q~6enD*GhgXVD}r7vPF{^#9V}D5~RR zD;Vo9u=Z{~IR2ip-^DKj&@P>5f5rTgK&-G<8JAw}PVD2pxiH^a`@KazvGBkGrE4($ z6^u0-*e>x!vx4otPsWtRB;J0SdH2HeLeHWXZ}4F4O9<=32lja*lwi_ef653mGFJz_ z4a`zD?4G>p+Fv7;$@2a|wy+5;Ckk}m=%_5)+wL|n6%+njrkau%oOQPL2{fEvMA|6m zQYC*dk)bx(kNot!2a>l`@n-G6qecP4xa|%xxHL!L31F_;lcJJ3IXsQ%lN&bKJOArb zdxX$UL*435#dC%u2Vu)Ue^kR67~uoCSZ$$y#)bJ0O8KSmqH_)^SlTc}R3gL-zdsa( z*s)IRFk6FQN>T@JH4S|@1Seg@{RC7z3`A`m7j2GS|0=q)8}YXzXj7I%Ajx8?j#mUj z?5dwE%Q+^rzouRvxvA&`br%<5W=3s#2-Vhy&+@llM6m`PTdaASf4}y`(1z5+f=2)$*XnG1K2m&1k^BgU2GH<<0}7MYcRimBqWDh0@uyZ`ko+IJ!IP=yb*?T z=UN|Won_B!P$Anz6J~~qRl72dy-5M_qyGG)W>etX5SBg%?z*<0c-K}{>{>tZ-P$sEnx zkV1-HtC|lC{EG>h2+r|0mR&&{=t!HwBEpSP;TxkpzB-X_VI)2(_ec&$ zhISP1IMnQ%e>g~8jw`~ZVRDk}!u){BU`4y51y?Rb!$pa|w|V>RJbD|Wpe6|TvJfgh z`{X))sCn{OQ>&Tv^+e$L1>n;j1zRo`0T&6c0I(t;@^m;hBWurIO(P5@DR$|VUAHy3 zxIxB=QCwss4ZU8!7kAH@0gE97Gnlc}+dJWzE!eI?e`*%DWBRki^P%hViCL#FGmW^0 z`OZ(YBqU_}nRl!VCMD|NlIOZzOj18Efl9h4VQ`x}B-kG~K!Rvfn|c=V^YUdj`lzp} zDOR^Yu8=F`Od%z(HcqH-ce|rE-qx05XCN6L&u+x{tp>3wU;;WHIm9RpHzCruE(g_Srd-J0=1IYZb_@QZ#NK@J;!CnZkhs(PW}`L?hv^@6B<=jA*tr z=SxB@#idZxPXhlf3fcoq8HSc!juR8s;)bcZe_^CDkdrs*+OsCc-dX17lu0^sw4xB# z#q+0~zka%ND^7r|Nm%DloytMKRh3Sh8CI2u-eSh5c4#T~UU>(91QTa1=1b)Tm_=6| zd_etkN$_xDQuU4&^BdF`cJ(&dhDA ze?-Bn1lpu%P0Av290WWCPzP*#c3o0gK@mM0VS$l0+`pp0*`zL>3;o zfoT*^$pRg)qM9t5-{Qj%Ah6x;Skak;5o%9X_~NJGhi7Dh{N}n4glHr=s2A6- z%(Ql>JsmRykSP|DS~~l>Pa!3rz50-lS2L;3CpF12ZRSMj)1u)Ab)?$JaD*d$l4AO2tg7MlSP-FCNEtR>(H z{atE{*>gx0K8u^hv-vo-tm659(fZ;|Rn+-duh<0+Nt7O_fvH#- zRRr681GxC4%0zu*A_&N2ZHbfYm}Qxa4r>4bWpZdKUIJ&C-~S*|+%%02jU*S+FLHqJ9c5Qh| z(2xnx=|!#kLBJ@@4+il?gCGHJ5nYD%zVIT8K`KTNw+|Dxr)*s%;U!gMGlT63)ub-4 z;U^W3VnG7;5;0&PdL&m`e@fhxZ;c5n1xygwM1EZ@u{+}FAo5ev3)5YC~#<7}mI*SrET-dtgzEalC*Jw;s@0F;Ztv#11Zh$(PWA-M zW^z;ocH2s&`OG~7m_M&E7#TOt)zc0x@B)kGb`**9n=bI*WG__s1*`hy$h+{MdC?xI z3ixDO9Ns~TZ^sowe^|dJMaW+iK^juP^wT|JCCItkx0UZ}f=C|7SedL(vtE0HBb-~9 z(Ob)&7}Q6-K7W?O$N7m|E(NTb6igU7cVA8bw(Ll33oCo;0^;*a!-R<84SbyhLBLDN z@jH05XQpTTqL@K`(V@e82^fEAY=afTAAv!v=vyDX_(;n?Zh#DwpA2SdX5uUziYJ~y?7y}C^E=s*Rwaw{N{X9iE;#j?Nu6UQAWYzdV zSW<`mW!I9zxj7lmZ#Yu~$I92>shL7Om*AiuQtZ+6e~rn#dU=D+I7tl2jy(a3UhoyO zzR;a>H!n+z;o{*;L7zQGJ9jzbd8I8=!M$XSG<=k zE<+Uje`a)35;Q~LYq>|`IW=AJP0axkUR`3{#DQJ2dpq?Q33#RnV1p)l$vI#Uhf+$2 z+h>%_uc@B(i(1ACghuoS1M5so0Rg!B$MxdQX98}iT^&g7o5bw48!onh*>82#MxfSo zBXL(n+{5%+@R{8JZ~&-%Q!flozPPX`u1%1hf2YVV{zH_Ys>1%#uR zhA&;vyeq~LwZu9(naFbzhvJ!?sw+}5>9pDr&b|gpU0QVS9}<~UsuP8nEoI=Sn-=mVE;N|yxZn-T1@wP8(@8$vF&QCr?IS5 zV0vge?~afkg!O3sbHpwt&F26&GRVATHr42P|R}pf0}d- z5m^~UB~yDET?IYWHt?LuZ&+-_olN;)VYt8^8a7!z^H}5v1=N@1mlETdVoa?xfBomX zPM17#h#`_Q$x!7oXiU6%(Kj5pBpI+Z%{ABlnFeTSG82Vm%j$4$HA3hiJbh0=8s~K{ zw?r1Wf3=TW5*;1j&>h58KuCdWe=j|A!?&`gG7hpio|3eh_E<1zV?_ie_^GA~4Pu!Q zyxbT7gB15@>j|Ud{)A@YgL^X~$Pe^NnGO|+UPV+fT+!32^ z{E91_rg)<15yCqp{JugUigdS^FweVF2aO*X89Z$O?q*SGIG&(Yjsa?9g$49Q|2r- zgsLDTZ6BN@RdqYr%i_|DjR4#DS9(zX3WA@^bW9@euNts_3l-}V8m>dtXR9D|5_*@H zD%;SCemLcvt}{bb;&mCQe@$gdQv<15k#q4F$qQ>O*KT)uL5p7;WQWxpm_M}74nE*S z30L^Adw_2Ct(?P>0;X6mBS~toT1ImoiGb)>5(er zRgDiWhG}@@897o~a5;7%s+%LX7bHDgBg9@SuKXw>JwflwwK?`|e-Cr0YPTfSKO2M@ zg~RssOpo2_V6oMbP7!f6Gk&lqKEsvY+8A(){1G99nQIAlH^mI>xWB<~id86fYs1r* zrvW1Hf%7xTIUv#99^YFSEC#5u9`^N=SA1cYp9jw_Bbpl07M`^rxRQUaZ=f48p>h#&Fb3e;Q(t1b=Yp+Z&v)YlOhX zjUE_|V< zSnFBqd4#o6f?@?VFAYol*@Cg=Ir5~rNx)i5{A#3j)^RYiy=kpy)KCT3ppUPyCy)_4n#&Kxe&OQNgv!wvJvEw|QMk(^pzEoHF$>3o^Gq+=8Tg3#`hi9_g$8- zMX1^m$u4N5FByX7iu5)d$lJ&xYS_m?X`k`tB%R`2@`?XVwN@CZHWEi+6Yxj6{r%~k z3O~h!e=;VL>FxWL)AzXj~A-Dz*9oKZsZe;IK*4gNzn6(_UoGVj5hq3C^UQQd%? zJg}NY+y-sK>GIdv*Ztbju%#vOTp}Q{ryWK85%Z@&=)Jvs8-EQuLpcr8x}Swjj&q&~ zlSst^t)ZoYIf^KQ(#(W0)WATWUHm|ynbob#Qn~$z;TES*puI3{6&m*JHOqms5OG;P zf1eNXBV?)Iy@$8n*p7*wWwGBgFfu*;`2(3R4##&F({G)`{zI@>SMI&k@W#vSSYx+b z7jtLQ{jIXM51gKCK5q11N-aN=o>0Y1pDn2w!uu20sqyZ*X>!0?DQT>9XG&HL5K?Xu z)h`=Iod!`$&EPF{I5mLCmP$j?3@Z%(f3<^+A;8jv(&p4vV}V`l$Q9Tr+HAu`solW2 zb{_v8NBJNazch`@@^mxz5qNZsZC(3;PQwILle_z?{Z|31kWwMlo9#M|K=)3a-b;!m zoUcuGqELdR4?`e-5Z{WvD#$-QW}_5olfGABL6qi7PgTA(RY{-(i8@*0zFl?Cf7Ax1 zpD8q+e$SNY<7i^3gr>dPza;k$n9U!)FP0%xQaMR&ixwv_f0K^vzIj6b;UOQ^exef*h+l*NyTP zVdN8NR)UpKLvN(kfLNqagSV3F5wAbMkR>0_Xg+Bu9L8CP@bW!Ey}X;Jm6X&r4yRsG zM6Q$&EEc1H-r~Are3G-<`GE$I)s4J^z2c>O(VE{dJ^2)R0$gGsq(8D$e;Reg5($O| zrKSjar-ex7by{wV;X#XvUvn9Jya2UJZSYqlivv;MpCE36urq2Eeq;(wc@@p#8L&UeTM`!RZ_+zkfDReE~Qa8~Ej8Di;Mr(2%S z^0>q#cA$#2sN*1?=5mZjIKX%z*kqSyI2Y#hSZdiNserbtBK42Ue}=_(FU-$T6Pvp> z#t~%RH@grkeX`)MjaoS@PU`d>4U|-&olj+*x(LR*P?CtcY-!R3e2Y&`TsUG4h?G$5 z_t7239!(+)QAhRsuYkzGx$fcgEJpwGb`KFK@^76Yd~ zPP21A;gA~=!s}(Qf0q9J9sfI91EG%7^+H+-lhV;}0{m|>XHQZUel0|CpC=rSUvpl# zn@5Ql#mjWbuI045%Qv&7X`47C2%;$pIuD=(lkMVou+@TVrosnrjef}VET4)1 delta 7346 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 Date: Mon, 15 May 2023 22:14:22 +0200 Subject: [PATCH 059/101] Fix yul function calls --- slither/solc_parsing/yul/parse_yul.py | 28 ++++++++++++++++-- tests/e2e/solc_parsing/test_ast_parsing.py | 4 +++ .../test_data/assembly-functions.sol | 11 +++++++ .../assembly-functions.sol-0.6.9-compact.zip | Bin 0 -> 1537 bytes .../assembly-functions.sol-0.6.9-legacy.zip | Bin 0 -> 1403 bytes .../assembly-functions.sol-0.7.6-compact.zip | Bin 0 -> 1510 bytes .../assembly-functions.sol-0.7.6-legacy.zip | Bin 0 -> 1378 bytes .../assembly-functions.sol-0.8.16-compact.zip | Bin 0 -> 1528 bytes .../assembly-functions.sol-0.6.9-compact.json | 9 ++++++ .../assembly-functions.sol-0.6.9-legacy.json | 5 ++++ .../assembly-functions.sol-0.7.6-compact.json | 9 ++++++ .../assembly-functions.sol-0.7.6-legacy.json | 5 ++++ ...assembly-functions.sol-0.8.16-compact.json | 9 ++++++ 13 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/solc_parsing/test_data/assembly-functions.sol create mode 100644 tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-compact.zip create mode 100644 tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-legacy.zip create mode 100644 tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-compact.zip create mode 100644 tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-legacy.zip create mode 100644 tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.8.16-compact.zip create mode 100644 tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json create mode 100644 tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-legacy.json create mode 100644 tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json create mode 100644 tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-legacy.json create mode 100644 tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index 35d5cdd9d..978859a53 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -181,7 +181,7 @@ class YulScope(metaclass=abc.ABCMeta): def add_yul_local_function(self, func: "YulFunction") -> None: self._yul_local_functions.append(func) - def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulLocalVariable"]: + def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulFunction"]: return next( (v for v in self._yul_local_functions if v.underlying.name == func_name), None, @@ -252,6 +252,10 @@ class YulFunction(YulScope): def function(self) -> Function: return self._function + @property + def root(self) -> YulScope: + return self._root + def convert_body(self) -> None: node = self.new_node(NodeType.ENTRYPOINT, self._ast["src"]) link_underlying_nodes(self._entrypoint, node) @@ -271,6 +275,9 @@ class YulFunction(YulScope): def parse_body(self) -> None: for node in self._nodes: node.analyze_expressions() + for f in self._yul_local_functions: + if f != self: + f.parse_body() def new_node(self, node_type: NodeType, src: str) -> YulNode: if self._function: @@ -325,7 +332,10 @@ class YulBlock(YulScope): return yul_node def convert(self, ast: Dict) -> YulNode: - return convert_yul(self, self._entrypoint, ast, self.node_scope) + yul_node = convert_yul(self, self._entrypoint, ast, self.node_scope) + for f in self._yul_local_functions: + f.parse_body() + return yul_node def analyze_expressions(self) -> None: for node in self._nodes: @@ -390,7 +400,6 @@ def convert_yul_function_definition( root.add_yul_local_function(yul_function) yul_function.convert_body() - yul_function.parse_body() return parent @@ -809,6 +818,19 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[ if func: return Identifier(func.underlying) + # check yul-block scoped function + if isinstance(root, YulFunction): + yul_block = root.root + + # Iterate until we get to the YulBlock scope + while not isinstance(yul_block, YulBlock): + if isinstance(yul_block, YulFunction): + yul_block = yul_block.root + + func = yul_block.get_yul_local_function_from_name(name) + if func: + return Identifier(func.underlying) + magic_suffix = _parse_yul_magic_suffixes(name, root) if magic_suffix: return magic_suffix diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py index a561343de..ebdd04d9a 100644 --- a/tests/e2e/solc_parsing/test_ast_parsing.py +++ b/tests/e2e/solc_parsing/test_ast_parsing.py @@ -453,6 +453,10 @@ ALL_TESTS = [ Test("complex_imports/import_aliases_issue_1319/test.sol", ["0.5.12"]), Test("yul-state-constant-access.sol", ["0.8.16"]), Test("negate-unary-element.sol", ["0.8.16"]), + Test( + "assembly-functions.sol", + ["0.6.9", "0.7.6", "0.8.16"], + ), ] # create the output folder if needed try: diff --git a/tests/e2e/solc_parsing/test_data/assembly-functions.sol b/tests/e2e/solc_parsing/test_data/assembly-functions.sol new file mode 100644 index 000000000..c7b1c17d3 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/assembly-functions.sol @@ -0,0 +1,11 @@ +contract A { + function foo() public { + assembly { + function f() { function z() { function x() { g() } x() } z() } + function g() { + f() + } + g() + } + } +} diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-compact.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-compact.zip new file mode 100644 index 0000000000000000000000000000000000000000..10b6ee248058585c51cf6277ed5477a09f86838b GIT binary patch literal 1537 zcma*nX*|;n00;2@Vn#Wl#*;Z09Ue!hJnsA4M2faOq{SLFTedMW%q^<9a+H!)dI}-o zVeX@Oh{D>e!^e?pJ?{G`kDm9>=kxr2FMh9nZ@=yc5m8HE4^g}|^~I9?0s=#Ex>qSAqJ$_I@Bsh@ z0GKBy;|;eBgA4n6t4*cyJBWdrO)3euJrpfe`IH8E{S{k_JD*@!*|G6I7x#HRWUe?V zE|u$f%;x4eJ=-tfQiC55#T$FtgA)j>itJL*prS>rzbR1&?SuQOj}Md-cAX5q_e+}1 zfgT_)9;g~`GmlyjdufNrC(D_{D1C9v<)Q=DQh8rGsnW_8a`JmyHcla|HCmR-2-5-w zr>tk8Ixi&MkcsikKm@^_87JFpfyvYzX?sAOx8~qP*cT{VEp9kCumGL zOB-JKI@<29IK3dY6#tU7JdWU~tUTqk7^%ZuJ3V99kcJuhZXXqpA;F)J)L7AI%ie_d zJAP2*d;2K4m6(b3)o#NBGJH`r*yN7fRXx+tJR$&5Xf0s*LkOy>Y0o*>L6PBkhifo; zk+{kW~X2#hj^29l*Nu3_3_pv#)N&MV#7IZ+FO(M_L38PHC9#6G456bkNo8D>eG zVk0$NYOV66(%e#}*&aomJ8Vrv+DTw5B*poLcOdaePa}$gP1-N*w%SE(znMd;$eTD{_a2K`5!w3p4pg2c89R8`|ZlHOU+EB_{2?HNPBIL7t5-5&lM-;HZR4cHmD(Xk*?^ zd&Jqe7Opgxyl2mr2FXs&K*B9OOjB#BdRXg>3sPgB|8Ogk(G6`cRvb8g)7e9`{!9jU z_3#AcIGkwqwpjFY3fw0(RU;F5V4ysmE8EWa>cp5bP0o;f6{(kyI8#%D=03vIMQMT4 z3Bnjz23WKsbuXN5VZ5;dtM13s)p)O0KgFCtbSZV&Q_9|3fhPDZ57x38X}G6};a@BL z9cn9I(^lajboJsD;#`d zg5eEh!LpJe!XF_*TSrkVP;=*FjJkJJJm=WR;^;x~Fiujsf(Zz^sfiv0@hY_iGmzD2 zXj!YoHF4qwILHU2s((CI%d7456Lu!Iq+s#t^2MTaW64`@q=lZBD>4%HZW=t^$mbV@ zIn|F3@5g9cuB^D1CCP@3ye4CL3S>c#itWDRccSAI;f8np#yXui@Mk$Wr4#f`J z@?mrrvLLJb`ts6vIa6%(D{DNH#QLs)*L%#1+Ueuu*1ID>dn`r%8|7V>{|y85bN)Zk U?g+5h&j)C?sNK!ng&qL@0g`UfH~;_u literal 0 HcmV?d00001 diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-legacy.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-legacy.zip new file mode 100644 index 0000000000000000000000000000000000000000..b429bc3e35436fff28f2da46365ac4367ed774e7 GIT binary patch literal 1403 zcma)+YdF&j0LK5zZEl&{Fqg@3iQJ+ilFg9YRI9P5+@@jJmJP8KVI@R*oP!>8aa=+S z2SY+9=Dtl1%{Am+ZmlTd)cJm%=e+NS-}8QWzy2_2Az>tN05}M+nN_&qcii?4VE~Yj z0{{a60Q_k*0tp`$V-y%k4u~L9$TVXbCCrx)eU(Cu@b#w#1QVkO#vwEcSzK5I@CJZ* z0I*3;4l>_04}UT^P;V`r*Fh%gv}!Q!orfXdBeWLi(a-jX^f6U`+1WYPrX;Q)#$`=b()9EVfglsj5ua#{~&a*?x!hFA-$MHkU7 zw$hin8Dy7(Df$YpwYJwdq_?19ZLg~(@D`;BYBSY;uyGdATY`4`_K`bEkE1rD)(DYq zFUkeHgp;`lF3pxBLJvLCAhlJbuNZl!-W92-qTBY_O18tv#Ww30rVJ7ujvm>`j2bRq zU|2fdZdI;LD2moWuj7%YPsOTnqmG(`7T_0}l0I^ys;{Ovj;b27?D83KA$9~)6?MA9 zEPi|-7qdJc!yz??i|;}L_59>Ls=!J?5%==Ms%EG@o+C57ZJ+qQ>-sT#Q(}Mqr9oNd zr;H1iv81*as98e8g1*%Lj^k8d1FAKvbWIwZQFGWp(-Y5i@V1(Fnrsl+NVy!OC=pmP zJnpqV5=iQ@C$-(JSUyxHJgtto)ZErH+aSYH95dB8XH63XgDqWeH(JTB>(y&Cxo9Nd znHvKal8Ae(;bcEo?6fa*7n*0s$Iq_VzBJn|0yWSG#P9R#AG{7Mi9-3R?h>K?G?^?A z6=$+mvGllBTzKPaabl|eGXoprXiS23!`GD~7iCzK@3&d+t8WRFYrP(KqATQN#lGV^ zEgI>XJ4pkjGJ~+q8uQMnM)+2Y*YWf&vDm4t#mWa8ug(!tH-n(4{ozcoN>qxaL~Z^; zz&_1wc!B)k0!j)v$-}|= zI$R6zy}9^_Q{FyF)}nB%vuAyr@%IlB$`$;u!^BcdWMmQJ5)VFF6b2OhndWU+S)-#~ zF81kdNlms@ou#CI@pLHL#Dq0D&E73NiFR+(q)r4Y$T(Y<5B1sF3)-=gG5MCEqK~T* z!4~0bI&lNBN`7wph?L-qHpn`=n&4eI4;f#?THg@|^VOz= z@3W83m(^b literal 0 HcmV?d00001 diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-compact.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-compact.zip new file mode 100644 index 0000000000000000000000000000000000000000..300b4bf39f0421ea4e2f730dcc8d585c358cc42e GIT binary patch literal 1510 zcma*nYdF&j00!{CYfLmG8FHya7`bFIQz$e^NJx{*G?_7zYvkIP!?dR4&N<61EJ7Wd zyG%2kP_%?N8@W{&(Z$jEexB#N?}zu(`}OCH6cM!ogaH|VMXz`7Kx_;diUGh`MF2Po z0Dv!nfDZA)#^?v$!1+f6h2jWMLMYY;9UT@L9^vB~?vDwIL_@C=LUEF!V!#yuNCW`O z^z>`S-;MEQLxas0(uKXapu_D4(z0A22(?iHbKfz64I=xUvajq6@9Bz^dofvH4_T!f zxGd-0x32)d9f`mf;UwBPJ@_T2YuL{fkQEP z+HQn;^E9aF3lZn~BvTz^(pP-J*T8jdLGfGtIRiGXF?^062ogu z3c0VJEKpopB^v(dxGVvKIY{x{-j{6Dg#Nh4WzTDbI`577JB>pEAp-qKOqF`R)-z>e zeraY@59@sbF@WI34C2i$F*8DZF4N#q1i!}%vc(+j@3MeX>@@u~EUj?=-YA9}UjjBRRp+^#{!tnbc$l3=x*3VC zJIeKRgFA)D++5c?$~^c{Zf)cn?Ns{-tAeu%115i!8{c`k3IlJQeW-AOM(uCwB%4gw z~YW;x~R!=e6~ruX-UJM+TZBeGt}iG+d<*Uio8CF*mC z=ll5kz}1z`Z}I-CP1LuA0>*h^NW3fVbkN@2lV^QkfWuA&y2`=@)ow+ z?X8TWqev}xrAdENwLC)(^>ES8L@eLi&EbQ>hF-)cfk>;;0)4h|&u`T>`~9`6Fq0k1 zY)lj#EAtG524G-auY}ibZ2eNo5TdQUS&LhDNG0f6%b#OYtXRMB00BqIM=Aa}!j$}I zP#$j08aVk0G^&zOCemRme25-aIRG^iiYq=he0TTj){D|pyegKmDK9pW;)t+$Y4DqWf9fOyg~5*T7?^zJGtx&hiB zWCl4))Ubf(Xy^DxNfP0?pgdIbA!8anWwmog2a%QL!YmtfzVbkeO{-NKI1X~11qVBj z#BBwH>V{}tSBiMyq@4D{-G}4qxDf-y{5FEN<4-~7{ti52h0wLNC*HPf*C@7IxMtG9?Z{ z*#WX((m8>JjVZI$`fB`hH3|yg|K5u!FDzXbOPouBV_awT4l18+Il-t6i__>4_si%8 z4}S%anxYJDLN)KjMVK761SM literal 0 HcmV?d00001 diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-legacy.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-legacy.zip new file mode 100644 index 0000000000000000000000000000000000000000..916e32eb2547685b6e0825b6413fdbcb1d3213ec GIT binary patch literal 1378 zcma*nYdF&j00!{CHn%XhXhv>P$ukOB$7MLgkPHzO3z=DLW`trSw20EF$hNY%bR$uO zT*6$Al9=Z(moAtg#nx~p+0prap69&phxgO_^@sKZfgONt00iiLRvnl^|ng%E<%)Xn{HN~FAS+SLT=GKxZ;e=AJ-05o#r(N6$0*EUqQjX zKQs6JR#b?3pu!!FCou?et^I6LRy3>TPL=*=~grxgxkEW1?b%pcV_+poSqmFB^&b93wLAQ1Yw7g zN#%-2{rV}tg)~&JCNKQ#MaFSDy!bL|vKr~+i8HC2I2|;dbN117ib0nqaPa5zp$!2_ za}#zymiBZRCl_MJ`eOHPn^M2)?YvY_dq)swkK;#1+A`~wx6dj{@*@Iz1mRdUp6(tp z>bJtp8Nl6ZE}Di~Y*Hybpw(e=P*mxoGj%R}V|MuQ(EiKacY1XeL8x_420ipR>z^l0l{Ad-dt=rfOJ8odMHEB2&}N+~d1b>i0_pS6P&7cf4jX`$5o{r_b`quUp37Ol$t$)+6&A>P(qJ z!Y}o?tDU^frZRMLQ;V)_j^;CxyAt#zH$8r7dosujJagz?w%OsS{V4xrrUIA8Nt{ zVXY67?o1`ntRbf|GAj47_%Gl;v(r%^x+iPsXp|1n>b>bx%gl{(8)eQ|6R=xF+s(2yZ_hDdJfJe1E z=B8UeJq#DdLfy9m%FUs=59LJrEVxYwEWM(LvDR6^t=KgUyV$x%x?-(w!}#EdtP6jx zg~f0Tu+1bto)ZmmbWp1sEwA7G)e6aG$vyyAT`e8wWDYG%!mOB0+s77^ubG_QXK3~C zf)k^_n;67*IyTkd^0|)}x0oQhXc`FqiYPcvtmX&P)ii#mnV&$<_H8 zxf1X+&jt+FJ#qNPnvo^6Hn7cWdUj*uWHS#phA5}M!i;ntpk%)EVJ@KS9|sY17$r4LRtv0=09aHHDrn4*LB%OG4e} zLpi7s1=p_fCcYrS*C}f(SSe=QT=x{;*29n1c@FY-eKLFVu?7gXmb~Vt5QPoaDM&SW zVSuJRu{O4q9#kn~YPcR*WjiDZse;{JTMc%aSxngdHhh+9HdiP~ zsSZ=2kn;oc5<8wj(hy>;!0>dYJd6nV z)abJXUjBO|gNqURe@Vk=v?rKS^6=>Bau3(u8#zGI1B7D-%47CktMx*ed31C1+A`ts z@{Tl|xWY$LD>3eX2^`LlCWglF?dO6^CGYKSmZ3eRwmE?QTfHs#{!O~nulfH5qCKT$ Qem$hN8o0HhElC039~*>+yZ`_I literal 0 HcmV?d00001 diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.8.16-compact.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.8.16-compact.zip new file mode 100644 index 0000000000000000000000000000000000000000..9a88417ba3a962eddb6396cdbd698545008b2b95 GIT binary patch literal 1528 zcma)+YdF&j0LK49lw6v-2x*pTp<&0ct-@S#FSkrKmxyi6hO|SI$(^LwqN9<{aV^5+ zHkWL2SrJmyArf=xL5hf@^Zh)}dEXDePw$8K%g@^})0Dz-V0txuAXlPIb(VrSZB2rFJNMRQPuU;XAQ!nDe{ewdy15aG0kcfN4!~rY- z&;h_ADd`ey6Gmna2cx}4o@M82nT z>$H3yz+wQkpE@jJmv&cL+UJLZ5OM17G0Z`)5bL@AZ_s_V0lbDTR~wLcGjH%QxhK6wD=2( zkmjeKYDI!A&F3E^v1o2K&dHPz6C>%+Ak>a|&?fU>a&bde5?_m0VS!^CX%xs8{vN zl`-|6G<}xDWY<-`>QmO}tmjmjwrRn=6bae%>jBB7C_ScJ{q6+{J=^u?LsaT zupRb$)7?<|{TL4S=Fw`6Yy1cOxOe5T8bf2JV9%2?t_NzHdY);Nr>UE2<$7e+Ybf3} zi?Qi-v=mP?E_NXT6!{*16zK3orl$M#pANaHuRBG|rnBcHmm?U4*KUXB)eJxn{DxaE z7m|kP&sx{(Qt|G4ntl3j8MZv~82JLz)``fBXa^lu53j~Y&Ho&3CM=9*6!CHmN;b8} zgA<%$bK<-~DKX4Lb%_(4wY2oJ!g8m8v_WUNWxJ&jl_7-0cTk6^!njt!@s}XMWV15W zWwu+V%U3f984z%k`PRBv%JE1n+%u6hHGm|9Jgl$x`3~CGaZ1s4Zus{Zf@C5^ssTbS zQVkQ#<0CnJjonOh0D6}d)L`kC4~p8T;F+rY-8fv9G!ykucK}rsh^Xkmo%~_NqzOXnaFmmM1ueIS1{)1ALbLwwqkzC(d6yyRXRk$E|@hUx8(&< zJxNs~ST?;}Lbu-N>NF{v&XbfEdROXc^969>kL6@Wl#*NecsTq=WWNWijIsHFXkxNklGcRX!IAXQOT(kKC?4LX&~|Innellu+pC5_XHJQA1_i$Am!y zT&s%}zhZ}l#FHJ?rq5N#lt^Q?_wnliXM2%dDAE6R@s5uF4PE3H{=b=>?ZtQhT8Qk_ MbB8HAeg}Ym030d55dZ)H literal 0 HcmV?d00001 diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json new file mode 100644 index 000000000..ea4faf796 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json @@ -0,0 +1,9 @@ +{ + "A": { + "foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-legacy.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-legacy.json new file mode 100644 index 000000000..09c0a51f7 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-legacy.json @@ -0,0 +1,5 @@ +{ + "A": { + "foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json new file mode 100644 index 000000000..ea4faf796 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json @@ -0,0 +1,9 @@ +{ + "A": { + "foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-legacy.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-legacy.json new file mode 100644 index 000000000..09c0a51f7 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-legacy.json @@ -0,0 +1,5 @@ +{ + "A": { + "foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json new file mode 100644 index 000000000..ea4faf796 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json @@ -0,0 +1,9 @@ +{ + "A": { + "foo()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" + } +} \ No newline at end of file From 413a50a1d2561fbec306c1823079e6b1579b0f54 Mon Sep 17 00:00:00 2001 From: Simone Date: Mon, 15 May 2023 22:57:40 +0200 Subject: [PATCH 060/101] Fix call to a function in an inner scope --- slither/solc_parsing/yul/parse_yul.py | 7 ++++++- .../test_data/assembly-functions.sol | 1 + .../assembly-functions.sol-0.6.9-compact.zip | Bin 1537 -> 1607 bytes .../assembly-functions.sol-0.6.9-legacy.zip | Bin 1403 -> 1433 bytes .../assembly-functions.sol-0.7.6-compact.zip | Bin 1510 -> 1588 bytes .../assembly-functions.sol-0.7.6-legacy.zip | Bin 1378 -> 1414 bytes .../assembly-functions.sol-0.8.16-compact.zip | Bin 1528 -> 1605 bytes .../assembly-functions.sol-0.6.9-compact.json | 3 +++ .../assembly-functions.sol-0.7.6-compact.json | 3 +++ ...assembly-functions.sol-0.8.16-compact.json | 3 +++ 10 files changed, 16 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index 978859a53..8657947ea 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -787,6 +787,7 @@ def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression] return None +# pylint: disable=too-many-branches def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[Expression]: name = ast["name"] @@ -822,8 +823,12 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[ if isinstance(root, YulFunction): yul_block = root.root - # Iterate until we get to the YulBlock scope + # Iterate until we searched in all the scopes until the YulBlock scope while not isinstance(yul_block, YulBlock): + func = yul_block.get_yul_local_function_from_name(name) + if func: + return Identifier(func.underlying) + if isinstance(yul_block, YulFunction): yul_block = yul_block.root diff --git a/tests/e2e/solc_parsing/test_data/assembly-functions.sol b/tests/e2e/solc_parsing/test_data/assembly-functions.sol index c7b1c17d3..224e16bab 100644 --- a/tests/e2e/solc_parsing/test_data/assembly-functions.sol +++ b/tests/e2e/solc_parsing/test_data/assembly-functions.sol @@ -2,6 +2,7 @@ contract A { function foo() public { assembly { function f() { function z() { function x() { g() } x() } z() } + function w() { function a() {} function b() { a() } b() } function g() { f() } diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-compact.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.6.9-compact.zip index 10b6ee248058585c51cf6277ed5477a09f86838b..8389eb6f593dc3d0adfaf206f8ba933852ff32db 100644 GIT binary patch delta 1135 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 delta 1056 zcmV+*1mF9#4CV_PP)h>@KL7#%4gkoouU5c5_tG#0002rC001hJ@dYihNM!+kS^tQ* zz*RdDUu(x4b(wR3NuRSGk0%!$Ejg(m11qzM3$_crFw#j!?&6c(%A6D$+WUJV4UWi! z4>dGG@ex`m(Kc{LA&57Muq)TvSyVhuZ4P|)EGoSt=@RtR`=vp@IX|0970fiyqBD`t z^EeRtN~RT2rIg9O!H+cFLzo$Vcy&LGTK92|9x%j7DbiIU$V|EUrF=c5aY6azemdYr zs=IBSY#hipSvte7WxU_bb~{}g_=gf);zl^%k&0QpT;@pW3Jyt@XWDSiXx<2`yXarV zVeQKuONpo4h`iim)MblCLbY{XuDmJgS7gY%KcC`S%atlTONR6aP=aoMOm-a_IyW+# zkEBFsSaz=&PKsVG)2`^~EPyS|hfl}m77(haP}y-~^R<-Oob|j<0vdf&Zq>y0>~oLn zIU9uHjwfiaB|JDOHr_&q?Ng_ezqv8}tfwf+{gRtP$!vO7X!KY3j(R%#Qa*?J zmKD5C+gL9A>|!(pnp{*#;T88R zbno>8yBX`S=d%xtszVrKD5+@Qz#GeGcd%>@xe8?fI1npZe$`aVEufOpKEA)UCfcqf z9BF5_y4c{zdSY^9_01Vy8GXh+HIJ;$6Ffr5%s}r7*BptV1HeRo0wl0-sLU=m0ehZJ z(~}I9>Ta1!rF6IG`fKF{;_j7_jhxir5Ss(FQ4{QoK&2fKIfv25n&-ZOr|7A8Cydkf7MjuZ}Mw$>yR}KsF+DUw8YeAZ8b96V2XIMF3D*(8cK&DJ3NpmkYjfVhdh-Em|nIdGo`SD z^TFdOK8=M^y`j!fU704!rL7;#HVRYc5Nk<_21NC9s0D^rpNoZk{CrVJ%}uG)0W zhnl`~C`|tK!5F}Cy!U@1T$DJ5rA-&kY%FM0pU%P|AJY&#QnP`Se7z8KYjQL{<(@Cz z!g8Q)GO{Y@nqJLE+?5=#p(s{<6^q)O!LAp*2qZ4cWDyXGP4zoNHNCq+V133Pl3oDj zqs3^VoS^n>1%T**JZMwrEF&95w>iA6aC;%d24acB5YzYyzNmxTr!VAQE~y(LaFBa# z!DPJpZ*cJ69T`@0WwIK8kn)uL@b{~Ol*~Bed8`DECkg)Y2V77~0Rle*KL7#%4gkoo auU5c5_tG#0002rCli&qT26zPk0002MruXpx diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-legacy.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.7.6-legacy.zip index 916e32eb2547685b6e0825b6413fdbcb1d3213ec..c1285733d81f638c7c279f1709d3547f7ab2bc15 100644 GIT binary patch delta 775 zcmV+i1Ni*n3Wf_CP)h>@KL7#%4ghhsuT}?yUb@f(006lX001hJ9|bM3NE`xxoGV&c zNQzLkntiT4sxbO%@AgGWA@uTHtLyc;eoT)tJ)TLce&ui)usM!pqTpf*JtlO)j~H@y zN=92@*wtNL@3|3cD+V@Q7t3KrHYw{k!6_^C;uQ1Sxf551bj-U$&BqH>vP`_;xJx z;+a;|<#3GRc_p!2TfDy-fTOH|Fla8(O!%YGk{4R;Up(!FKW`elABo0V?<`5QRco?7 z^R9{s@npNX-NC0s^XjIs@sdBEHJgo+6{Ko19edb{b|uTfy2?1)rG6iO0x5B2%?Mu< zRp(lAe3oarftqhAD@U)Y3Cj)+Q*mApkoG)A;YFV|$Y|>2Kkh4n>Q;Q^6{A5R)$V>r z$_K706I5?_FCi==Wv}mXVjB5{Skib|Q~h~}2t!FMiB9&g3YmFb{U)Jh_OmqOqp^v0 zyx)1j^j2?f?->q@eLYWqQ*)4XGjmc71iTxf65&&Sz=MSlSU~P=dpyAFf_k!i@LPeq z5`8Kd>aW=2WzhINnlzZ^=lubVxACU92BF37&WBK+gAe}Z-@RGJS&GmHs;+0H1Nw84kbPhd}b zwOD>7nhh9k_rlHyT(%Jh$7dgjL59gdIw6ZK0WSpW_8dt*-Y||Pv+31lZN~*ZkZ(_Q z8QjQ9BW$7weo+ajxuaZyLIp9lvaJqr1B8)yVmwS-`nj9XgU!4=MXo&?GS4-FY(@>wNWjnD*XD{&)YAIc=9th{(AOiEt3x3q--O`>DyF z1F-l1vLkgjxS#D$|H5yK!ca>A0zU&k00ICG0CBdjRtJP$y3hmw0J##A4h2pI7zF?T F005F0ZcYFI delta 732 zcmV<20weu~3*rhGP)h>@KL7#%4gkoouT~Jaj(MyE008?Du^3DOe;WNZ#L;y9+no{r z;Mv1aW*EbrqrFGq34ZQ|hl@Kz8obvIecXq<*>}Ql-mzoxV!o(&mg04rHV#{Yf~+T^ z=d>;Th7-p#KZti|4m@FHUv3Y=i%6uRE1Ts+$Qk+xxlhpK7i@^r6rlib1!q4}z`pOj z{71lMbv5nJJ+=Aoe+D;FhMY!6Pz3U=%uxZRwD~CB&+zeTNWnmhfHtl;E%I;}NeTcD zykQ=w2=pj2*}6AooTiwa_rs>yrXAxNd%h+GruWMHB{5fpddc@*x2yL**#A_&v73B^4c(^Z6}PfO-L?E#?v3age`OlK$~d! zrcwX0FaIUN8u341aop0{YQbw=qyNx6lB7^e0Rle*KL7#%4gkoouT~Jaj(MyE008?D OlQIQP2J{2~0000P>ts{_ diff --git a/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.8.16-compact.zip b/tests/e2e/solc_parsing/test_data/compile/assembly-functions.sol-0.8.16-compact.zip index 9a88417ba3a962eddb6396cdbd698545008b2b95..a2b78d7b0698f3f623f5a2a37a95314adaa14197 100644 GIT binary patch delta 1124 zcmV-q1e^Q#3&ji=P)h>@KL7#%4ghhsuU3)0TTYJ!006@ou^6fWe<-GIdm<|qgPLus z)qD0-))LC{XRyf6q?%Mw;DE6v;AqFDBD`g$GJmK~n2I)dBUew?9u4ZEj7{G%%dMvC zo|A`23WLAw0RdeA6Z&w+9r1m-`FX5inpPjU`fOkXCGOWJJrLO}|2c-9CbgMBz%yQr zHonDr0KAwxvjbT)e|oHxZ4EKT@r@7f5ERW0532P3>YF##3f|8HlVGq2%zjYp7yxv? z(xU@Y1+` zly7+DlOysBqqlr%RC3e(rD}2#qZ_ayE7DyGqxW#w+f#}cB=`|jIk})i(;$25F1K)0 z6&Yr?8emN&$Gz6bZ}I5CxZkU)d@m3r=ygM7N(C5{VEeLA@W~M25=Z;kO;rO0gdI44 zDx#^~+5B0ef76BDay4+tZ_f_q!l2apS7Z(3OMEp!bKe|s|J!!ng!0phm3N?DgR+NptX zjCIi9Rm)Xk`a`D$jC3ZK50W}RJUZ2r{mKSlO*v#Z> zVu^%LDM#LfQ^(vmbK<}IQnVbS^RxGnN~N>o$aE#r61-zVd|@p|0~LJhD5RfG|DpIG z0)$ajSC{oT?Rs~ue)@0cbUW$utT#_QOX22J9*ty1!4~dhA*WcOT?I(RcFmqTHQ;kVx;2 zesF5Lb874TrHjk2AzlnAk6h~i;t=6I$GVvFFeKrYv0H9`-4yUg4Xn)9J9>n{n| zM603pkkEAnI|_e6@+Z`FB<*Y9N~Rp!JGo1uYYCKsv1IzmK_QWc05d)!Ap-p{0T8Rx zj9SRrF9sS+*$Y@oWrhjh`rJ5TiSvd*H)qcXe}X!0m%jW0Z8FzRZ`i00rNoIw;Q#rI qNZ3$I0Rle*KL7#%4ghhsuU3)0TTYJ!006@olcWVs2G<1u00012$s;ZR delta 1046 zcmV+x1nK+54EPHeP)h>@KL7#%4gkoouT~F7x9CCz001Nwu^6fWf7~3)j4zo7Y3O#k)O_+bY(3~EzSS~ zvgtt6<`p}AwuHNM2(`Wg5$!0)NnYrtpr+^&Z}t!Qd15St zG=PL^QW)dC`|auLf6uLA+9sy71^!c%?A-Z4jRRL(gl*z-pJB>EY1K{tY<#?xYk-}7 zx4(uevK@NIpwVI4tbQHU+(9$MiH?6|Avst>yAyjHIUtu;kGLHbk~@7!%uYlFhc%^9 zZUA!=%VR$hB;k)AR)76Xmy6TI0XycC>j?67gfV)Oa+|c$e=Qq0Vfn1;Z`FXg!1=a| zVpa*gT+xs*zNS~$?=vAIb&qwz5+odQvSN4a{nov0?c0u{#+NXu{2}0Ig-?E#fm#xZ zeV8J)hvB#QjEzj{tWRW&)KC!eO7Ykj)j9|F62mzZ;2@tY&l1k#y%%&*>B=L>ULR&Y zWMnFs*+!)ce@-ZVH&};n(+eeZ3u^R3%Vz9qZw?@>|%De z&AZB&JOC|~nP$2~V4Mn<~7~i|rtBB}#q$bioqh&g+!(lS|MVNHe_2=ExPs}tY z6oiAJPB%?z&oJ3F&Tw99(ELvWvZY!YT|n_z7s!p{f5(AIwC-cC!e|(J`E8*UDZney zZp$4S#wp}R!O$nc9-(wRz?+QRM6)wl0TZr!SpGk@J;geCdv1XHCuzSX9PwtimL~P? zHk00^cZ>M6ZENkh4tBy_2ZdvQ&@-6l`aI$CNd#btd2#=f^?)Z7N@$tb5dK*w88UPl zZA84!fAK)Tg2=@-tLB>s6Y5&7BiumfR5Ww9MI-L}W8N{>%oczvGrSy2Sz%yMnZ6V1 z^dam~#s^a9Z5H#?1T7&|F;2_b!;r1Rj=ou#+a)n=TstMkMuR;;7nN2wDS};G2r@r= zKLo7nDK8(FQ}LsIV#cArJ~Im*H#Sc3OlyW$ecmTM{*CqPS?)6@0N=6nddvzdezenaX;F+@69ex|kNET%|-j1rxjK^YPBF0*Q zY1_2F4hq7L+Gj_Do15}V)yMX+Tw9{)caki2|FS514lQ6$doNq5bAUjpp}sjZx~_rZ zh15DXAjp}6^GaPOgKqL1x^Nv1poj50L}&Uh5!Hn diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json index ea4faf796..a48faa23d 100644 --- a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.6.9-compact.json @@ -4,6 +4,9 @@ "foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.w()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.w.a()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n}\n", + "foo.asm_0.w.b()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" } } \ No newline at end of file diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json index ea4faf796..a48faa23d 100644 --- a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.7.6-compact.json @@ -4,6 +4,9 @@ "foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.w()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.w.a()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n}\n", + "foo.asm_0.w.b()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" } } \ No newline at end of file diff --git a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json index ea4faf796..a48faa23d 100644 --- a/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json +++ b/tests/e2e/solc_parsing/test_data/expected/assembly-functions.sol-0.8.16-compact.json @@ -4,6 +4,9 @@ "foo.asm_0.f()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.f.z()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.f.z.x()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.w()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", + "foo.asm_0.w.a()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n}\n", + "foo.asm_0.w.b()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n", "foo.asm_0.g()": "digraph{\n0[label=\"Node Type: INLINE ASM 0\n\"];\n0->1;\n1[label=\"Node Type: ENTRY_POINT 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" } } \ No newline at end of file From 649e8d5e9426ef6b67dcadab4ce6be6689d16b70 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Fri, 19 May 2023 14:43:55 +0200 Subject: [PATCH 061/101] Minor python improvements --- .../statements/incorrect_using_for.py | 305 +++++++++--------- ..._8_17_IncorrectUsingForTopLevel_sol__0.txt | 24 +- 2 files changed, 173 insertions(+), 156 deletions(-) diff --git a/slither/detectors/statements/incorrect_using_for.py b/slither/detectors/statements/incorrect_using_for.py index 4df0701a2..e2e87e7e0 100644 --- a/slither/detectors/statements/incorrect_using_for.py +++ b/slither/detectors/statements/incorrect_using_for.py @@ -1,3 +1,5 @@ +from typing import List + from slither.core.declarations import Contract, Structure, Enum from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.solidity_types import ( @@ -9,7 +11,148 @@ from slither.core.solidity_types import ( ArrayType, ) from slither.core.solidity_types.elementary_type import Uint, Int, Byte -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.utils.output import Output + + +def _is_correctly_used(type_: Type, library: Contract) -> bool: + """ + Checks if a `using library for type_` statement is used correctly (that is, does library contain any function + with type_ as the first argument). + """ + for f in library.functions: + if len(f.parameters) == 0: + continue + if f.parameters[0].type and not _implicitly_convertible_to(type_, f.parameters[0].type): + continue + return True + return False + + +def _implicitly_convertible_to(type1: Type, type2: Type) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias): + if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias): + return type1.type == type2.type + return False + + if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType): + if isinstance(type1.type, Contract) and isinstance(type2.type, Contract): + return _implicitly_convertible_to_for_contracts(type1.type, type2.type) + + if isinstance(type1.type, Structure) and isinstance(type2.type, Structure): + return type1.type.canonical_name == type2.type.canonical_name + + if isinstance(type1.type, Enum) and isinstance(type2.type, Enum): + return type1.type.canonical_name == type2.type.canonical_name + + if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType): + return _implicitly_convertible_to_for_elementary_types(type1, type2) + + if isinstance(type1, MappingType) and isinstance(type2, MappingType): + return _implicitly_convertible_to_for_mappings(type1, type2) + + if isinstance(type1, ArrayType) and isinstance(type2, ArrayType): + return _implicitly_convertible_to_for_arrays(type1, type2) + + return False + + +def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + return _implicitly_convertible_to(type1.type, type2.type) + + +def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + return type1.type_from == type2.type_from and type1.type_to == type2.type_to + + +def _implicitly_convertible_to_for_elementary_types( + type1: ElementaryType, type2: ElementaryType +) -> bool: + """ + Returns True if type1 may be implicitly converted to type2. + """ + if type1.type == "bool" and type2.type == "bool": + return True + if type1.type == "string" and type2.type == "string": + return True + if type1.type == "bytes" and type2.type == "bytes": + return True + if type1.type == "address" and type2.type == "address": + return _implicitly_convertible_to_for_addresses(type1, type2) + if type1.type in Uint and type2.type in Uint: + return _implicitly_convertible_to_for_uints(type1, type2) + if type1.type in Int and type2.type in Int: + return _implicitly_convertible_to_for_ints(type1, type2) + if ( + type1.type != "bytes" + and type2.type != "bytes" + and type1.type in Byte + and type2.type in Byte + ): + return _implicitly_convertible_to_for_bytes(type1, type2) + return False + + +def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both bytes. + """ + assert type1.type in Byte and type2.type in Byte + assert type1.size is not None + assert type2.size is not None + + return type1.size <= type2.size + + +def _implicitly_convertible_to_for_addresses(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both addresses. + """ + assert type1.type == "address" and type2.type == "address" + # payable attribute to be implemented; for now, always return True + return True + + +def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both ints. + """ + assert type1.type in Int and type2.type in Int + assert type1.size is not None + assert type2.size is not None + + return type1.size <= type2.size + + +def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool: + """ + Returns True if type1 may be implicitly converted to type2 assuming they are both uints. + """ + assert type1.type in Uint and type2.type in Uint + assert type1.size is not None + assert type2.size is not None + + return type1.size <= type2.size + + +def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool: + """ + Returns True if contract1 may be implicitly converted to contract2. + """ + return contract1 == contract2 or contract2 in contract1.inheritance class IncorrectUsingFor(AbstractDetector): @@ -48,150 +191,19 @@ class IncorrectUsingFor(AbstractDetector): "matching a type used in these statements. " ) - @staticmethod - def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool: - """ - Returns True if contract1 may be implicitly converted to contract2. - """ - return contract1 == contract2 or contract2 in contract1.inheritance - - @staticmethod - def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both uints. - """ - assert type1.type in Uint and type2.type in Uint - - return type1.size <= type2.size - - @staticmethod - def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both ints. - """ - assert type1.type in Int and type2.type in Int - - return type1.size <= type2.size - - @staticmethod - def _implicitly_convertible_to_for_addresses( - type1: ElementaryType, type2: ElementaryType - ) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both addresses. - """ - assert type1.type == "address" and type2.type == "address" - # payable attribute to be implemented; for now, always return True - return True - - @staticmethod - def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2 assuming they are both bytes. - """ - assert type1.type in Byte and type2.type in Byte - - return type1.size <= type2.size - - @staticmethod - def _implicitly_convertible_to_for_elementary_types( - type1: ElementaryType, type2: ElementaryType - ) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - if type1.type == "bool" and type2.type == "bool": - return True - if type1.type == "string" and type2.type == "string": - return True - if type1.type == "bytes" and type2.type == "bytes": - return True - if type1.type == "address" and type2.type == "address": - return IncorrectUsingFor._implicitly_convertible_to_for_addresses(type1, type2) - if type1.type in Uint and type2.type in Uint: - return IncorrectUsingFor._implicitly_convertible_to_for_uints(type1, type2) - if type1.type in Int and type2.type in Int: - return IncorrectUsingFor._implicitly_convertible_to_for_ints(type1, type2) - if ( - type1.type != "bytes" - and type2.type != "bytes" - and type1.type in Byte - and type2.type in Byte - ): - return IncorrectUsingFor._implicitly_convertible_to_for_bytes(type1, type2) - return False - - @staticmethod - def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - return type1.type_from == type2.type_from and type1.type_to == type2.type_to - - @staticmethod - def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - return IncorrectUsingFor._implicitly_convertible_to(type1.type, type2.type) - - @staticmethod - def _implicitly_convertible_to(type1: Type, type2: Type) -> bool: - """ - Returns True if type1 may be implicitly converted to type2. - """ - if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias): - if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias): - return type1.type == type2.type - return False - - if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType): - if isinstance(type1.type, Contract) and isinstance(type2.type, Contract): - return IncorrectUsingFor._implicitly_convertible_to_for_contracts( - type1.type, type2.type - ) - - if isinstance(type1.type, Structure) and isinstance(type2.type, Structure): - return type1.type.canonical_name == type2.type.canonical_name - - if isinstance(type1.type, Enum) and isinstance(type2.type, Enum): - return type1.type.canonical_name == type2.type.canonical_name - - if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType): - return IncorrectUsingFor._implicitly_convertible_to_for_elementary_types(type1, type2) - - if isinstance(type1, MappingType) and isinstance(type2, MappingType): - return IncorrectUsingFor._implicitly_convertible_to_for_mappings(type1, type2) - - if isinstance(type1, ArrayType) and isinstance(type2, ArrayType): - return IncorrectUsingFor._implicitly_convertible_to_for_arrays(type1, type2) - - return False - - @staticmethod - def _is_correctly_used(type_: Type, library: Contract) -> bool: - """ - Checks if a `using library for type_` statement is used correctly (that is, does library contain any function - with type_ as the first argument). - """ - for f in library.functions: - if len(f.parameters) == 0: - continue - if not IncorrectUsingFor._implicitly_convertible_to(type_, f.parameters[0].type): - continue - return True - return False - - def _append_result(self, results: list, uf: UsingForTopLevel, type_: Type, library: Contract): - info = ( - f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in " - f"{library}.\n" - ) + def _append_result( + self, results: List[Output], uf: UsingForTopLevel, type_: Type, library: Contract + ) -> None: + info: DETECTOR_INFO = [ + f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in ", + library, + ".\n", + ] res = self.generate_result(info) results.append(res) - def _detect(self): - results = [] + def _detect(self) -> List[Output]: + results: List[Output] = [] for uf in self.compilation_unit.using_for_top_level: # UsingForTopLevel.using_for is a dict with a single entry, which is mapped to a list of functions/libraries @@ -200,7 +212,12 @@ class IncorrectUsingFor(AbstractDetector): for lib_or_fcn in uf.using_for[type_]: # checking for using-for with functions is already performed by the compiler; we only consider libraries if isinstance(lib_or_fcn, UserDefinedType): - if not self._is_correctly_used(type_, lib_or_fcn.type): - self._append_result(results, uf, type_, lib_or_fcn.type) + lib_or_fcn_type = lib_or_fcn.type + if ( + isinstance(type_, Type) + and isinstance(lib_or_fcn_type, Contract) + and not _is_correctly_used(type_, lib_or_fcn_type) + ): + self._append_result(results, uf, type_, lib_or_fcn_type) return results diff --git a/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt index 4a85bca5f..518fba20d 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_IncorrectUsingFor_0_8_17_IncorrectUsingForTopLevel_sol__0.txt @@ -1,24 +1,24 @@ -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#84 is incorrect - no matching function for bytes17[] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#85 is incorrect - no matching function for uint256 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#90 is incorrect - no matching function for mapping(int256 => uint128) found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#86 is incorrect - no matching function for int256 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#89 is incorrect - no matching function for E2 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#93 is incorrect - no matching function for bytes[][] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#92 is incorrect - no matching function for string[][] found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#91 is incorrect - no matching function for mapping(int128 => uint256) found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#87 is incorrect - no matching function for bytes18 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#88 is incorrect - no matching function for S2 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#83 is incorrect - no matching function for C3 found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). -using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L. +using-for statement at tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#94 is incorrect - no matching function for custom_int found in L (tests/e2e/detectors/test_data/incorrect-using-for/0.8.17/IncorrectUsingForTopLevel.sol#48-64). From fda41f4fb47be2c9aaa6276c07fa67aac2878dcb Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 23 May 2023 18:11:59 -0500 Subject: [PATCH 062/101] chore: bump pip-audit to 1.08 --- .github/workflows/pip-audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip-audit.yml b/.github/workflows/pip-audit.yml index 20367787d..4fbc1a3fd 100644 --- a/.github/workflows/pip-audit.yml +++ b/.github/workflows/pip-audit.yml @@ -34,6 +34,6 @@ jobs: python -m pip install . - name: Run pip-audit - uses: pypa/gh-action-pip-audit@v1.0.7 + uses: pypa/gh-action-pip-audit@v1.0.8 with: virtual-environment: /tmp/pip-audit-env From b932371e4c4b57237a4c40c0d255f5b9e8dfb170 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 24 May 2023 12:55:10 -0500 Subject: [PATCH 063/101] Avoid IndexError in `is_function_modified` --- slither/utils/upgradeability.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 7b4e8493a..9fb55ce81 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -220,6 +220,8 @@ def is_function_modified(f1: Function, f2: Function) -> bool: visited.extend([node_f1, node_f2]) queue_f1.extend(son for son in node_f1.sons if son not in visited) queue_f2.extend(son for son in node_f2.sons if son not in visited) + if len(node_f1.irs) != len(node_f2.irs): + return True for i, ir in enumerate(node_f1.irs): if encode_ir_for_compare(ir) != encode_ir_for_compare(node_f2.irs[i]): return True From 58391ae487406a1ed1870028a5e50d0bf80af3b1 Mon Sep 17 00:00:00 2001 From: webthethird Date: Wed, 24 May 2023 12:57:12 -0500 Subject: [PATCH 064/101] Improve encoding for comparison --- slither/utils/upgradeability.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 9fb55ce81..8d18dd67c 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -275,13 +275,13 @@ def encode_ir_for_compare(ir: Operation) -> str: if isinstance(ir, Assignment): return f"({encode_var_for_compare(ir.lvalue)}):=({encode_var_for_compare(ir.rvalue)})" if isinstance(ir, Index): - return f"index({ntype(ir.index_type)})" + return f"index({ntype(ir.variable_right.type)})" if isinstance(ir, Member): return "member" # .format(ntype(ir._type)) if isinstance(ir, Length): return "length" if isinstance(ir, Binary): - return f"binary({str(ir.variable_left)}{str(ir.type)}{str(ir.variable_right)})" + return f"binary({encode_var_for_compare(ir.variable_left)}{ir.type}{encode_var_for_compare(ir.variable_right)})" if isinstance(ir, Unary): return f"unary({str(ir.type)})" if isinstance(ir, Condition): @@ -332,7 +332,7 @@ def encode_var_for_compare(var: Variable) -> str: # variables if isinstance(var, Constant): - return f"constant({ntype(var.type)})" + return f"constant({ntype(var.type)},{var.value})" if isinstance(var, SolidityVariableComposed): return f"solidity_variable_composed({var.name})" if isinstance(var, SolidityVariable): @@ -342,9 +342,16 @@ def encode_var_for_compare(var: Variable) -> str: if isinstance(var, ReferenceVariable): return f"reference({ntype(var.type)})" if isinstance(var, LocalVariable): - return f"local_solc_variable({var.location})" + return f"local_solc_variable({ntype(var.type)},{var.location})" if isinstance(var, StateVariable): - return f"state_solc_variable({ntype(var.type)})" + if not (var.is_constant or var.is_immutable): + try: + slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var) + except KeyError: + slot = var.name + else: + slot = var.name + return f"state_solc_variable({ntype(var.type)},{slot})" if isinstance(var, LocalVariableInitFromTuple): return "local_variable_init_tuple" if isinstance(var, TupleVariable): From e85572824d593540e9dc6c8b40b1898538b003e6 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Thu, 25 May 2023 12:07:31 +0200 Subject: [PATCH 065/101] - Move LoC features into utils.loc as they are now used by two printers - Remove the usage of dict and create LoC/LoCInfo dataclass to reduce complexity - Add to_pretty_table in LoC to reduce complexity - Use proper type (remove PEP 585 for python 3.8 support) --- slither/printers/all_printers.py | 2 +- slither/printers/summary/human_summary.py | 26 +++--- slither/printers/summary/loc.py | 106 ++-------------------- slither/utils/loc.py | 105 +++++++++++++++++++++ slither/utils/myprettytable.py | 39 -------- 5 files changed, 125 insertions(+), 153 deletions(-) create mode 100644 slither/utils/loc.py diff --git a/slither/printers/all_printers.py b/slither/printers/all_printers.py index 3dd64da3e..5555fe708 100644 --- a/slither/printers/all_printers.py +++ b/slither/printers/all_printers.py @@ -1,7 +1,7 @@ # pylint: disable=unused-import,relative-beyond-top-level from .summary.function import FunctionSummary from .summary.contract import ContractSummary -from .summary.loc import Loc +from .summary.loc import LocPrinter from .inheritance.inheritance import PrinterInheritance from .inheritance.inheritance_graph import PrinterInheritanceGraph from .call.call_graph import PrinterCallGraph diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py index 157b8228a..314335ebf 100644 --- a/slither/printers/summary/human_summary.py +++ b/slither/printers/summary/human_summary.py @@ -163,7 +163,7 @@ class PrinterHumanSummary(AbstractPrinter): def _number_functions(contract): return len(contract.functions) - def _get_number_of_assembly_lines(self): + def _get_number_of_assembly_lines(self) -> int: total_asm_lines = 0 for contract in self.contracts: for function in contract.functions_declared: @@ -179,9 +179,7 @@ class PrinterHumanSummary(AbstractPrinter): return "Compilation non standard\n" return f"Compiled with {str(self.slither.crytic_compile.type)}\n" - def _number_contracts(self): - if self.slither.crytic_compile is None: - return len(self.slither.contracts), 0, 0 + def _number_contracts(self) -> Tuple[int, int, int]: contracts = self.slither.contracts deps = [c for c in contracts if c.is_from_dependency()] tests = [c for c in contracts if c.is_test] @@ -267,7 +265,7 @@ class PrinterHumanSummary(AbstractPrinter): "Proxy": contract.is_upgradeable_proxy, } - def _get_contracts(self, txt): + def _get_contracts(self, txt: str) -> str: ( number_contracts, number_contracts_deps, @@ -280,18 +278,18 @@ class PrinterHumanSummary(AbstractPrinter): txt += f"Number of contracts in tests : {number_contracts_tests}\n" return txt - def _get_number_lines(self, txt, results): - lines_dict = compute_loc_metrics(self.slither) + def _get_number_lines(self, txt: str, results: Dict) -> Tuple[str, Dict]: + loc = compute_loc_metrics(self.slither) txt += "Source lines of code (SLOC) in source files: " - txt += f"{lines_dict['src']['sloc']}\n" - if lines_dict["dep"]["sloc"] > 0: + txt += f"{loc.src.sloc}\n" + if loc.dep.sloc > 0: txt += "Source lines of code (SLOC) in dependencies: " - txt += f"{lines_dict['dep']['sloc']}\n" - if lines_dict["test"]["sloc"] > 0: + txt += f"{loc.dep.sloc}\n" + if loc.test.sloc > 0: txt += "Source lines of code (SLOC) in tests : " - txt += f"{lines_dict['test']['sloc']}\n" - results["number_lines"] = lines_dict["src"]["sloc"] - results["number_lines__dependencies"] = lines_dict["dep"]["sloc"] + txt += f"{loc.test.sloc}\n" + results["number_lines"] = loc.src.sloc + results["number_lines__dependencies"] = loc.dep.sloc total_asm_lines = self._get_number_of_assembly_lines() txt += f"Number of assembly lines: {total_asm_lines}\n" results["number_lines_assembly"] = total_asm_lines diff --git a/slither/printers/summary/loc.py b/slither/printers/summary/loc.py index 4ed9aa6ab..35bb20fc4 100644 --- a/slither/printers/summary/loc.py +++ b/slither/printers/summary/loc.py @@ -9,102 +9,13 @@ dep: dependency files test: test files """ -from pathlib import Path -from slither.printers.abstract_printer import AbstractPrinter -from slither.utils.myprettytable import transpose, make_pretty_table -from slither.utils.tests_pattern import is_test_file - - -def count_lines(contract_lines: list) -> tuple: - """Function to count and classify the lines of code in a contract. - Args: - contract_lines: list(str) representing the lines of a contract. - Returns: - tuple(int, int, int) representing (cloc, sloc, loc) - """ - multiline_comment = False - cloc = 0 - sloc = 0 - loc = 0 - - for line in contract_lines: - loc += 1 - stripped_line = line.strip() - if not multiline_comment: - if stripped_line.startswith("//"): - cloc += 1 - elif "/*" in stripped_line: - # Account for case where /* is followed by */ on the same line. - # If it is, then multiline_comment does not need to be set to True - start_idx = stripped_line.find("/*") - end_idx = stripped_line.find("*/", start_idx + 2) - if end_idx == -1: - multiline_comment = True - cloc += 1 - elif stripped_line: - sloc += 1 - else: - cloc += 1 - if "*/" in stripped_line: - multiline_comment = False - - return cloc, sloc, loc - -def _update_lines_dict(file_type: str, lines: list, lines_dict: dict) -> dict: - """An internal function used to update (mutate in place) the lines_dict. - Args: - file_type: str indicating "src" (source files), "dep" (dependency files), or "test" tests. - lines: list(str) representing the lines of a contract. - lines_dict: dict to be updated with this shape: - { - "src" : {"loc": 30, "sloc": 20, "cloc": 5}, # code in source files - "dep" : {"loc": 50, "sloc": 30, "cloc": 10}, # code in dependencies - "test": {"loc": 80, "sloc": 60, "cloc": 10}, # code in tests - } - Returns: - an updated lines_dict - """ - cloc, sloc, loc = count_lines(lines) - lines_dict[file_type]["loc"] += loc - lines_dict[file_type]["cloc"] += cloc - lines_dict[file_type]["sloc"] += sloc - return lines_dict - - -def compute_loc_metrics(slither) -> dict: - """Used to compute the lines of code metrics for a Slither object. - Args: - slither: A Slither object - Returns: - A new dict with the following shape: - { - "src" : {"loc": 30, "sloc": 20, "cloc": 5}, # code in source files - "dep" : {"loc": 50, "sloc": 30, "cloc": 10}, # code in dependencies - "test": {"loc": 80, "sloc": 60, "cloc": 10}, # code in tests - } - """ - - lines_dict = { - "src": {"loc": 0, "sloc": 0, "cloc": 0}, - "dep": {"loc": 0, "sloc": 0, "cloc": 0}, - "test": {"loc": 0, "sloc": 0, "cloc": 0}, - } - - if not slither.source_code: - return lines_dict - - for filename, source_code in slither.source_code.items(): - current_lines = source_code.splitlines() - is_dep = False - if slither.crytic_compile: - is_dep = slither.crytic_compile.is_dependency(filename) - file_type = "dep" if is_dep else "test" if is_test_file(Path(filename)) else "src" - lines_dict = _update_lines_dict(file_type, current_lines, lines_dict) - return lines_dict +from slither.printers.abstract_printer import AbstractPrinter +from slither.utils.loc import compute_loc_metrics +from slither.utils.output import Output -class Loc(AbstractPrinter): +class LocPrinter(AbstractPrinter): ARGUMENT = "loc" HELP = """Count the total number lines of code (LOC), source lines of code (SLOC), \ and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), \ @@ -112,14 +23,11 @@ class Loc(AbstractPrinter): WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#loc" - def output(self, _filename): + def output(self, _filename: str) -> Output: # compute loc metrics - lines_dict = compute_loc_metrics(self.slither) + loc = compute_loc_metrics(self.slither) - # prepare the table - headers = [""] + list(lines_dict.keys()) - report_dict = transpose(lines_dict) - table = make_pretty_table(headers, report_dict) + table = loc.to_pretty_table() txt = "Lines of Code \n" + str(table) self.info(txt) res = self.generate_output(txt) diff --git a/slither/utils/loc.py b/slither/utils/loc.py new file mode 100644 index 000000000..0e51dfa46 --- /dev/null +++ b/slither/utils/loc.py @@ -0,0 +1,105 @@ +from dataclasses import dataclass +from pathlib import Path +from typing import List, Tuple + +from slither import Slither +from slither.utils.myprettytable import MyPrettyTable +from slither.utils.tests_pattern import is_test_file + + +@dataclass +class LoCInfo: + loc: int = 0 + sloc: int = 0 + cloc: int = 0 + + def total(self) -> int: + return self.loc + self.sloc + self.cloc + + +@dataclass +class LoC: + src: LoCInfo = LoCInfo() + dep: LoCInfo = LoCInfo() + test: LoCInfo = LoCInfo() + + def to_pretty_table(self) -> MyPrettyTable: + table = MyPrettyTable(["", "src", "dep", "test"]) + + table.add_row(["loc", str(self.src.loc), str(self.dep.loc), str(self.test.loc)]) + table.add_row(["sloc", str(self.src.sloc), str(self.dep.sloc), str(self.test.sloc)]) + table.add_row(["cloc", str(self.src.cloc), str(self.dep.cloc), str(self.test.cloc)]) + table.add_row( + ["Total", str(self.src.total()), str(self.dep.total()), str(self.test.total())] + ) + return table + + +def count_lines(contract_lines: List[str]) -> Tuple[int, int, int]: + """Function to count and classify the lines of code in a contract. + Args: + contract_lines: list(str) representing the lines of a contract. + Returns: + tuple(int, int, int) representing (cloc, sloc, loc) + """ + multiline_comment = False + cloc = 0 + sloc = 0 + loc = 0 + + for line in contract_lines: + loc += 1 + stripped_line = line.strip() + if not multiline_comment: + if stripped_line.startswith("//"): + cloc += 1 + elif "/*" in stripped_line: + # Account for case where /* is followed by */ on the same line. + # If it is, then multiline_comment does not need to be set to True + start_idx = stripped_line.find("/*") + end_idx = stripped_line.find("*/", start_idx + 2) + if end_idx == -1: + multiline_comment = True + cloc += 1 + elif stripped_line: + sloc += 1 + else: + cloc += 1 + if "*/" in stripped_line: + multiline_comment = False + + return cloc, sloc, loc + + +def _update_lines(loc_info: LoCInfo, lines: list) -> None: + """An internal function used to update (mutate in place) the loc_info. + + Args: + loc_info: LoCInfo to be updated + lines: list(str) representing the lines of a contract. + """ + cloc, sloc, loc = count_lines(lines) + loc_info.loc += loc + loc_info.cloc += cloc + loc_info.sloc += sloc + + +def compute_loc_metrics(slither: Slither) -> LoC: + """Used to compute the lines of code metrics for a Slither object. + + Args: + slither: A Slither object + Returns: + A LoC object + """ + + loc = LoC() + + for filename, source_code in slither.source_code.items(): + current_lines = source_code.splitlines() + is_dep = False + if slither.crytic_compile: + is_dep = slither.crytic_compile.is_dependency(filename) + loc_type = loc.dep if is_dep else loc.test if is_test_file(Path(filename)) else loc.src + _update_lines(loc_type, current_lines) + return loc diff --git a/slither/utils/myprettytable.py b/slither/utils/myprettytable.py index 57e130884..2f2be7e72 100644 --- a/slither/utils/myprettytable.py +++ b/slither/utils/myprettytable.py @@ -22,42 +22,3 @@ class MyPrettyTable: def __str__(self) -> str: return str(self.to_pretty_table()) - - -# **Dict to MyPrettyTable utility functions** - - -# Converts a dict to a MyPrettyTable. Dict keys are the row headers. -# @param headers str[] of column names -# @param body dict of row headers with a dict of the values -# @param totals bool optional add Totals row -def make_pretty_table(headers: list, body: dict, totals: bool = False) -> MyPrettyTable: - table = MyPrettyTable(headers) - for row in body: - table_row = [row] + [body[row][key] for key in headers[1:]] - table.add_row(table_row) - if totals: - table.add_row(["Total"] + [sum([body[row][key] for row in body]) for key in headers[1:]]) - return table - - -# takes a dict of dicts and returns a dict of dicts with the keys transposed -# example: -# in: -# { -# "dep": {"loc": 0, "sloc": 0, "cloc": 0}, -# "test": {"loc": 0, "sloc": 0, "cloc": 0}, -# "src": {"loc": 0, "sloc": 0, "cloc": 0}, -# } -# out: -# { -# 'loc': {'dep': 0, 'test': 0, 'src': 0}, -# 'sloc': {'dep': 0, 'test': 0, 'src': 0}, -# 'cloc': {'dep': 0, 'test': 0, 'src': 0}, -# } -def transpose(table): - any_key = list(table.keys())[0] - return { - inner_key: {outer_key: table[outer_key][inner_key] for outer_key in table} - for inner_key in table[any_key] - } From 00461aad9a6c9205cbf826aa83f6c258609ade91 Mon Sep 17 00:00:00 2001 From: William E Bodell III Date: Fri, 2 Jun 2023 08:39:42 -0500 Subject: [PATCH 066/101] slither-read-storage native POA support (#1843) * Native support for POA networks in read_storage * Set `srs.rpc` before `srs.block` * Type hint * New RpcInfo class w/ RpcInfo.web3 and RpcInfo.block In SlitherReadStorage, self.rpc_info: Optional[RpcInfo] replaces self.rpc, self._block, self._web3 * Black * Update test_read_storage.py * Add import in __init__.py * Avoid instantiating SRS twice * Add comment about `get_block` for POA networks * Pylint * Black * Allow other valid block string arguments ["latest", "earliest", "pending", "safe", "finalized"] * `args.block` can be in ["latest", "earliest", "pending", "safe", "finalized"] * Use BlockTag enum class for valid `str` arguments * Tweak `RpcInfo.__init__()` signature * get rid of `or "latest"` * Import BlockTag * Use `web3.types.BlockIdentifier` * Revert BlockTag enum * Pylint and black * Replace missing newline * Update slither/tools/read_storage/__main__.py Better, cleaner python Co-authored-by: alpharush <0xalpharush@protonmail.com> * Drop try/except around args.block parsing allow ValueError if user provides invalid block arg * Remove unused import --------- Co-authored-by: alpharush <0xalpharush@protonmail.com> --- slither/tools/read_storage/__init__.py | 2 +- slither/tools/read_storage/__main__.py | 29 +++++----- slither/tools/read_storage/read_storage.py | 57 +++++++++++++------ tests/tools/read-storage/test_read_storage.py | 6 +- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/slither/tools/read_storage/__init__.py b/slither/tools/read_storage/__init__.py index dbc1e5bc0..df9b8280d 100644 --- a/slither/tools/read_storage/__init__.py +++ b/slither/tools/read_storage/__init__.py @@ -1 +1 @@ -from .read_storage import SlitherReadStorage +from .read_storage import SlitherReadStorage, RpcInfo diff --git a/slither/tools/read_storage/__main__.py b/slither/tools/read_storage/__main__.py index 1a8901321..f6635ab4b 100644 --- a/slither/tools/read_storage/__main__.py +++ b/slither/tools/read_storage/__main__.py @@ -7,7 +7,7 @@ import argparse from crytic_compile import cryticparser from slither import Slither -from slither.tools.read_storage.read_storage import SlitherReadStorage +from slither.tools.read_storage.read_storage import SlitherReadStorage, RpcInfo def parse_args() -> argparse.Namespace: @@ -126,22 +126,19 @@ def main() -> None: else: contracts = slither.contracts - srs = SlitherReadStorage(contracts, args.max_depth) - - try: - srs.block = int(args.block) - except ValueError: - srs.block = str(args.block or "latest") - + rpc_info = None if args.rpc_url: - # Remove target prefix e.g. rinkeby:0x0 -> 0x0. - address = target[target.find(":") + 1 :] - # Default to implementation address unless a storage address is given. - if not args.storage_address: - args.storage_address = address - srs.storage_address = args.storage_address - - srs.rpc = args.rpc_url + valid = ["latest", "earliest", "pending", "safe", "finalized"] + block = args.block if args.block in valid else int(args.block) + rpc_info = RpcInfo(args.rpc_url, block) + + srs = SlitherReadStorage(contracts, args.max_depth, rpc_info) + # Remove target prefix e.g. rinkeby:0x0 -> 0x0. + address = target[target.find(":") + 1 :] + # Default to implementation address unless a storage address is given. + if not args.storage_address: + args.storage_address = address + srs.storage_address = args.storage_address if args.variable_name: # Use a lambda func to only return variables that have same name as target. diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 2947081da..72331f66a 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -6,8 +6,11 @@ import dataclasses from eth_abi import decode, encode from eth_typing.evm import ChecksumAddress -from eth_utils import keccak +from eth_utils import keccak, to_checksum_address from web3 import Web3 +from web3.types import BlockIdentifier +from web3.exceptions import ExtraDataLengthError +from web3.middleware import geth_poa_middleware from slither.core.declarations import Contract, Structure from slither.core.solidity_types import ArrayType, ElementaryType, MappingType, UserDefinedType @@ -42,18 +45,43 @@ class SlitherReadStorageException(Exception): pass +class RpcInfo: + def __init__(self, rpc_url: str, block: BlockIdentifier = "latest") -> None: + assert isinstance(block, int) or block in [ + "latest", + "earliest", + "pending", + "safe", + "finalized", + ] + self.rpc: str = rpc_url + self._web3: Web3 = Web3(Web3.HTTPProvider(self.rpc)) + """If the RPC is for a POA network, the first call to get_block fails, so we inject geth_poa_middleware""" + try: + self._block: int = self.web3.eth.get_block(block)["number"] + except ExtraDataLengthError: + self._web3.middleware_onion.inject(geth_poa_middleware, layer=0) + self._block: int = self.web3.eth.get_block(block)["number"] + + @property + def web3(self) -> Web3: + return self._web3 + + @property + def block(self) -> int: + return self._block + + # pylint: disable=too-many-instance-attributes class SlitherReadStorage: - def __init__(self, contracts: List[Contract], max_depth: int) -> None: + def __init__(self, contracts: List[Contract], max_depth: int, rpc_info: RpcInfo = None) -> None: self._checksum_address: Optional[ChecksumAddress] = None self._contracts: List[Contract] = contracts self._log: str = "" self._max_depth: int = max_depth self._slot_info: Dict[str, SlotInfo] = {} self._target_variables: List[Tuple[Contract, StateVariable]] = [] - self._web3: Optional[Web3] = None - self.block: Union[str, int] = "latest" - self.rpc: Optional[str] = None + self.rpc_info: Optional[RpcInfo] = rpc_info self.storage_address: Optional[str] = None self.table: Optional[MyPrettyTable] = None @@ -73,18 +101,12 @@ class SlitherReadStorage: def log(self, log: str) -> None: self._log = log - @property - def web3(self) -> Web3: - if not self._web3: - self._web3 = Web3(Web3.HTTPProvider(self.rpc)) - return self._web3 - @property def checksum_address(self) -> ChecksumAddress: if not self.storage_address: raise ValueError if not self._checksum_address: - self._checksum_address = self.web3.to_checksum_address(self.storage_address) + self._checksum_address = to_checksum_address(self.storage_address) return self._checksum_address @property @@ -223,11 +245,12 @@ class SlitherReadStorage: """Fetches the slot value of `SlotInfo` object :param slot_info: """ + assert self.rpc_info is not None hex_bytes = get_storage_data( - self.web3, + self.rpc_info.web3, self.checksum_address, int.to_bytes(slot_info.slot, 32, byteorder="big"), - self.block, + self.rpc_info.block, ) slot_info.value = self.convert_value_to_type( hex_bytes, slot_info.size, slot_info.offset, slot_info.type_string @@ -600,15 +623,15 @@ class SlitherReadStorage: (int): The length of the array. """ val = 0 - if self.rpc: + if self.rpc_info: # The length of dynamic arrays is stored at the starting slot. # Convert from hexadecimal to decimal. val = int( get_storage_data( - self.web3, + self.rpc_info.web3, self.checksum_address, int.to_bytes(slot, 32, byteorder="big"), - self.block, + self.rpc_info.block, ).hex(), 16, ) diff --git a/tests/tools/read-storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py index 6d2ab007d..ea04a91fe 100644 --- a/tests/tools/read-storage/test_read_storage.py +++ b/tests/tools/read-storage/test_read_storage.py @@ -12,7 +12,7 @@ from web3 import Web3 from web3.contract import Contract from slither import Slither -from slither.tools.read_storage import SlitherReadStorage +from slither.tools.read_storage import SlitherReadStorage, RpcInfo TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" @@ -105,8 +105,8 @@ def test_read_storage(web3, ganache, solc_binary_path) -> None: sl = Slither(Path(TEST_DATA_DIR, "storage_layout-0.8.10.sol").as_posix(), solc=solc_path) contracts = sl.contracts - srs = SlitherReadStorage(contracts, 100) - srs.rpc = ganache.provider + rpc_info: RpcInfo = RpcInfo(ganache.provider) + srs = SlitherReadStorage(contracts, 100, rpc_info) srs.storage_address = address srs.get_all_storage_variables() srs.get_storage_layout() From a82683139d46fca135299bc7c751aa48ea2c58b0 Mon Sep 17 00:00:00 2001 From: 0xGusMcCrae <0xGusMcCrae@protonmail.com> Date: Fri, 2 Jun 2023 16:12:24 -0400 Subject: [PATCH 067/101] initial optimization --- slither/detectors/variables/similar_variables.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/slither/detectors/variables/similar_variables.py b/slither/detectors/variables/similar_variables.py index 465e1ce01..5db955241 100644 --- a/slither/detectors/variables/similar_variables.py +++ b/slither/detectors/variables/similar_variables.py @@ -47,9 +47,7 @@ class SimilarVarsDetection(AbstractDetector): Returns: bool: true if names are similar """ - if len(seq1) != len(seq2): - return False - val = difflib.SequenceMatcher(a=seq1.lower(), b=seq2.lower()).ratio() + val = difflib.SequenceMatcher(a=seq1, b=seq2).ratio() ret = val > 0.90 return ret @@ -69,11 +67,15 @@ class SimilarVarsDetection(AbstractDetector): ret = [] for v1 in all_var: + _len_v1 = len(v1.name) for v2 in all_var: - if v1.name.lower() != v2.name.lower(): - if SimilarVarsDetection.similar(v1.name, v2.name): - if (v2, v1) not in ret: - ret.append((v1, v2)) + if _len_v1 != len(v2.name): + continue + _v1_name_lower = v1.name.lower() + _v2_name_lower = v2.name.lower() + if _v1_name_lower != _v2_name_lower: + if SimilarVarsDetection.similar(_v1_name_lower, _v2_name_lower): + ret.append((v1, v2)) return set(ret) From 1b4c0b913727ff6306db9e6f7bb9a4cf0ff900c3 Mon Sep 17 00:00:00 2001 From: 0xGusMcCrae <0xGusMcCrae@protonmail.com> Date: Fri, 2 Jun 2023 18:07:45 -0400 Subject: [PATCH 068/101] reduced num iterations in inner loop --- slither/detectors/variables/similar_variables.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/slither/detectors/variables/similar_variables.py b/slither/detectors/variables/similar_variables.py index 5db955241..8bd5b93d7 100644 --- a/slither/detectors/variables/similar_variables.py +++ b/slither/detectors/variables/similar_variables.py @@ -63,12 +63,14 @@ class SimilarVarsDetection(AbstractDetector): contract_var = contract.variables - all_var = set(all_var + contract_var) + all_var = list(set(all_var + contract_var)) ret = [] - for v1 in all_var: + for i in range(len(all_var)): + v1 = all_var[i] _len_v1 = len(v1.name) - for v2 in all_var: + for j in range(i,len(all_var)): + v2 = all_var[j] if _len_v1 != len(v2.name): continue _v1_name_lower = v1.name.lower() From 15e8ce9e330df30090a5ce82c1fbef0491ee6f1d Mon Sep 17 00:00:00 2001 From: 0xGusMcCrae <0xGusMcCrae@protonmail.com> Date: Sat, 3 Jun 2023 07:15:57 -0400 Subject: [PATCH 069/101] remove changes to how similar() works --- .../detectors/variables/similar_variables.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/slither/detectors/variables/similar_variables.py b/slither/detectors/variables/similar_variables.py index 8bd5b93d7..7cb10cbd5 100644 --- a/slither/detectors/variables/similar_variables.py +++ b/slither/detectors/variables/similar_variables.py @@ -47,7 +47,9 @@ class SimilarVarsDetection(AbstractDetector): Returns: bool: true if names are similar """ - val = difflib.SequenceMatcher(a=seq1, b=seq2).ratio() + if len(seq1) != len(seq2): + return False + val = difflib.SequenceMatcher(a=seq1.lower(), b=seq2.lower()).ratio() ret = val > 0.90 return ret @@ -68,16 +70,13 @@ class SimilarVarsDetection(AbstractDetector): ret = [] for i in range(len(all_var)): v1 = all_var[i] - _len_v1 = len(v1.name) - for j in range(i,len(all_var)): + _v1_name_lower = v1.name.lower() + for j in range(i,len(all_var)): v2 = all_var[j] - if _len_v1 != len(v2.name): - continue - _v1_name_lower = v1.name.lower() - _v2_name_lower = v2.name.lower() - if _v1_name_lower != _v2_name_lower: - if SimilarVarsDetection.similar(_v1_name_lower, _v2_name_lower): - ret.append((v1, v2)) + if _v1_name_lower != v2.name.lower(): + if SimilarVarsDetection.similar(v1.name, v2.name): + if (v2, v1) not in ret: + ret.append((v1, v2)) return set(ret) From 173698d8e0b598ad2a79e5eab20db48bf601d898 Mon Sep 17 00:00:00 2001 From: 0xGusMcCrae <0xGusMcCrae@protonmail.com> Date: Sat, 3 Jun 2023 11:55:54 -0400 Subject: [PATCH 070/101] linting --- slither/detectors/variables/similar_variables.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slither/detectors/variables/similar_variables.py b/slither/detectors/variables/similar_variables.py index 7cb10cbd5..9f8eaaa2d 100644 --- a/slither/detectors/variables/similar_variables.py +++ b/slither/detectors/variables/similar_variables.py @@ -68,10 +68,11 @@ class SimilarVarsDetection(AbstractDetector): all_var = list(set(all_var + contract_var)) ret = [] + # pylint: disable=consider-using-enumerate for i in range(len(all_var)): v1 = all_var[i] _v1_name_lower = v1.name.lower() - for j in range(i,len(all_var)): + for j in range(i, len(all_var)): v2 = all_var[j] if _v1_name_lower != v2.name.lower(): if SimilarVarsDetection.similar(v1.name, v2.name): From 5f9abc2e89e8d49eb20a1d4b882ec98721b5381f Mon Sep 17 00:00:00 2001 From: A23187 <41769181+A-23187@users.noreply.github.com> Date: Sun, 4 Jun 2023 02:14:59 +0800 Subject: [PATCH 071/101] fix evm printer for solc > 0.6.0 (#1567) --- slither/analyses/evm/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/analyses/evm/convert.py b/slither/analyses/evm/convert.py index bff308cbc..5e8c4a128 100644 --- a/slither/analyses/evm/convert.py +++ b/slither/analyses/evm/convert.py @@ -186,7 +186,7 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither if mapping_item[i] == "": mapping_item[i] = int(prev_mapping[i]) - offset, _length, file_id, _ = mapping_item + offset, _length, file_id, *_ = mapping_item prev_mapping = mapping_item if file_id == "-1": From 0c27b000aa6d1ffd88439078af4086ee0cd12542 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 5 Jun 2023 09:22:27 -0500 Subject: [PATCH 072/101] add support for prevrando (solc 0.8.18) --- CONTRIBUTING.md | 2 +- slither/core/declarations/solidity_variables.py | 4 +++- slither/solc_parsing/yul/evm_functions.py | 3 +++ tests/e2e/solc_parsing/test_ast_parsing.py | 1 + ...lobal_variables-0.8.18.sol-0.8.18-compact.zip | Bin 0 -> 2023 bytes ...obal_variables-0.8.18.sol-0.8.18-compact.json | 6 ++++++ .../test_data/global_variables-0.8.18.sol | 11 +++++++++++ 7 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/e2e/solc_parsing/test_data/compile/global_variables-0.8.18.sol-0.8.18-compact.zip create mode 100644 tests/e2e/solc_parsing/test_data/expected/global_variables-0.8.18.sol-0.8.18-compact.json create mode 100644 tests/e2e/solc_parsing/test_data/global_variables-0.8.18.sol diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d1a9497f..c00fda8aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,7 +106,7 @@ For each new detector, at least one regression tests must be present. > - To run tests for a specific test case, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument). > - To run tests for a specific version, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k 0.8.12`. > - To run tests for a specific compiler json format, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k legacy` (can be legacy or compact). -> - The IDs of tests can be inspected using ``pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`. +> - The IDs of tests can be inspected using `pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`. ### Synchronization with crytic-compile diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 552cf9e7f..f6a0f0839 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -21,10 +21,11 @@ SOLIDITY_VARIABLES_COMPOSED = { "block.basefee": "uint", "block.coinbase": "address", "block.difficulty": "uint256", + "block.prevrandao": "uint256", "block.gaslimit": "uint256", "block.number": "uint256", "block.timestamp": "uint256", - "block.blockhash": "uint256", # alias for blockhash. It's a call + "block.blockhash": "bytes32", # alias for blockhash. It's a call "block.chainid": "uint256", "msg.data": "bytes", "msg.gas": "uint256", @@ -60,6 +61,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = { "log2(bytes32,bytes32,bytes32)": [], "log3(bytes32,bytes32,bytes32,bytes32)": [], "blockhash(uint256)": ["bytes32"], + "prevrandao()": ["uint256"], # the following need a special handling # as they are recognized as a SolidityVariableComposed # and converted to a SolidityFunction by SlithIR diff --git a/slither/solc_parsing/yul/evm_functions.py b/slither/solc_parsing/yul/evm_functions.py index dfb52a244..28ea70e93 100644 --- a/slither/solc_parsing/yul/evm_functions.py +++ b/slither/solc_parsing/yul/evm_functions.py @@ -51,6 +51,7 @@ evm_opcodes = [ "TIMESTAMP", "NUMBER", "DIFFICULTY", + "PREVRANDAO", "GASLIMIT", "CHAINID", "SELFBALANCE", @@ -168,6 +169,7 @@ builtins = [ ) ] + yul_funcs +# "identifier": [input_count, output_count] function_args = { "byte": [2, 1], "addmod": [3, 1], @@ -221,6 +223,7 @@ function_args = { "timestamp": [0, 1], "number": [0, 1], "difficulty": [0, 1], + "prevrandao": [0, 1], "gaslimit": [0, 1], } diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py index a561343de..316f2cc38 100644 --- a/tests/e2e/solc_parsing/test_ast_parsing.py +++ b/tests/e2e/solc_parsing/test_ast_parsing.py @@ -308,6 +308,7 @@ ALL_TESTS = [ Test("units_and_global_variables-0.8.0.sol", VERSIONS_08), Test("units_and_global_variables-0.8.4.sol", make_version(8, 4, 6)), Test("units_and_global_variables-0.8.7.sol", make_version(8, 7, 9)), + Test("global_variables-0.8.18.sol", make_version(8, 18, 18)), Test( "push-all.sol", ALL_VERSIONS, diff --git a/tests/e2e/solc_parsing/test_data/compile/global_variables-0.8.18.sol-0.8.18-compact.zip b/tests/e2e/solc_parsing/test_data/compile/global_variables-0.8.18.sol-0.8.18-compact.zip new file mode 100644 index 0000000000000000000000000000000000000000..04dde0da9c01b3b5d61bed184e30c285e572dcdd GIT binary patch literal 2023 zcmb8wX&}=N0|)TmHb-TKa>TIY$dxk;WgCAL!rVfHEk_t8k!0nXE1L)*=fs~mQYngM z2uUqD|BhUV_qT3X>BoU*#)5@iApwf*vWXe?QYMHI;wy3OjdMbYv>Z>Dkr+TYY)y<) zAk3sKpyNePyfpvVb}eGHa>5v`q12ajpHwS;^jMNqUftu7ddNoas;#2#7Y(5^jMTTxyUTEcEN+Es#yzL4wJppOt-=qZ$3zdOOa`L6Yc5*c1?V* zLBc|XbaRS3Xirw9NbF2ouyokFw5pW+nkSU?aR(~Acv3|g2=6m+!q6b3i1?a8tV=RZ zE%erv%eYa&!e;EeY5vKwQ~{mdLDXL82V2%S+4MGeVOA!k{sm~+E-pLDjAQ+9-SG(d zK(^|I>U}%=_#}V(cVk}*&CAz}dfy%Hoj%pV8877Y!7SG&G!oiv*Dc-3@nSzR@*9=k zHhpWLFj(;oD{gu_Hj?x!x5 zuURqDb6Sn)nnoaD?l;a|XgcP`*Bon;?OC*I7_W9PX(Sd?jS@Rhf4HUZgs7K^hhFq>7Rqk`rpOB*nw!pU zMCrKJdJ2>ZyDwl=DqqG%V0wI5;i6$`L#9)19B}dVXXILroN0J^YFU)e3x?0v2c0^W zxgdyx*4R>7#7Xta#b0c?NFZ}q5@W3j?Kt^MXnlaLmiF^5_0ED!_w!6f{(jx_gRHgu zY`s07izK%en`PG0;0VflZ~3i%fh|~SYJ`3$-GyNo>y)~g-5S788>8pwGuB`p1_8-4 z@7~-xlCZ*c!P+_FN=?=5ha;f0GZkTWqkG8ZVqEQkJni531=1m7h)_{`3w3n#q5C(d z;v@qDL~U$A{ozgQ z`(*~NjvVYdXf3xPn>JezeM`@nXp)`b?5vbKLt|{Q35GTe_k+fK!}hNV(@!snq|Wb{ z?AXn`#TmY`n9BDIrh^-}33~lsvxj`SHI=>UYZ=4!#dCE{p0nPUjnUPGsC}m?Ui&ii zw*Dy!iMeOBd%TqU9bQB$f8jXYVbYrT%ro%lg!zwPXVr{<ump%om zej+p!arKaqBqHQy(R&dEUA2lOm;n-WR*!3L{W*S0?*j(B!~YPRVsxedvYwW95sL>8 zC-+nBE*d=&Al}wm4CYVFI6tZ+&#q1T?3$25T(LfJZS%|bCriy+P1k=7eF)8YVW~Ce zi?}W{V8kGOFYguuxq3WL98~DT7EiFQxwkom#?NQJX>tF#RkVgG>$8kBq<}cr83RK% zskZmu(?n^?3D&M~j~Tke=XbRVNQKXXx4p05G%-geiyPy-j0~5d3e9b!(pM8c;U$IX zk8@G~2!ey=>cAz*AuNj+-Aw-`n1z!=?%P)(R`4q2b{J32%5kRHMoE=*^E!-D@-QUu zx5x+63&QI>tVSl9Q;}_;B+1Wncd}UPTYJnUoUhIllmP~`1_)HwN2=6unZZ1z%X|^? zVs2;y@e_DLkr-P+(F5^oxgT+Zuewxyue3Mq1}61w5KlMx_2g#f`~eDF47*)02Q@md z_43V75y9WS>f&5(qFhIfI^Me<#H`KJ5^rm*mayCrhZM}#ox1cP@8<=qHQ}&~K9q8_ zCMq&akR`BK2@5xgVmza;pr4qpW5Q>jtcA9mJBmeYrayAGDVd=;Z}eET&8|>dqR*}x zOH@l^TMO(YF$CAR(()_keKWh_@*BI3W89G6@;Z>~c~(R4EBL2kP9A8%%Ek(`Uo)F` z$~o^r@+(h$c}Hp7ooiRyRWxx?PXVwtl1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "g()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: INLINE ASM 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/e2e/solc_parsing/test_data/global_variables-0.8.18.sol b/tests/e2e/solc_parsing/test_data/global_variables-0.8.18.sol new file mode 100644 index 000000000..f21ae5d8f --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/global_variables-0.8.18.sol @@ -0,0 +1,11 @@ +contract C { + function f() public view returns (uint256) { + return block.prevrandao; + } + + function g() public view returns (uint256 ret) { + assembly { + ret := prevrandao() + } + } +} \ No newline at end of file From 30a4428e6bf65975ba1330ded69e8eb2ec246829 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 7 Jun 2023 07:42:03 -0500 Subject: [PATCH 073/101] use current scope instead of parent scope to determine if arith. is checked --- slither/core/cfg/scope.py | 6 +- slither/solc_parsing/declarations/function.py | 139 +++++++----------- 2 files changed, 56 insertions(+), 89 deletions(-) diff --git a/slither/core/cfg/scope.py b/slither/core/cfg/scope.py index d3ac4e836..3f9959851 100644 --- a/slither/core/cfg/scope.py +++ b/slither/core/cfg/scope.py @@ -7,8 +7,10 @@ if TYPE_CHECKING: # pylint: disable=too-few-public-methods class Scope: - def __init__(self, is_checked: bool, is_yul: bool, scope: Union["Scope", "Function"]) -> None: + def __init__( + self, is_checked: bool, is_yul: bool, parent_scope: Union["Scope", "Function"] + ) -> None: self.nodes: List["Node"] = [] self.is_checked = is_checked self.is_yul = is_yul - self.father = scope + self.father = parent_scope diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 5a29a9b50..af87892bc 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -354,7 +354,7 @@ class FunctionSolc(CallerContextExpression): ################################################################################### ################################################################################### - def _parse_if(self, if_statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_if(self, if_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: # IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? falseStatement = None @@ -362,21 +362,15 @@ class FunctionSolc(CallerContextExpression): condition = if_statement["condition"] # Note: check if the expression could be directly # parsed here - condition_node = self._new_node( - NodeType.IF, condition["src"], node.underlying_node.scope - ) + condition_node = self._new_node(NodeType.IF, condition["src"], scope) condition_node.add_unparsed_expression(condition) link_underlying_nodes(node, condition_node) - true_scope = Scope( - node.underlying_node.scope.is_checked, False, node.underlying_node.scope - ) + true_scope = Scope(scope.is_checked, False, scope) trueStatement = self._parse_statement( if_statement["trueBody"], condition_node, true_scope ) if "falseBody" in if_statement and if_statement["falseBody"]: - false_scope = Scope( - node.underlying_node.scope.is_checked, False, node.underlying_node.scope - ) + false_scope = Scope(scope.is_checked, False, scope) falseStatement = self._parse_statement( if_statement["falseBody"], condition_node, false_scope ) @@ -385,22 +379,16 @@ class FunctionSolc(CallerContextExpression): condition = children[0] # Note: check if the expression could be directly # parsed here - condition_node = self._new_node( - NodeType.IF, condition["src"], node.underlying_node.scope - ) + condition_node = self._new_node(NodeType.IF, condition["src"], scope) condition_node.add_unparsed_expression(condition) link_underlying_nodes(node, condition_node) - true_scope = Scope( - node.underlying_node.scope.is_checked, False, node.underlying_node.scope - ) + true_scope = Scope(scope.is_checked, False, scope) trueStatement = self._parse_statement(children[1], condition_node, true_scope) if len(children) == 3: - false_scope = Scope( - node.underlying_node.scope.is_checked, False, node.underlying_node.scope - ) + false_scope = Scope(scope.is_checked, False, scope) falseStatement = self._parse_statement(children[2], condition_node, false_scope) - endIf_node = self._new_node(NodeType.ENDIF, if_statement["src"], node.underlying_node.scope) + endIf_node = self._new_node(NodeType.ENDIF, if_statement["src"], scope) link_underlying_nodes(trueStatement, endIf_node) if falseStatement: @@ -409,32 +397,26 @@ class FunctionSolc(CallerContextExpression): link_underlying_nodes(condition_node, endIf_node) return endIf_node - def _parse_while(self, whilte_statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_while(self, whilte_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: # WhileStatement = 'while' '(' Expression ')' Statement - node_startWhile = self._new_node( - NodeType.STARTLOOP, whilte_statement["src"], node.underlying_node.scope - ) + node_startWhile = self._new_node(NodeType.STARTLOOP, whilte_statement["src"], scope) - body_scope = Scope(node.underlying_node.scope.is_checked, False, node.underlying_node.scope) + body_scope = Scope(scope.is_checked, False, scope) if self.is_compact_ast: node_condition = self._new_node( - NodeType.IFLOOP, whilte_statement["condition"]["src"], node.underlying_node.scope + NodeType.IFLOOP, whilte_statement["condition"]["src"], scope ) node_condition.add_unparsed_expression(whilte_statement["condition"]) statement = self._parse_statement(whilte_statement["body"], node_condition, body_scope) else: children = whilte_statement[self.get_children("children")] expression = children[0] - node_condition = self._new_node( - NodeType.IFLOOP, expression["src"], node.underlying_node.scope - ) + node_condition = self._new_node(NodeType.IFLOOP, expression["src"], scope) node_condition.add_unparsed_expression(expression) statement = self._parse_statement(children[1], node_condition, body_scope) - node_endWhile = self._new_node( - NodeType.ENDLOOP, whilte_statement["src"], node.underlying_node.scope - ) + node_endWhile = self._new_node(NodeType.ENDLOOP, whilte_statement["src"], scope) link_underlying_nodes(node, node_startWhile) link_underlying_nodes(node_startWhile, node_condition) @@ -562,7 +544,7 @@ class FunctionSolc(CallerContextExpression): return pre, cond, post, body - def _parse_for(self, statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_for(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: # ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement if self.is_compact_ast: @@ -570,17 +552,13 @@ class FunctionSolc(CallerContextExpression): else: pre, cond, post, body = self._parse_for_legacy_ast(statement) - node_startLoop = self._new_node( - NodeType.STARTLOOP, statement["src"], node.underlying_node.scope - ) - node_endLoop = self._new_node( - NodeType.ENDLOOP, statement["src"], node.underlying_node.scope - ) + node_startLoop = self._new_node(NodeType.STARTLOOP, statement["src"], scope) + node_endLoop = self._new_node(NodeType.ENDLOOP, statement["src"], scope) - last_scope = node.underlying_node.scope + last_scope = scope if pre: - pre_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope) + pre_scope = Scope(scope.is_checked, False, last_scope) last_scope = pre_scope node_init_expression = self._parse_statement(pre, node, pre_scope) link_underlying_nodes(node_init_expression, node_startLoop) @@ -588,7 +566,7 @@ class FunctionSolc(CallerContextExpression): link_underlying_nodes(node, node_startLoop) if cond: - cond_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope) + cond_scope = Scope(scope.is_checked, False, last_scope) last_scope = cond_scope node_condition = self._new_node(NodeType.IFLOOP, cond["src"], cond_scope) node_condition.add_unparsed_expression(cond) @@ -599,7 +577,7 @@ class FunctionSolc(CallerContextExpression): node_condition = None node_beforeBody = node_startLoop - body_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope) + body_scope = Scope(scope.is_checked, False, last_scope) last_scope = body_scope node_body = self._parse_statement(body, node_beforeBody, body_scope) @@ -619,14 +597,10 @@ class FunctionSolc(CallerContextExpression): return node_endLoop - def _parse_dowhile(self, do_while_statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_dowhile(self, do_while_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: - node_startDoWhile = self._new_node( - NodeType.STARTLOOP, do_while_statement["src"], node.underlying_node.scope - ) - condition_scope = Scope( - node.underlying_node.scope.is_checked, False, node.underlying_node.scope - ) + node_startDoWhile = self._new_node(NodeType.STARTLOOP, do_while_statement["src"], scope) + condition_scope = Scope(scope.is_checked, False, scope) if self.is_compact_ast: node_condition = self._new_node( @@ -644,7 +618,7 @@ class FunctionSolc(CallerContextExpression): node_condition.add_unparsed_expression(expression) statement = self._parse_statement(children[1], node_condition, condition_scope) - body_scope = Scope(node.underlying_node.scope.is_checked, False, condition_scope) + body_scope = Scope(scope.is_checked, False, condition_scope) node_endDoWhile = self._new_node(NodeType.ENDLOOP, do_while_statement["src"], body_scope) link_underlying_nodes(node, node_startDoWhile) @@ -660,29 +634,27 @@ class FunctionSolc(CallerContextExpression): link_underlying_nodes(node_condition, node_endDoWhile) return node_endDoWhile - def _parse_try_catch(self, statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_try_catch(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: externalCall = statement.get("externalCall", None) if externalCall is None: raise ParsingError(f"Try/Catch not correctly parsed by Slither {statement}") - catch_scope = Scope( - node.underlying_node.scope.is_checked, False, node.underlying_node.scope - ) + catch_scope = Scope(scope.is_checked, False, scope) new_node = self._new_node(NodeType.TRY, statement["src"], catch_scope) new_node.add_unparsed_expression(externalCall) link_underlying_nodes(node, new_node) node = new_node for clause in statement.get("clauses", []): - self._parse_catch(clause, node) + self._parse_catch(clause, node, scope) return node - def _parse_catch(self, statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_catch(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: block = statement.get("block", None) if block is None: raise ParsingError(f"Catch not correctly parsed by Slither {statement}") - try_scope = Scope(node.underlying_node.scope.is_checked, False, node.underlying_node.scope) + try_scope = Scope(scope.is_checked, False, scope) try_node = self._new_node(NodeType.CATCH, statement["src"], try_scope) link_underlying_nodes(node, try_node) @@ -699,7 +671,7 @@ class FunctionSolc(CallerContextExpression): return self._parse_statement(block, try_node, try_scope) - def _parse_variable_definition(self, statement: Dict, node: NodeSolc) -> NodeSolc: + def _parse_variable_definition(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: try: local_var = LocalVariable() local_var.set_function(self._function) @@ -709,9 +681,7 @@ class FunctionSolc(CallerContextExpression): self._add_local_variable(local_var_parser) # local_var.analyze(self) - new_node = self._new_node( - NodeType.VARIABLE, statement["src"], node.underlying_node.scope - ) + new_node = self._new_node(NodeType.VARIABLE, statement["src"], scope) new_node.underlying_node.add_variable_declaration(local_var) link_underlying_nodes(node, new_node) return new_node @@ -741,7 +711,7 @@ class FunctionSolc(CallerContextExpression): "declarations": [variable], "initialValue": init, } - new_node = self._parse_variable_definition(new_statement, new_node) + new_node = self._parse_variable_definition(new_statement, new_node, scope) else: # If we have @@ -763,7 +733,7 @@ class FunctionSolc(CallerContextExpression): variables.append(variable) new_node = self._parse_variable_definition_init_tuple( - new_statement, i, new_node + new_statement, i, new_node, scope ) i = i + 1 @@ -795,9 +765,7 @@ class FunctionSolc(CallerContextExpression): "typeDescriptions": {"typeString": "tuple()"}, } node = new_node - new_node = self._new_node( - NodeType.EXPRESSION, statement["src"], node.underlying_node.scope - ) + new_node = self._new_node(NodeType.EXPRESSION, statement["src"], scope) new_node.add_unparsed_expression(expression) link_underlying_nodes(node, new_node) @@ -877,16 +845,14 @@ class FunctionSolc(CallerContextExpression): ], } node = new_node - new_node = self._new_node( - NodeType.EXPRESSION, statement["src"], node.underlying_node.scope - ) + new_node = self._new_node(NodeType.EXPRESSION, statement["src"], scope) new_node.add_unparsed_expression(expression) link_underlying_nodes(node, new_node) return new_node def _parse_variable_definition_init_tuple( - self, statement: Dict, index: int, node: NodeSolc + self, statement: Dict, index: int, node: NodeSolc, scope ) -> NodeSolc: local_var = LocalVariableInitFromTuple() local_var.set_function(self._function) @@ -896,7 +862,7 @@ class FunctionSolc(CallerContextExpression): self._add_local_variable(local_var_parser) - new_node = self._new_node(NodeType.VARIABLE, statement["src"], node.underlying_node.scope) + new_node = self._new_node(NodeType.VARIABLE, statement["src"], scope) new_node.underlying_node.add_variable_declaration(local_var) link_underlying_nodes(node, new_node) return new_node @@ -917,15 +883,15 @@ class FunctionSolc(CallerContextExpression): name = statement[self.get_key()] # SimpleStatement = VariableDefinition | ExpressionStatement if name == "IfStatement": - node = self._parse_if(statement, node) + node = self._parse_if(statement, node, scope) elif name == "WhileStatement": - node = self._parse_while(statement, node) + node = self._parse_while(statement, node, scope) elif name == "ForStatement": - node = self._parse_for(statement, node) + node = self._parse_for(statement, node, scope) elif name == "Block": - node = self._parse_block(statement, node) + node = self._parse_block(statement, node, scope) elif name == "UncheckedBlock": - node = self._parse_unchecked_block(statement, node) + node = self._parse_unchecked_block(statement, node, scope) elif name == "InlineAssembly": # Added with solc 0.6 - the yul code is an AST if "AST" in statement and not self.compilation_unit.core.skip_assembly: @@ -947,7 +913,7 @@ class FunctionSolc(CallerContextExpression): link_underlying_nodes(node, asm_node) node = asm_node elif name == "DoWhileStatement": - node = self._parse_dowhile(statement, node) + node = self._parse_dowhile(statement, node, scope) # For Continue / Break / Return / Throw # The is fixed later elif name == "Continue": @@ -988,7 +954,7 @@ class FunctionSolc(CallerContextExpression): link_underlying_nodes(node, new_node) node = new_node elif name in ["VariableDefinitionStatement", "VariableDeclarationStatement"]: - node = self._parse_variable_definition(statement, node) + node = self._parse_variable_definition(statement, node, scope) elif name == "ExpressionStatement": # assert len(statement[self.get_children('expression')]) == 1 # assert not 'attributes' in statement @@ -1002,7 +968,7 @@ class FunctionSolc(CallerContextExpression): link_underlying_nodes(node, new_node) node = new_node elif name == "TryStatement": - node = self._parse_try_catch(statement, node) + node = self._parse_try_catch(statement, node, scope) # elif name == 'TryCatchClause': # self._parse_catch(statement, node) elif name == "RevertStatement": @@ -1019,7 +985,7 @@ class FunctionSolc(CallerContextExpression): return node - def _parse_block(self, block: Dict, node: NodeSolc, check_arithmetic: bool = False) -> NodeSolc: + def _parse_block(self, block: Dict, node: NodeSolc, scope: Scope) -> NodeSolc: """ Return: Node @@ -1031,13 +997,12 @@ class FunctionSolc(CallerContextExpression): else: statements = block[self.get_children("children")] - check_arithmetic = check_arithmetic | node.underlying_node.scope.is_checked - new_scope = Scope(check_arithmetic, False, node.underlying_node.scope) + new_scope = Scope(scope.is_checked, False, scope) for statement in statements: node = self._parse_statement(statement, node, new_scope) return node - def _parse_unchecked_block(self, block: Dict, node: NodeSolc): + def _parse_unchecked_block(self, block: Dict, node: NodeSolc, scope): """ Return: Node @@ -1049,7 +1014,8 @@ class FunctionSolc(CallerContextExpression): else: statements = block[self.get_children("children")] - new_scope = Scope(False, False, node.underlying_node.scope) + new_scope = Scope(False, False, scope) + for statement in statements: node = self._parse_statement(statement, node, new_scope) return node @@ -1070,8 +1036,7 @@ class FunctionSolc(CallerContextExpression): self._function.is_empty = True else: self._function.is_empty = False - check_arithmetic = self.compilation_unit.solc_version >= "0.8.0" - self._parse_block(cfg, node, check_arithmetic=check_arithmetic) + self._parse_block(cfg, node, self.underlying_function) self._remove_incorrect_edges() self._remove_alone_endif() From 3ed6dee1a374d02212d0ff288e57c0e5442dcf0e Mon Sep 17 00:00:00 2001 From: Kevin Clancy Date: Wed, 7 Jun 2023 15:50:07 -0700 Subject: [PATCH 074/101] fix: make _convert_to_structure_to_list return a type instead of an ElementaryType's `type` field (#1935) * make _convert_to_structure_to_list return a type instead of an elementary type's type field * fix pylint warning preventing me from using elif after a return * re-arranged the assert and isinstance check * removed extra colon * reformatted --- slither/slithir/convert.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 665a7c8f9..63c3745eb 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1583,7 +1583,9 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]: # } if isinstance(return_type, (MappingType, ArrayType)): return [] - return [return_type.type] + + assert isinstance(return_type, (ElementaryType, UserDefinedType, TypeAlias)) + return [return_type] def convert_type_of_high_and_internal_level_call( From adabce6180522059d25ced40b260c121eded0d4e Mon Sep 17 00:00:00 2001 From: Simone <79767264+smonicas@users.noreply.github.com> Date: Thu, 8 Jun 2023 19:47:53 +0200 Subject: [PATCH 075/101] Detect when ether is sent in Yul (#1909) * Detect when ether is sent in Yul * address pylint warning instead of supressing * fix guard clause * remove branch to appease pylint * check that call can send eth prior to reading args --------- Co-authored-by: alpharush <0xalpharush@protonmail.com> --- slither/detectors/attributes/locked_ether.py | 28 ++++++++++++++++-- ...LockedEther_0_4_25_locked_ether_sol__0.txt | 7 ++++- ...LockedEther_0_5_16_locked_ether_sol__0.txt | 7 ++++- ...LockedEther_0_6_11_locked_ether_sol__0.txt | 2 +- ..._LockedEther_0_7_6_locked_ether_sol__0.txt | 2 +- .../locked-ether/0.4.25/locked_ether.sol | 11 +++++++ .../0.4.25/locked_ether.sol-0.4.25.zip | Bin 3082 -> 3497 bytes .../locked-ether/0.5.16/locked_ether.sol | 11 +++++++ .../0.5.16/locked_ether.sol-0.5.16.zip | Bin 3063 -> 3457 bytes .../locked-ether/0.6.11/locked_ether.sol | 10 +++++++ .../0.6.11/locked_ether.sol-0.6.11.zip | Bin 3078 -> 3560 bytes .../locked-ether/0.7.6/locked_ether.sol | 10 +++++++ .../0.7.6/locked_ether.sol-0.7.6.zip | Bin 2998 -> 3478 bytes 13 files changed, 81 insertions(+), 7 deletions(-) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index a6f882922..91ec68650 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -3,7 +3,7 @@ """ from typing import List -from slither.core.declarations.contract import Contract +from slither.core.declarations import Contract, SolidityFunction from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, @@ -17,7 +17,9 @@ from slither.slithir.operations import ( NewContract, LibraryCall, InternalCall, + SolidityCall, ) +from slither.slithir.variables import Constant from slither.utils.output import Output @@ -68,8 +70,28 @@ Every Ether sent to `Locked` will be lost.""" ): if ir.call_value and ir.call_value != 0: return False - if isinstance(ir, (LowLevelCall)): - if ir.function_name in ["delegatecall", "callcode"]: + if isinstance(ir, (LowLevelCall)) and ir.function_name in [ + "delegatecall", + "callcode", + ]: + return False + if isinstance(ir, SolidityCall): + call_can_send_ether = ir.function in [ + SolidityFunction( + "delegatecall(uint256,uint256,uint256,uint256,uint256,uint256)" + ), + SolidityFunction( + "callcode(uint256,uint256,uint256,uint256,uint256,uint256,uint256)" + ), + SolidityFunction( + "call(uint256,uint256,uint256,uint256,uint256,uint256,uint256)" + ), + ] + nonzero_call_value = call_can_send_ether and ( + not isinstance(ir.arguments[2], Constant) + or ir.arguments[2].value != 0 + ) + if nonzero_call_value: return False # If a new internal call or librarycall # Add it to the list to explore diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_4_25_locked_ether_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_4_25_locked_ether_sol__0.txt index edca6eb2e..680f77d0d 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_4_25_locked_ether_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_4_25_locked_ether_sol__0.txt @@ -1,5 +1,10 @@ Contract locking ether found: - Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol#26) has payable functions: + Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol#37) has payable functions: + - Locked.receive() (tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol#4-6) + But does not have a function to withdraw the ether + +Contract locking ether found: + Contract UnlockedAssembly (tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol#27-35) has payable functions: - Locked.receive() (tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol#4-6) But does not have a function to withdraw the ether diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_5_16_locked_ether_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_5_16_locked_ether_sol__0.txt index d1ff3314b..961ba8c48 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_5_16_locked_ether_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_5_16_locked_ether_sol__0.txt @@ -1,5 +1,10 @@ Contract locking ether found: - Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol#26) has payable functions: + Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol#37) has payable functions: + - Locked.receive() (tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol#4-6) + But does not have a function to withdraw the ether + +Contract locking ether found: + Contract UnlockedAssembly (tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol#27-35) has payable functions: - Locked.receive() (tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol#4-6) But does not have a function to withdraw the ether diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_6_11_locked_ether_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_6_11_locked_ether_sol__0.txt index 212015c29..079104879 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_6_11_locked_ether_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_6_11_locked_ether_sol__0.txt @@ -1,5 +1,5 @@ Contract locking ether found: - Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol#26) has payable functions: + Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol#36) has payable functions: - Locked.receive_eth() (tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol#4-6) But does not have a function to withdraw the ether diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_7_6_locked_ether_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_7_6_locked_ether_sol__0.txt index 8b6ddfa59..14835871f 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_7_6_locked_ether_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LockedEther_0_7_6_locked_ether_sol__0.txt @@ -1,5 +1,5 @@ Contract locking ether found: - Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.7.6/locked_ether.sol#26) has payable functions: + Contract OnlyLocked (tests/e2e/detectors/test_data/locked-ether/0.7.6/locked_ether.sol#36) has payable functions: - Locked.receive_eth() (tests/e2e/detectors/test_data/locked-ether/0.7.6/locked_ether.sol#4-6) But does not have a function to withdraw the ether diff --git a/tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol b/tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol index 65942ed2e..f3be911be 100644 --- a/tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol +++ b/tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol @@ -23,4 +23,15 @@ contract Unlocked is Locked, Send{ } +// Still reported because solidity < 0.6.0 doesn't have assembly in the AST +contract UnlockedAssembly is Locked{ + + function withdraw() public { + assembly { + let success := call(gas(), caller(),100,0,0,0,0) + } + } + +} + contract OnlyLocked is Locked{ } diff --git a/tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol-0.4.25.zip b/tests/e2e/detectors/test_data/locked-ether/0.4.25/locked_ether.sol-0.4.25.zip index b6092ecdb88e0c093fca52733727fc3c9715fc5e..e3e6f6c2193b35ead58cb3ceb458bdeac28bca9e 100644 GIT binary patch delta 3388 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 S007)wle`N^20;w~00006^P}bf delta 2969 zcmV;K3ug4G8;TekP)h>@KL7#%4gfZdcUGwag~(tF007`l001PDYz-%oLMngpM-PRi zm0nlZdjJq|?-l5q5W}VAK!X$~yhYNz{x3$S;g((ndY>|}t(QfD>0DX%3?)eh2qMDV z%C@wv9=7dQwIh!2zm5U0T8cs0s5KJCc6PbrL z6p`Gd#^laN}4z1j;Ewe&CIpcnVR?t$XFn2lc$z7nS6g5di!6A9MSbP zZpc9JVUJ#%*cxGdXxJo`;PWqvjO1_mne0P}ezM|cw0rK)jLu2>b$fzQ$*Pui_VHKN zfg~2_YRmZ(AbODMz_sRUt?+Wb(Pqi|xjjM;7i(6*G`Xu|W`9u=!eEG-_=$V7h!QNX zs(+D1&ILyzF8F|O%Upl`Of&=_XkB>|#CLI%D^lfc#@otO1McjQme0L^6L7p8!%iuX z6C`rcP^iFydQPlnk)LV17oOi%2-cV3>Q*h-g6L&jvxbH7wBCz99}Hj04I9DBGbLGb zpb>l|xX^CE0#-|!nhq0!QD~Wwi3DvoJi3Y2t|xd~m(0Tar7;B`_{{r2yRfJ zA@?BL**}V)N-uu`bR`=0{#IAG_Shvhpxjrfxxuf;OcLN8G01xK=yXg&H8Ln4mxQ08 zKnuj@zjg?*Ss;p69E3CN3QEjl&0@l{*P9A^v;_z6OF{3AdCJ7B1<*HofAc!vCi;Bk zIV_%QS9ec%%B&O2XD`AbO{E8b7dE2um=oHrPDo=k@cw90Es4j6`Tqe7DK^2^!TJO_`QeWPSD2_=T%%BE6&?Mgvo8l~b;Y=jggxaamejFb;kmb! zjB`K%%t%$@bYVA(3uS@f78@Yf_nTSDJV&coz9c+5hLSv-3q%@H;@huGq^1ck?+JzS zLr$o2ajK$Hd$f7#^y;DfJS-CTw$GJgeUE?DYXF6;O{yZ1BUQ^`QK4U#lx>lZ9l*~S z1fF7ZCWFTIa}QuHE+V-bcWroMk&tR!ZvHU#yF)iVhL$Wvb-ZGQR)IzmWMzs;Mu&(lEJXZjASMd6MVn@TWTB~4GQW&PyG+$7^B~huZLv@l~l(bssmVz=K%Uo9qYcrLf@0WN->_OtRUhjVV`xTdC zAbaakT}5qTcu)Dlz#GhAOuXqgdjfyTeI>OHL0OEM-qW%(;5dR{Elsoz$fr92ufL963zYD!btHRU_KBt6QmAwncr^`P;?|2B|eHkhzqF*O9tL7Xp$0bw~ z<#evm-1;m>dBX>(pqhVXU(+=pmN43?O+?Hmk1$XaoHN*9{Y5L-dn)0CBwpet~T*F;H-Q#705TG4# zn{?du-ZyDVp;pv2CeGEwA<=I*{?RR5hQ+no%2N_@%^7^{zD|F4ipiDZ-y(8hIa2(L zT^?OZAD!5_iHgjlAaZ>6Iu=i<1Leubr@t!B3av~|1Oq-ZC|Aq6`(1BHaPDUUw=(gl zE9KEv{{JgHJOmIsE+~(s`AE|%YiEU<&7fS(FU(fpVQuv9S}#3nO^*F7ow)BZA07vW zkf}q z$C9wvI);br1i|TWYEmHB33{a3kY=Ty`j(*Z5Job|)mZSO8Efw0!3#=ew&FX>x>XCrRl7;7rAQHwM}|%L z_IAYkM2CM7!neg0V?E{)xKz!59pWzL<8RSfC_=oEZ~v+4jIwpgnL-VzZ6`a>c**8w ziMXn~zg>fjNt7x4Qx39i_$ zS!lA^L=9ke|7Oa*)bw|nUVQUjWw`My#%U4dKvazH2eQNVTn@i};0`H(bg9TdQXBs_ zOr~bLni58l%kA#gj;PIy)!AVo((gs;DMq4s!7mep`tn-D^r{S!JZ7J_G>~Ey%f#*J z_%&?S@2KB4?8=9$vk~xf*jeXE0xSRPD0zlZO928u13v%)01f~)jdxb50)@z63jhG% PPLo>=NCu({00000hq1`C diff --git a/tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol b/tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol index 0269ce855..e5671b7ad 100644 --- a/tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol +++ b/tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol @@ -23,4 +23,15 @@ contract Unlocked is Locked, Send{ } +// Still reported because solidity < 0.6.0 doesn't have assembly in the AST +contract UnlockedAssembly is Locked{ + + function withdraw() public { + assembly { + let success := call(gas(), caller(),100,0,0,0,0) + } + } + +} + contract OnlyLocked is Locked{ } diff --git a/tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol-0.5.16.zip b/tests/e2e/detectors/test_data/locked-ether/0.5.16/locked_ether.sol-0.5.16.zip index 88255d730b0a6e7c46c18a15bb93bb7be61ba5e3..5dfba9e3fe6a261361334b8be8f71fb748a8b30e 100644 GIT binary patch delta 3340 zcmV+n4fFE%7l9iXP)h>@KL7#%4ggP`tyb2T7U=>Q=SZc8!d&auzNu5H;(lNZKHBH49tq(2WooGB21-1X46*&{*bb0t4;);51iNt= zWCp*}Mf!YKGmtS^0t!oXj$|gE-@$i4cvniMqV*ebX(C{MJvR2u^=B8~o1zDtlOcZT zoxEHW#~ba%UjcfQ1NV$o)Op+EIPKu^8eFJSiez*ih*=jahj_?i20n;{rimJOofH z9$TALg4UdF6w_3K$CuBwz#{EI_L!%=8Xxs7o-HZQA1BaE_M;d!JpRcz;lCw|x~He# zmOPEjZo^0}C1Vi#9 zM{bZsnwdvW4>uafPIGTS}N9*O`gXkb<8+ns)|y} zeksC#wDNY{5$W&}+1`?!=%Sfb_e?qjKI;}33It_rH68T8tnI1Hshr}?_6d@rL#BJ@ z0!ol`MJ;*&wzx=G+VugmEdK`D%pD~~BX9323EE-3}$a)pV;0;ny;IuG^a`>x&_2TCj; z9!08^XKMrTmW^b9I)qHM+Slde4HI%UFW}+qUl>itevD$q0}EH!eL%CW;@h> zeX#=&QTMZ{T3Z_ZW~#02 zDA$bhkWJMC2DGgIj%ld{bvox_hco~wS{3c@Xz3wKnfSZEuvUXGSYg5So6Z)TQ}8E< z4f(3Yebw1+=IlU`$q710`jX^^g*mY#<-Z>JC;md|Ytc0vaaJ4wn6H-pG}a-1(mb2( z$-~VZfuaxvFS64odP+GCm=j!+XiAN3S zV!=4rh)=hRuT(6PYIepqc6QpY1kb)^PFU*a)?BEA5o;HtZ$go5<$=V1vkp8YH2s@3 zB(Tto_`VUuy^iJuQ^YqrZXO3^s#Bi)V(sAp^6)L}@rczAKu+IpgVFCFG9Zhp(Hu<# z@gFxf9@`6Re{jVeN;y6afb6R{SDFn9{EJ1<>~n519?>-?lh+yM%P zy4o@%A1_yZ$(!4MprxpP^#Pwn+AGLA;*o;XLYZ_F9`co=Gz9)173p@Xm$~hL!0?Dy zQGt~@T*YMVDyeSKt%iLR3z<`tWTvjylh3@T7s-A%di8{gAvw(?n>uuuB?){)q>XFQ zp5)mm6z0|U2w-oa)^SNIdoO#yqZBhAvn(Jgajd{8@IP<4EZWk4h9i#b!tVqns-j!r z06eD%M1j)oE)W92pD_$22r>qt&|7s^5#?zV@2WS8=F@~aHT#U9H##u- zO||Epv6b6wT;%c0ay$UVjc6~8zLvW9ZYO^!CR+w%^nC_@%I`6se={1}*XY1Kpzy>$^|&uCSbY6&?EsB9<12i$m3Ane+=X~VgTVcOA0S_>_4mDzXtZ#4j0!* zhHb%0iqrFt_8l7kx2=f+N`frv>#k|@P3}1h8X5_Tz>dc>Yn}@Ft{aduU5;$n9e6x` z=cZT+78)Rbt`%-do99L+JQ_F6i&M85SU)gTXHk1m@Eh*n*442(HtRY|KnajIKUuP_ z4OZ-f=@4a0T~vKf$pg_)f9n0%hZT2HK&;}pJN7TGZQHVsd-h}p`lvN{qT{W6a9DAE zCj_RQAj+wmoiTcz-My+m$!lxJcw)HT@Jy3Jwyo)Znmy7`=K_^MUqgV(mLES!fy~-+ zlc0>4FT^H;dZs)0aQ{C;qxtp5R^Weg)>>k?`vZMVdK?VvUuwg1|18A$kvWU2eEOt; z69xe!%D^znoz8K+liqH5D~H5C!;eQCj;=NQ?PK2>aQoanJ*^|K|(; zXkLDQJTL0ECD?WrVykqG{%BBK>E(J-(H@#?1*ew|Q~<>aPYA$ap@8m!sonHi()WEe z1mGZTbJ*R`_Or!iv19sgp2JF-20;j0Wdbpx8s3WFfHzL~uOT>}ecy6StlIY5cO|A( zxMRPENt6;qu|uN_240eiahp$Bv%!4I!U4F9 z|FN!n!hr&XOam6Qpcqe`q4cTrd4b6&(w%56YwJg%i4feUN5#Ut##UXMdX*JG?Huh( zNlX@Gb_~v*@EHGfW{1)E4D%7G>#voS3ix?D&M^(A`)woZH308!ATVcF@P;*|{{3ykbg$?{r&ov_&IOLvw5W zax#-O&?S&QiIp;XW*SfC5LvL1j|!S%z*|U21V5W)u0Z9_5GB{mIRP9}MyQfSaXaa` ziA&xk9TcWnbli)94&cl<*kx3FM8h{#(2(#)a!WS+S?Obp9en6Z>;?Q9HYh58A0rS5 zD%@|xm(s%yTN9ZSq5-|zli?cX>QIK0>3(keh&_(f-?tX>HpD%7hgv~oBScwugr+_J&hbeB2*SXm6od!%pm$($>FjYc^e6tyU^#E)<%Rn#K zdJZxaIvvMzZPfY7cZLu6iwCpLL(i6*={{2O7cKrWXELi>7sh+xm9-LJzToaf0MjSS@AK zBfGAJ=IXS+dzZ7pw$e3ijfG~Mef|Q0TkGEi?Rxz5(V$RE0Rle*KL7#%4ggP`tyb2T W7U28}}C&P)h>@KL7#%4gfZdcUBJOzy?hV000$Ckr-5e@kbAZrIlV+*LwgE zaqkuAn-IgLC!WA0R{ z>lG7lP+0#Xv#9K09jH3@ve%y)ZbDE9el8`JeR1GDfF!eV4?tYuPqxb_#YR+J25<2b z@a=IP_G!M(@V}pbsI?7nB5q8pgz#A6)zrnCt06XPuMrBeALrDO%92GNaYIhG>f%gZn+^{)Uf+>&nS7Y3@ z|K!rY)|*Vk(elfcpBsYUbrUDcz~7l-+W%uKDZf8J-4&0246KanD!2|p$A8l9U(_~$ zbK>^d>6M4+95JsmdDwjBJ#)Ui z|3^KIJ{))90Zvv>!s>3u4s*Gq_TI2H!VA*aGA7Y#dIxeJ$?a^)fBEsytn4ypq{ z3M}Ps@vpgjb|0;tHaP(3G&Y+xglgp7P|ZE;)erA~F2-;(=$7vzlGKkT%K|4@kQ8L+kSNgY3!W%w;$Hl-eTIgxIdMt`;M+tppfK14eag?c)n0q zE!e?-Yc5>c0jc}nBPHv6NgNAyrt`-^fOR=2Z1yUKZ2WlyIf8NWw|)f&=-C?q?aXOw zTJE`8`dxSScuMLilfn8M@2oDRVgh(-o$w_DfwakGeEx}~3N50HqsC=zm8(rns$q6o ztu56ZJwm|KAC_dKa;+f)CLu5GFKdr_ZBKrGsDfzHJiVBBBr62|XHiL{K6tJ??`E#E zBJ0G>Lzn+==>+?ebcp86JGlA9-m3;lY&6Y|D(RTJ=r>VjeE@rD_m*FkEpi=OzENyc z#%TMoQoS(%nPoDGbs+Y+lp6ml>JqIceGa0YQ1K`$(UceW-Yp=n@JHk&+>fLg0I#o;7z9IFI$fAVVTyiUk4o1HB?d z6D2z2JlTKDWPZ@+MDy1)0WqmrVqSGkIzqn0ayyGP3yFh``iU;7r9S<}pun8hnKmUP z{x;4YsMc{J(j^=$)<+h_IR-n*AeS_MqL@1sP`&6$?FN!5(DHMuiM?v;BnGF?aOPif z^0P$>1`qG{6GLAuj0Y_wr?rNiuhbzMml5=-tDKytb!}s0lRRbtNa%}f&s{`{ zKVLHC>8-a2faey)qbKD%F%bG$h(74KsV@{uGR_zLuyEA9f@!W;_ig4Sj za@;pDw4*L@!zv1A0^imOuVIDCi-uy6`$-3Lg z;^2sG24RCi9I=VGlSi+)m!+_=;w?O0FejpItGdklUXpos??RlBn@iYeJy1a!06G^8 zMd-4^g*DthYf~oe3I*URQ*?rrLAO%gGvn`iX>o4drLn!goSDRbW~5CjAjGwNESyoW z#;57t5tr`WBb)OZ-W`J0DBc+o(c}B88HLd&+2isdDa5E>Y@)G9Cwjb-Hee#%*wsDO z#k{HNdX+=I&j3vE0cApP`*?&r##W5vbX5`H{p6v^Y$?!PrIQEcsWmz81>m6|#%ZCx z^LU=&Awa=%=dhB0#vhM6gN7FC41_euFbI5>?K`HAm%}yujF)Gg!_~>*665G- z2ZAP{(j41c6dGT&ckBwKI%Jl0`i6*);$uk}=*_`@&fog^k-z>A3UK{5h;{oVSgfoA z+u=jusGw*5`Y5Gl!}4KKwaZ%UZqqq{H1;e%u#j)*nI?XdNx?D5ebnj6peHe~O_xzF zG;=0u5RV-Oe(b3}{uvoqDD(5|z#`pRdNyX+Z$%V=60Q0O5iaUxHR+^`11!FZx#ur& zI|wR&34r#BD8fu!;C1Qw%C_khT~|h8s_P#lS0(ZS9$>O6*m86<+LEq@Ul`E zC|1LYX+WwAfeejCQ5P%`(e4(7e=|k1Wmxp&dHkyY4Q>};=!|UfzZlpW>PD|?2(RB3 ze&>*46gq)NS3|4Rrh%BnS;=x{Pc-KyGRBMCh+MKKu_2SpDnUOIs|Ptx)Fo`fz|N2;ED zgLIvX(Fg_FJG-p!d^l0bYK?WhbLiWB;@jss@LLrE$?R}tPUrEFO;sPs*H&=lsv4?) zBuT4Ti{tnOE8!sfqIC&p7^xJS#1Uy{sk#zpNtK4)XJI^31%9mr^fqkbUdhZgUiNu- zCq<5yWN>UFPcPii_&u<~Y9k{JZi$B?lrdzh1eXDpnXyI7EENZ6(d@Nds=~aNJ=zDx zH^{X>R<$ua`fgSt9w(9nNkFeC#?6I)0TEz8ITB&kd+0cI2?}vf$de~>n=Ms$x_Gkz zFZB81kxtE?z+>#|>JQ1XX9%emcq~N))Z~Q33t!r24WOh{eP^=qCR7hyN|yT`d5o5% zWJ0YJ|Odr-#1`?HMtcJSV|J@2N pI8aLg0zU&k00ICG05*+xRu1OC22Be902NG=GYv=vj|%_*006NRtBU{t diff --git a/tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol b/tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol index 7f02028e7..aef9ca6e7 100644 --- a/tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol +++ b/tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol @@ -23,4 +23,14 @@ contract Unlocked is Locked, Send{ } +contract UnlockedAssembly is Locked{ + + function withdraw() public { + assembly { + let success := call(gas(), caller(),100,0,0,0,0) + } + } + +} + contract OnlyLocked is Locked{ } diff --git a/tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol-0.6.11.zip b/tests/e2e/detectors/test_data/locked-ether/0.6.11/locked_ether.sol-0.6.11.zip index 2408eeb82bab590d4fe3a2a8303bf31cc5528608..48b675d9a326ccc17e93d23e6e5688a6b86dc2e4 100644 GIT binary patch delta 3444 zcmV-)4U6)I80Z@qP)h>@KL7#%4ggP`tybm08uLC4004nrkr-5e^+P_6+Z$mKB8mG47sOh$XJrXM0aaIfmh>WQO-|HR zhez1+DBzzN%$*G8s&{5@HM)u;5LnMLjA%bh`-t)JL)hy1F1lE=)Xw35Q!$%NlWrKpHEvR#+tO66 zjszxZ)x7CZ=#d`jEYRP0M*&!XM>{Z_Wd9D19(str)qSbU(su{Q7zAVE!}yM}$g=OT z(os|Om8>{($1y{7eel|2F#sni)$xwL$2afM(x}eM!kDq??uBZk;Uv;guk{*d&_>%9}Tn}>gU0dFHCb5VZJ zuSCAOE!{^bBL!C=<(lNkckkt483j&Q{fenz!B{YVqdT$#0u0i6HvZ@NvNaL_f>CG< z6gr2sBp;o`v@d%mo|zTtXRUFV+(Jw}MS6>a;#pBCmODE#IWh0vH@%$ELdy-l`AKF_ zo)Zng-Us^*xytpvL#h1cIu1Va6ii7Mu>Itcy-tMzR68c=-!nXgqE?u8v4X@5ItzBUaOt9P zI3cG?-;d9$9`u&mi%>^QC!=L!RIY`!%e?Cm=oKPQrn-*42#yg!TWy*Sg#}~@K zk0c7T?g$CoaA>9lnBCRPa%*+M?SCz`i_OrzIgW~me778f(BVo9 z6)yC;Qbd$Fqpv=mYQn&2jgV4*)!j&C*jm}lA;*=NLA|URvYMuxi>975Vmg_nk#e^2 zrzQmfA|m-c-l8aWN$JG6Z$u4RthDBWPU$TBJiZ}_+mQP{%9+#ozBU&B;YKeixbVaV zKdD3y=jPzlr;uG0GU0#>H}mkl##mStXWI4ipv)sldzTc#+FWzgON*?31GaBJg6&AV z@*byTRwBlj@|qhebua}BEr69@<@ZXz*>@cP&{wGqTpM33Gqz!+nEIz6rc)XspI2;W38ubzlcAk*8ZW88c<3T zRzLHZ0YCBlXVKWd4`S1Q=0gotq#5cckzH!|cVokgL{K@KlB$>WXPQK~JN9A0+;OO9 zb$el*_O|%SRB+w=tL9uioxf5)!MQ6RL`Y4yiRGw1LFvB3f?t|k%#183ivjKU#+jag z@0eU9$+>>tH`4qr41cM2qi1Vej2q2!1bboM8jfy|C(HJ$ie|xo*&&dHEpgqZ+JLY< zeH1cNdEIXTL;{Ay9)Sm8gyQlBY?jyc>z92dr;g5R6Ss}b@3@QL+kTlt4}ZHEqLG%Z z1;2*gjQfakG=UCaZZ@x`Qmka~Js66efha~dA61iNk()X&9dbxcn))fOwXH7SXWo|l z0`>Boxt+yn=16{jKl<;#(n2B)Cj|U52Dsjmn;+9IKNp$&yrj+$+Zsz=g|z|UKIMd8 zXJ4cchPZI65WVf=k?Gs<0x$;EFQ8)M8TqvsW%b4 zRaTGb>Cxarb|V`k_!M{O?+imSdE_ib_*e^UElS)J79;|HpDX$bQRFOR(HzE!Iddf< ziR`5JWn4KGiUqGuqF}a0S1FoBQbQtXjwzH1Gv1Vt)HJhSGr0-s2q?^D+JKyM*&X04 zvG(t%Q9jzSZEg>FFe0vuC+zv!FS$^gQS)bM`RpLu@2l#YTaZ_=mH3Kf z-;z*&(Q{RQR;Ra+nrPtUc9#OmX{v2^fC*XO4D6~HO?Q>$X~~W&+ciKs zAw@!es2MwYZ9TR|0DA6h{M3;QMUz>oYW<@-z-ZUivc6Gd>&Bktnk7r#5ZSYwCd9XS ziK!6_dz#b+Z82g(yliUa^`t#!_f#0q!7=mYA(UiDv*v%{8YK|=!7vpsX-}SMK3^M> z%BbP@r(jLHt%m5TJdK!KAV|NKg?i%p0r#SRY&1;4*@jrVqdU;<66jsX#jzhNh3Fw>5fQ{Wv_wf z5QD{l=iV+{p-sHS4EPqdkhrRc!x^x)-Xt_>n@26i>yd42-X(Cd8 zLbZHBT=gK1XA(sC(&2U27iM+NpGBT=jb`n|Sb?wNM#DSa4_MJ^HK`U>Dt4*h)iDS| z_nlHyuWt1THP~~6-jK$G^1FeYg=@{Rqi!4kmA2vs=SZZb-RYWKw6w>kJOXMx8F#uh zv!hZ?MN+Gabiyr>1Y-@Wk$Br3)|z>Le#YwE2TEW^^Z&Q)p2r0}o~>DqO2kD!j*G7b z)Ka_9REpO@RVWJ)4qu)FJ+r=!10cGUT5Ts7r%jSX!>I`Srcq?Ur>{~5n+Ix01&zk} zWaYDes00){H9!$jYi{OYI=wiAm#J}5>P47_D6EVN0(J~5leYqieov)TzDgc{7TVY+ zuW*&~=P0LpeRS6OJl6bE;y$Jwplnl3dQVst4KZ4|I*k;TW!oU&F;eO|YRwk?BwKZY z2NV+^!VQ2~eXhfvHulxirfB89>i5;6=r-Bbj(scup|r)(nvKFX82aimq|obn%Txp7 zkobc7T0n~r&z4M@#s@#`erK_N)Gdv~#aLj3Yd%UkG@qjZq90t`3^n6f$Oqxz^k^*& zjGOG!d_pH9xz{kuD?W<{j+$zoswxg+nD4|EaZe0j0gzSS6XE#RliBc-I7|0=LGeQf z{Pp00E$n8;t28` z*Q^w~2L%+xQNl>XA($diJhlcDxSIKqK!U!f0vMt>QE(GPK6T_^_36 zb=5*+cDhIUfdBnoKT9K-C8v7v|P78J2inB4lp3{0zV zrb7sW`Q-@?G)b9YhGpf5{O?sfeVAMv|9D6~+)iC~4NYN0YD9rZPw2h|$=<9%BRO@KL7#%4gfZdcUI{`%EDX=004JPkr-5e@kbAZrIlV+*LwgE zaqkuAn-IgLlorvIZ8-g7 zQ>ISGACE9LPoi;E3klx7DSG{Tk;gj$ssNJ;ZI1qtqMdioDNH8P;1J3H*==1SIalc? z;2G22Kd?Nz^w>moOIEpIC8;XDWDM9D8bSB)3?q?-DVrdFQzdeRjaulCA;qs*QLbM# z;&zeZ1;KqJ9H@l5@Ou|1a&_p0r8bb#h*$bM$NQkh27}4T$ws_73+S5_obF@!C{lW$ zMwAM6gp9wcWIv>yZLdc?^CLf^5Z8yNO%{sc$@DU@G2|H8c0(+qMXwE6g`LrVm5py` z&cWp zS|m~jsMZz$KMx41W zPww;E2SkFX0YR@+Qr0xG`US>q=P($shUAnlB~MD-$;DutR2HIt1RjIFQx5qWRh;|+ zMjknTC#wz4OZ3{oDi));eWT(@m*n!Pu|lw(nPdIFr`=l+fa5(-(pR&nrHX3KvCJhe z(UeLMMBhVfLE1J;VC`wsdgc5oY)#)X7E`>Cl1{R)8DQOPER-`8xFAyz6%xgJdzS-` zl|fS4Md)d@7cNaSNf5Kjt({pIVZQ|t*e4o);>ni=_UY^@`lxro4rRE}Y>R}N0{m%x zMg%2yD@lNb1l`4>2bvJXpXAi=BNdzsq{-07TJ^Bi-MYD+m8JxWa5e1_9g;XnNVB*` z?yi}Mibf1auV#TP@vtPG3Q#FSc`IJP$D#1FzMPF%%nrKe9H}nAab2rP}P!f@MKA`yH|I| zEV_8HfsQm$bzQXAOx-zyv%xn;-xD{ zgYidB@GWMr_g1>_PmY}OnjqMHA~&~|HGEZ%W`hj~hdsJbKV7VLI6OmYSEt5*0+%DE ziYgD4E=H=#4Wp!pam=<|Bvh zs0;{I@RRJ!S{y=dzdZl(LTx^o!jA~#lofxi@N0|hlcVBnS5?OQ8bo=PVJA|U++O3^ zNO{-4t}I1@EuMXxIpd&6-nNaM7o-|cjHbK>1f>zOGN%V92!A6)STZ?(fX8hCne5w; z$YSJ>PIFaVb$L_ffz0|eaBslT7Lg*eq$#F-RP!Dwi-!EsWEv{dVks2ka@Mx8A=$mSG6Ig0ul1_t!vz4(R3wr9G%?IX|xy$cZ&$5oQe2%`@9sfh zy^hdMsQ{@qwh1f;W)yEitm?6>r(@&qr99*o|5_@r{1|k}Ze^8!VTV0|Lpc+h7+7Fx z>+rO3?@b+%4%6na>ZE?n-!$HvI{F#2Q5^Sb#em?=r`kBMcVc0F^)fBSW|$X9v48^u zL;3TZs&M0tPh~akb(`7@2`|m^Mo|i9c36^8saTRRzE)1rk#nL^Ke}!~-wEGJ27dHh7z@DsOa~S;)GBn;4ls^8K2dSj5{aNOsg-TRE50XSl zwj1||J{To@T-+$hz9u954`-^vZfQ@sbx_qu-dfO`?JXgG_^ot>w~}6EN=gVm{?5jm zjx|Z%d1}}h7`A$IC9nK29{*6ECs#v#p~T>YRg#`1^|gS1dk^LT+#nNd;m9soy?E=@ z^d3%V?Zg(8|3SW4De*c`%-OCC_nE)+bF91``!nvsW9&ekq!92JHl-CCk?*b>4l)SC zyn&rtunP-G^52V)9w;h-MQKhgv7$p(OdTp+kY(4T_SjD4>DEHV;gFKQTEduu3wTZcNfCIvXuj4UHtKqqYYU7d$NW z(gvMLwoV`PUcvc-pZoz1L?R;}qT9(3w|nNzMaCz%{?;SjlCVOyPWykLNgocUjHTBJ zf&ukZ@D4doJX#Zt%JE{_cQ`a>_ivjhKm{8qwP8ws>%?ica(dO9MuGl=!zqIac})l} z{0=$bk`!y{yvZ0vuqj2oMXOHyR7R6=n>C9{Tdkfon5fSR=J>j92k6#eXLBJfZfaxh z2D6)J1BtMEki=tAqJoq&!gl4KL?3IZl0>g*{^5Z?%9Zcv%Nc1t;wC%r6odc^U=!vd zHu=hb*4{B|8|7T^vk-O@fHoWr;)C52h^=aJkdi`YI7?G92xbNK`@;Bl3E}ofe&rqg zc^ucmARYM-+6z0ip55n^%GvxJ*}F4?rY^P|9SAIrnvt@LmT#)Vh>NJFU!-7E31(=FP8JK5pV z4~!J<6aa)ScNl4B&Fp?lbdX>5u`Mb2{_B#!S&?>conV4lBE(MNS~$!gCNgR4+YS_e zQU*S0m!OssO>Nj>EV2|Qq9F=1rDZV0D7C~W`>!{1Aaf8G8SGV6&kena85{+c&m|O>@+%HNt~eS=73FxpA9wBF3=Jnk4j`NTaw4x-*JCTSPMlV!3gn( z=5==Ndm-I#&4=;iUC>vY0!Kd=o2xRfS5%Ll;XZ%T&lTn1UfNAN6g&{HX!>Ge;G%ol zqsSHRJWLhu4`bdYYwR0RggGL43Cek}{)pnIlT);z{DH5_?~foxs2VkP5)%1&*|e$W zh~r3$L)I-6@5Dd<@hI@1P)h*@KL7#%4ggS{tyZNcBM9sa004nnkr-5e^+P_6+Z$mKSC#rALyEI}*&)9V8mLyvJA5B~4frL|-K1ZIa1v5!zONZ3O z($CcG9YiJqI1A2y=?U*N|GgFSlI$d{i1&jMJERwFfqfDdoyO z3UK4^Y?hS?eR{RRfR%lajT&lB?92E&4gv|41b2~eTFRHX*)X*88X%{tk*0;{+6k1Q zU?z3PX9rkIPi`chZGP9Bz?l&B`87+{vz({GOFEI2E7@9<5El*3! z!~ED7f-|NDk7O{TV@A-{5fRKY#uDjXHOn{*QCp~gkJ71A%!n|0=QWw?9d|8CSQq&m)$d$)V~QONyAM}5{Yy;6c$Z5~**@J$ zd5qv|=94j1hTtc?wdA2G)UMl9gcx!DDFl2~#+3D4VgVrcqusi~)vz;_L1A3Q!Ro3a zZaamCKKRi5k|E{;UW!#G*GJ-~Y>ApyhSBJ9I;>-Va#t>Q#jAP9uE?r+pI001u8Y70 zGhXarXW4`}Y~e0EJL>ZUF(>2rDE8WhN`OklPOj>apYNz!}x&&Z9p` z_wPP`{JJ_iz_c%nn27jK1G3y2H3#gfODQ~)>Z`R}?8{}Rhh*fQ9!@b=@%g1<^|q<} zIuE!@C)}g}f8P>oWZGFlaB0P+6gM@KAh#`c7>^lL1rdPs6TeGPf zrczvJKwD`8Ht8y@vo8y(3e|`B&&H$Cq^5j-q==z|PX`zM#aeTdzmlfC9c>%x55i>y z#|Ma0ID(LWd31S1n}{=E?dd$uJ+~KK9RpGwt(_5TznWO`1>1$*+$o;i}TORk>GX-HuSv|G=mJ9IZQU2o_8tH*X7-(@d*> z+HZsa8JazDHYx)N6 z<-Wq0bac2V@4Ps`?uCsyhn~}9gPTP+0R^*_2xv(#A|PyJ9(|($s$RzPbm{kgd)3V( z2mzBsDu8RsgxPP|8PgLUpAWn`D)JwHA*aNq+s4fQ9G=-|)$KpA;KKLK9;6!dP#>P3 z6JEn?PHPB*l|G9Q8ie|IeG}~-x0p+nEIy_W{tcAus^Zu&}-BN@)U}_oAEM43{ygXcD|cY zm+Xz2Ykeas`)siAkUzBOVAeW@XEweoVG`8yl8dLE$+2bJ5;;ByIedjKMKTw;E0w3m z`>fM*Uo+wZ^oJd$_0(z>SL0z9=np_I9TJDNK#t5n^hYk;)Q>rm(Xjx>?8K=gO4khD z33~JLV;96^KIF*;q1q#3yiAUNf=RXq4>t#|aC+bREE~CO6@SBd?u*yY6rP1_;yTFL zB~jG@G>(^Yai#bRx^=YB%=}N+5#}e?L&$sA7J31~OD&$yw!YtEy}@1kv_0AMbhazr z$P~5It*=&C4W|^^cg+!>fdMLeBp!O_m|)T#rbHP}r*spR<+o~Zq4hd{Y|;#wq<5(& z=emtgrFWh(sr;8C1c50LU<;KtQoKC-=C zV_RB0bh{P@iv_9$R_eAjmH$v3f?!3^w>Hj5>LzO~r}PJ?`8AHJouOkV8d*e(I95E$ zFNSWU=#YBg8giKbYJi8^=XnjBX+Ykol0?#kJXD$emTRHrZ9!fDX$x9=eN{ge>UDm2aLao;f_S`z9oJnRon-O zC-b*>{k*cz%p>rUA6pC?fP{?kf7l)O3}?-L0{R|6TS4~`K?0b6nDOMVmmUsMyD!?7afLOC7J!W8pkuPJ8w3yTG--H9`U--3{>|t_gW|LAZ$`Gv9`#$_^ zYEqm49gT?dt}Htv!oPeg9QK)6B>)hlGG<#XrY_VnF#za)x-5$=`WQRP_8HSqHG@JE zSK6^a57esEOC-?;j01Z|$0stn=qP+f#Q<59pP8k*XwaL%8bkoC_{Ac^ z^^GA&=QCY@b!r$Oq?Cte>qWS2*Zc!C$#UN$Y%*PiqNewq_e?S3O|zdwUlsXUJmc#x z6~jekD~KXYoDfM-*l7$t54?%6#Pu~8`Y_8xL}I52r%zGJU})AUQC5d=uDyvHmR2fH z7RM$(*#QK&v^zNZkz{VR|HL7{< zi}%T6L{3Q+h`^@`zP@GBEIpiSWmr_AFXNM}MF<1RPd7)cY9d#Y;f_y&B?Wg;QupSA ziKzU4dL7M%>W#4<%h_;5F|i6N!hwaEt9O{sTeLPafd$!zz9VN$s%Iz*3uZaHFV)8TI_Q|M!H83stUA5au=H?Z(9QbP7pk%~^iDCAF zmA;LDk}TH@|KgN0NYZ<+K5dw|?6mW2RF>YQ;Yy@2c^>9C+NJo)SNi)**9E%q=KP=z zl2s=jvXCY>GjA%HAtEnPK6KElwX5$0`#+DBq4Rhx5 zcLC0U$*LKj+#b&)7*SUX%tVL*t!*uTwL$v1qEnX}lTP+aY1_`KJPOGs0(tZlD1 z>;;}nJjEd1B&=!zKedUiI6l-=p7{?v=Yv_F zb_oDz7$v}j+}8FkQ{~Zfc00H2U9V<6QDHRn1YL&GMdE+0d#8tKq^fJ1$;YT35&7|X zupgNvp*j&n4wDH`!I6*MJbpZX(Z0$BcNWs4q5AW?w;#dtU>fR;xh1SX9`N#9RYLhB zs2Fgiqcx%zR|Sv0o8+Mtza(c!1=vI)2=8b2AXNNu5W1WH%|~GkR|-iKC6BN;HBqwl zL!(pR2aB5azU#Z#E#;)?LsvwBqJS|H|wnv{in_$ADEyMNPKE zCJ~lq?G>sJwUTV)r*N{CUY8eeP+uCEUM$k()Y&M)T|KkBFQQLnsEPx zw2q8WO928u13v%)01g09ovl`-CnE^#3;+OuS^xkf000000001!atl5NE)4(x005Y} BZdCvP delta 2878 zcmV-E3&Hf38@3l1P)h>@KL7#%4gfcecUD(TaRm$u008Dlkr-5e@kbAZrIlV+*LwgE zaqkuAn-IgLTge!$df`E^ z?Ro1j46EM`Z}*duTw66krOU6zS8q;e1dtTox)zjgw1UXsr=oI8HyLI`Vy9nTSv)Cg zDX=l80T7y`ln?PINM2GE_(nrv+XVon9@c(M>t}E|X+U#+2(h*oTbsq8 ze(r>;_2H^rVjObwd9?^p z$w#XXjSg^RjDFr`<5p{m*w`?FKMJMq6e>#TOHT1%2k8-W zd><%|4&2|Lxxsink;s{bM}o%kM4>4K)ulf`keBR!87-ot8hk=j0;AZJ?asR9#z!!k zW07*&f0LGQZNz-i{hSbQelokO2m}5Ct#_xc*mRbia;2ts|iCo(V*?C*d-4 ztXl-6=3M#8k`NpaQv?1w^9XE0zP;^#m&sJ7BLpGwdua3)VTO*xUxARKP1dS+-Y?+O z$8ugs`11>%WLL8OcNXG!NrTV5%QI6#!cbvQsdk}(6ChAkQO9XCyDcV+}I8+zKFCu zpK!B1{~yd1cNE;a4QR3FaDHa94!ZO^P)5OrIPKO5PdgrElvK0ck2y1UX0{@ z8|Xk`S&Da}^GaGqZ*F>cC&M_|9tGxZ?)89SNyEL*bkXK~>Ms#dQpYtBxYGE)&}*sf zn7#E^X$*ywa`H?r=b~`ag#5k?fw3eW)!R$xY=8k2X@m8Nc4Dsp2IX9TDB%adb99wT zxyUx(_uklr77{x6_X?GDnL^SKPHo;d`D%8&3)akO9QuHx(U~<5%Jtk@`x|Xwa@q|K zMs%`M{Nmlidk2oE|8MRxl=`MgwA!{XIZaMvNKo|DVQH5bZg}#Q23t(C!$rIS>V?DN z!)PXJnWlE?RoW4ZK-JKH@nr;bR8V4_9bH&okbSPsZ_$aUl7|#Hh%E*H2)P!xAMwKQ+{8L z6Pr{>i^_dVUO0Cj$`^k4Y)KU}J(C%1XjfVye_K`H;`C8V)x-HCR77+2`K4f|CwcUj z$5IrDuQ7sPS5>Fq(x3Z2~Kq_nw`Px$xF_Y z1IJ4VxvB>=qd{M+A2ft2aJG%r= zA}#cIlJ%Z{3mZ+nS&5Ps^vWMm^)^9LF)oeCwvTZ@IT9IxvvYnv2qYAb6y)C$0gDv% zKl9=a+gW-z9HesN`&)$>&O8;aoA)0Xpu*4E!@O@!>D~Xr7TG0_8w2wGe4M>K&VqU8?LuX*^hT_Qk#tGyXKFAqfP9RovdOb8$pM^(mV zAu}sodzD9+OkM?Kdb?$|=>?(!UjZu`rEt!bHj0nlA&=2Y*VKCL%Dv53P67DU^ttXP z`In9vp!Y1}yqmf3PKf*m660hScN1~AwEqZE;f_NGGIAa)^_XW2q9m-ldkr?^5EbYu z%OB@|gYZcKVBcr$gyGh@s@1F=bFVLSG(Th|ESMk&9#SV^d>O+H!^9;g?Xgp3r!F7P z+XY`A6xVIsWLwIVLhp?L4TyXXrffHqH30e(Z)a|%fq#R3(i_OJzjx~gf0l@a)@A^c zME!93;qRhnvhR)?Ydy%`RJF@a>cLb|jvCZ|y7U-L=y|5Mk*er9GxCv6EMp>35)H3) zB!67?t6v)~r!J(oTp$9kGf0_6M>n>4b8&W4f4;7--~N?+8r|a;F$2FT1JN;cjXr1*SL542&DU zGm%_Di$MW8y0sF-``N`pW)xI=$1**e3u1I7)Jr$M*5jY_?uh1coqLnwy2#Sw&IRts z@-*=|R<9JAR13NterFSm2r8aKBnq@c)*;x=Uv0ADM$-WNgJ^S5O928u13v%~0ssyG cH;s2zS50vR3=04N=17xB4M+w~3jhEB0AYk@LjV8( From d0f12dc710ca488f0ccfbdf3989c884b587176db Mon Sep 17 00:00:00 2001 From: Simone <79767264+smonicas@users.noreply.github.com> Date: Thu, 8 Jun 2023 23:05:57 +0200 Subject: [PATCH 076/101] Fix bytes pop ir (#1926) --- slither/slithir/convert.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 63c3745eb..d40715c4f 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1363,11 +1363,12 @@ def convert_to_pop(ir: HighLevelCall, node: "Node") -> List[Operation]: # TODO the following is equivalent to length.points_to = arr # Should it be removed? ir_length.lvalue.points_to = arr - # Note bytes is an ElementaryType not ArrayType so in that case we use ir.destination.type + # Note bytes is an ElementaryType not ArrayType and bytes1 should be returned + # since bytes is bytes1[] without padding between the elements # while in other cases such as uint256[] (ArrayType) we use ir.destination.type.type # in this way we will have the type always set to the corresponding ElementaryType element_to_delete.set_type( - ir.destination.type + ElementaryType("bytes1") if isinstance(ir.destination.type, ElementaryType) else ir.destination.type.type ) From c8d20acca6f5039096907757832777f7d2b34bd7 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 8 Jun 2023 21:03:45 -0500 Subject: [PATCH 077/101] do not recommend changing mutability for abstract contracts (#1952) --- slither/detectors/variables/unchanged_state_variables.py | 2 ++ slither/detectors/variables/unused_state_variables.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/slither/detectors/variables/unchanged_state_variables.py b/slither/detectors/variables/unchanged_state_variables.py index f12cc5784..0e73ab57b 100644 --- a/slither/detectors/variables/unchanged_state_variables.py +++ b/slither/detectors/variables/unchanged_state_variables.py @@ -87,6 +87,8 @@ class UnchangedStateVariables: def detect(self) -> None: """Detect state variables that could be constant or immutable""" for c in self.compilation_unit.contracts_derived: + if c.is_signature_only(): + continue variables = [] functions = [] diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index afb4e3ac5..830ca34ca 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -20,8 +20,6 @@ from slither.visitors.expression.export_values import ExportValues def detect_unused(contract: Contract) -> Optional[List[StateVariable]]: - if contract.is_signature_only(): - return None # Get all the variables read in all the functions and modifiers all_functions = [ @@ -73,6 +71,8 @@ class UnusedStateVars(AbstractDetector): """Detect unused state variables""" results = [] for c in self.compilation_unit.contracts_derived: + if c.is_signature_only(): + continue unusedVars = detect_unused(c) if unusedVars: for var in unusedVars: From fc9377d28cc3818213f4f5fa39a763c595b10a85 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 8 Jun 2023 21:04:23 -0500 Subject: [PATCH 078/101] fail if artifact does not existing (#1947) --- tests/e2e/solc_parsing/test_ast_parsing.py | 9 +++------ .../expected/yul-top-level-0.8.0.sol-0.8.0-compact.json | 5 +++++ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 tests/e2e/solc_parsing/test_data/expected/yul-top-level-0.8.0.sol-0.8.0-compact.json diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py index a561343de..a1d294c1b 100644 --- a/tests/e2e/solc_parsing/test_ast_parsing.py +++ b/tests/e2e/solc_parsing/test_ast_parsing.py @@ -495,12 +495,9 @@ class TestASTParsing: actual = generate_output(sl) - try: - with open(expected, "r", encoding="utf8") as f: - expected = json.load(f) - except OSError: - pytest.xfail("the file for this test was not generated") - raise + assert os.path.isfile(expected), f"Expected file {expected} does not exist" + with open(expected, "r", encoding="utf8") as f: + expected = json.load(f) diff = DeepDiff(expected, actual, ignore_order=True, verbose_level=2, view="tree") if diff: diff --git a/tests/e2e/solc_parsing/test_data/expected/yul-top-level-0.8.0.sol-0.8.0-compact.json b/tests/e2e/solc_parsing/test_data/expected/yul-top-level-0.8.0.sol-0.8.0-compact.json new file mode 100644 index 000000000..f9655dff5 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/yul-top-level-0.8.0.sol-0.8.0-compact.json @@ -0,0 +1,5 @@ +{ + "Test": { + "test()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + } +} \ No newline at end of file From 1e709fd5bbc43d39d2639d6d3d16bdd2e0f23d10 Mon Sep 17 00:00:00 2001 From: 0xGusMcCrae <0xGusMcCrae@protonmail.com> Date: Sat, 10 Jun 2023 14:24:54 -0400 Subject: [PATCH 079/101] additional optimizations for similar_variables.py --- .../detectors/variables/similar_variables.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/slither/detectors/variables/similar_variables.py b/slither/detectors/variables/similar_variables.py index 9f8eaaa2d..4aef01b03 100644 --- a/slither/detectors/variables/similar_variables.py +++ b/slither/detectors/variables/similar_variables.py @@ -47,9 +47,7 @@ class SimilarVarsDetection(AbstractDetector): Returns: bool: true if names are similar """ - if len(seq1) != len(seq2): - return False - val = difflib.SequenceMatcher(a=seq1.lower(), b=seq2.lower()).ratio() + val = difflib.SequenceMatcher(a=seq1, b=seq2).ratio() ret = val > 0.90 return ret @@ -67,19 +65,21 @@ class SimilarVarsDetection(AbstractDetector): all_var = list(set(all_var + contract_var)) - ret = [] + ret = set() # pylint: disable=consider-using-enumerate for i in range(len(all_var)): v1 = all_var[i] _v1_name_lower = v1.name.lower() for j in range(i, len(all_var)): v2 = all_var[j] - if _v1_name_lower != v2.name.lower(): - if SimilarVarsDetection.similar(v1.name, v2.name): - if (v2, v1) not in ret: - ret.append((v1, v2)) + if len(v1.name) != len(v2.name): + continue + _v2_name_lower = v2.name.lower() + if _v1_name_lower != _v2_name_lower: + if SimilarVarsDetection.similar(_v1_name_lower, _v2_name_lower): + ret.add((v1, v2)) - return set(ret) + return ret def _detect(self) -> List[Output]: """Detect similar variables name From 7cb7cb94ad00df6fe78775b656189643d896a993 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Thu, 15 Jun 2023 17:21:44 -0500 Subject: [PATCH 080/101] inform user if inheritance cannot be resolved (#1956) * inform user if inheritance cannot be resolved * lint * update explanation --- .../slither_compilation_unit_solc.py | 11 ++++++++++- .../contract_with_duplicate_names.sol | 2 ++ .../inheritance_resolution_error/import.sol | 1 + tests/unit/core/test_error_messages.py | 18 ++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/unit/core/test_data/inheritance_resolution_error/contract_with_duplicate_names.sol create mode 100644 tests/unit/core/test_data/inheritance_resolution_error/import.sol create mode 100644 tests/unit/core/test_error_messages.py diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index f4258cd41..a739c6f97 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -33,6 +33,10 @@ logger = logging.getLogger("SlitherSolcParsing") logger.setLevel(logging.INFO) +class InheritanceResolutionError(SlitherException): + pass + + def _handle_import_aliases( symbol_aliases: Dict, import_directive: Import, scope: FileScope ) -> None: @@ -432,7 +436,12 @@ Please rename it, this name is reserved for Slither's internals""" target = contract_parser.underlying_contract.file_scope.get_contract_from_name( contract_name ) - assert target + if target == contract_parser.underlying_contract: + raise InheritanceResolutionError( + "Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)." + f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name." + ) + assert target, f"Contract {contract_name} not found" ancestors.append(target) elif i in self._contracts_by_id: ancestors.append(self._contracts_by_id[i]) diff --git a/tests/unit/core/test_data/inheritance_resolution_error/contract_with_duplicate_names.sol b/tests/unit/core/test_data/inheritance_resolution_error/contract_with_duplicate_names.sol new file mode 100644 index 000000000..d5c6816e2 --- /dev/null +++ b/tests/unit/core/test_data/inheritance_resolution_error/contract_with_duplicate_names.sol @@ -0,0 +1,2 @@ +import {ERC20 as ERC20_1} from "./import.sol"; +contract ERC20 is ERC20_1 {} \ No newline at end of file diff --git a/tests/unit/core/test_data/inheritance_resolution_error/import.sol b/tests/unit/core/test_data/inheritance_resolution_error/import.sol new file mode 100644 index 000000000..e3221165a --- /dev/null +++ b/tests/unit/core/test_data/inheritance_resolution_error/import.sol @@ -0,0 +1 @@ +contract ERC20 {} \ No newline at end of file diff --git a/tests/unit/core/test_error_messages.py b/tests/unit/core/test_error_messages.py new file mode 100644 index 000000000..d0d915d56 --- /dev/null +++ b/tests/unit/core/test_error_messages.py @@ -0,0 +1,18 @@ +from pathlib import Path +import pytest + + +from slither import Slither +from slither.solc_parsing.slither_compilation_unit_solc import InheritanceResolutionError + +TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" +INHERITANCE_ERROR_ROOT = Path(TEST_DATA_DIR, "inheritance_resolution_error") + + +def test_inheritance_resolution_error(solc_binary_path) -> None: + with pytest.raises(InheritanceResolutionError): + solc_path = solc_binary_path("0.8.0") + Slither( + Path(INHERITANCE_ERROR_ROOT, "contract_with_duplicate_names.sol").as_posix(), + solc=solc_path, + ) From ccad54263de5802c9893bd6eb151aa68d1a2cb95 Mon Sep 17 00:00:00 2001 From: Simone <79767264+smonicas@users.noreply.github.com> Date: Fri, 16 Jun 2023 00:21:59 +0200 Subject: [PATCH 081/101] Handle if crytic-compile returns an empty ast (#1961) --- slither/solc_parsing/slither_compilation_unit_solc.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index a739c6f97..00ac3a519 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -198,6 +198,13 @@ class SlitherCompilationUnitSolc(CallerContextExpression): # pylint: disable=too-many-branches,too-many-statements,too-many-locals def parse_top_level_from_loaded_json(self, data_loaded: Dict, filename: str) -> None: + if not data_loaded or data_loaded is None: + logger.error( + "crytic-compile returned an empty AST. " + "If you are trying to analyze a contract from etherscan or similar make sure it has source code available." + ) + return + if "nodeType" in data_loaded: self._is_compact_ast = True From 473576994024940ded6d895290c7e881f19efa6a Mon Sep 17 00:00:00 2001 From: Tigran Avagyan Date: Tue, 20 Jun 2023 11:48:45 +0400 Subject: [PATCH 082/101] Fixed issue which disallowed using operator[] with TopLevelVariables --- slither/slithir/operations/index.py | 7 +++++-- slither/slithir/variables/reference.py | 3 ++- .../local_variable_init_from_tuple.py | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/slither/slithir/operations/index.py b/slither/slithir/operations/index.py index f38a25927..4fcfb8a6d 100644 --- a/slither/slithir/operations/index.py +++ b/slither/slithir/operations/index.py @@ -3,6 +3,7 @@ from typing import List, Union from slither.core.declarations import SolidityVariableComposed from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables.variable import Variable +from slither.core.variables.top_level_variable import TopLevelVariable from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue, RVALUE, LVALUE from slither.slithir.variables.reference import ReferenceVariable @@ -13,8 +14,10 @@ class Index(OperationWithLValue): self, result: ReferenceVariable, left_variable: Variable, right_variable: RVALUE ) -> None: super().__init__() - assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed( - "msg.data" + assert ( + is_valid_lvalue(left_variable) + or left_variable == SolidityVariableComposed("msg.data") + or isinstance(left_variable, TopLevelVariable) ) assert is_valid_rvalue(right_variable) assert isinstance(result, ReferenceVariable) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index 9ab51be65..2f99d322e 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -2,6 +2,7 @@ from typing import Optional, TYPE_CHECKING from slither.core.declarations import Contract, Enum, SolidityVariable, Function from slither.core.variables.variable import Variable +from slither.core.variables.top_level_variable import TopLevelVariable if TYPE_CHECKING: from slither.core.cfg.node import Node @@ -46,7 +47,7 @@ class ReferenceVariable(Variable): from slither.slithir.utils.utils import is_valid_lvalue assert is_valid_lvalue(points_to) or isinstance( - points_to, (SolidityVariable, Contract, Enum) + points_to, (SolidityVariable, Contract, Enum, TopLevelVariable) ) self._points_to = points_to diff --git a/slither/solc_parsing/variables/local_variable_init_from_tuple.py b/slither/solc_parsing/variables/local_variable_init_from_tuple.py index 1a551c695..f1c872848 100644 --- a/slither/solc_parsing/variables/local_variable_init_from_tuple.py +++ b/slither/solc_parsing/variables/local_variable_init_from_tuple.py @@ -16,3 +16,21 @@ class LocalVariableInitFromTupleSolc(VariableDeclarationSolc): # Todo: Not sure how to overcome this with mypy assert isinstance(self._variable, LocalVariableInitFromTuple) return self._variable + + def _analyze_variable_attributes(self, attributes: Dict) -> None: + """' + Variable Location + Can be storage/memory or default + """ + if "storageLocation" in attributes: + location = attributes["storageLocation"] + self.underlying_variable.set_location(location) + else: + if "memory" in attributes["type"]: + self.underlying_variable.set_location("memory") + elif "storage" in attributes["type"]: + self.underlying_variable.set_location("storage") + else: + self.underlying_variable.set_location("default") + + super()._analyze_variable_attributes(attributes) From 8a5aab62c9a0a4b9dde91eb9d0ffd847b8987bdb Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 20 Jun 2023 11:03:42 -0500 Subject: [PATCH 083/101] reduce false positives on modifying storage array by value detector (#1962) Check the respective parameter's storage location for each argument --------- Co-authored-by: bossjoker1 <1397157763@qq.com> --- .../compiler_bugs/array_by_reference.py | 13 ++++++----- ...rence_0_4_25_array_by_reference_sol__0.txt | 14 +++++++----- ...rence_0_5_16_array_by_reference_sol__0.txt | 14 +++++++----- ...rence_0_6_11_array_by_reference_sol__0.txt | 14 +++++++----- ...erence_0_7_6_array_by_reference_sol__0.txt | 14 +++++++----- .../0.4.25/array_by_reference.sol | 21 ++++++++++++++++++ .../0.4.25/array_by_reference.sol-0.4.25.zip | Bin 4879 -> 6184 bytes .../0.5.16/array_by_reference.sol | 21 ++++++++++++++++++ .../0.5.16/array_by_reference.sol-0.5.16.zip | Bin 4925 -> 6194 bytes .../0.6.11/array_by_reference.sol | 21 ++++++++++++++++++ .../0.6.11/array_by_reference.sol-0.6.11.zip | Bin 4841 -> 6086 bytes .../0.7.6/array_by_reference.sol | 21 ++++++++++++++++++ .../0.7.6/array_by_reference.sol-0.7.6.zip | Bin 4741 -> 5972 bytes 13 files changed, 124 insertions(+), 29 deletions(-) diff --git a/slither/detectors/compiler_bugs/array_by_reference.py b/slither/detectors/compiler_bugs/array_by_reference.py index 04dfe085a..47e2af581 100644 --- a/slither/detectors/compiler_bugs/array_by_reference.py +++ b/slither/detectors/compiler_bugs/array_by_reference.py @@ -133,7 +133,7 @@ As a result, Bob's usage of the contract is incorrect.""" continue # Verify one of these parameters is an array in storage. - for arg in ir.arguments: + for (param, arg) in zip(ir.function.parameters, ir.arguments): # Verify this argument is a variable that is an array type. if not isinstance(arg, (StateVariable, LocalVariable)): continue @@ -141,8 +141,11 @@ As a result, Bob's usage of the contract is incorrect.""" continue # If it is a state variable OR a local variable referencing storage, we add it to the list. - if isinstance(arg, StateVariable) or ( - isinstance(arg, LocalVariable) and arg.location == "storage" + if ( + isinstance(arg, StateVariable) + or (isinstance(arg, LocalVariable) and arg.location == "storage") + ) and ( + isinstance(param.type, ArrayType) and param.location != "storage" ): results.append((node, arg, ir.function)) return results @@ -165,9 +168,9 @@ As a result, Bob's usage of the contract is incorrect.""" calling_node.function, " passes array ", affected_argument, - "by reference to ", + " by reference to ", invoked_function, - "which only takes arrays by value\n", + " which only takes arrays by value\n", ] res = self.generate_result(info) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt index f056bea10..5cb8add39 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt index 4264c809a..6e97d8cc2 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt index e71930b51..39574b5f5 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt index 7c0f9ccd9..74ea36a0c 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol b/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol index 304af6a48..c2707601a 100644 --- a/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol +++ b/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol @@ -48,4 +48,25 @@ contract D { } +} + +contract E { + uint[1] public x; // storage + uint[1] public y; // storage + + function f() public { + uint[1] memory temp; + setByValue(temp, x); // can set temp, but cannot set x + setByRef(temp, y); // can set temp and y + } + + function setByValue(uint[1] memory arr, uint[1] memory arr2) internal { + arr[0] = 1; + arr2[0] = 2; + } + + function setByRef(uint[1] memory arr, uint[1] storage arr2) internal { + arr[0] = 2; + arr2[0] = 3; + } } \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol-0.4.25.zip b/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol-0.4.25.zip index d9daf8f4d7dc27c4f371b018a31cc9736180c278..96bdaa0f85c484feec1e0f47ea8e3409f0cd1dc9 100644 GIT binary patch delta 5938 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 G0001;R*ZlE From cec07db510e6064dea17a349839880d98adc4121 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 20 Jun 2023 11:12:06 -0500 Subject: [PATCH 084/101] feat: add `--unstructured` to slither-read-storage; retrieve custom storage layouts (#1963) Retrieve storage values of constant arguments passed to SLOAD. If a call to keccack is made, the expression is evaluated and treated as a constant. --------- Co-authored-by: webthethird --- slither/printers/guidance/echidna.py | 17 +- .../expressions/expression_parsing.py | 2 + slither/tools/read_storage/__main__.py | 7 + slither/tools/read_storage/read_storage.py | 315 ++++++++++++++++-- slither/tools/read_storage/utils/utils.py | 2 + slither/utils/integer_conversion.py | 4 +- .../visitors/expression/constants_folding.py | 71 ++-- tests/conftest.py | 6 +- .../tools/{check-erc => check_erc}/erc20.sol | 0 .../tools/{check-erc => check_erc}/test_1.txt | 0 .../safeAdd/safeAdd.sol | 0 .../safeAdd/spec.md | 0 .../{check-kspec => check_kspec}/test_1.txt | 0 .../contractV1.sol | 0 .../contractV1_struct.sol | 0 .../contractV2.sol | 0 .../contractV2_bug.sol | 0 .../contractV2_bug2.sol | 0 .../contractV2_struct.sol | 0 .../contractV2_struct_bug.sol | 0 .../contract_initialization.sol | 0 .../contract_v1_var_init.sol | 0 .../contract_v2_constant.sol | 0 .../proxy.sol | 0 .../test_1.txt | 0 .../test_10.txt | 0 .../test_11.txt | 0 .../test_12.txt | 0 .../test_13.txt | 0 .../test_2.txt | 0 .../test_3.txt | 0 .../test_4.txt | 0 .../test_5.txt | 0 .../test_6.txt | 0 .../test_7.txt | 0 .../test_8.txt | 0 .../test_9.txt | 0 tests/tools/read-storage/conftest.py | 56 ++++ ...ge_layout-0.8.10.sol => StorageLayout.sol} | 3 +- .../test_data/TEST_unstructured_storage.json | 56 ++++ .../test_data/UnstructuredStorageLayout.abi | 1 + .../test_data/UnstructuredStorageLayout.bin | 1 + .../test_data/UnstructuredStorageLayout.sol | 141 ++++++++ tests/tools/read-storage/test_read_storage.py | 66 +--- tests/unit/core/test_constant_folding.py | 59 ++-- .../constant_folding_binop.sol | 2 + 46 files changed, 680 insertions(+), 129 deletions(-) rename tests/tools/{check-erc => check_erc}/erc20.sol (100%) rename tests/tools/{check-erc => check_erc}/test_1.txt (100%) rename tests/tools/{check-kspec => check_kspec}/safeAdd/safeAdd.sol (100%) rename tests/tools/{check-kspec => check_kspec}/safeAdd/spec.md (100%) rename tests/tools/{check-kspec => check_kspec}/test_1.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contractV1.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contractV1_struct.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contractV2.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contractV2_bug.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contractV2_bug2.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contractV2_struct.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contractV2_struct_bug.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contract_initialization.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contract_v1_var_init.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/contract_v2_constant.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/proxy.sol (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_1.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_10.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_11.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_12.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_13.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_2.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_3.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_4.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_5.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_6.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_7.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_8.txt (100%) rename tests/tools/{check-upgradeability => check_upgradeability}/test_9.txt (100%) create mode 100644 tests/tools/read-storage/conftest.py rename tests/tools/read-storage/test_data/{storage_layout-0.8.10.sol => StorageLayout.sol} (95%) create mode 100644 tests/tools/read-storage/test_data/TEST_unstructured_storage.json create mode 100644 tests/tools/read-storage/test_data/UnstructuredStorageLayout.abi create mode 100644 tests/tools/read-storage/test_data/UnstructuredStorageLayout.bin create mode 100644 tests/tools/read-storage/test_data/UnstructuredStorageLayout.sol diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index acbf5b015..25e0968cd 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -32,7 +32,7 @@ from slither.slithir.operations import ( from slither.slithir.operations.binary import Binary from slither.slithir.variables import Constant from slither.utils.output import Output -from slither.visitors.expression.constants_folding import ConstantFolding +from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant def _get_name(f: Union[Function, Variable]) -> str: @@ -178,11 +178,16 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n all_cst_used_in_binary[str(ir.type)].append( ConstantValue(str(r.value), str(r.type)) ) - if isinstance(ir.variable_left, Constant) and isinstance(ir.variable_right, Constant): - if ir.lvalue: - type_ = ir.lvalue.type - cst = ConstantFolding(ir.expression, type_).result() - all_cst_used.append(ConstantValue(str(cst.value), str(type_))) + if isinstance(ir.variable_left, Constant) or isinstance( + ir.variable_right, Constant + ): + if ir.lvalue: + try: + type_ = ir.lvalue.type + cst = ConstantFolding(ir.expression, type_).result() + all_cst_used.append(ConstantValue(str(cst.value), str(type_))) + except NotConstant: + pass if isinstance(ir, TypeConversion): if isinstance(ir.variable, Constant): if isinstance(ir.type, TypeAlias): diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 945a60b8f..4d2cfc00f 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -433,6 +433,8 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression) type_candidate = ElementaryType("uint256") else: type_candidate = ElementaryType("string") + elif type_candidate.startswith("rational_const "): + type_candidate = ElementaryType("uint256") elif type_candidate.startswith("int_const "): type_candidate = ElementaryType("uint256") elif type_candidate.startswith("bool"): diff --git a/slither/tools/read_storage/__main__.py b/slither/tools/read_storage/__main__.py index f6635ab4b..8415ae185 100644 --- a/slither/tools/read_storage/__main__.py +++ b/slither/tools/read_storage/__main__.py @@ -104,6 +104,12 @@ def parse_args() -> argparse.Namespace: default="latest", ) + parser.add_argument( + "--unstructured", + action="store_true", + help="Include unstructured storage slots", + ) + cryticparser.init(parser) return parser.parse_args() @@ -133,6 +139,7 @@ def main() -> None: rpc_info = RpcInfo(args.rpc_url, block) srs = SlitherReadStorage(contracts, args.max_depth, rpc_info) + srs.unstructured = bool(args.unstructured) # Remove target prefix e.g. rinkeby:0x0 -> 0x0. address = target[target.find(":") + 1 :] # Default to implementation address unless a storage address is given. diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 72331f66a..8c0cf515d 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -15,9 +15,21 @@ from web3.middleware import geth_poa_middleware from slither.core.declarations import Contract, Structure from slither.core.solidity_types import ArrayType, ElementaryType, MappingType, UserDefinedType from slither.core.solidity_types.type import Type +from slither.core.cfg.node import NodeType from slither.core.variables.state_variable import StateVariable from slither.core.variables.structure_variable import StructureVariable +from slither.core.expressions import ( + AssignmentOperation, + Literal, + Identifier, + BinaryOperation, + UnaryOperation, + TupleExpression, + TypeConversion, + CallExpression, +) from slither.utils.myprettytable import MyPrettyTable +from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant from .utils import coerce_type, get_offset_value, get_storage_data @@ -72,7 +84,7 @@ class RpcInfo: return self._block -# pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-instance-attributes,too-many-public-methods class SlitherReadStorage: def __init__(self, contracts: List[Contract], max_depth: int, rpc_info: RpcInfo = None) -> None: self._checksum_address: Optional[ChecksumAddress] = None @@ -81,9 +93,11 @@ class SlitherReadStorage: self._max_depth: int = max_depth self._slot_info: Dict[str, SlotInfo] = {} self._target_variables: List[Tuple[Contract, StateVariable]] = [] + self._constant_storage_slots: List[Tuple[Contract, StateVariable]] = [] self.rpc_info: Optional[RpcInfo] = rpc_info self.storage_address: Optional[str] = None self.table: Optional[MyPrettyTable] = None + self.unstructured: bool = False @property def contracts(self) -> List[Contract]: @@ -114,6 +128,11 @@ class SlitherReadStorage: """Storage variables (not constant or immutable) and their associated contract.""" return self._target_variables + @property + def constant_slots(self) -> List[Tuple[Contract, StateVariable]]: + """Constant bytes32 variables and their associated contract.""" + return self._constant_storage_slots + @property def slot_info(self) -> Dict[str, SlotInfo]: """Contains the location, type, size, offset, and value of contract slots.""" @@ -133,9 +152,48 @@ class SlitherReadStorage: elif isinstance(type_, ArrayType): elems = self._all_array_slots(var, contract, type_, info.slot) tmp[var.name].elems = elems - + if self.unstructured: + tmp.update(self.get_unstructured_layout()) self._slot_info = tmp + def get_unstructured_layout(self) -> Dict[str, SlotInfo]: + tmp: Dict[str, SlotInfo] = {} + for _, var in self.constant_slots: + var_name = var.name + try: + exp = var.expression + if isinstance( + exp, + ( + BinaryOperation, + UnaryOperation, + Identifier, + TupleExpression, + TypeConversion, + CallExpression, + ), + ): + exp = ConstantFolding(exp, "bytes32").result() + if isinstance(exp, Literal): + slot = coerce_type("int", exp.value) + else: + continue + offset = 0 + type_string, size = self.find_constant_slot_storage_type(var) + if type_string: + tmp[var.name] = SlotInfo( + name=var_name, type_string=type_string, slot=slot, size=size, offset=offset + ) + self.log += ( + f"\nSlot Name: {var_name}\nType: bytes32" + f"\nStorage Type: {type_string}\nSlot: {str(exp)}\n" + ) + logger.info(self.log) + self.log = "" + except NotConstant: + continue + return tmp + # TODO: remove this pylint exception (montyly) # pylint: disable=too-many-locals def get_storage_slot( @@ -144,7 +202,8 @@ class SlitherReadStorage: contract: Contract, **kwargs: Any, ) -> Union[SlotInfo, None]: - """Finds the storage slot of a variable in a given contract. + """ + Finds the storage slot of a variable in a given contract. Args: target_variable (`StateVariable`): The variable to retrieve the slot for. contracts (`Contract`): The contract that contains the given state variable. @@ -230,6 +289,78 @@ class SlitherReadStorage: if slot_info: self._slot_info[f"{contract.name}.{var.name}"] = slot_info + def find_constant_slot_storage_type( + self, var: StateVariable + ) -> Tuple[Optional[str], Optional[int]]: + """ + Given a constant bytes32 StateVariable, tries to determine which variable type is stored there, using the + heuristic that if a function reads from the slot and returns a value, it probably stores that type of value. + Also uses the StorageSlot library as a heuristic when a function has no return but uses the library's getters. + Args: + var (StateVariable): The constant bytes32 storage slot. + + Returns: + type (str): The type of value stored in the slot. + size (int): The type's size in bits. + """ + assert var.is_constant and var.type == ElementaryType("bytes32") + storage_type = None + size = None + funcs = [] + for c in self.contracts: + c_funcs = c.get_functions_reading_from_variable(var) + c_funcs.extend( + f + for f in c.functions + if any(str(v.expression) == str(var.expression) for v in f.variables) + ) + c_funcs = list(set(c_funcs)) + funcs.extend(c_funcs) + fallback = [f for f in var.contract.functions if f.is_fallback] + funcs += fallback + for func in funcs: + rets = func.return_type if func.return_type is not None else [] + for ret in rets: + size, _ = ret.storage_size + if size <= 32: + return str(ret), size * 8 + for node in func.all_nodes(): + exp = node.expression + # Look for use of the common OpenZeppelin StorageSlot library + if f"getAddressSlot({var.name})" in str(exp): + return "address", 160 + if f"getBooleanSlot({var.name})" in str(exp): + return "bool", 1 + if f"getBytes32Slot({var.name})" in str(exp): + return "bytes32", 256 + if f"getUint256Slot({var.name})" in str(exp): + return "uint256", 256 + # Look for variable assignment in assembly loaded from a hardcoded slot + if ( + isinstance(exp, AssignmentOperation) + and isinstance(exp.expression_left, Identifier) + and isinstance(exp.expression_right, CallExpression) + and "sload" in str(exp.expression_right.called) + and str(exp.expression_right.arguments[0]) == str(var.expression) + ): + if func.is_fallback: + return "address", 160 + storage_type = exp.expression_left.value.type.name + size, _ = exp.expression_left.value.type.storage_size + return storage_type, size * 8 + # Look for variable storage in assembly stored to a hardcoded slot + if ( + isinstance(exp, CallExpression) + and "sstore" in str(exp.called) + and isinstance(exp.arguments[0], Identifier) + and isinstance(exp.arguments[1], Identifier) + and str(exp.arguments[0].value.expression) == str(var.expression) + ): + storage_type = exp.arguments[1].value.type.name + size, _ = exp.arguments[1].value.type.storage_size + return storage_type, size * 8 + return storage_type, size + def walk_slot_info(self, func: Callable) -> None: stack = list(self.slot_info.values()) while stack: @@ -242,7 +373,8 @@ class SlitherReadStorage: func(slot_info) def get_slot_values(self, slot_info: SlotInfo) -> None: - """Fetches the slot value of `SlotInfo` object + """ + Fetches the slot value of `SlotInfo` object :param slot_info: """ assert self.rpc_info is not None @@ -257,25 +389,162 @@ class SlitherReadStorage: ) logger.info(f"\nValue: {slot_info.value}\n") - def get_all_storage_variables(self, func: Callable = None) -> None: - """Fetches all storage variables from a list of contracts. + def get_all_storage_variables(self, func: Callable = lambda x: x) -> None: + """ + Fetches all storage variables from a list of contracts. kwargs: func (Callable, optional): A criteria to filter functions e.g. name. """ for contract in self.contracts: - self._target_variables.extend( - filter( - func, - [ - (contract, var) - for var in contract.state_variables_ordered - if not var.is_constant and not var.is_immutable - ], - ) + for var in contract.state_variables_ordered: + if func(var): + if not var.is_constant and not var.is_immutable: + self._target_variables.append((contract, var)) + elif ( + self.unstructured + and var.is_constant + and var.type == ElementaryType("bytes32") + ): + self._constant_storage_slots.append((contract, var)) + if self.unstructured: + hardcoded_slot = self.find_hardcoded_slot_in_fallback(contract) + if hardcoded_slot is not None: + self._constant_storage_slots.append((contract, hardcoded_slot)) + + def find_hardcoded_slot_in_fallback(self, contract: Contract) -> Optional[StateVariable]: + """ + Searches the contract's fallback function for a sload from a literal storage slot, i.e., + `let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)`. + + Args: + contract: a Contract object, which should have a fallback function. + + Returns: + A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None. + """ + fallback = None + for func in contract.functions_entry_points: + if func.is_fallback: + fallback = func + break + if fallback is None: + return None + queue = [fallback.entry_point] + visited = [] + while len(queue) > 0: + node = queue.pop(0) + visited.append(node) + queue.extend(son for son in node.sons if son not in visited) + if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str): + return SlitherReadStorage.find_hardcoded_slot_in_asm_str(node.inline_asm, contract) + if node.type == NodeType.EXPRESSION: + sv = self.find_hardcoded_slot_in_exp(node.expression, contract) + if sv is not None: + return sv + return None + + @staticmethod + def find_hardcoded_slot_in_asm_str( + inline_asm: str, contract: Contract + ) -> Optional[StateVariable]: + """ + Searches a block of assembly code (given as a string) for a sload from a literal storage slot. + Does not work if the argument passed to sload does not start with "0x", i.e., `sload(add(1,1))` + or `and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)`. + + Args: + inline_asm: a string containing all the code in an assembly node (node.inline_asm for solc < 0.6.0). + + Returns: + A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None. + """ + asm_split = inline_asm.split("\n") + for asm in asm_split: + if "sload(" in asm: # Only handle literals + arg = asm.split("sload(")[1].split(")")[0] + if arg.startswith("0x"): + exp = Literal(arg, ElementaryType("bytes32")) + sv = StateVariable() + sv.name = "fallback_sload_hardcoded" + sv.expression = exp + sv.is_constant = True + sv.type = exp.type + sv.set_contract(contract) + return sv + return None + + def find_hardcoded_slot_in_exp( + self, exp: "Expression", contract: Contract + ) -> Optional[StateVariable]: + """ + Parses an expression to see if it contains a sload from a literal storage slot, + unrolling nested expressions if necessary to determine which slot it loads from. + Args: + exp: an Expression object to search. + contract: the Contract containing exp. + + Returns: + A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None. + """ + if isinstance(exp, AssignmentOperation): + exp = exp.expression_right + while isinstance(exp, BinaryOperation): + exp = next( + (e for e in exp.expressions if isinstance(e, (CallExpression, BinaryOperation))), + exp.expression_left, ) + while isinstance(exp, CallExpression) and len(exp.arguments) > 0: + called = exp.called + exp = exp.arguments[0] + if "sload" in str(called): + break + if isinstance( + exp, + ( + BinaryOperation, + UnaryOperation, + Identifier, + TupleExpression, + TypeConversion, + CallExpression, + ), + ): + try: + exp = ConstantFolding(exp, "bytes32").result() + except NotConstant: + return None + if ( + isinstance(exp, Literal) + and isinstance(exp.type, ElementaryType) + and exp.type.name in ["bytes32", "uint256"] + ): + sv = StateVariable() + sv.name = "fallback_sload_hardcoded" + value = exp.value + str_value = str(value) + if str_value.isdecimal(): + value = int(value) + if isinstance(value, (int, bytes)): + if isinstance(value, bytes): + str_value = "0x" + value.hex() + value = int(str_value, 16) + exp = Literal(str_value, ElementaryType("bytes32")) + state_var_slots = [ + self.get_variable_info(contract, var)[0] + for contract, var in self.target_variables + ] + if value in state_var_slots: + return None + sv.expression = exp + sv.is_constant = True + sv.type = ElementaryType("bytes32") + sv.set_contract(contract) + return sv + return None def convert_slot_info_to_rows(self, slot_info: SlotInfo) -> None: - """Convert and append slot info to table. Create table if it + """ + Convert and append slot info to table. Create table if it does not yet exist :param slot_info: """ @@ -293,7 +562,8 @@ class SlitherReadStorage: def _find_struct_var_slot( elems: List[StructureVariable], slot_as_bytes: bytes, struct_var: str ) -> Tuple[str, str, bytes, int, int]: - """Finds the slot of a structure variable. + """ + Finds the slot of a structure variable. Args: elems (List[StructureVariable]): Ordered list of structure variables. slot_as_bytes (bytes): The slot of the struct to begin searching at. @@ -335,7 +605,8 @@ class SlitherReadStorage: deep_key: int = None, struct_var: str = None, ) -> Tuple[str, str, bytes, int, int]: - """Finds the slot of array's index. + """ + Finds the slot of array's index. Args: target_variable (`StateVariable`): The array that contains the target variable. slot (bytes): The starting slot of the array. @@ -438,7 +709,8 @@ class SlitherReadStorage: deep_key: Union[int, str] = None, struct_var: str = None, ) -> Tuple[str, str, bytes, int, int]: - """Finds the data slot of a target variable within a mapping. + """ + Finds the data slot of a target variable within a mapping. target_variable (`StateVariable`): The mapping that contains the target variable. slot (bytes): The starting slot of the mapping. key (Union[int, str]): The key the variable is stored at. @@ -509,7 +781,7 @@ class SlitherReadStorage: ) info += info_tmp - # TODO: suppory mapping with dynamic arrays + # TODO: support mapping with dynamic arrays # mapping(elem => elem) elif isinstance(target_variable_type.type_to, ElementaryType): @@ -615,7 +887,8 @@ class SlitherReadStorage: return elems def _get_array_length(self, type_: Type, slot: int) -> int: - """Gets the length of dynamic and fixed arrays. + """ + Gets the length of dynamic and fixed arrays. Args: type_ (`AbstractType`): The array type. slot (int): Slot a dynamic array's length is stored at. diff --git a/slither/tools/read_storage/utils/utils.py b/slither/tools/read_storage/utils/utils.py index 4a04a5b6d..20e7c1372 100644 --- a/slither/tools/read_storage/utils/utils.py +++ b/slither/tools/read_storage/utils/utils.py @@ -37,6 +37,8 @@ def coerce_type( (Union[int, bool, str, ChecksumAddress, hex]): The type representation of the value. """ if "int" in solidity_type: + if str(value).startswith("0x"): + return to_int(hexstr=value) return to_int(value) if "bool" in solidity_type: return bool(to_int(value)) diff --git a/slither/utils/integer_conversion.py b/slither/utils/integer_conversion.py index e4dff333c..99064f564 100644 --- a/slither/utils/integer_conversion.py +++ b/slither/utils/integer_conversion.py @@ -4,7 +4,9 @@ from typing import Union from slither.exceptions import SlitherError -def convert_string_to_fraction(val: Union[str, int]) -> Fraction: +def convert_string_to_fraction(val: Union[str, bytes, int]) -> Fraction: + if isinstance(val, bytes): + return int.from_bytes(val, byteorder="big") if isinstance(val, int): return Fraction(val) if val.startswith(("0x", "0X")): diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 12eb6be9d..b1fa570c6 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -1,5 +1,6 @@ from fractions import Fraction from typing import Union +from Crypto.Hash import keccak from slither.core import expressions from slither.core.expressions import ( @@ -11,9 +12,9 @@ from slither.core.expressions import ( UnaryOperation, TupleExpression, TypeConversion, + CallExpression, ) from slither.core.variables import Variable - from slither.utils.integer_conversion import convert_string_to_fraction, convert_string_to_int from slither.visitors.expression.expression import ExpressionVisitor from slither.core.solidity_types.elementary_type import ElementaryType @@ -65,23 +66,31 @@ class ConstantFolding(ExpressionVisitor): value = value & (2**256 - 1) return Literal(value, self._type) + # pylint: disable=import-outside-toplevel def _post_identifier(self, expression: Identifier) -> None: - if not isinstance(expression.value, Variable): - return - if not expression.value.is_constant: + from slither.core.declarations.solidity_variables import SolidityFunction + + if isinstance(expression.value, Variable): + if expression.value.is_constant: + expr = expression.value.expression + # assumption that we won't have infinite loop + # Everything outside of literal + if isinstance( + expr, + (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), + ): + cf = ConstantFolding(expr, self._type) + expr = cf.result() + assert isinstance(expr, Literal) + set_val(expression, convert_string_to_int(expr.converted_value)) + else: + raise NotConstant + elif isinstance(expression.value, SolidityFunction): + set_val(expression, expression.value) + else: raise NotConstant - expr = expression.value.expression - # assumption that we won't have infinite loop - # Everything outside of literal - if isinstance( - expr, (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion) - ): - cf = ConstantFolding(expr, self._type) - expr = cf.result() - assert isinstance(expr, Literal) - set_val(expression, convert_string_to_int(expr.converted_value)) - # pylint: disable=too-many-branches + # pylint: disable=too-many-branches,too-many-statements def _post_binary_operation(self, expression: BinaryOperation) -> None: expression_left = expression.expression_left expression_right = expression.expression_right @@ -95,7 +104,6 @@ class ConstantFolding(ExpressionVisitor): (Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), ): raise NotConstant - left = get_val(expression_left) right = get_val(expression_right) @@ -183,7 +191,9 @@ class ConstantFolding(ExpressionVisitor): raise NotConstant def _post_literal(self, expression: Literal) -> None: - if expression.converted_value in ["true", "false"]: + if str(expression.type) == "bool": + set_val(expression, expression.converted_value) + elif str(expression.type) == "string": set_val(expression, expression.converted_value) else: try: @@ -195,7 +205,14 @@ class ConstantFolding(ExpressionVisitor): raise NotConstant def _post_call_expression(self, expression: expressions.CallExpression) -> None: - raise NotConstant + called = get_val(expression.called) + args = [get_val(arg) for arg in expression.arguments] + if called.name == "keccak256(bytes)": + digest = keccak.new(digest_bits=256) + digest.update(str(args[0]).encode("utf-8")) + set_val(expression, digest.digest()) + else: + raise NotConstant def _post_conditional_expression(self, expression: expressions.ConditionalExpression) -> None: raise NotConstant @@ -247,10 +264,24 @@ class ConstantFolding(ExpressionVisitor): expr = expression.expression if not isinstance( expr, - (Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion), + ( + Literal, + BinaryOperation, + UnaryOperation, + Identifier, + TupleExpression, + TypeConversion, + CallExpression, + ), ): raise NotConstant cf = ConstantFolding(expr, self._type) expr = cf.result() assert isinstance(expr, Literal) - set_val(expression, convert_string_to_fraction(expr.converted_value)) + if str(expression.type).startswith("uint") and isinstance(expr.value, bytes): + value = int.from_bytes(expr.value, "big") + elif str(expression.type).startswith("byte") and isinstance(expr.value, int): + value = int.to_bytes(expr.value, 32, "big") + else: + value = convert_string_to_fraction(expr.converted_value) + set_val(expression, value) diff --git a/tests/conftest.py b/tests/conftest.py index 5ea228fd3..63fccfa12 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,12 @@ # pylint: disable=redefined-outer-name import os -from pathlib import Path -import tempfile import shutil +import tempfile +from pathlib import Path from contextlib import contextmanager -import pytest from filelock import FileLock from solc_select import solc_select +import pytest from slither import Slither diff --git a/tests/tools/check-erc/erc20.sol b/tests/tools/check_erc/erc20.sol similarity index 100% rename from tests/tools/check-erc/erc20.sol rename to tests/tools/check_erc/erc20.sol diff --git a/tests/tools/check-erc/test_1.txt b/tests/tools/check_erc/test_1.txt similarity index 100% rename from tests/tools/check-erc/test_1.txt rename to tests/tools/check_erc/test_1.txt diff --git a/tests/tools/check-kspec/safeAdd/safeAdd.sol b/tests/tools/check_kspec/safeAdd/safeAdd.sol similarity index 100% rename from tests/tools/check-kspec/safeAdd/safeAdd.sol rename to tests/tools/check_kspec/safeAdd/safeAdd.sol diff --git a/tests/tools/check-kspec/safeAdd/spec.md b/tests/tools/check_kspec/safeAdd/spec.md similarity index 100% rename from tests/tools/check-kspec/safeAdd/spec.md rename to tests/tools/check_kspec/safeAdd/spec.md diff --git a/tests/tools/check-kspec/test_1.txt b/tests/tools/check_kspec/test_1.txt similarity index 100% rename from tests/tools/check-kspec/test_1.txt rename to tests/tools/check_kspec/test_1.txt diff --git a/tests/tools/check-upgradeability/contractV1.sol b/tests/tools/check_upgradeability/contractV1.sol similarity index 100% rename from tests/tools/check-upgradeability/contractV1.sol rename to tests/tools/check_upgradeability/contractV1.sol diff --git a/tests/tools/check-upgradeability/contractV1_struct.sol b/tests/tools/check_upgradeability/contractV1_struct.sol similarity index 100% rename from tests/tools/check-upgradeability/contractV1_struct.sol rename to tests/tools/check_upgradeability/contractV1_struct.sol diff --git a/tests/tools/check-upgradeability/contractV2.sol b/tests/tools/check_upgradeability/contractV2.sol similarity index 100% rename from tests/tools/check-upgradeability/contractV2.sol rename to tests/tools/check_upgradeability/contractV2.sol diff --git a/tests/tools/check-upgradeability/contractV2_bug.sol b/tests/tools/check_upgradeability/contractV2_bug.sol similarity index 100% rename from tests/tools/check-upgradeability/contractV2_bug.sol rename to tests/tools/check_upgradeability/contractV2_bug.sol diff --git a/tests/tools/check-upgradeability/contractV2_bug2.sol b/tests/tools/check_upgradeability/contractV2_bug2.sol similarity index 100% rename from tests/tools/check-upgradeability/contractV2_bug2.sol rename to tests/tools/check_upgradeability/contractV2_bug2.sol diff --git a/tests/tools/check-upgradeability/contractV2_struct.sol b/tests/tools/check_upgradeability/contractV2_struct.sol similarity index 100% rename from tests/tools/check-upgradeability/contractV2_struct.sol rename to tests/tools/check_upgradeability/contractV2_struct.sol diff --git a/tests/tools/check-upgradeability/contractV2_struct_bug.sol b/tests/tools/check_upgradeability/contractV2_struct_bug.sol similarity index 100% rename from tests/tools/check-upgradeability/contractV2_struct_bug.sol rename to tests/tools/check_upgradeability/contractV2_struct_bug.sol diff --git a/tests/tools/check-upgradeability/contract_initialization.sol b/tests/tools/check_upgradeability/contract_initialization.sol similarity index 100% rename from tests/tools/check-upgradeability/contract_initialization.sol rename to tests/tools/check_upgradeability/contract_initialization.sol diff --git a/tests/tools/check-upgradeability/contract_v1_var_init.sol b/tests/tools/check_upgradeability/contract_v1_var_init.sol similarity index 100% rename from tests/tools/check-upgradeability/contract_v1_var_init.sol rename to tests/tools/check_upgradeability/contract_v1_var_init.sol diff --git a/tests/tools/check-upgradeability/contract_v2_constant.sol b/tests/tools/check_upgradeability/contract_v2_constant.sol similarity index 100% rename from tests/tools/check-upgradeability/contract_v2_constant.sol rename to tests/tools/check_upgradeability/contract_v2_constant.sol diff --git a/tests/tools/check-upgradeability/proxy.sol b/tests/tools/check_upgradeability/proxy.sol similarity index 100% rename from tests/tools/check-upgradeability/proxy.sol rename to tests/tools/check_upgradeability/proxy.sol diff --git a/tests/tools/check-upgradeability/test_1.txt b/tests/tools/check_upgradeability/test_1.txt similarity index 100% rename from tests/tools/check-upgradeability/test_1.txt rename to tests/tools/check_upgradeability/test_1.txt diff --git a/tests/tools/check-upgradeability/test_10.txt b/tests/tools/check_upgradeability/test_10.txt similarity index 100% rename from tests/tools/check-upgradeability/test_10.txt rename to tests/tools/check_upgradeability/test_10.txt diff --git a/tests/tools/check-upgradeability/test_11.txt b/tests/tools/check_upgradeability/test_11.txt similarity index 100% rename from tests/tools/check-upgradeability/test_11.txt rename to tests/tools/check_upgradeability/test_11.txt diff --git a/tests/tools/check-upgradeability/test_12.txt b/tests/tools/check_upgradeability/test_12.txt similarity index 100% rename from tests/tools/check-upgradeability/test_12.txt rename to tests/tools/check_upgradeability/test_12.txt diff --git a/tests/tools/check-upgradeability/test_13.txt b/tests/tools/check_upgradeability/test_13.txt similarity index 100% rename from tests/tools/check-upgradeability/test_13.txt rename to tests/tools/check_upgradeability/test_13.txt diff --git a/tests/tools/check-upgradeability/test_2.txt b/tests/tools/check_upgradeability/test_2.txt similarity index 100% rename from tests/tools/check-upgradeability/test_2.txt rename to tests/tools/check_upgradeability/test_2.txt diff --git a/tests/tools/check-upgradeability/test_3.txt b/tests/tools/check_upgradeability/test_3.txt similarity index 100% rename from tests/tools/check-upgradeability/test_3.txt rename to tests/tools/check_upgradeability/test_3.txt diff --git a/tests/tools/check-upgradeability/test_4.txt b/tests/tools/check_upgradeability/test_4.txt similarity index 100% rename from tests/tools/check-upgradeability/test_4.txt rename to tests/tools/check_upgradeability/test_4.txt diff --git a/tests/tools/check-upgradeability/test_5.txt b/tests/tools/check_upgradeability/test_5.txt similarity index 100% rename from tests/tools/check-upgradeability/test_5.txt rename to tests/tools/check_upgradeability/test_5.txt diff --git a/tests/tools/check-upgradeability/test_6.txt b/tests/tools/check_upgradeability/test_6.txt similarity index 100% rename from tests/tools/check-upgradeability/test_6.txt rename to tests/tools/check_upgradeability/test_6.txt diff --git a/tests/tools/check-upgradeability/test_7.txt b/tests/tools/check_upgradeability/test_7.txt similarity index 100% rename from tests/tools/check-upgradeability/test_7.txt rename to tests/tools/check_upgradeability/test_7.txt diff --git a/tests/tools/check-upgradeability/test_8.txt b/tests/tools/check_upgradeability/test_8.txt similarity index 100% rename from tests/tools/check-upgradeability/test_8.txt rename to tests/tools/check_upgradeability/test_8.txt diff --git a/tests/tools/check-upgradeability/test_9.txt b/tests/tools/check_upgradeability/test_9.txt similarity index 100% rename from tests/tools/check-upgradeability/test_9.txt rename to tests/tools/check_upgradeability/test_9.txt diff --git a/tests/tools/read-storage/conftest.py b/tests/tools/read-storage/conftest.py new file mode 100644 index 000000000..3e2cfb400 --- /dev/null +++ b/tests/tools/read-storage/conftest.py @@ -0,0 +1,56 @@ +""" +Testing utilities for the read-storage tool +""" + +import shutil +import subprocess +from time import sleep +from typing import Generator +from dataclasses import dataclass +from web3 import Web3 +import pytest + + +@dataclass +class GanacheInstance: + def __init__(self, provider: str, eth_address: str, eth_privkey: str): + self.provider = provider + self.eth_address = eth_address + self.eth_privkey = eth_privkey + + +@pytest.fixture(scope="module", name="ganache") +def fixture_ganache() -> Generator[GanacheInstance, None, None]: + """Fixture that runs ganache""" + if not shutil.which("ganache"): + raise Exception( + "ganache was not found in PATH, you can install it with `npm install -g ganache`" + ) + + # Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH + eth_address = "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6" + eth_privkey = "0xe48ba530a63326818e116be262fd39ae6dcddd89da4b1f578be8afd4e8894b8d" + eth = int(1e18 * 1e6) + port = 8545 + with subprocess.Popen( + f"""ganache + --port {port} + --chain.networkId 1 + --chain.chainId 1 + --account {eth_privkey},{eth} + """.replace( + "\n", " " + ), + shell=True, + ) as p: + + sleep(3) + yield GanacheInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey) + p.kill() + p.wait() + + +@pytest.fixture(scope="module", name="web3") +def fixture_web3(ganache: GanacheInstance): + w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30})) + return w3 diff --git a/tests/tools/read-storage/test_data/storage_layout-0.8.10.sol b/tests/tools/read-storage/test_data/StorageLayout.sol similarity index 95% rename from tests/tools/read-storage/test_data/storage_layout-0.8.10.sol rename to tests/tools/read-storage/test_data/StorageLayout.sol index 28d1428eb..0940b6769 100644 --- a/tests/tools/read-storage/test_data/storage_layout-0.8.10.sol +++ b/tests/tools/read-storage/test_data/StorageLayout.sol @@ -1,5 +1,6 @@ +pragma solidity 0.8.10; // overwrite abi and bin: -// solc tests/storage-layout/storage_layout-0.8.10.sol --abi --bin -o tests/storage-layout --overwrite +// solc StorageLayout.sol --abi --bin --overwrite contract StorageLayout { uint248 packedUint = 1; bool packedBool = true; diff --git a/tests/tools/read-storage/test_data/TEST_unstructured_storage.json b/tests/tools/read-storage/test_data/TEST_unstructured_storage.json new file mode 100644 index 000000000..6dbdbddca --- /dev/null +++ b/tests/tools/read-storage/test_data/TEST_unstructured_storage.json @@ -0,0 +1,56 @@ +{ + "masterCopy": { + "name": "masterCopy", + "type_string": "address", + "slot": 0, + "size": 160, + "offset": 0, + "value": "0x0000000000000000000000000000000000000000", + "elems": {} + }, + "ADMIN_SLOT": { + "name": "ADMIN_SLOT", + "type_string": "address", + "slot": 7616251639890160809447714111544359812065171195189364993079081710756264753419, + "size": 160, + "offset": 0, + "value": "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6", + "elems": {} + }, + "IMPLEMENTATION_SLOT": { + "name": "IMPLEMENTATION_SLOT", + "type_string": "address", + "slot": 24440054405305269366569402256811496959409073762505157381672968839269610695612, + "size": 160, + "offset": 0, + "value": "0x54006763154c764da4AF42a8c3cfc25Ea29765D5", + "elems": {} + }, + "ROLLBACK_SLOT": { + "name": "ROLLBACK_SLOT", + "type_string": "bool", + "slot": 33048860383849004559742813297059419343339852917517107368639918720169455489347, + "size": 1, + "offset": 0, + "value": true, + "elems": {} + }, + "BEACON_SLOT": { + "name": "BEACON_SLOT", + "type_string": "address", + "slot": 74152234768234802001998023604048924213078445070507226371336425913862612794704, + "size": 160, + "offset": 0, + "value": "0x54006763154c764da4AF42a8c3cfc25Ea29765D5", + "elems": {} + }, + "fallback_sload_hardcoded": { + "name": "fallback_sload_hardcoded", + "type_string": "address", + "slot": 89532207833283453166981358064394884954800891875771469636219037672473505217783, + "size": 160, + "offset": 0, + "value": "0x54006763154c764da4AF42a8c3cfc25Ea29765D5", + "elems": {} + } +} \ No newline at end of file diff --git a/tests/tools/read-storage/test_data/UnstructuredStorageLayout.abi b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.abi new file mode 100644 index 000000000..9b579f254 --- /dev/null +++ b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.abi @@ -0,0 +1 @@ +[{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"store","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/tests/tools/read-storage/test_data/UnstructuredStorageLayout.bin b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.bin new file mode 100644 index 000000000..9f20de74c --- /dev/null +++ b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b5061030b806100206000396000f3fe608060405234801561001057600080fd5b506004361061002f5760003560e01c8063975057e71461009757610030565b5b600180035473ffffffffffffffffffffffffffffffffffffffff600054167fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7543660008037600080366000845af43d806000803e816000811461009257816000f35b816000fd5b61009f6100a1565b005b60006100ab6101a8565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146100e657600080fd5b60007f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050600033905080825560007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b905060007354006763154c764da4af42a8c3cfc25ea29765d59050808255807fc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf75561018460016101d6565b6101a17354006763154c764da4af42a8c3cfc25ea29765d5610220565b5050505050565b6000807f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b9050805491505090565b806102037f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd914360001b61025e565b60000160006101000a81548160ff02191690831515021790555050565b600060017fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d5160001c61025291906102a1565b60001b90508181555050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102ac82610268565b91506102b783610268565b9250828210156102ca576102c9610272565b5b82820390509291505056fea2646970667358221220f079473c1b94744ac2818f521ccef06187a433d996633e61e51a86dfb60cc6ff64736f6c634300080a0033 \ No newline at end of file diff --git a/tests/tools/read-storage/test_data/UnstructuredStorageLayout.sol b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.sol new file mode 100644 index 000000000..81b14a119 --- /dev/null +++ b/tests/tools/read-storage/test_data/UnstructuredStorageLayout.sol @@ -0,0 +1,141 @@ +pragma solidity 0.8.10; +// overwrite abi and bin: +// solc UnstructuredStorageLayout.sol --abi --bin --overwrite + +library StorageSlot { + struct AddressSlot { + address value; + } + + struct BooleanSlot { + bool value; + } + + struct Bytes32Slot { + bytes32 value; + } + + struct Uint256Slot { + uint256 value; + } + + /** + * @dev Returns an `AddressSlot` with member `value` located at `slot`. + */ + function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `BooleanSlot` with member `value` located at `slot`. + */ + function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. + */ + function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } + + /** + * @dev Returns an `Uint256Slot` with member `value` located at `slot`. + */ + function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { + /// @solidity memory-safe-assembly + assembly { + r.slot := slot + } + } +} + +contract UnstructuredStorageLayout { + + bytes32 constant ADMIN_SLOT = keccak256("org.zeppelinos.proxy.admin"); + // This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 + bytes32 private constant ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; + bytes32 constant BEACON_SLOT = bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1); + + address internal masterCopy; + + function _admin() internal view returns (address admin) { + bytes32 slot = ADMIN_SLOT; + assembly { + admin := sload(slot) + } + } + + function _implementation() internal view returns (address) { + address _impl; + bytes32 slot = IMPLEMENTATION_SLOT; + assembly { + _impl := sload(slot) + } + return _impl; + } + + function _set_rollback(bool _rollback) internal { + StorageSlot.getBooleanSlot(ROLLBACK_SLOT).value = _rollback; + } + + function _set_beacon(address _beacon) internal { + bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1); + assembly { + sstore(slot, _beacon) + } + } + + function store() external { + address admin = _admin(); + require(admin == address(0)); + + bytes32 admin_slot = ADMIN_SLOT; + address sender = msg.sender; + assembly { + sstore(admin_slot, sender) + } + + bytes32 impl_slot = IMPLEMENTATION_SLOT; + address _impl = address(0x0054006763154c764da4af42a8c3cfc25ea29765d5); + assembly { + sstore(impl_slot, _impl) + sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, _impl) + } + + _set_rollback(true); + _set_beacon(address(0x0054006763154c764da4af42a8c3cfc25ea29765d5)); + } + + // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" + fallback() external { + assembly { // solium-disable-line + let nonsense := sload(sub(1,1)) + let _masterCopy := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) + let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) + calldatacopy(0x0, 0x0, calldatasize()) + let success := delegatecall(gas(), contractLogic, 0x0, calldatasize(), 0, 0) + let retSz := returndatasize() + returndatacopy(0, 0, retSz) + switch success + case 0 { + revert(0, retSz) + } + default { + return(0, retSz) + } + } + } +} diff --git a/tests/tools/read-storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py index ea04a91fe..b056ad056 100644 --- a/tests/tools/read-storage/test_read_storage.py +++ b/tests/tools/read-storage/test_read_storage.py @@ -1,14 +1,9 @@ -import json import re -import shutil -import subprocess -from time import sleep +import json from pathlib import Path -from typing import Generator import pytest from deepdiff import DeepDiff -from web3 import Web3 from web3.contract import Contract from slither import Slither @@ -16,50 +11,6 @@ from slither.tools.read_storage import SlitherReadStorage, RpcInfo TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" -# pylint: disable=too-few-public-methods -class GanacheInstance: - def __init__(self, provider: str, eth_address: str, eth_privkey: str): - self.provider = provider - self.eth_address = eth_address - self.eth_privkey = eth_privkey - - -@pytest.fixture(scope="module", name="web3") -def fixture_web3(ganache: GanacheInstance): - w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30})) - return w3 - - -@pytest.fixture(scope="module", name="ganache") -def fixture_ganache() -> Generator[GanacheInstance, None, None]: - """Fixture that runs ganache""" - if not shutil.which("ganache"): - raise Exception( - "ganache was not found in PATH, you can install it with `npm install -g ganache`" - ) - - # Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH - eth_address = "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6" - eth_privkey = "0xe48ba530a63326818e116be262fd39ae6dcddd89da4b1f578be8afd4e8894b8d" - eth = int(1e18 * 1e6) - port = 8545 - with subprocess.Popen( - f"""ganache - --port {port} - --chain.networkId 1 - --chain.chainId 1 - --account {eth_privkey},{eth} - """.replace( - "\n", " " - ), - shell=True, - ) as p: - - sleep(3) - yield GanacheInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey) - p.kill() - p.wait() - def get_source_file(file_path) -> str: with open(file_path, "r", encoding="utf8") as f: @@ -89,24 +40,29 @@ def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract: # pylint: disable=too-many-locals +@pytest.mark.parametrize( + "test_contract, storage_file", + [("StorageLayout", "storage_layout"), ("UnstructuredStorageLayout", "unstructured_storage")], +) @pytest.mark.usefixtures("web3", "ganache") -def test_read_storage(web3, ganache, solc_binary_path) -> None: +def test_read_storage(test_contract, storage_file, web3, ganache, solc_binary_path) -> None: solc_path = solc_binary_path(version="0.8.10") assert web3.is_connected() - bin_path = Path(TEST_DATA_DIR, "StorageLayout.bin").as_posix() - abi_path = Path(TEST_DATA_DIR, "StorageLayout.abi").as_posix() + bin_path = Path(TEST_DATA_DIR, f"{test_contract}.bin").as_posix() + abi_path = Path(TEST_DATA_DIR, f"{test_contract}.abi").as_posix() bytecode = get_source_file(bin_path) abi = get_source_file(abi_path) contract = deploy_contract(web3, ganache, bytecode, abi) contract.functions.store().transact({"from": ganache.eth_address}) address = contract.address - sl = Slither(Path(TEST_DATA_DIR, "storage_layout-0.8.10.sol").as_posix(), solc=solc_path) + sl = Slither(Path(TEST_DATA_DIR, f"{test_contract}.sol").as_posix(), solc=solc_path) contracts = sl.contracts rpc_info: RpcInfo = RpcInfo(ganache.provider) srs = SlitherReadStorage(contracts, 100, rpc_info) + srs.unstructured = True srs.storage_address = address srs.get_all_storage_variables() srs.get_storage_layout() @@ -116,7 +72,7 @@ def test_read_storage(web3, ganache, solc_binary_path) -> None: slot_infos_json = srs.to_json() json.dump(slot_infos_json, file, indent=4) - expected_file = Path(TEST_DATA_DIR, "TEST_storage_layout.json").as_posix() + expected_file = Path(TEST_DATA_DIR, f"TEST_{storage_file}.json").as_posix() with open(expected_file, "r", encoding="utf8") as f: expected = json.load(f) diff --git a/tests/unit/core/test_constant_folding.py b/tests/unit/core/test_constant_folding.py index 489b4e0ec..6c0cc8295 100644 --- a/tests/unit/core/test_constant_folding.py +++ b/tests/unit/core/test_constant_folding.py @@ -21,39 +21,40 @@ def test_constant_folding_rational(solc_binary_path): variable_a = contract.get_state_variable_from_name("a") assert str(variable_a.type) == "uint256" - assert str(ConstantFolding(variable_a.expression, "uint256").result()) == "10" + assert ConstantFolding(variable_a.expression, "uint256").result().value == 10 variable_b = contract.get_state_variable_from_name("b") assert str(variable_b.type) == "int128" - assert str(ConstantFolding(variable_b.expression, "int128").result()) == "2" + assert ConstantFolding(variable_b.expression, "int128").result().value == 2 variable_c = contract.get_state_variable_from_name("c") assert str(variable_c.type) == "int64" - assert str(ConstantFolding(variable_c.expression, "int64").result()) == "3" + assert ConstantFolding(variable_c.expression, "int64").result().value == 3 variable_d = contract.get_state_variable_from_name("d") assert str(variable_d.type) == "int256" - assert str(ConstantFolding(variable_d.expression, "int256").result()) == "1500" + assert ConstantFolding(variable_d.expression, "int256").result().value == 1500 variable_e = contract.get_state_variable_from_name("e") assert str(variable_e.type) == "uint256" assert ( - str(ConstantFolding(variable_e.expression, "uint256").result()) - == "57896044618658097711785492504343953926634992332820282019728792003956564819968" + ConstantFolding(variable_e.expression, "uint256").result().value + == 57896044618658097711785492504343953926634992332820282019728792003956564819968 ) variable_f = contract.get_state_variable_from_name("f") assert str(variable_f.type) == "uint256" assert ( - str(ConstantFolding(variable_f.expression, "uint256").result()) - == "115792089237316195423570985008687907853269984665640564039457584007913129639935" + ConstantFolding(variable_f.expression, "uint256").result().value + == 115792089237316195423570985008687907853269984665640564039457584007913129639935 ) variable_g = contract.get_state_variable_from_name("g") assert str(variable_g.type) == "int64" - assert str(ConstantFolding(variable_g.expression, "int64").result()) == "-7" + assert ConstantFolding(variable_g.expression, "int64").result().value == -7 +# pylint: disable=too-many-locals def test_constant_folding_binary_expressions(solc_binary_path): sl = Slither( Path(CONSTANT_FOLDING_TEST_ROOT, "constant_folding_binop.sol").as_posix(), @@ -63,51 +64,65 @@ def test_constant_folding_binary_expressions(solc_binary_path): variable_a = contract.get_state_variable_from_name("a") assert str(variable_a.type) == "uint256" - assert str(ConstantFolding(variable_a.expression, "uint256").result()) == "0" + assert ConstantFolding(variable_a.expression, "uint256").result().value == 0 variable_b = contract.get_state_variable_from_name("b") assert str(variable_b.type) == "uint256" - assert str(ConstantFolding(variable_b.expression, "uint256").result()) == "3" + assert ConstantFolding(variable_b.expression, "uint256").result().value == 3 variable_c = contract.get_state_variable_from_name("c") assert str(variable_c.type) == "uint256" - assert str(ConstantFolding(variable_c.expression, "uint256").result()) == "3" + assert ConstantFolding(variable_c.expression, "uint256").result().value == 3 variable_d = contract.get_state_variable_from_name("d") assert str(variable_d.type) == "bool" - assert str(ConstantFolding(variable_d.expression, "bool").result()) == "False" + assert ConstantFolding(variable_d.expression, "bool").result().value is False variable_e = contract.get_state_variable_from_name("e") assert str(variable_e.type) == "bool" - assert str(ConstantFolding(variable_e.expression, "bool").result()) == "False" + assert ConstantFolding(variable_e.expression, "bool").result().value is False variable_f = contract.get_state_variable_from_name("f") assert str(variable_f.type) == "bool" - assert str(ConstantFolding(variable_f.expression, "bool").result()) == "True" + assert ConstantFolding(variable_f.expression, "bool").result().value is True variable_g = contract.get_state_variable_from_name("g") assert str(variable_g.type) == "bool" - assert str(ConstantFolding(variable_g.expression, "bool").result()) == "False" + assert ConstantFolding(variable_g.expression, "bool").result().value is False variable_h = contract.get_state_variable_from_name("h") assert str(variable_h.type) == "bool" - assert str(ConstantFolding(variable_h.expression, "bool").result()) == "False" + assert ConstantFolding(variable_h.expression, "bool").result().value is False variable_i = contract.get_state_variable_from_name("i") assert str(variable_i.type) == "bool" - assert str(ConstantFolding(variable_i.expression, "bool").result()) == "True" + assert ConstantFolding(variable_i.expression, "bool").result().value is True variable_j = contract.get_state_variable_from_name("j") assert str(variable_j.type) == "bool" - assert str(ConstantFolding(variable_j.expression, "bool").result()) == "False" + assert ConstantFolding(variable_j.expression, "bool").result().value is False variable_k = contract.get_state_variable_from_name("k") assert str(variable_k.type) == "bool" - assert str(ConstantFolding(variable_k.expression, "bool").result()) == "True" + assert ConstantFolding(variable_k.expression, "bool").result().value is True variable_l = contract.get_state_variable_from_name("l") assert str(variable_l.type) == "uint256" assert ( - str(ConstantFolding(variable_l.expression, "uint256").result()) - == "115792089237316195423570985008687907853269984665640564039457584007913129639935" + ConstantFolding(variable_l.expression, "uint256").result().value + == 115792089237316195423570985008687907853269984665640564039457584007913129639935 ) + + IMPLEMENTATION_SLOT = contract.get_state_variable_from_name("IMPLEMENTATION_SLOT") + assert str(IMPLEMENTATION_SLOT.type) == "bytes32" + assert ( + int.from_bytes( + ConstantFolding(IMPLEMENTATION_SLOT.expression, "bytes32").result().value, + byteorder="big", + ) + == 24440054405305269366569402256811496959409073762505157381672968839269610695612 + ) + + variable_m = contract.get_state_variable_from_name("m") + assert str(variable_m.type) == "bytes2" + assert ConstantFolding(variable_m.expression, "bytes2").result().value == "ab" diff --git a/tests/unit/core/test_data/constant_folding/constant_folding_binop.sol b/tests/unit/core/test_data/constant_folding/constant_folding_binop.sol index 923418ce7..3935585b8 100644 --- a/tests/unit/core/test_data/constant_folding/constant_folding_binop.sol +++ b/tests/unit/core/test_data/constant_folding/constant_folding_binop.sol @@ -11,4 +11,6 @@ contract BinOp { bool j = true && false; bool k = true || false; uint l = uint(1) - uint(2); + bytes32 IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); + bytes2 m = "ab"; } \ No newline at end of file From eacbf5c224fb85a5f70263d32937700468ee28ff Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 20 Jun 2023 11:35:47 -0500 Subject: [PATCH 085/101] Create dependabot.yml (#1972) --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..dd3015cbc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +--- +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "dev" + schedule: + interval: "weekly" From d56f66338051eb07b2f280a1dd2f355ab6f95394 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 20 Jun 2023 11:36:16 -0500 Subject: [PATCH 086/101] create release action with sigstore (#1957) --- .github/workflows/publish.yml | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..529752f80 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,54 @@ +name: Publish to PyPI + +on: + release: + types: [published] + +jobs: + build-release: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Build distributions + run: | + python -m pip install --upgrade pip + python -m pip install build + python -m build + - name: Upload distributions + uses: actions/upload-artifact@v3 + with: + name: slither-dists + path: dist/ + + publish: + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write # For trusted publishing + codesigning. + contents: write # For attaching signing artifacts to the release. + needs: + - build-release + steps: + - name: fetch dists + uses: actions/download-artifact@v3 + with: + name: slither-dists + path: dist/ + + - name: publish + uses: pypa/gh-action-pypi-publish@v1.8.6 + + - name: sign + uses: sigstore/gh-action-sigstore-python@v1.2.3 + with: + inputs: ./dist/*.tar.gz ./dist/*.whl + release-signing-artifacts: true + bundle-only: true From 7f7ff770d7c558916fee1d530f6d5a0ec7e36e38 Mon Sep 17 00:00:00 2001 From: Paul Razvan Berg Date: Tue, 20 Jun 2023 21:56:44 +0300 Subject: [PATCH 087/101] docs: update recommendation for msg.value-inside-a-loop (#1971) --- slither/detectors/statements/msg_value_in_loop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/statements/msg_value_in_loop.py b/slither/detectors/statements/msg_value_in_loop.py index 55bd9bfc2..83c5658ca 100644 --- a/slither/detectors/statements/msg_value_in_loop.py +++ b/slither/detectors/statements/msg_value_in_loop.py @@ -79,7 +79,7 @@ contract MsgValueInLoop{ # endregion wiki_exploit_scenario WIKI_RECOMMENDATION = """ -Track msg.value through a local variable and decrease its amount on every iteration/usage. +Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`. """ def _detect(self) -> List[Output]: From 068eee83412ad15823be79900dfd1e033056d072 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 20 Jun 2023 17:25:40 -0500 Subject: [PATCH 088/101] add test --- tests/unit/core/test_arithmetic.py | 30 ++++++++++++++++++- .../arithmetic_usage/unchecked_scope.sol | 17 +++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/unit/core/test_data/arithmetic_usage/unchecked_scope.sol diff --git a/tests/unit/core/test_arithmetic.py b/tests/unit/core/test_arithmetic.py index 6de63d767..f82885aa8 100644 --- a/tests/unit/core/test_arithmetic.py +++ b/tests/unit/core/test_arithmetic.py @@ -2,7 +2,8 @@ from pathlib import Path from slither import Slither from slither.utils.arithmetic import unchecked_arithemtic_usage - +from slither.slithir.operations import Binary, Unary, Assignment +from slither.slithir.variables.temporary import TemporaryVariable TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" / "arithmetic_usage" @@ -14,3 +15,30 @@ def test_arithmetic_usage(solc_binary_path) -> None: assert { f.source_mapping.content_hash for f in unchecked_arithemtic_usage(slither.contracts[0]) } == {"2b4bc73cf59d486dd9043e840b5028b679354dd9", "e4ecd4d0fda7e762d29aceb8425f2c5d4d0bf962"} + + +def test_arithmetic_usage(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") + slither = Slither(Path(TEST_DATA_DIR, "test.sol").as_posix(), solc=solc_path) + + assert { + f.source_mapping.content_hash for f in unchecked_arithemtic_usage(slither.contracts[0]) + } == {"2b4bc73cf59d486dd9043e840b5028b679354dd9", "e4ecd4d0fda7e762d29aceb8425f2c5d4d0bf962"} + + +def test_scope_is_checked(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") + slither = Slither(Path(TEST_DATA_DIR, "unchecked_scope.sol").as_posix(), solc=solc_path) + func = slither.get_contract_from_name("TestScope")[0].get_function_from_full_name( + "scope(uint256)" + ) + bin_op_is_checked = {} + for node in func.nodes: + for op in node.irs: + if isinstance(op, (Binary, Unary)): + bin_op_is_checked[op.lvalue] = op.node.scope.is_checked + if isinstance(op, Assignment) and isinstance(op.rvalue, TemporaryVariable): + if op.lvalue.name.startswith("checked"): + assert bin_op_is_checked[op.rvalue] is True + else: + assert bin_op_is_checked[op.rvalue] is False diff --git a/tests/unit/core/test_data/arithmetic_usage/unchecked_scope.sol b/tests/unit/core/test_data/arithmetic_usage/unchecked_scope.sol new file mode 100644 index 000000000..f0cee1f10 --- /dev/null +++ b/tests/unit/core/test_data/arithmetic_usage/unchecked_scope.sol @@ -0,0 +1,17 @@ +contract TestScope { + function scope(uint256 x) public { + uint checked1 = x - x; + unchecked { + uint unchecked1 = x - x; + if (true) { + uint unchecked2 = x - x; + + } + for (uint i = 0; i < 10; i++) { + uint unchecked3 = x - x; + + } + } + uint checked2 = x - x; + } +} \ No newline at end of file From 96b103225f4867caca8fe699ab97ee8cc8727910 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 20 Jun 2023 17:34:14 -0500 Subject: [PATCH 089/101] fix merge/fmt --- slither/solc_parsing/declarations/function.py | 8 +++++--- tests/unit/core/test_arithmetic.py | 9 --------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index aeb05cd05..35ca51aeb 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -726,7 +726,9 @@ class FunctionSolc(CallerContextExpression): self._parse_catch(clause, node, catch_scope, False) return node - def _parse_catch(self, statement: Dict, node: NodeSolc, scope: Scope, add_param: bool) -> NodeSolc: + def _parse_catch( + self, statement: Dict, node: NodeSolc, scope: Scope, add_param: bool + ) -> NodeSolc: block = statement.get("block", None) if block is None: @@ -874,7 +876,7 @@ class FunctionSolc(CallerContextExpression): self.get_children("children"): [variable, init], } - new_node = self._parse_variable_definition(new_statement, new_node) + new_node = self._parse_variable_definition(new_statement, new_node, scope) else: # If we have # var (a, b) = f() @@ -893,7 +895,7 @@ class FunctionSolc(CallerContextExpression): variables.append(variable) new_node = self._parse_variable_definition_init_tuple( - new_statement, i, new_node + new_statement, i, new_node, scope ) i = i + 1 var_identifiers = [] diff --git a/tests/unit/core/test_arithmetic.py b/tests/unit/core/test_arithmetic.py index f82885aa8..760b6d397 100644 --- a/tests/unit/core/test_arithmetic.py +++ b/tests/unit/core/test_arithmetic.py @@ -8,15 +8,6 @@ from slither.slithir.variables.temporary import TemporaryVariable TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" / "arithmetic_usage" -def test_arithmetic_usage(solc_binary_path) -> None: - solc_path = solc_binary_path("0.8.15") - slither = Slither(Path(TEST_DATA_DIR, "test.sol").as_posix(), solc=solc_path) - - assert { - f.source_mapping.content_hash for f in unchecked_arithemtic_usage(slither.contracts[0]) - } == {"2b4bc73cf59d486dd9043e840b5028b679354dd9", "e4ecd4d0fda7e762d29aceb8425f2c5d4d0bf962"} - - def test_arithmetic_usage(solc_binary_path) -> None: solc_path = solc_binary_path("0.8.15") slither = Slither(Path(TEST_DATA_DIR, "test.sol").as_posix(), solc=solc_path) From 8e0e7b79e7603c73fa50e43342cf96e3cf57228d Mon Sep 17 00:00:00 2001 From: xfu Date: Wed, 21 Jun 2023 08:54:37 -0500 Subject: [PATCH 090/101] fix event canonical_name --- slither/core/declarations/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/declarations/event.py b/slither/core/declarations/event.py index 9d42ac224..1b58ff63b 100644 --- a/slither/core/declarations/event.py +++ b/slither/core/declarations/event.py @@ -45,7 +45,7 @@ class Event(ContractLevel, SourceMapping): Returns: str: contract.func_name(type1,type2) """ - return self.contract.name + self.full_name + return self.contract.name + "." + self.full_name @property def elems(self) -> List["EventVariable"]: From 1b5e2e932765316fba8a1166d893a9f1e8d9b746 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 21 Jun 2023 08:56:07 -0500 Subject: [PATCH 091/101] update detector tests to reflect event fmt --- ...lShadowing_0_4_25_shadowing_builtin_symbols_sol__0.txt | 4 ++-- ...lShadowing_0_5_16_shadowing_builtin_symbols_sol__0.txt | 4 ++-- ...alShadowing_0_4_25_shadowing_local_variable_sol__0.txt | 2 +- ...alShadowing_0_5_16_shadowing_local_variable_sol__0.txt | 2 +- ...alShadowing_0_6_11_shadowing_local_variable_sol__0.txt | 2 +- ...calShadowing_0_7_6_shadowing_local_variable_sol__0.txt | 2 +- ...r_NamingConvention_0_4_25_naming_convention_sol__0.txt | 4 ++-- ...r_NamingConvention_0_5_16_naming_convention_sol__0.txt | 4 ++-- ...r_NamingConvention_0_6_11_naming_convention_sol__0.txt | 4 ++-- ...or_NamingConvention_0_7_6_naming_convention_sol__0.txt | 4 ++-- ...edERC20EventParameters_0_4_25_erc20_indexed_sol__0.txt | 8 ++++---- ...edERC20EventParameters_0_5_16_erc20_indexed_sol__0.txt | 8 ++++---- ...edERC20EventParameters_0_6_11_erc20_indexed_sol__0.txt | 8 ++++---- ...xedERC20EventParameters_0_7_6_erc20_indexed_sol__0.txt | 8 ++++---- 14 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_4_25_shadowing_builtin_symbols_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_4_25_shadowing_builtin_symbols_sol__0.txt index 7c94735da..283b93ee5 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_4_25_shadowing_builtin_symbols_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_4_25_shadowing_builtin_symbols_sol__0.txt @@ -12,12 +12,12 @@ FurtherExtendedContract.this (tests/e2e/detectors/test_data/shadowing-builtin/0. BaseContract.now (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#5) (state variable) shadows built-in symbol" -BaseContractrevert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol" - ExtendedContract.assert(bool).msg (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#14) (local variable) shadows built-in symbol" ExtendedContract.assert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#13-15) (function) shadows built-in symbol" +BaseContract.revert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol" + FurtherExtendedContract.require().sha3 (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#26) (local variable) shadows built-in symbol" FurtherExtendedContract.blockhash (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#19) (state variable) shadows built-in symbol" diff --git a/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_5_16_shadowing_builtin_symbols_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_5_16_shadowing_builtin_symbols_sol__0.txt index f576e19e0..2780f9e62 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_5_16_shadowing_builtin_symbols_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_5_16_shadowing_builtin_symbols_sol__0.txt @@ -10,12 +10,12 @@ FurtherExtendedContract.this (tests/e2e/detectors/test_data/shadowing-builtin/0. BaseContract.now (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#5) (state variable) shadows built-in symbol" -BaseContractrevert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol" - ExtendedContract.assert(bool).msg (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#14) (local variable) shadows built-in symbol" ExtendedContract.assert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#13-15) (function) shadows built-in symbol" +BaseContract.revert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol" + FurtherExtendedContract.require().sha3 (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#26) (local variable) shadows built-in symbol" FurtherExtendedContract.blockhash (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#19) (state variable) shadows built-in symbol" diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_4_25_shadowing_local_variable_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_4_25_shadowing_local_variable_sol__0.txt index 913acf4ea..0f98475aa 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_4_25_shadowing_local_variable_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_4_25_shadowing_local_variable_sol__0.txt @@ -10,7 +10,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat - BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#5) (state variable) FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#25) shadows: - - ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#13) (event) + - ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#13) (event) FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#25) shadows: - FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#20-23) (modifier) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_5_16_shadowing_local_variable_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_5_16_shadowing_local_variable_sol__0.txt index 7fcbe83d2..56d139bea 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_5_16_shadowing_local_variable_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_5_16_shadowing_local_variable_sol__0.txt @@ -10,7 +10,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat - BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#5) (state variable) FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#25) shadows: - - ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#13) (event) + - ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#13) (event) FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#25) shadows: - FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#20-23) (modifier) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_6_11_shadowing_local_variable_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_6_11_shadowing_local_variable_sol__0.txt index b4de3265f..a52ac48af 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_6_11_shadowing_local_variable_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_6_11_shadowing_local_variable_sol__0.txt @@ -5,7 +5,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat - BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#5) (state variable) FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#25) shadows: - - ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#13) (event) + - ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#13) (event) FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#25) shadows: - FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#20-23) (modifier) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_7_6_shadowing_local_variable_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_7_6_shadowing_local_variable_sol__0.txt index 43ecfab68..e618b988c 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_7_6_shadowing_local_variable_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_7_6_shadowing_local_variable_sol__0.txt @@ -5,7 +5,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat - BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#5) (state variable) FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#25) shadows: - - ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#13) (event) + - ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#13) (event) FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#25) shadows: - FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#20-23) (modifier) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_4_25_naming_convention_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_4_25_naming_convention_sol__0.txt index 0419c1b9a..ed4177ca1 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_4_25_naming_convention_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_4_25_naming_convention_sol__0.txt @@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#56) is not in mixedCase -Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#23) is not in CapWords - Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#68) is single letter l, O, or I, which should not be used +Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#23) is not in CapWords + Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#41-43) is not in mixedCase Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#30-33) is not in mixedCase diff --git a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_5_16_naming_convention_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_5_16_naming_convention_sol__0.txt index c5fd1f30f..35c11193f 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_5_16_naming_convention_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_5_16_naming_convention_sol__0.txt @@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#56) is not in mixedCase -Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#23) is not in CapWords - Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#68) is single letter l, O, or I, which should not be used +Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#23) is not in CapWords + Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#41-43) is not in mixedCase Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#30-33) is not in mixedCase diff --git a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_6_11_naming_convention_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_6_11_naming_convention_sol__0.txt index 1bbe28843..f692e211b 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_6_11_naming_convention_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_6_11_naming_convention_sol__0.txt @@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#56) is not in mixedCase -Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#23) is not in CapWords - Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#68) is single letter l, O, or I, which should not be used +Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#23) is not in CapWords + Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#41-43) is not in mixedCase Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#30-33) is not in mixedCase diff --git a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_7_6_naming_convention_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_7_6_naming_convention_sol__0.txt index 5f560ba51..af17cabe8 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_7_6_naming_convention_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_7_6_naming_convention_sol__0.txt @@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#56) is not in mixedCase -Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#23) is not in CapWords - Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#68) is single letter l, O, or I, which should not be used +Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#23) is not in CapWords + Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#41-43) is not in mixedCase Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#30-33) is not in mixedCase diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_4_25_erc20_indexed_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_4_25_erc20_indexed_sol__0.txt index a571ef77a..b4097bd10 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_4_25_erc20_indexed_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_4_25_erc20_indexed_sol__0.txt @@ -1,8 +1,8 @@ -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter to +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter owner -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter owner +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter from -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter from +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter spender -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter spender +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter to diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_5_16_erc20_indexed_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_5_16_erc20_indexed_sol__0.txt index 09171d188..77ab9c2bd 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_5_16_erc20_indexed_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_5_16_erc20_indexed_sol__0.txt @@ -1,8 +1,8 @@ -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter to +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter owner -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter owner +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter from -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter from +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter spender -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter spender +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter to diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_6_11_erc20_indexed_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_6_11_erc20_indexed_sol__0.txt index c0cbe9270..9e11f44c8 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_6_11_erc20_indexed_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_6_11_erc20_indexed_sol__0.txt @@ -1,8 +1,8 @@ -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter to +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter owner -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter owner +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter from -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter from +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter spender -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter spender +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter to diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_7_6_erc20_indexed_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_7_6_erc20_indexed_sol__0.txt index 532a8b6cd..8083e8f71 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_7_6_erc20_indexed_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_7_6_erc20_indexed_sol__0.txt @@ -1,8 +1,8 @@ -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter to +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter owner -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter owner +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter from -ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter from +ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter spender -ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter spender +ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter to From e1febdd74ecd2c26fbf1524d872034b57b15e3e2 Mon Sep 17 00:00:00 2001 From: bossjoker1 <1397157763@qq.com> Date: Wed, 21 Jun 2023 09:34:59 -0500 Subject: [PATCH 092/101] Check the respective parameter's storage location for each argument --- .../compiler_bugs/array_by_reference.py | 13 ++++++----- ...rence_0_4_25_array_by_reference_sol__0.txt | 14 +++++++----- ...rence_0_5_16_array_by_reference_sol__0.txt | 14 +++++++----- ...rence_0_6_11_array_by_reference_sol__0.txt | 14 +++++++----- ...erence_0_7_6_array_by_reference_sol__0.txt | 14 +++++++----- .../0.4.25/array_by_reference.sol | 21 ++++++++++++++++++ .../0.4.25/array_by_reference.sol-0.4.25.zip | Bin 4879 -> 6184 bytes .../0.5.16/array_by_reference.sol | 21 ++++++++++++++++++ .../0.5.16/array_by_reference.sol-0.5.16.zip | Bin 4925 -> 6194 bytes .../0.6.11/array_by_reference.sol | 21 ++++++++++++++++++ .../0.6.11/array_by_reference.sol-0.6.11.zip | Bin 4841 -> 6086 bytes .../0.7.6/array_by_reference.sol | 21 ++++++++++++++++++ .../0.7.6/array_by_reference.sol-0.7.6.zip | Bin 4741 -> 5972 bytes 13 files changed, 124 insertions(+), 29 deletions(-) diff --git a/slither/detectors/compiler_bugs/array_by_reference.py b/slither/detectors/compiler_bugs/array_by_reference.py index 04dfe085a..47e2af581 100644 --- a/slither/detectors/compiler_bugs/array_by_reference.py +++ b/slither/detectors/compiler_bugs/array_by_reference.py @@ -133,7 +133,7 @@ As a result, Bob's usage of the contract is incorrect.""" continue # Verify one of these parameters is an array in storage. - for arg in ir.arguments: + for (param, arg) in zip(ir.function.parameters, ir.arguments): # Verify this argument is a variable that is an array type. if not isinstance(arg, (StateVariable, LocalVariable)): continue @@ -141,8 +141,11 @@ As a result, Bob's usage of the contract is incorrect.""" continue # If it is a state variable OR a local variable referencing storage, we add it to the list. - if isinstance(arg, StateVariable) or ( - isinstance(arg, LocalVariable) and arg.location == "storage" + if ( + isinstance(arg, StateVariable) + or (isinstance(arg, LocalVariable) and arg.location == "storage") + ) and ( + isinstance(param.type, ArrayType) and param.location != "storage" ): results.append((node, arg, ir.function)) return results @@ -165,9 +168,9 @@ As a result, Bob's usage of the contract is incorrect.""" calling_node.function, " passes array ", affected_argument, - "by reference to ", + " by reference to ", invoked_function, - "which only takes arrays by value\n", + " which only takes arrays by value\n", ] res = self.generate_result(info) diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt index f056bea10..5cb8add39 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt index 4264c809a..6e97d8cc2 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt index e71930b51..39574b5f5 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt index 7c0f9ccd9..74ea36a0c 100644 --- a/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt +++ b/tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt @@ -1,12 +1,14 @@ -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value -D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value -C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value +C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value +D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value -C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value +C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value + +E.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#63-66) which only takes arrays by value diff --git a/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol b/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol index 304af6a48..c2707601a 100644 --- a/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol +++ b/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol @@ -48,4 +48,25 @@ contract D { } +} + +contract E { + uint[1] public x; // storage + uint[1] public y; // storage + + function f() public { + uint[1] memory temp; + setByValue(temp, x); // can set temp, but cannot set x + setByRef(temp, y); // can set temp and y + } + + function setByValue(uint[1] memory arr, uint[1] memory arr2) internal { + arr[0] = 1; + arr2[0] = 2; + } + + function setByRef(uint[1] memory arr, uint[1] storage arr2) internal { + arr[0] = 2; + arr2[0] = 3; + } } \ No newline at end of file diff --git a/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol-0.4.25.zip b/tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol-0.4.25.zip index d9daf8f4d7dc27c4f371b018a31cc9736180c278..96bdaa0f85c484feec1e0f47ea8e3409f0cd1dc9 100644 GIT binary patch delta 5938 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 G0001;R*ZlE From b89ca1a1e05130b05832fe3696799f88f86828e9 Mon Sep 17 00:00:00 2001 From: 0xGusMcCrae <0xGusMcCrae@protonmail.com> Date: Wed, 21 Jun 2023 15:30:41 -0400 Subject: [PATCH 093/101] linting --- slither/detectors/variables/similar_variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/detectors/variables/similar_variables.py b/slither/detectors/variables/similar_variables.py index 4aef01b03..dccaf09c4 100644 --- a/slither/detectors/variables/similar_variables.py +++ b/slither/detectors/variables/similar_variables.py @@ -77,7 +77,7 @@ class SimilarVarsDetection(AbstractDetector): _v2_name_lower = v2.name.lower() if _v1_name_lower != _v2_name_lower: if SimilarVarsDetection.similar(_v1_name_lower, _v2_name_lower): - ret.add((v1, v2)) + ret.add((v1, v2)) return ret From a6fb92f17962cb8811782921163561b94cb7a6bd Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 23 Jun 2023 09:19:31 +0200 Subject: [PATCH 094/101] Bound function search to first parameter type --- slither/solc_parsing/declarations/using_for_top_level.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/using_for_top_level.py b/slither/solc_parsing/declarations/using_for_top_level.py index 8ac5b4462..3b7bb280c 100644 --- a/slither/solc_parsing/declarations/using_for_top_level.py +++ b/slither/solc_parsing/declarations/using_for_top_level.py @@ -112,7 +112,12 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- self, operator: str, function_name: str, type_name: TypeAliasTopLevel ) -> None: for tl_function in self.compilation_unit.functions_top_level: - if tl_function.name == function_name: + # The library function is bound to the first parameter's type + if ( + tl_function.name == function_name + and tl_function.parameters + and type_name == tl_function.parameters[0].type + ): type_name.operators[operator] = tl_function break From 3f1db7af7799da2172b9b8e93b839131602ec91c Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 23 Jun 2023 09:21:03 +0200 Subject: [PATCH 095/101] Look for library functions in the current scope --- slither/solc_parsing/declarations/using_for_top_level.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/solc_parsing/declarations/using_for_top_level.py b/slither/solc_parsing/declarations/using_for_top_level.py index 3b7bb280c..fe72e5780 100644 --- a/slither/solc_parsing/declarations/using_for_top_level.py +++ b/slither/solc_parsing/declarations/using_for_top_level.py @@ -111,7 +111,7 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- def _analyze_operator( self, operator: str, function_name: str, type_name: TypeAliasTopLevel ) -> None: - for tl_function in self.compilation_unit.functions_top_level: + for tl_function in self._using_for.file_scope.functions: # The library function is bound to the first parameter's type if ( tl_function.name == function_name From f2accfd77d3d256be0e300c376abdbb7fc2443b9 Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 23 Jun 2023 09:32:26 +0200 Subject: [PATCH 096/101] Add test --- tests/e2e/solc_parsing/test_ast_parsing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py index b694d1044..307e6736f 100644 --- a/tests/e2e/solc_parsing/test_ast_parsing.py +++ b/tests/e2e/solc_parsing/test_ast_parsing.py @@ -458,6 +458,7 @@ ALL_TESTS = [ "assembly-functions.sol", ["0.6.9", "0.7.6", "0.8.16"], ), + Test("user_defined_operators-0.8.19.sol", ["0.8.19"]), ] # create the output folder if needed try: From ace672e27d1e97375cc7359b1f7a05d240a6ca6d Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 23 Jun 2023 09:41:29 +0200 Subject: [PATCH 097/101] Rename tests to run to ALL_TESTS --- tests/e2e/detectors/test_detectors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index d003e7ce0..a34f1932e 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -56,7 +56,7 @@ def id_test(test_item: Test): return f"{test_item.detector.__name__}-{test_item.solc_ver}-{test_item.test_file}" -ALL_TEST_OBJECTS = [ +ALL_TESTS = [ Test( all_detectors.UninitializedFunctionPtrsConstructor, "uninitialized_function_ptr_constructor.sol", @@ -1656,7 +1656,7 @@ GENERIC_PATH = "/GENERIC_PATH" TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" # pylint: disable=too-many-locals -@pytest.mark.parametrize("test_item", ALL_TEST_OBJECTS, ids=id_test) +@pytest.mark.parametrize("test_item", ALL_TESTS, ids=id_test) def test_detector(test_item: Test, snapshot): test_dir_path = Path( TEST_DATA_DIR, @@ -1704,5 +1704,5 @@ if __name__ == "__main__": "To generate the zip artifacts run\n\tpython tests/e2e/tests/test_detectors.py --compile" ) elif sys.argv[1] == "--compile": - for next_test in ALL_TEST_OBJECTS: + for next_test in ALL_TESTS: _generate_compile(next_test, skip_existing=True) From 940a0a17d911af07dc2994dc224cdc1fc034973d Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 23 Jun 2023 09:42:49 +0200 Subject: [PATCH 098/101] Update CONTRIBUTING.md --- CONTRIBUTING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c00fda8aa..0ebaa8d05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,7 +81,7 @@ For each new detector, at least one regression tests must be present. 1. Create a folder in `tests/e2e/detectors/test_data` with the detector's argument name. 2. Create a test contract in `tests/e2e/detectors/test_data//`. -3. Update `ALL_TEST` in `tests/e2e/detectors/test_detectors.py` +3. Update `ALL_TESTS` in `tests/e2e/detectors/test_detectors.py` 4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a zip file of the compilation artifacts. 5. `pytest tests/e2e/detectors/test_detectors.py --insta update-new`. This will generate a snapshot of the detector output in `tests/e2e/detectors/snapshots/`. If updating an existing detector, run `pytest tests/e2e/detectors/test_detectors.py --insta review` and accept or reject the updates. 6. Run `pytest tests/e2e/detectors/test_detectors.py` to ensure everything worked. Then, add and commit the files to git. @@ -97,8 +97,9 @@ For each new detector, at least one regression tests must be present. 1. Create a test in `tests/e2e/solc_parsing/` 2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git. -3. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git. -4. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked. +3. Update `ALL_TESTS` in `tests/e2e/solc_parsing/test_ast_parsing.py` +4. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git. +5. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked. > ##### Helpful commands for parsing tests > From e1fd6702f9bb41a16e664c3bcad340cbc3046e6c Mon Sep 17 00:00:00 2001 From: Simone Date: Fri, 23 Jun 2023 09:48:08 +0200 Subject: [PATCH 099/101] Minor --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ebaa8d05..5cf02136b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,7 +81,7 @@ For each new detector, at least one regression tests must be present. 1. Create a folder in `tests/e2e/detectors/test_data` with the detector's argument name. 2. Create a test contract in `tests/e2e/detectors/test_data//`. -3. Update `ALL_TESTS` in `tests/e2e/detectors/test_detectors.py` +3. Update `ALL_TESTS` in `tests/e2e/detectors/test_detectors.py`. 4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a zip file of the compilation artifacts. 5. `pytest tests/e2e/detectors/test_detectors.py --insta update-new`. This will generate a snapshot of the detector output in `tests/e2e/detectors/snapshots/`. If updating an existing detector, run `pytest tests/e2e/detectors/test_detectors.py --insta review` and accept or reject the updates. 6. Run `pytest tests/e2e/detectors/test_detectors.py` to ensure everything worked. Then, add and commit the files to git. @@ -97,7 +97,7 @@ For each new detector, at least one regression tests must be present. 1. Create a test in `tests/e2e/solc_parsing/` 2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git. -3. Update `ALL_TESTS` in `tests/e2e/solc_parsing/test_ast_parsing.py` +3. Update `ALL_TESTS` in `tests/e2e/solc_parsing/test_ast_parsing.py`. 4. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git. 5. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked. From 8b0fd32cbe32b6e873bccc07de5231f8793e1686 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 23 Jun 2023 09:33:05 -0500 Subject: [PATCH 100/101] use enum instead of value in config, lint --- slither/__main__.py | 1 - slither/utils/command_line.py | 18 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 4fcfd896d..d9201a90d 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -36,7 +36,6 @@ from slither.utils.output_capture import StandardOutputCapture from slither.utils.colors import red, set_colorization_enabled from slither.utils.command_line import ( FailOnLevel, - migrate_config_options, output_detectors, output_results_to_markdown, output_detectors_json, diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 8518ada4a..082472582 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -54,7 +54,7 @@ defaults_flag_in_config = { "exclude_low": False, "exclude_medium": False, "exclude_high": False, - "fail_on": FailOnLevel.PEDANTIC.value, + "fail_on": FailOnLevel.PEDANTIC, "json": None, "sarif": None, "json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES), @@ -118,22 +118,22 @@ def migrate_config_options(args: argparse.Namespace, key: str, elem): if key.startswith("fail_") and getattr(args, "fail_on") == defaults_flag_in_config["fail_on"]: if key == "fail_pedantic": pedantic_setting = elem - fail_on = pedantic_setting and FailOnLevel.PEDANTIC or FailOnLevel.NONE + fail_on = FailOnLevel.PEDANTIC if pedantic_setting else FailOnLevel.NONE setattr(args, "fail_on", fail_on) - logger.info( - "Migrating fail_pedantic: {} as fail_on: {}".format(pedantic_setting, fail_on.value) - ) - elif key == "fail_low" and elem == True: + logger.info(f"Migrating fail_pedantic: {pedantic_setting} as fail_on: {fail_on.value}") + elif key == "fail_low" and elem is True: logger.info("Migrating fail_low: true -> fail_on: low") setattr(args, "fail_on", FailOnLevel.LOW) - elif key == "fail_medium" and elem == True: + + elif key == "fail_medium" and elem is True: logger.info("Migrating fail_medium: true -> fail_on: medium") setattr(args, "fail_on", FailOnLevel.MEDIUM) - elif key == "fail_high" and elem == True: + + elif key == "fail_high" and elem is True: logger.info("Migrating fail_high: true -> fail_on: high") setattr(args, "fail_on", FailOnLevel.HIGH) else: - logger.warn(yellow("Key {} was deprecated but no migration was provided".format(key))) + logger.warning(yellow(f"Key {key} was deprecated but no migration was provided")) def output_to_markdown( From 0e86f3d3045fd3669e19824ff950b160438c26c1 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Fri, 23 Jun 2023 11:23:22 -0500 Subject: [PATCH 101/101] 0.9.4 --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 70d4f71fd..798d43936 100644 --- a/setup.py +++ b/setup.py @@ -8,15 +8,15 @@ setup( description="Slither is a Solidity static analysis framework written in Python 3.", url="https://github.com/crytic/slither", author="Trail of Bits", - version="0.9.3", + version="0.9.4", packages=find_packages(), python_requires=">=3.8", install_requires=[ "packaging", "prettytable>=3.3.0", "pycryptodome>=3.4.6", - # "crytic-compile>=0.3.1,<0.4.0", - "crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile", + "crytic-compile>=0.3.2,<0.4.0", + # "crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile", "web3>=6.0.0", "eth-abi>=4.0.0", "eth-typing>=3.0.0",