Merge branch 'dev' into dev-flattening

pull/328/head
Josselin 5 years ago
commit 0c264374cf
  1. 6
      README.md
  2. 5
      scripts/travis_test_dapp.sh
  3. 2
      scripts/travis_test_etherscan.sh
  4. 4
      setup.py
  5. 2
      slither/__main__.py
  6. 7
      slither/core/declarations/function.py
  7. 24
      slither/detectors/functions/external_function.py
  8. 2
      slither/detectors/statements/calls_in_loop.py
  9. 7
      slither/detectors/variables/unused_state_variables.py
  10. 13
      slither/printers/summary/human_summary.py
  11. 4
      slither/slither.py
  12. 65
      slither/slithir/convert.py
  13. 10
      slither/solc_parsing/cfg/node.py
  14. 25
      slither/solc_parsing/declarations/contract.py
  15. 3
      slither/solc_parsing/declarations/function.py
  16. 33
      slither/solc_parsing/expressions/expression_parsing.py
  17. 10
      tests/expected_json/external_function.external-function.json
  18. 19
      tests/expected_json/external_function.external-function.txt
  19. 2
      tests/expected_json/external_function_2.external-function.txt

@ -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

@ -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

@ -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

@ -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={

@ -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

@ -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):

@ -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

@ -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'

@ -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']

@ -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))

@ -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`.

@ -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

@ -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)

@ -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)

@ -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

@ -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)

@ -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",

@ -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

@ -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

Loading…
Cancel
Save