diff --git a/README.md b/README.md index ec9e762b0..846b8c6eb 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ Run Slither on a single file: $ slither tests/uninitialized.sol ``` -For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation. +For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation. + +Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. ## Detectors @@ -104,7 +106,7 @@ Num | Printer | Description ## How to install -Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. +Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler. ### Using Pip diff --git a/scripts/travis_test_dapp.sh b/scripts/travis_test_dapp.sh index 934261312..05a0b823f 100755 --- a/scripts/travis_test_dapp.sh +++ b/scripts/travis_test_dapp.sh @@ -7,6 +7,9 @@ cd test_dapp curl https://nixos.org/nix/install | sh . "$HOME/.nix-profile/etc/profile.d/nix.sh" +nix-env -iA nixpkgs.cachix +cachix use dapp + git clone --recursive https://github.com/dapphub/dapptools $HOME/.dapp/dapptools nix-env -f $HOME/.dapp/dapptools -iA dapp seth solc hevm ethsign @@ -14,7 +17,7 @@ dapp init slither . -if [ $? -eq 23 ] +if [ $? -eq 22 ] then exit 0 fi diff --git a/scripts/travis_test_etherscan.sh b/scripts/travis_test_etherscan.sh index 68b03f89f..57b026cc5 100755 --- a/scripts/travis_test_etherscan.sh +++ b/scripts/travis_test_etherscan.sh @@ -18,7 +18,7 @@ fi slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --solc "./solc-0.4.25" -if [ $? -ne 75 ] +if [ $? -ne 70 ] then echo "Etherscan test failed" exit -1 diff --git a/setup.py b/setup.py index 7889ab8af..018dcd4a4 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,8 @@ setup( python_requires='>=3.6', install_requires=['prettytable>=0.7.2', 'pysha3>=1.0.2', - 'crytic-compile>=0.1.3'], - #dependency_links=['git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile'], + 'crytic-compile'], + dependency_links=['git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile'], license='AGPL-3.0', long_description=open('README.md').read(), entry_points={ diff --git a/slither/__main__.py b/slither/__main__.py index b6a80bc42..364b23543 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -617,7 +617,7 @@ def main_impl(all_detector_classes, all_printer_classes): if printer_classes: logger.info('%s analyzed (%d contracts)', filename, number_contracts) else: - logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results)) + logger.info('%s analyzed (%d contracts with %d detectors), %d result(s) found', filename, number_contracts, len(detector_classes), len(results)) if args.ignore_return_value: return diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 71f22ddfc..998b4d970 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -55,6 +55,7 @@ class FunctionType(Enum): CONSTRUCTOR = 1 FALLBACK = 2 CONSTRUCTOR_VARIABLES = 3 # Fake function to hold variable declaration statements + CONSTRUCTOR_CONSTANT_VARIABLES = 4 # Fake function to hold variable declaration statements class Function(ChildContract, ChildInheritance, SourceMapping): """ @@ -153,6 +154,8 @@ class Function(ChildContract, ChildInheritance, SourceMapping): return 'fallback' elif self._function_type == FunctionType.CONSTRUCTOR_VARIABLES: return 'slitherConstructorVariables' + elif self._function_type == FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES: + return 'slitherConstructorConstantVariables' return self._name @property @@ -245,9 +248,9 @@ class Function(ChildContract, ChildInheritance, SourceMapping): def is_constructor_variables(self): """ bool: True if the function is the constructor of the variables - Slither has a inbuilt function to hold the state variables initialization + Slither has inbuilt functions to hold the state variables initialization """ - return self._function_type == FunctionType.CONSTRUCTOR_VARIABLES + return self._function_type in [FunctionType.CONSTRUCTOR_VARIABLES, FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES] @property def is_fallback(self): diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index e2250ba3a..732488780 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -172,14 +172,24 @@ class ExternalFunction(AbstractDetector): if is_called: continue - # Loop for each function definition, and recommend it be declared external. - for function_definition in all_function_definitions: - txt = "{} ({}) should be declared external\n" - info = txt.format(function_definition.canonical_name, - function_definition.source_mapping_str) - - json = self.generate_json_result(info) + # As we collect all shadowed functions in get_all_function_definitions + # Some function coming from a base might already been declared as external + all_function_definitions = [f for f in all_function_definitions if f.visibility == 'public' and + f.contract == f.contract_declarer] + if all_function_definitions: + function_definition = all_function_definitions[0] + all_function_definitions = all_function_definitions[1:] + + txt = f"{function_definition.full_name} should be declared external:\n" + txt += f"\t- {function_definition.canonical_name} ({function_definition.source_mapping_str})\n" + for other_function_definition in all_function_definitions: + txt += f"\t- {other_function_definition.canonical_name}" + txt += f" ({other_function_definition.source_mapping_str})\n" + + json = self.generate_json_result(txt) self.add_function_to_json(function_definition, json) + for other_function_definition in all_function_definitions: + self.add_function_to_json(other_function_definition, json) results.append(json) return results diff --git a/slither/detectors/statements/calls_in_loop.py b/slither/detectors/statements/calls_in_loop.py index 27622ad4a..092003c8f 100644 --- a/slither/detectors/statements/calls_in_loop.py +++ b/slither/detectors/statements/calls_in_loop.py @@ -16,7 +16,7 @@ class MultipleCallsInLoop(AbstractDetector): IMPACT = DetectorClassification.LOW CONFIDENCE = DetectorClassification.MEDIUM - WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation/_edit#calls-inside-a-loop' + WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop' WIKI_TITLE = 'Calls inside a loop' diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 8f03cae8e..378a0dbf4 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -31,8 +31,8 @@ class UnusedStateVars(AbstractDetector): # Get all the variables read in all the functions and modifiers all_functions = (contract.all_functions_called + contract.modifiers) - variables_used = [x.state_variables_read + x.state_variables_written for x in - all_functions] + variables_used = [x.state_variables_read for x in all_functions] + variables_used += [x.state_variables_written for x in all_functions if not x.is_constructor_variables] array_candidates = [x.variables for x in all_functions] array_candidates = [i for sl in array_candidates for i in sl] + contract.state_variables @@ -41,9 +41,12 @@ class UnusedStateVars(AbstractDetector): array_candidates = [i for sl in array_candidates for i in sl] array_candidates = [v for v in array_candidates if isinstance(v, StateVariable)] + + # Flat list variables_used = [item for sublist in variables_used for item in sublist] variables_used = list(set(variables_used + array_candidates)) + # Return the variables unused that are not public return [x for x in contract.variables if x not in variables_used and x.visibility != 'public'] diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py index 2e126f8a3..d83fbb759 100644 --- a/slither/printers/summary/human_summary.py +++ b/slither/printers/summary/human_summary.py @@ -66,11 +66,16 @@ class PrinterHumanSummary(AbstractPrinter): logger = logging.getLogger('Detectors') logger.setLevel(logging.ERROR) + checks_optimization = self.slither.detectors_optimization checks_informational = self.slither.detectors_informational checks_low = self.slither.detectors_low checks_medium = self.slither.detectors_medium checks_high = self.slither.detectors_high + issues_optimization = [c.detect() for c in checks_optimization] + issues_optimization = [c for c in issues_optimization if c] + issues_optimization = [item for sublist in issues_optimization for item in sublist] + issues_informational = [c.detect() for c in checks_informational] issues_informational = [c for c in issues_informational if c] issues_informational = [item for sublist in issues_informational for item in sublist] @@ -89,14 +94,16 @@ class PrinterHumanSummary(AbstractPrinter): - return (len(issues_informational), + return (len(issues_optimization), + len(issues_informational), len(issues_low), len(issues_medium), len(issues_high)) def get_detectors_result(self): - issues_informational, issues_low, issues_medium, issues_high = self._get_detectors_result() - txt = "Number of informational issues: {}\n".format(green(issues_informational)) + issues_optimization, issues_informational, issues_low, issues_medium, issues_high = self._get_detectors_result() + txt = "Number of optimization issues: {}\n".format(green(issues_optimization)) + txt += "Number of informational issues: {}\n".format(green(issues_informational)) txt += "Number of low issues: {}\n".format(green(issues_low)) if issues_medium > 0: txt += "Number of medium issues: {}\n".format(yellow(issues_medium)) diff --git a/slither/slither.py b/slither/slither.py index 9a7732404..1152a7ddb 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -122,6 +122,10 @@ class Slither(SlitherSolc): def detectors_informational(self): return [d for d in self.detectors if d.IMPACT == DetectorClassification.INFORMATIONAL] + @property + def detectors_optimization(self): + return [d for d in self.detectors if d.IMPACT == DetectorClassification.OPTIMIZATION] + def register_detector(self, detector_class): """ :param detector_class: Class inheriting from `AbstractDetector`. diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index c50a4f8a6..af564602e 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -375,6 +375,10 @@ def propagate_types(ir, node): if t is None: return + if isinstance(t, ElementaryType) and t.name == 'address': + if can_be_solidity_func(ir): + return convert_to_solidity_func(ir) + # convert library if t in using_for or '*' in using_for: new_ir = convert_to_library(ir, node, using_for) @@ -392,7 +396,8 @@ def propagate_types(ir, node): if isinstance(t, ElementaryType) and t.name == 'address': if ir.destination.name == 'this': return convert_type_of_high_and_internal_level_call(ir, node.function.contract) - return convert_to_low_level(ir) + if can_be_low_level(ir): + return convert_to_low_level(ir) # Convert push operations # May need to insert a new operation @@ -605,13 +610,21 @@ def extract_tmp_call(ins, contract): ################################################################################### ################################################################################### +def can_be_low_level(ir): + return ir.function_name in ['transfer', + 'send', + 'call', + 'delegatecall', + 'callcode', + 'staticcall'] + def convert_to_low_level(ir): """ Convert to a transfer/send/or low level call The funciton assume to receive a correct IR The checks must be done by the caller - Additionally convert abi... to solidityfunction + Must be called after can_be_low_level """ if ir.function_name == 'transfer': assert len(ir.arguments) == 1 @@ -622,20 +635,6 @@ def convert_to_low_level(ir): ir = Send(ir.destination, ir.arguments[0], ir.lvalue) ir.lvalue.set_type(ElementaryType('bool')) return ir - elif ir.destination.name == 'abi' and ir.function_name in ['encode', - 'encodePacked', - 'encodeWithSelector', - 'encodeWithSignature', - 'decode']: - - call = SolidityFunction('abi.{}()'.format(ir.function_name)) - new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call) - new_ir.arguments = ir.arguments - if isinstance(call.return_type, list) and len(call.return_type) == 1: - new_ir.lvalue.set_type(call.return_type[0]) - else: - new_ir.lvalue.set_type(call.return_type) - return new_ir elif ir.function_name in ['call', 'delegatecall', 'callcode', @@ -652,6 +651,29 @@ def convert_to_low_level(ir): return new_ir raise SlithIRError('Incorrect conversion to low level {}'.format(ir)) + +def can_be_solidity_func(ir): + return ir.destination.name == 'abi' and ir.function_name in ['encode', + 'encodePacked', + 'encodeWithSelector', + 'encodeWithSignature', + 'decode'] + +def convert_to_solidity_func(ir): + """ + Must be called after can_be_solidity_func + :param ir: + :return: + """ + call = SolidityFunction('abi.{}()'.format(ir.function_name)) + new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call) + new_ir.arguments = ir.arguments + if isinstance(call.return_type, list) and len(call.return_type) == 1: + new_ir.lvalue.set_type(call.return_type[0]) + else: + new_ir.lvalue.set_type(call.return_type) + return new_ir + def convert_to_push(ir, node): """ Convert a call to a PUSH operaiton @@ -815,12 +837,11 @@ def convert_type_of_high_and_internal_level_call(ir, contract): func = function break # lowlelvel lookup needs to be done at last step - if not func and ir.function_name in ['call', - 'delegatecall', - 'callcode', - 'transfer', - 'send']: - return convert_to_low_level(ir) + if not func: + if can_be_low_level(ir): + return convert_to_low_level(ir) + if can_be_solidity_func(ir): + return convert_to_solidity_func(ir) if not func: logger.error('Function not found {}'.format(sig)) ir.function = func diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index 1775084f6..08be502e2 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -37,10 +37,12 @@ class NodeSolc(Node): if self.type == NodeType.VARIABLE: # Update the expression to be an assignement to the variable #print(self.variable_declaration) - self._expression = AssignmentOperation(Identifier(self.variable_declaration), - self.expression, - AssignmentOperationType.ASSIGN, - self.variable_declaration.type) + _expression = AssignmentOperation(Identifier(self.variable_declaration), + self.expression, + AssignmentOperationType.ASSIGN, + self.variable_declaration.type) + _expression.set_offset(self.expression.source_mapping, self.slither) + self._expression = _expression expression = self.expression pp = ReadVar(expression) diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index d36e9150a..ebb43a185 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -382,6 +382,7 @@ class ContractSolc04(Contract): AssignmentOperationType.ASSIGN, variable.type) + expression.set_offset(variable.source_mapping, self.slither) node.add_expression(expression) return node @@ -408,12 +409,36 @@ class ContractSolc04(Contract): prev_node.add_son(next_node) next_node.add_father(prev_node) counter += 1 + break + + for (idx, variable_candidate) in enumerate(self.state_variables): + if variable_candidate.expression and variable_candidate.is_constant: + + constructor_variable = Function() + constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES) + constructor_variable.set_contract(self) + constructor_variable.set_contract_declarer(self) + constructor_variable.set_visibility('internal') + # For now, source mapping of the constructor variable is the whole contract + # Could be improved with a targeted source mapping + constructor_variable.set_offset(self.source_mapping, self.slither) + self._functions[constructor_variable.canonical_name] = constructor_variable + + prev_node = self._create_node(constructor_variable, 0, variable_candidate) + counter = 1 + for v in self.state_variables[idx+1:]: + if v.expression and v.is_constant: + next_node = self._create_node(constructor_variable, counter, v) + prev_node.add_son(next_node) + next_node.add_father(prev_node) + counter += 1 break + def analyze_state_variables(self): for var in self.variables: var.analyze(self) diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 15b222989..e54f7cce6 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -499,7 +499,8 @@ class FunctionSolc(Function): variables = statement['declarations'] count = len(variables) - if statement['initialValue']['nodeType'] == 'TupleExpression': + if statement['initialValue']['nodeType'] == 'TupleExpression' and \ + len(statement['initialValue']['components']) == count: inits = statement['initialValue']['components'] i = 0 new_node = node diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index e76deb8fd..f256611f6 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -228,7 +228,7 @@ def filter_name(value): ################################################################################### def parse_call(expression, caller_context): - + src = expression['src'] if caller_context.is_compact_ast: attributes = expression type_conversion = expression['kind'] == 'typeConversion' @@ -261,6 +261,7 @@ def parse_call(expression, caller_context): expression = parse_expression(expression_to_parse, caller_context) t = TypeConversion(expression, type_call) + t.set_offset(src, caller_context.slither) return t if caller_context.is_compact_ast: @@ -276,7 +277,7 @@ def parse_call(expression, caller_context): if isinstance(called, SuperCallExpression): return SuperCallExpression(called, arguments, type_return) call_expression = CallExpression(called, arguments, type_return) - call_expression.set_offset(expression['src'], caller_context.slither) + call_expression.set_offset(src, caller_context.slither) return call_expression def parse_super_name(expression, is_compact_ast): @@ -311,7 +312,9 @@ def _parse_elementary_type_name_expression(expression, is_compact_ast, caller_co value = expression['attributes']['value'] t = parse_type(UnknownType(value), caller_context) - return ElementaryTypeNameExpression(t) + e = ElementaryTypeNameExpression(t) + e.set_offset(expression['src'], caller_context.slither) + return e def parse_expression(expression, caller_context): """ @@ -345,6 +348,7 @@ def parse_expression(expression, caller_context): # The AST naming does not follow the spec name = expression[caller_context.get_key()] is_compact_ast = caller_context.is_compact_ast + src = expression['src'] if name == 'UnaryOperation': if is_compact_ast: @@ -360,6 +364,7 @@ def parse_expression(expression, caller_context): assert len(expression['children']) == 1 expression = parse_expression(expression['children'][0], caller_context) unary_op = UnaryOperation(expression, operation_type) + unary_op.set_offset(src, caller_context.slither) return unary_op elif name == 'BinaryOperation': @@ -377,6 +382,7 @@ def parse_expression(expression, caller_context): left_expression = parse_expression(expression['children'][0], caller_context) right_expression = parse_expression(expression['children'][1], caller_context) binary_op = BinaryOperation(left_expression, right_expression, operation_type) + binary_op.set_offset(src, caller_context.slither) return binary_op elif name == 'FunctionCall': @@ -414,6 +420,7 @@ def parse_expression(expression, caller_context): if elems[idx] == '': expressions.insert(idx, None) t = TupleExpression(expressions) + t.set_offset(src, caller_context.slither) return t elif name == 'Conditional': @@ -428,6 +435,7 @@ def parse_expression(expression, caller_context): then_expression = parse_expression(children[1], caller_context) else_expression = parse_expression(children[2], caller_context) conditional = ConditionalExpression(if_expression, then_expression, else_expression) + conditional.set_offset(src, caller_context.slither) return conditional elif name == 'Assignment': @@ -449,6 +457,7 @@ def parse_expression(expression, caller_context): operation_return_type = attributes['type'] assignement = AssignmentOperation(left_expression, right_expression, operation_type, operation_return_type) + assignement.set_offset(src, caller_context.slither) return assignement @@ -498,6 +507,7 @@ def parse_expression(expression, caller_context): else: type = ElementaryType('string') literal = Literal(value, type, subdenomination) + literal.set_offset(src, caller_context.slither) return literal elif name == 'Identifier': @@ -528,7 +538,7 @@ def parse_expression(expression, caller_context): var = find_variable(value, caller_context, referenced_declaration) identifier = Identifier(var) - identifier.set_offset(expression['src'], caller_context.slither) + identifier.set_offset(src, caller_context.slither) return identifier elif name == 'IndexAccess': @@ -551,6 +561,7 @@ def parse_expression(expression, caller_context): left_expression = parse_expression(left, caller_context) right_expression = parse_expression(right, caller_context) index = IndexAccess(left_expression, right_expression, index_type) + index.set_offset(src, caller_context.slither) return index elif name == 'MemberAccess': @@ -569,10 +580,15 @@ def parse_expression(expression, caller_context): var = find_variable(super_name, caller_context, is_super=True) if var is None: raise VariableNotFound('Variable not found: {}'.format(super_name)) - return SuperIdentifier(var) + sup = SuperIdentifier(var) + sup.set_offset(src, caller_context.slither) + return sup member_access = MemberAccess(member_name, member_type, member_expression) + member_access.set_offset(src, caller_context.slither) if str(member_access) in SOLIDITY_VARIABLES_COMPOSED: - return Identifier(SolidityVariableComposed(str(member_access))) + idx = Identifier(SolidityVariableComposed(str(member_access))) + idx.set_offset(src, caller_context.slither) + return idx return member_access elif name == 'ElementaryTypeNameExpression': @@ -614,6 +630,7 @@ def parse_expression(expression, caller_context): else: raise ParsingError('Incorrect type array {}'.format(type_name)) array = NewArray(depth, array_type) + array.set_offset(src, caller_context.slither) return array if type_name[caller_context.get_key()] == 'ElementaryTypeName': @@ -622,6 +639,7 @@ def parse_expression(expression, caller_context): else: elem_type = ElementaryType(type_name['attributes']['name']) new_elem = NewElementaryType(elem_type) + new_elem.set_offset(src, caller_context.slither) return new_elem assert type_name[caller_context.get_key()] == 'UserDefinedTypeName' @@ -631,6 +649,7 @@ def parse_expression(expression, caller_context): else: contract_name = type_name['attributes']['name'] new = NewContract(contract_name) + new.set_offset(src, caller_context.slither) return new elif name == 'ModifierInvocation': @@ -646,7 +665,7 @@ def parse_expression(expression, caller_context): arguments = [parse_expression(a, caller_context) for a in children[1::]] call = CallExpression(called, arguments, 'Modifier') - call.set_offset(expression['src'], caller_context.slither) + call.set_offset(src, caller_context.slither) return call raise ParsingError('Expression not parsed %s'%name) diff --git a/tests/expected_json/external_function.external-function.json b/tests/expected_json/external_function.external-function.json index d60c5ef49..026c45140 100644 --- a/tests/expected_json/external_function.external-function.json +++ b/tests/expected_json/external_function.external-function.json @@ -7,7 +7,7 @@ "check": "external-function", "impact": "Optimization", "confidence": "High", - "description": "ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15) should be declared external\n", + "description": "funcNotCalled3() should be declared external:\n\t- ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15)\n", "elements": [ { "type": "function", @@ -74,7 +74,7 @@ "check": "external-function", "impact": "Optimization", "confidence": "High", - "description": "ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19) should be declared external\n", + "description": "funcNotCalled2() should be declared external:\n\t- ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19)\n", "elements": [ { "type": "function", @@ -141,7 +141,7 @@ "check": "external-function", "impact": "Optimization", "confidence": "High", - "description": "ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23) should be declared external\n", + "description": "funcNotCalled() should be declared external:\n\t- ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23)\n", "elements": [ { "type": "function", @@ -208,7 +208,7 @@ "check": "external-function", "impact": "Optimization", "confidence": "High", - "description": "ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39) should be declared external\n", + "description": "funcNotCalled() should be declared external:\n\t- ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39)\n", "elements": [ { "type": "function", @@ -271,7 +271,7 @@ "check": "external-function", "impact": "Optimization", "confidence": "High", - "description": "FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76) should be declared external\n", + "description": "parameter_read_ok_for_external(uint256) should be declared external:\n\t- FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76)\n", "elements": [ { "type": "function", diff --git a/tests/expected_json/external_function.external-function.txt b/tests/expected_json/external_function.external-function.txt index 82470ba0d..a0f3d6499 100644 --- a/tests/expected_json/external_function.external-function.txt +++ b/tests/expected_json/external_function.external-function.txt @@ -1,8 +1,13 @@ -INFO:Detectors: -ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15) should be declared external -ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19) should be declared external -ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23) should be declared external -ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39) should be declared external -FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76) should be declared external + +funcNotCalled3() should be declared external: + - ContractWithFunctionNotCalled.funcNotCalled3() (tests/external_function.sol#13-15) +funcNotCalled2() should be declared external: + - ContractWithFunctionNotCalled.funcNotCalled2() (tests/external_function.sol#17-19) +funcNotCalled() should be declared external: + - ContractWithFunctionNotCalled.funcNotCalled() (tests/external_function.sol#21-23) +funcNotCalled() should be declared external: + - ContractWithFunctionNotCalled2.funcNotCalled() (tests/external_function.sol#32-39) +parameter_read_ok_for_external(uint256) should be declared external: + - FunctionParameterWrite.parameter_read_ok_for_external(uint256) (tests/external_function.sol#74-76) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-as-external -INFO:Slither:tests/external_function.sol analyzed (6 contracts), 5 result(s) found +tests/external_function.sol analyzed (6 contracts with 1 detectors), 5 result(s) found diff --git a/tests/expected_json/external_function_2.external-function.txt b/tests/expected_json/external_function_2.external-function.txt index 352324d7f..a61fb7600 100644 --- a/tests/expected_json/external_function_2.external-function.txt +++ b/tests/expected_json/external_function_2.external-function.txt @@ -1 +1 @@ -INFO:Slither:tests/external_function_2.sol analyzed (4 contracts), 0 result(s) found +tests/external_function_2.sol analyzed (4 contracts with 1 detectors), 0 result(s) found