pull/612/head
Josselin 4 years ago
parent d2655802d9
commit 88954ac6d9
  1. 2
      slither/__init__.py
  2. 619
      slither/__main__.py
  3. 2
      slither/all_exceptions.py
  4. 135
      slither/analyses/data_dependency/data_dependency.py
  5. 102
      slither/analyses/evm/convert.py
  6. 4
      slither/analyses/evm/evm_cfg_builder.py
  7. 22
      slither/analyses/write/are_variables_written.py
  8. 8
      slither/core/cfg/node.py
  9. 12
      slither/core/declarations/function.py
  10. 2
      slither/core/declarations/solidity_variables.py
  11. 4
      slither/core/slither_core.py
  12. 2
      slither/core/solidity_types/elementary_type.py
  13. 3
      slither/core/solidity_types/type.py
  14. 187
      slither/detectors/abstract_detector.py
  15. 4
      slither/detectors/all_detectors.py
  16. 26
      slither/detectors/attributes/const_functions_asm.py
  17. 28
      slither/detectors/attributes/const_functions_state.py
  18. 13
      slither/detectors/attributes/constant_pragma.py
  19. 80
      slither/detectors/attributes/incorrect_solc.py
  20. 38
      slither/detectors/attributes/locked_ether.py
  21. 44
      slither/detectors/erc/incorrect_erc20_interface.py
  22. 62
      slither/detectors/erc/incorrect_erc721_interface.py
  23. 23
      slither/detectors/erc/unindexed_event_parameters.py
  24. 19
      slither/detectors/examples/backdoor.py
  25. 54
      slither/detectors/functions/arbitrary_send.py
  26. 47
      slither/detectors/functions/complex_function.py
  27. 60
      slither/detectors/functions/external_function.py
  28. 21
      slither/detectors/functions/suicidal.py
  29. 95
      slither/detectors/naming_convention/naming_convention.py
  30. 35
      slither/detectors/operations/block_timestamp.py
  31. 19
      slither/detectors/operations/low_level_calls.py
  32. 21
      slither/detectors/operations/unchecked_low_level_return_values.py
  33. 19
      slither/detectors/operations/unchecked_send_return_value.py
  34. 19
      slither/detectors/operations/unused_return_values.py
  35. 18
      slither/detectors/operations/void_constructor.py
  36. 64
      slither/detectors/reentrancy/reentrancy.py
  37. 112
      slither/detectors/reentrancy/reentrancy_benign.py
  38. 107
      slither/detectors/reentrancy/reentrancy_eth.py
  39. 91
      slither/detectors/reentrancy/reentrancy_events.py
  40. 146
      slither/detectors/reentrancy/reentrancy_no_gas.py
  41. 90
      slither/detectors/reentrancy/reentrancy_read_before_write.py
  42. 21
      slither/detectors/shadowing/abstract.py
  43. 88
      slither/detectors/shadowing/builtin_symbols.py
  44. 22
      slither/detectors/shadowing/local.py
  45. 20
      slither/detectors/shadowing/state.py
  46. 50
      slither/detectors/slither/name_reused.py
  47. 32
      slither/detectors/source/rtlo.py
  48. 16
      slither/detectors/statements/assembly.py
  49. 16
      slither/detectors/statements/boolean_constant_equality.py
  50. 34
      slither/detectors/statements/boolean_constant_misuse.py
  51. 31
      slither/detectors/statements/calls_in_loop.py
  52. 29
      slither/detectors/statements/controlled_delegatecall.py
  53. 38
      slither/detectors/statements/deprecated_calls.py
  54. 28
      slither/detectors/statements/divide_before_multiply.py
  55. 68
      slither/detectors/statements/incorrect_strict_equality.py
  56. 28
      slither/detectors/statements/too_many_digits.py
  57. 33
      slither/detectors/statements/tx_origin.py
  58. 38
      slither/detectors/statements/type_based_tautology.py
  59. 55
      slither/detectors/variables/possible_const_state_variables.py
  60. 24
      slither/detectors/variables/uninitialized_local_variables.py
  61. 41
      slither/detectors/variables/uninitialized_state_variables.py
  62. 23
      slither/detectors/variables/uninitialized_storage_variables.py
  63. 35
      slither/detectors/variables/unused_state_variables.py
  64. 7
      slither/exceptions.py
  65. 51
      slither/formatters/attributes/const_functions.py
  66. 48
      slither/formatters/attributes/constant_pragma.py
  67. 56
      slither/formatters/attributes/incorrect_solc.py
  68. 8
      slither/formatters/exceptions.py
  69. 63
      slither/formatters/functions/external_function.py
  70. 499
      slither/formatters/naming_convention/naming_convention.py
  71. 43
      slither/formatters/utils/patches.py
  72. 44
      slither/formatters/variables/possible_const_state_variables.py
  73. 40
      slither/formatters/variables/unused_state_variables.py
  74. 21
      slither/printers/abstract_printer.py
  75. 2
      slither/printers/all_printers.py
  76. 170
      slither/printers/call/call_graph.py
  77. 34
      slither/printers/functions/authorization.py
  78. 18
      slither/printers/functions/cfg.py
  79. 193
      slither/printers/guidance/echidna.py
  80. 48
      slither/printers/inheritance/inheritance.py
  81. 127
      slither/printers/inheritance/inheritance_graph.py
  82. 32
      slither/printers/summary/constructor_calls.py
  83. 53
      slither/printers/summary/contract.py
  84. 30
      slither/printers/summary/data_depenency.py
  85. 87
      slither/printers/summary/evm.py
  86. 73
      slither/printers/summary/function.py
  87. 20
      slither/printers/summary/function_ids.py
  88. 177
      slither/printers/summary/human_summary.py
  89. 17
      slither/printers/summary/modifier_calls.py
  90. 34
      slither/printers/summary/require_calls.py
  91. 26
      slither/printers/summary/slithir.py
  92. 26
      slither/printers/summary/slithir_ssa.py
  93. 16
      slither/printers/summary/variable_order.py
  94. 1
      slither/slither.py
  95. 461
      slither/slithir/convert.py
  96. 4
      slither/slithir/exceptions.py
  97. 12
      slither/slithir/operations/assignment.py
  98. 3
      slither/slithir/operations/balance.py
  99. 81
      slither/slithir/operations/binary.py
  100. 9
      slither/slithir/operations/call.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1 +1 @@
from .slither import Slither
from .slither import Slither

@ -14,18 +14,26 @@ from crytic_compile import cryticparser
from crytic_compile.platform.standard import generate_standard_export
from slither.detectors import all_detectors
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither
from slither.utils.output import output_to_json, output_to_zip, ZIP_TYPES_ACCEPTED
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, blue, set_colorization_enabled
from slither.utils.command_line import (output_detectors, output_results_to_markdown,
output_detectors_json, output_printers, output_printers_json,
output_to_markdown, output_wiki, defaults_flag_in_config,
read_config_file, JSON_OUTPUT_TYPES, DEFAULT_JSON_OUTPUT_TYPES)
from slither.utils.command_line import (
output_detectors,
output_results_to_markdown,
output_detectors_json,
output_printers,
output_printers_json,
output_to_markdown,
output_wiki,
defaults_flag_in_config,
read_config_file,
JSON_OUTPUT_TYPES,
DEFAULT_JSON_OUTPUT_TYPES,
)
from crytic_compile import compile_all, is_supported
from slither.exceptions import SlitherException
@ -47,12 +55,10 @@ def process_single(target, args, detector_classes, printer_classes):
Returns:
list(result), int: Result list and number of contracts analyzed
"""
ast = '--ast-compact-json'
ast = "--ast-compact-json"
if args.legacy_ast:
ast = '--ast-json'
slither = Slither(target,
ast_format=ast,
**vars(args))
ast = "--ast-json"
slither = Slither(target, ast_format=ast, **vars(args))
return _process(slither, detector_classes, printer_classes)
@ -64,8 +70,12 @@ def process_all(target, args, detector_classes, printer_classes):
results_printers = []
analyzed_contracts_count = 0
for compilation in compilations:
(slither, current_results_detectors, current_results_printers, current_analyzed_count) = process_single(
compilation, args, detector_classes, printer_classes)
(
slither,
current_results_detectors,
current_results_printers,
current_analyzed_count,
) = process_single(compilation, args, detector_classes, printer_classes)
results_detectors.extend(current_results_detectors)
results_printers.extend(current_results_printers)
slither_instances.append(slither)
@ -103,9 +113,9 @@ def process_from_asts(filenames, args, detector_classes, printer_classes):
all_contracts = []
for filename in filenames:
with open(filename, encoding='utf8') as f:
with open(filename, encoding="utf8") as f:
contract_loaded = json.load(f)
all_contracts.append(contract_loaded['ast'])
all_contracts.append(contract_loaded["ast"])
return process_single(all_contracts, args, detector_classes, printer_classes)
@ -117,6 +127,7 @@ def process_from_asts(filenames, args, detector_classes, printer_classes):
###################################################################################
###################################################################################
def exit(results):
if not results:
sys.exit(0)
@ -130,6 +141,7 @@ def exit(results):
###################################################################################
###################################################################################
def get_detectors_and_printers():
"""
NOTE: This contains just a few detectors and printers that we made public.
@ -142,16 +154,16 @@ def get_detectors_and_printers():
printers = [p for p in printers if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
# Handle plugins!
for entry_point in iter_entry_points(group='slither_analyzer.plugin', name=None):
for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None):
make_plugin = entry_point.load()
plugin_detectors, plugin_printers = make_plugin()
if not all(issubclass(d, AbstractDetector) for d in plugin_detectors):
raise Exception('Error when loading plugin %s, %r is not a detector' % (entry_point, d))
raise Exception("Error when loading plugin %s, %r is not a detector" % (entry_point, d))
if not all(issubclass(p, AbstractPrinter) for p in plugin_printers):
raise Exception('Error when loading plugin %s, %r is not a printer' % (entry_point, p))
raise Exception("Error when loading plugin %s, %r is not a printer" % (entry_point, p))
# We convert those to lists in case someone returns a tuple
detectors += list(plugin_detectors)
@ -166,41 +178,43 @@ def choose_detectors(args, all_detector_classes):
detectors_to_run = []
detectors = {d.ARGUMENT: d for d in all_detector_classes}
if args.detectors_to_run == 'all':
if args.detectors_to_run == "all":
detectors_to_run = all_detector_classes
if args.detectors_to_exclude:
detectors_excluded = args.detectors_to_exclude.split(',')
detectors_excluded = args.detectors_to_exclude.split(",")
for d in detectors:
if d in detectors_excluded:
detectors_to_run.remove(detectors[d])
else:
for d in args.detectors_to_run.split(','):
for d in args.detectors_to_run.split(","):
if d in detectors:
detectors_to_run.append(detectors[d])
else:
raise Exception('Error: {} is not a detector'.format(d))
raise Exception("Error: {} is not a detector".format(d))
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
if args.exclude_optimization:
detectors_to_run = [d for d in detectors_to_run if
d.IMPACT != DetectorClassification.OPTIMIZATION]
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION
]
if args.exclude_informational:
detectors_to_run = [d for d in detectors_to_run if
d.IMPACT != DetectorClassification.INFORMATIONAL]
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL
]
if args.exclude_low:
detectors_to_run = [d for d in detectors_to_run if
d.IMPACT != DetectorClassification.LOW]
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW]
if args.exclude_medium:
detectors_to_run = [d for d in detectors_to_run if
d.IMPACT != DetectorClassification.MEDIUM]
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM
]
if args.exclude_high:
detectors_to_run = [d for d in detectors_to_run if
d.IMPACT != DetectorClassification.HIGH]
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH]
if args.detectors_to_exclude:
detectors_to_run = [d for d in detectors_to_run if
d.ARGUMENT not in args.detectors_to_exclude]
detectors_to_run = [
d for d in detectors_to_run if d.ARGUMENT not in args.detectors_to_exclude
]
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
@ -214,15 +228,15 @@ def choose_printers(args, all_printer_classes):
if args.printers_to_run is None:
return []
if args.printers_to_run == 'all':
if args.printers_to_run == "all":
return all_printer_classes
printers = {p.ARGUMENT: p for p in all_printer_classes}
for p in args.printers_to_run.split(','):
for p in args.printers_to_run.split(","):
if p in printers:
printers_to_run.append(printers[p])
else:
raise Exception('Error: {} is not a printer'.format(p))
raise Exception("Error: {} is not a printer".format(p))
return printers_to_run
@ -233,203 +247,236 @@ def choose_printers(args, all_printer_classes):
###################################################################################
###################################################################################
def parse_filter_paths(args):
if args.filter_paths:
return args.filter_paths.split(',')
return args.filter_paths.split(",")
return []
def parse_args(detector_classes, printer_classes):
parser = argparse.ArgumentParser(
description='Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage',
usage="slither.py contract.sol [flag]")
description="Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage",
usage="slither.py contract.sol [flag]",
)
parser.add_argument('filename',
help='contract.sol')
parser.add_argument("filename", help="contract.sol")
cryticparser.init(parser)
parser.add_argument('--version',
help='displays the current version',
version=require('slither-analyzer')[0].version,
action='version')
group_detector = parser.add_argument_group('Detectors')
group_printer = parser.add_argument_group('Printers')
group_misc = parser.add_argument_group('Additional options')
group_detector.add_argument('--detect',
help='Comma-separated list of detectors, defaults to all, '
'available detectors: {}'.format(
', '.join(d.ARGUMENT for d in detector_classes)),
action='store',
dest='detectors_to_run',
default=defaults_flag_in_config['detectors_to_run'])
group_printer.add_argument('--print',
help='Comma-separated list fo contract information printers, '
'available printers: {}'.format(
', '.join(d.ARGUMENT for d in printer_classes)),
action='store',
dest='printers_to_run',
default=defaults_flag_in_config['printers_to_run'])
group_detector.add_argument('--list-detectors',
help='List available detectors',
action=ListDetectors,
nargs=0,
default=False)
group_printer.add_argument('--list-printers',
help='List available printers',
action=ListPrinters,
nargs=0,
default=False)
group_detector.add_argument('--exclude',
help='Comma-separated list of detectors that should be excluded',
action='store',
dest='detectors_to_exclude',
default=defaults_flag_in_config['detectors_to_exclude'])
group_detector.add_argument('--exclude-dependencies',
help='Exclude results that are only related to dependencies',
action='store_true',
default=defaults_flag_in_config['exclude_dependencies'])
group_detector.add_argument('--exclude-optimization',
help='Exclude optimization analyses',
action='store_true',
default=defaults_flag_in_config['exclude_optimization'])
group_detector.add_argument('--exclude-informational',
help='Exclude informational impact analyses',
action='store_true',
default=defaults_flag_in_config['exclude_informational'])
group_detector.add_argument('--exclude-low',
help='Exclude low impact analyses',
action='store_true',
default=defaults_flag_in_config['exclude_low'])
group_detector.add_argument('--exclude-medium',
help='Exclude medium impact analyses',
action='store_true',
default=defaults_flag_in_config['exclude_medium'])
group_detector.add_argument('--exclude-high',
help='Exclude high impact analyses',
action='store_true',
default=defaults_flag_in_config['exclude_high'])
group_misc.add_argument('--json',
help='Export the results as a JSON file ("--json -" to export to stdout)',
action='store',
default=defaults_flag_in_config['json'])
group_misc.add_argument('--json-types',
help=f'Comma-separated list of result types to output to JSON, defaults to ' + \
f'{",".join(output_type for output_type in DEFAULT_JSON_OUTPUT_TYPES)}. ' + \
f'Available types: {",".join(output_type for output_type in JSON_OUTPUT_TYPES)}',
action='store',
default=defaults_flag_in_config['json-types'])
group_misc.add_argument('--zip',
help='Export the results as a zipped JSON file',
action='store',
default=defaults_flag_in_config['zip'])
group_misc.add_argument('--zip-type',
help=f'Zip compression type. One of {",".join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma',
action='store',
default=defaults_flag_in_config['zip_type'])
group_misc.add_argument('--markdown-root',
help='URL for markdown generation',
action='store',
default="")
group_misc.add_argument('--disable-color',
help='Disable output colorization',
action='store_true',
default=defaults_flag_in_config['disable_color'])
group_misc.add_argument('--filter-paths',
help='Comma-separated list of paths for which results will be excluded',
action='store',
dest='filter_paths',
default=defaults_flag_in_config['filter_paths'])
group_misc.add_argument('--triage-mode',
help='Run triage mode (save results in slither.db.json)',
action='store_true',
dest='triage_mode',
default=False)
group_misc.add_argument('--config-file',
help='Provide a config file (default: slither.config.json)',
action='store',
dest='config_file',
default='slither.config.json')
group_misc.add_argument('--solc-ast',
help='Provide the contract as a json AST',
action='store_true',
default=False)
group_misc.add_argument('--generate-patches',
help='Generate patches (json output only)',
action='store_true',
default=False)
parser.add_argument(
"--version",
help="displays the current version",
version=require("slither-analyzer")[0].version,
action="version",
)
group_detector = parser.add_argument_group("Detectors")
group_printer = parser.add_argument_group("Printers")
group_misc = parser.add_argument_group("Additional options")
group_detector.add_argument(
"--detect",
help="Comma-separated list of detectors, defaults to all, "
"available detectors: {}".format(", ".join(d.ARGUMENT for d in detector_classes)),
action="store",
dest="detectors_to_run",
default=defaults_flag_in_config["detectors_to_run"],
)
group_printer.add_argument(
"--print",
help="Comma-separated list fo contract information printers, "
"available printers: {}".format(", ".join(d.ARGUMENT for d in printer_classes)),
action="store",
dest="printers_to_run",
default=defaults_flag_in_config["printers_to_run"],
)
group_detector.add_argument(
"--list-detectors",
help="List available detectors",
action=ListDetectors,
nargs=0,
default=False,
)
group_printer.add_argument(
"--list-printers",
help="List available printers",
action=ListPrinters,
nargs=0,
default=False,
)
group_detector.add_argument(
"--exclude",
help="Comma-separated list of detectors that should be excluded",
action="store",
dest="detectors_to_exclude",
default=defaults_flag_in_config["detectors_to_exclude"],
)
group_detector.add_argument(
"--exclude-dependencies",
help="Exclude results that are only related to dependencies",
action="store_true",
default=defaults_flag_in_config["exclude_dependencies"],
)
group_detector.add_argument(
"--exclude-optimization",
help="Exclude optimization analyses",
action="store_true",
default=defaults_flag_in_config["exclude_optimization"],
)
group_detector.add_argument(
"--exclude-informational",
help="Exclude informational impact analyses",
action="store_true",
default=defaults_flag_in_config["exclude_informational"],
)
group_detector.add_argument(
"--exclude-low",
help="Exclude low impact analyses",
action="store_true",
default=defaults_flag_in_config["exclude_low"],
)
group_detector.add_argument(
"--exclude-medium",
help="Exclude medium impact analyses",
action="store_true",
default=defaults_flag_in_config["exclude_medium"],
)
group_detector.add_argument(
"--exclude-high",
help="Exclude high impact analyses",
action="store_true",
default=defaults_flag_in_config["exclude_high"],
)
group_misc.add_argument(
"--json",
help='Export the results as a JSON file ("--json -" to export to stdout)',
action="store",
default=defaults_flag_in_config["json"],
)
group_misc.add_argument(
"--json-types",
help=f"Comma-separated list of result types to output to JSON, defaults to "
+ f'{",".join(output_type for output_type in DEFAULT_JSON_OUTPUT_TYPES)}. '
+ f'Available types: {",".join(output_type for output_type in JSON_OUTPUT_TYPES)}',
action="store",
default=defaults_flag_in_config["json-types"],
)
group_misc.add_argument(
"--zip",
help="Export the results as a zipped JSON file",
action="store",
default=defaults_flag_in_config["zip"],
)
group_misc.add_argument(
"--zip-type",
help=f'Zip compression type. One of {",".join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma',
action="store",
default=defaults_flag_in_config["zip_type"],
)
group_misc.add_argument(
"--markdown-root", help="URL for markdown generation", action="store", default=""
)
group_misc.add_argument(
"--disable-color",
help="Disable output colorization",
action="store_true",
default=defaults_flag_in_config["disable_color"],
)
group_misc.add_argument(
"--filter-paths",
help="Comma-separated list of paths for which results will be excluded",
action="store",
dest="filter_paths",
default=defaults_flag_in_config["filter_paths"],
)
group_misc.add_argument(
"--triage-mode",
help="Run triage mode (save results in slither.db.json)",
action="store_true",
dest="triage_mode",
default=False,
)
group_misc.add_argument(
"--config-file",
help="Provide a config file (default: slither.config.json)",
action="store",
dest="config_file",
default="slither.config.json",
)
group_misc.add_argument(
"--solc-ast", help="Provide the contract as a json AST", action="store_true", default=False
)
group_misc.add_argument(
"--generate-patches",
help="Generate patches (json output only)",
action="store_true",
default=False,
)
# debugger command
parser.add_argument('--debug',
help=argparse.SUPPRESS,
action="store_true",
default=False)
parser.add_argument('--markdown',
help=argparse.SUPPRESS,
action=OutputMarkdown,
default=False)
group_misc.add_argument('--checklist',
help=argparse.SUPPRESS,
action='store_true',
default=False)
parser.add_argument('--wiki-detectors',
help=argparse.SUPPRESS,
action=OutputWiki,
default=False)
parser.add_argument('--list-detectors-json',
help=argparse.SUPPRESS,
action=ListDetectorsJson,
nargs=0,
default=False)
parser.add_argument('--legacy-ast',
help=argparse.SUPPRESS,
action='store_true',
default=defaults_flag_in_config['legacy_ast'])
parser.add_argument('--ignore-return-value',
help=argparse.SUPPRESS,
action='store_true',
default=defaults_flag_in_config['ignore_return_value'])
parser.add_argument("--debug", help=argparse.SUPPRESS, action="store_true", default=False)
parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False)
group_misc.add_argument(
"--checklist", help=argparse.SUPPRESS, action="store_true", default=False
)
parser.add_argument(
"--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False
)
parser.add_argument(
"--list-detectors-json",
help=argparse.SUPPRESS,
action=ListDetectorsJson,
nargs=0,
default=False,
)
parser.add_argument(
"--legacy-ast",
help=argparse.SUPPRESS,
action="store_true",
default=defaults_flag_in_config["legacy_ast"],
)
parser.add_argument(
"--ignore-return-value",
help=argparse.SUPPRESS,
action="store_true",
default=defaults_flag_in_config["ignore_return_value"],
)
# if the json is splitted in different files
parser.add_argument('--splitted',
help=argparse.SUPPRESS,
action='store_true',
default=False)
parser.add_argument("--splitted", help=argparse.SUPPRESS, action="store_true", default=False)
# Disable the throw/catch on partial analyses
parser.add_argument('--disallow-partial',
help=argparse.SUPPRESS,
action="store_true",
default=False)
parser.add_argument(
"--disallow-partial", help=argparse.SUPPRESS, action="store_true", default=False
)
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
@ -441,10 +488,10 @@ def parse_args(detector_classes, printer_classes):
args.filter_paths = parse_filter_paths(args)
# Verify our json-type output is valid
args.json_types = set(args.json_types.split(','))
args.json_types = set(args.json_types.split(","))
for json_type in args.json_types:
if json_type not in JSON_OUTPUT_TYPES:
raise Exception(f"Error: \"{json_type}\" is not a valid JSON result output type.")
raise Exception(f'Error: "{json_type}" is not a valid JSON result output type.')
return args
@ -496,11 +543,11 @@ class OutputWiki(argparse.Action):
class FormatterCryticCompile(logging.Formatter):
def format(self, record):
# for i, msg in enumerate(record.msg):
if record.msg.startswith('Compilation warnings/errors on '):
if record.msg.startswith("Compilation warnings/errors on "):
txt = record.args[1]
txt = txt.split('\n')
txt = [red(x) if 'Error' in x else x for x in txt]
txt = '\n'.join(txt)
txt = txt.split("\n")
txt = [red(x) if "Error" in x else x for x in txt]
txt = "\n".join(txt)
record.args = (record.args[0], txt)
return super().format(record)
@ -538,10 +585,12 @@ def main_impl(all_detector_classes, all_printer_classes):
json_results = {}
output_error = None
outputting_json = args.json is not None
outputting_json_stdout = args.json == '-'
outputting_json_stdout = args.json == "-"
outputting_zip = args.zip is not None
if args.zip_type not in ZIP_TYPES_ACCEPTED.keys():
logger.error(f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}')
logger.error(
f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}'
)
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output.
@ -553,19 +602,20 @@ def main_impl(all_detector_classes, all_printer_classes):
default_log = logging.INFO if not args.debug else logging.DEBUG
for (l_name, l_level) in [('Slither', default_log),
('Contract', default_log),
('Function', default_log),
('Node', default_log),
('Parsing', default_log),
('Detectors', default_log),
('FunctionSolc', default_log),
('ExpressionParsing', default_log),
('TypeParsing', default_log),
('SSA_Conversion', default_log),
('Printers', default_log),
# ('CryticCompile', default_log)
]:
for (l_name, l_level) in [
("Slither", default_log),
("Contract", default_log),
("Function", default_log),
("Node", default_log),
("Parsing", default_log),
("Detectors", default_log),
("FunctionSolc", default_log),
("ExpressionParsing", default_log),
("TypeParsing", default_log),
("SSA_Conversion", default_log),
("Printers", default_log),
# ('CryticCompile', default_log)
]:
l = logging.getLogger(l_name)
l.setLevel(l_level)
@ -574,7 +624,7 @@ def main_impl(all_detector_classes, all_printer_classes):
console_handler.setFormatter(FormatterCryticCompile())
crytic_compile_error = logging.getLogger(('CryticCompile'))
crytic_compile_error = logging.getLogger(("CryticCompile"))
crytic_compile_error.addHandler(console_handler)
crytic_compile_error.propagate = False
crytic_compile_error.setLevel(logging.INFO)
@ -585,7 +635,7 @@ def main_impl(all_detector_classes, all_printer_classes):
filename = args.filename
# Determine if we are handling ast from solc
if args.solc_ast or (filename.endswith('.json') and not is_supported(filename)):
if args.solc_ast or (filename.endswith(".json") and not is_supported(filename)):
globbed_filenames = glob.glob(filename, recursive=True)
filenames = glob.glob(os.path.join(filename, "*.json"))
if not filenames:
@ -594,15 +644,21 @@ def main_impl(all_detector_classes, all_printer_classes):
slither_instances = []
if args.splitted:
(slither_instance, results_detectors, results_printers, number_contracts) = process_from_asts(filenames,
args,
detector_classes,
printer_classes)
(
slither_instance,
results_detectors,
results_printers,
number_contracts,
) = process_from_asts(filenames, args, detector_classes, printer_classes)
slither_instances.append(slither_instance)
else:
for filename in filenames:
(slither_instance, results_detectors_tmp, results_printers_tmp,
number_contracts_tmp) = process_single(filename, args, detector_classes, printer_classes)
(
slither_instance,
results_detectors_tmp,
results_printers_tmp,
number_contracts_tmp,
) = process_single(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp
results_detectors += results_detectors_tmp
results_printers += results_printers_tmp
@ -610,36 +666,41 @@ def main_impl(all_detector_classes, all_printer_classes):
# Rely on CryticCompile to discern the underlying type of compilations.
else:
(slither_instances, results_detectors, results_printers, number_contracts) = process_all(filename, args,
detector_classes,
printer_classes)
(
slither_instances,
results_detectors,
results_printers,
number_contracts,
) = process_all(filename, args, detector_classes, printer_classes)
# Determine if we are outputting JSON
if outputting_json or outputting_zip:
# Add our compilation information to JSON
if 'compilations' in args.json_types:
if "compilations" in args.json_types:
compilation_results = []
for slither_instance in slither_instances:
compilation_results.append(generate_standard_export(slither_instance.crytic_compile))
json_results['compilations'] = compilation_results
compilation_results.append(
generate_standard_export(slither_instance.crytic_compile)
)
json_results["compilations"] = compilation_results
# Add our detector results to JSON if desired.
if results_detectors and 'detectors' in args.json_types:
json_results['detectors'] = results_detectors
if results_detectors and "detectors" in args.json_types:
json_results["detectors"] = results_detectors
# Add our printer results to JSON if desired.
if results_printers and 'printers' in args.json_types:
json_results['printers'] = results_printers
if results_printers and "printers" in args.json_types:
json_results["printers"] = results_printers
# Add our detector types to JSON
if 'list-detectors' in args.json_types:
if "list-detectors" in args.json_types:
detectors, _ = get_detectors_and_printers()
json_results['list-detectors'] = output_detectors_json(detectors)
json_results["list-detectors"] = output_detectors_json(detectors)
# Add our detector types to JSON
if 'list-printers' in args.json_types:
if "list-printers" in args.json_types:
_, printers = get_detectors_and_printers()
json_results['list-printers'] = output_printers_json(printers)
json_results["list-printers"] = output_printers_json(printers)
# Output our results to markdown if we wish to compile a checklist.
if args.checklist:
@ -647,37 +708,45 @@ def main_impl(all_detector_classes, all_printer_classes):
# Dont print the number of result for printers
if number_contracts == 0:
logger.warning(red('No contract was analyzed'))
logger.warning(red("No contract was analyzed"))
if printer_classes:
logger.info('%s analyzed (%d contracts)', filename, number_contracts)
logger.info("%s analyzed (%d contracts)", filename, number_contracts)
else:
logger.info('%s analyzed (%d contracts with %d detectors), %d result(s) found', filename,
number_contracts, len(detector_classes), len(results_detectors))
logger.info(blue('Use https://crytic.io/ to get access to additional detectors and Github integration'))
logger.info(
"%s analyzed (%d contracts with %d detectors), %d result(s) found",
filename,
number_contracts,
len(detector_classes),
len(results_detectors),
)
logger.info(
blue(
"Use https://crytic.io/ to get access to additional detectors and Github integration"
)
)
if args.ignore_return_value:
return
except SlitherException as se:
output_error = str(se)
traceback.print_exc()
logging.error(red('Error:'))
logging.error(red("Error:"))
logging.error(red(output_error))
logging.error('Please report an issue to https://github.com/crytic/slither/issues')
logging.error("Please report an issue to https://github.com/crytic/slither/issues")
except Exception:
output_error = traceback.format_exc()
logging.error(traceback.print_exc())
logging.error('Error in %s' % args.filename)
logging.error("Error in %s" % args.filename)
logging.error(output_error)
# If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON.
if outputting_json:
if 'console' in args.json_types:
json_results['console'] = {
'stdout': StandardOutputCapture.get_stdout_output(),
'stderr': StandardOutputCapture.get_stderr_output()
if "console" in args.json_types:
json_results["console"] = {
"stdout": StandardOutputCapture.get_stdout_output(),
"stderr": StandardOutputCapture.get_stderr_output(),
}
StandardOutputCapture.disable()
output_to_json(None if outputting_json_stdout else args.json, output_error, json_results)
@ -692,7 +761,7 @@ def main_impl(all_detector_classes, all_printer_classes):
exit(results_detectors)
if __name__ == '__main__':
if __name__ == "__main__":
main()
# endregion

@ -4,4 +4,4 @@ This module import all slither exceptions
from slither.slithir.exceptions import SlithIRError
from slither.solc_parsing.exceptions import ParsingError, VariableNotFound
from slither.core.exceptions import SlitherCoreError
from slither.exceptions import SlitherException
from slither.exceptions import SlitherException

@ -3,15 +3,26 @@
"""
from typing import Union, Set, Dict
from slither.core.declarations import (Contract, Enum, Function,
SolidityFunction, SolidityVariable,
SolidityVariableComposed, Structure)
from slither.core.declarations import (
Contract,
Enum,
Function,
SolidityFunction,
SolidityVariable,
SolidityVariableComposed,
Structure,
)
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall
from slither.slithir.variables import (Constant, LocalIRVariable,
ReferenceVariable, ReferenceVariableSSA,
StateIRVariable,
TemporaryVariableSSA, TupleVariableSSA)
from slither.slithir.variables import (
Constant,
LocalIRVariable,
ReferenceVariable,
ReferenceVariableSSA,
StateIRVariable,
TemporaryVariableSSA,
TupleVariableSSA,
)
from slither.core.solidity_types.type import Type
@ -21,8 +32,9 @@ from slither.core.solidity_types.type import Type
###################################################################################
###################################################################################
def is_dependent(variable, source, context, only_unprotected=False):
'''
"""
Args:
variable (Variable)
source (Variable)
@ -30,7 +42,7 @@ def is_dependent(variable, source, context, only_unprotected=False):
only_unprotected (bool): True only unprotected function are considered
Returns:
bool
'''
"""
assert isinstance(context, (Contract, Function))
if isinstance(variable, Constant):
return False
@ -39,12 +51,15 @@ def is_dependent(variable, source, context, only_unprotected=False):
context = context.context
if only_unprotected:
return variable in context[KEY_NON_SSA_UNPROTECTED] and source in context[KEY_NON_SSA_UNPROTECTED][variable]
return (
variable in context[KEY_NON_SSA_UNPROTECTED]
and source in context[KEY_NON_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_NON_SSA] and source in context[KEY_NON_SSA][variable]
def is_dependent_ssa(variable, source, context, only_unprotected=False):
'''
"""
Args:
variable (Variable)
taint (Variable)
@ -52,7 +67,7 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False):
only_unprotected (bool): True only unprotected function are considered
Returns:
bool
'''
"""
assert isinstance(context, (Contract, Function))
context = context.context
if isinstance(variable, Constant):
@ -60,25 +75,30 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False):
if variable == source:
return True
if only_unprotected:
return variable in context[KEY_SSA_UNPROTECTED] and source in context[KEY_SSA_UNPROTECTED][variable]
return (
variable in context[KEY_SSA_UNPROTECTED]
and source in context[KEY_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_SSA] and source in context[KEY_SSA][variable]
GENERIC_TAINT = {SolidityVariableComposed('msg.sender'),
SolidityVariableComposed('msg.value'),
SolidityVariableComposed('msg.data'),
SolidityVariableComposed('tx.origin')}
GENERIC_TAINT = {
SolidityVariableComposed("msg.sender"),
SolidityVariableComposed("msg.value"),
SolidityVariableComposed("msg.data"),
SolidityVariableComposed("tx.origin"),
}
def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=False):
'''
"""
Args:
variable
context (Contract|Function)
only_unprotected (bool): True only unprotected function are considered
Returns:
bool
'''
"""
assert isinstance(context, (Contract, Function))
assert isinstance(only_unprotected, bool)
if isinstance(variable, Constant):
@ -87,18 +107,20 @@ def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=F
taints = slither.context[KEY_INPUT]
if not ignore_generic_taint:
taints |= GENERIC_TAINT
return variable in taints or any(is_dependent(variable, t, context, only_unprotected) for t in taints)
return variable in taints or any(
is_dependent(variable, t, context, only_unprotected) for t in taints
)
def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_taint=False):
'''
"""
Args:
variable
context (Contract|Function)
only_unprotected (bool): True only unprotected function are considered
Returns:
bool
'''
"""
assert isinstance(context, (Contract, Function))
assert isinstance(only_unprotected, bool)
if isinstance(variable, Constant):
@ -107,11 +129,14 @@ def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_tai
taints = slither.context[KEY_INPUT_SSA]
if not ignore_generic_taint:
taints |= GENERIC_TAINT
return variable in taints or any(is_dependent_ssa(variable, t, context, only_unprotected) for t in taints)
return variable in taints or any(
is_dependent_ssa(variable, t, context, only_unprotected) for t in taints
)
def get_dependencies(
variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False) -> Set[Variable]:
variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False
) -> Set[Variable]:
"""
Return the variables for which `variable` depends on.
@ -128,7 +153,8 @@ def get_dependencies(
def get_all_dependencies(
context: Union[Contract, Function], only_unprotected: bool = False) -> Dict[Variable, Set[Variable]]:
context: Union[Contract, Function], only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
@ -144,7 +170,8 @@ def get_all_dependencies(
def get_dependencies_ssa(
variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False) -> Set[Variable]:
variable: Variable, context: Union[Contract, Function], only_unprotected: bool = False
) -> Set[Variable]:
"""
Return the variables for which `variable` depends on (SSA version).
@ -161,7 +188,8 @@ def get_dependencies_ssa(
def get_all_dependencies_ssa(
context: Union[Contract, Function], only_unprotected: bool = False) -> Dict[Variable, Set[Variable]]:
context: Union[Contract, Function], only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
@ -201,19 +229,20 @@ KEY_INPUT_SSA = "DATA_DEPENDENCY_INPUT_SSA"
###################################################################################
###################################################################################
def pprint_dependency(context):
print('#### SSA ####')
print("#### SSA ####")
context = context.context
for k, values in context[KEY_SSA].items():
print('{} ({}):'.format(k, id(k)))
print("{} ({}):".format(k, id(k)))
for v in values:
print('\t- {}'.format(v))
print("\t- {}".format(v))
print('#### NON SSA ####')
print("#### NON SSA ####")
for k, values in context[KEY_NON_SSA].items():
print('{} ({}):'.format(k, hex(id(k))))
print("{} ({}):".format(k, hex(id(k))))
for v in values:
print('\t- {} ({})'.format(v, hex(id(v))))
print("\t- {} ({})".format(v, hex(id(v))))
# endregion
@ -223,6 +252,7 @@ def pprint_dependency(context):
###################################################################################
###################################################################################
def compute_dependency(slither):
slither.context[KEY_INPUT] = set()
slither.context[KEY_INPUT_SSA] = set()
@ -242,12 +272,9 @@ def compute_dependency_contract(contract, slither):
compute_dependency_function(function)
propagate_function(contract, function, KEY_SSA, KEY_NON_SSA)
propagate_function(contract,
function,
KEY_SSA_UNPROTECTED,
KEY_NON_SSA_UNPROTECTED)
propagate_function(contract, function, KEY_SSA_UNPROTECTED, KEY_NON_SSA_UNPROTECTED)
if function.visibility in ['public', 'external']:
if function.visibility in ["public", "external"]:
[slither.context[KEY_INPUT].add(p) for p in function.parameters]
[slither.context[KEY_INPUT_SSA].add(p) for p in function.parameters_ssa]
@ -272,7 +299,9 @@ def transitive_close_dependencies(context, context_key, context_key_non_ssa):
while changed:
changed = False
# Need to create new set() as its changed during iteration
data_depencencies = {k: set([v for v in values]) for k, values in context.context[context_key].items()}
data_depencencies = {
k: set([v for v in values]) for k, values in context.context[context_key].items()
}
for key, items in data_depencencies.items():
for item in items:
if item in data_depencencies:
@ -301,7 +330,11 @@ def add_dependency(lvalue, function, ir, is_protected):
read = ir.read
[function.context[KEY_SSA][lvalue].add(v) for v in read if not isinstance(v, Constant)]
if not is_protected:
[function.context[KEY_SSA_UNPROTECTED][lvalue].add(v) for v in read if not isinstance(v, Constant)]
[
function.context[KEY_SSA_UNPROTECTED][lvalue].add(v)
for v in read
if not isinstance(v, Constant)
]
def compute_dependency_function(function):
@ -324,13 +357,26 @@ def compute_dependency_function(function):
add_dependency(ir.lvalue, function, ir, is_protected)
function.context[KEY_NON_SSA] = convert_to_non_ssa(function.context[KEY_SSA])
function.context[KEY_NON_SSA_UNPROTECTED] = convert_to_non_ssa(function.context[KEY_SSA_UNPROTECTED])
function.context[KEY_NON_SSA_UNPROTECTED] = convert_to_non_ssa(
function.context[KEY_SSA_UNPROTECTED]
)
def convert_variable_to_non_ssa(v):
if isinstance(v, (LocalIRVariable, StateIRVariable, TemporaryVariableSSA, ReferenceVariableSSA, TupleVariableSSA)):
if isinstance(
v,
(
LocalIRVariable,
StateIRVariable,
TemporaryVariableSSA,
ReferenceVariableSSA,
TupleVariableSSA,
),
):
return v.non_ssa_version
assert isinstance(v, (Constant, SolidityVariable, Contract, Enum, SolidityFunction, Structure, Function, Type))
assert isinstance(
v, (Constant, SolidityVariable, Contract, Enum, SolidityFunction, Structure, Function, Type)
)
return v
@ -341,7 +387,6 @@ def convert_to_non_ssa(data_depencies):
var = convert_variable_to_non_ssa(k)
if not var in ret:
ret[var] = set()
ret[var] = ret[var].union(set([convert_variable_to_non_ssa(v) for v in
values]))
ret[var] = ret[var].union(set([convert_variable_to_non_ssa(v) for v in values]))
return ret

@ -1,11 +1,11 @@
import logging
from slither.core.declarations import (Contract, Function)
from slither.core.declarations import Contract, Function
from slither.core.cfg.node import Node
from slither.utils.function import get_function_id
from slither.exceptions import SlitherError
from .evm_cfg_builder import load_evm_cfg_builder
logger = logging.getLogger('ConvertToEVM')
logger = logging.getLogger("ConvertToEVM")
KEY_EVM_INS = "EVM_INSTRUCTIONS"
@ -20,30 +20,36 @@ def get_evm_instructions(obj):
slither = obj.slither
if not slither.crytic_compile:
raise SlitherError('EVM features require to compile with crytic-compile')
raise SlitherError("EVM features require to compile with crytic-compile")
contract_info = {}
function_info = {}
node_info = {}
if isinstance(obj, Node):
contract_info['contract'] = obj.function.contract
contract_info["contract"] = obj.function.contract
elif isinstance(obj, Function):
contract_info['contract'] = obj.contract
contract_info["contract"] = obj.contract
else:
contract_info['contract'] = obj
contract_info["contract"] = obj
# Get contract runtime bytecode, srcmap and cfg
contract_info['bytecode_runtime'] = slither.crytic_compile.bytecode_runtime(
contract_info['contract'].name)
contract_info['srcmap_runtime'] = slither.crytic_compile.srcmap_runtime(
contract_info['contract'].name)
contract_info['cfg'] = CFG(contract_info['bytecode_runtime'])
contract_info["bytecode_runtime"] = slither.crytic_compile.bytecode_runtime(
contract_info["contract"].name
)
contract_info["srcmap_runtime"] = slither.crytic_compile.srcmap_runtime(
contract_info["contract"].name
)
contract_info["cfg"] = CFG(contract_info["bytecode_runtime"])
# Get contract init bytecode, srcmap and cfg
contract_info['bytecode_init'] = slither.crytic_compile.bytecode_init(contract_info['contract'].name)
contract_info['srcmap_init'] = slither.crytic_compile.srcmap_init(contract_info['contract'].name)
contract_info['cfg_init'] = CFG(contract_info['bytecode_init'])
contract_info["bytecode_init"] = slither.crytic_compile.bytecode_init(
contract_info["contract"].name
)
contract_info["srcmap_init"] = slither.crytic_compile.srcmap_init(
contract_info["contract"].name
)
contract_info["cfg_init"] = CFG(contract_info["bytecode_init"])
# Get evm instructions
if isinstance(obj, Contract):
@ -52,26 +58,26 @@ def get_evm_instructions(obj):
elif isinstance(obj, Function):
# Get evm instructions for function
function_info['function'] = obj
function_info['contract_info'] = contract_info
function_info["function"] = obj
function_info["contract_info"] = contract_info
obj.context[KEY_EVM_INS] = _get_evm_instructions_function(function_info)
else:
# Get evm instructions for node
node_info['node'] = obj
node_info["node"] = obj
# CFG and srcmap depend on function being constructor or not
if node_info['node'].function.is_constructor:
cfg = contract_info['cfg_init']
srcmap = contract_info['srcmap_init']
if node_info["node"].function.is_constructor:
cfg = contract_info["cfg_init"]
srcmap = contract_info["srcmap_init"]
else:
cfg = contract_info['cfg']
srcmap = contract_info['srcmap_runtime']
cfg = contract_info["cfg"]
srcmap = contract_info["srcmap_runtime"]
node_info['cfg'] = cfg
node_info['srcmap'] = srcmap
node_info['contract'] = contract_info['contract']
node_info['slither'] = slither
node_info["cfg"] = cfg
node_info["srcmap"] = srcmap
node_info["contract"] = contract_info["contract"]
node_info["slither"] = slither
obj.context[KEY_EVM_INS] = _get_evm_instructions_node(node_info)
@ -80,15 +86,15 @@ def get_evm_instructions(obj):
def _get_evm_instructions_contract(contract_info):
# Combine the instructions of constructor and the rest of the contract
return contract_info['cfg_init'].instructions + contract_info['cfg'].instructions
return contract_info["cfg_init"].instructions + contract_info["cfg"].instructions
def _get_evm_instructions_function(function_info):
function = function_info['function']
function = function_info["function"]
# CFG depends on function being constructor or not
if function.is_constructor:
cfg = function_info['contract_info']['cfg_init']
cfg = function_info["contract_info"]["cfg_init"]
# _dispatcher is the only function recognised by evm-cfg-builder in bytecode_init.
# _dispatcher serves the role of the constructor in init code,
# given that there are no other functions.
@ -97,7 +103,7 @@ def _get_evm_instructions_function(function_info):
name = "_dispatcher"
hash = ""
else:
cfg = function_info['contract_info']['cfg']
cfg = function_info["contract_info"]["cfg"]
name = function.name
# Get first four bytes of function singature's keccak-256 hash used as function selector
hash = str(hex(get_function_id(function.full_name)))
@ -117,20 +123,26 @@ def _get_evm_instructions_function(function_info):
def _get_evm_instructions_node(node_info):
# Get evm instructions for node's contract
contract_pcs = generate_source_to_evm_ins_mapping(node_info['cfg'].instructions,
node_info['srcmap'],
node_info['slither'],
node_info['contract'].source_mapping['filename_absolute'])
contract_file = node_info['slither'].source_code[node_info['contract'].source_mapping['filename_absolute']].encode(
'utf-8')
contract_pcs = generate_source_to_evm_ins_mapping(
node_info["cfg"].instructions,
node_info["srcmap"],
node_info["slither"],
node_info["contract"].source_mapping["filename_absolute"],
)
contract_file = (
node_info["slither"]
.source_code[node_info["contract"].source_mapping["filename_absolute"]]
.encode("utf-8")
)
# Get evm instructions corresponding to node's source line number
node_source_line = contract_file[0:node_info['node'].source_mapping['start']].count("\n".encode("utf-8")) \
+ 1
node_source_line = (
contract_file[0 : node_info["node"].source_mapping["start"]].count("\n".encode("utf-8")) + 1
)
node_pcs = contract_pcs.get(node_source_line, [])
node_ins = []
for pc in node_pcs:
node_ins.append(node_info['cfg'].get_instruction_at(pc))
node_ins.append(node_info["cfg"].get_instruction_at(pc))
return node_ins
@ -141,7 +153,7 @@ def _get_function_evm(cfg, function_name, function_hash):
if function_evm.name[:2] == "0x" and function_evm.name == function_hash:
return function_evm
# Match function name
elif function_evm.name[:2] != "0x" and function_evm.name.split('(')[0] == function_name:
elif function_evm.name[:2] != "0x" and function_evm.name.split("(")[0] == function_name:
return function_evm
return None
@ -155,7 +167,7 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither
"""
source_to_evm_mapping = {}
file_source = slither.source_code[filename].encode('utf-8')
file_source = slither.source_code[filename].encode("utf-8")
prev_mapping = []
for idx, mapping in enumerate(srcmap_runtime):
@ -165,17 +177,17 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither
# If a field is empty, the value of the preceding element is used.
# If a : is missing, all following fields are considered empty.
mapping_item = mapping.split(':')
mapping_item += prev_mapping[len(mapping_item):]
mapping_item = mapping.split(":")
mapping_item += prev_mapping[len(mapping_item) :]
for i in range(len(mapping_item)):
if mapping_item[i] == '':
if mapping_item[i] == "":
mapping_item[i] = int(prev_mapping[i])
offset, length, file_id, _ = mapping_item
prev_mapping = mapping_item
if file_id == '-1':
if file_id == "-1":
# Internal compiler-generated code snippets to be ignored
# See https://github.com/ethereum/solidity/issues/6119#issuecomment-467797635
continue

@ -1,12 +1,14 @@
import logging
from slither.exceptions import SlitherError
logger = logging.getLogger('ConvertToEVM')
logger = logging.getLogger("ConvertToEVM")
def load_evm_cfg_builder():
try:
# Avoiding the addition of evm_cfg_builder as permanent dependency
from evm_cfg_builder.cfg import CFG
return CFG
except ImportError:
logger.error("To use evm features, you need to install evm-cfg-builder")

@ -7,13 +7,18 @@ from typing import Dict, Tuple, Set, List, Optional
from slither.core.cfg.node import NodeType, Node
from slither.core.declarations import SolidityFunction
from slither.core.variables.variable import Variable
from slither.slithir.operations import (Index, Member, OperationWithLValue,
SolidityCall, Length, Balance)
from slither.slithir.operations import (
Index,
Member,
OperationWithLValue,
SolidityCall,
Length,
Balance,
)
from slither.slithir.variables import ReferenceVariable, TemporaryVariable
class State:
def __init__(self):
# Map node -> list of variables set
# Were each variables set represents a configuration of a path
@ -28,7 +33,9 @@ class State:
self.nodes: Dict[Node, List[Set[Variable]]] = defaultdict(list)
def _visit(node: Node, state: State, variables_written: Set[Variable], variables_to_write: List[Variable]):
def _visit(
node: Node, state: State, variables_written: Set[Variable], variables_to_write: List[Variable]
):
"""
Explore all the nodes to look for values not written when the node's function return
Fixpoint reaches if no new written variables are found
@ -44,8 +51,7 @@ def _visit(node: Node, state: State, variables_written: Set[Variable], variables
for ir in node.irs:
if isinstance(ir, SolidityCall):
# TODO convert the revert to a THROW node
if ir.function in [SolidityFunction('revert(string)'),
SolidityFunction('revert()')]:
if ir.function in [SolidityFunction("revert(string)"), SolidityFunction("revert()")]:
return []
if not isinstance(ir, OperationWithLValue):
@ -62,7 +68,9 @@ def _visit(node: Node, state: State, variables_written: Set[Variable], variables
while isinstance(lvalue, ReferenceVariable):
if lvalue not in refs:
break
if refs[lvalue] and not isinstance(refs[lvalue], (TemporaryVariable, ReferenceVariable)):
if refs[lvalue] and not isinstance(
refs[lvalue], (TemporaryVariable, ReferenceVariable)
):
variables_written.add(refs[lvalue])
lvalue = refs[lvalue]

@ -168,7 +168,7 @@ class Node(SourceMapping, ChildFunction):
# key are variable name
self._phi_origins_state_variables: Dict[str, Tuple[StateVariable, Set["Node"]]] = {}
self._phi_origins_local_variables: Dict[str, Tuple[LocalVariable, Set["Node"]]] = {}
#self._phi_origins_member_variables: Dict[str, Tuple[MemberVariable, Set["Node"]]] = {}
# self._phi_origins_member_variables: Dict[str, Tuple[MemberVariable, Set["Node"]]] = {}
self._expression: Optional[Expression] = None
self._variable_declaration: Optional[LocalVariable] = None
@ -983,11 +983,11 @@ class Node(SourceMapping, ChildFunction):
###################################################################################
def __str__(self):
additional_info = ''
additional_info = ""
if self.expression:
additional_info += ' ' + str(self.expression)
additional_info += " " + str(self.expression)
elif self.variable_declaration:
additional_info += ' ' + str(self.variable_declaration)
additional_info += " " + str(self.variable_declaration)
txt = str(self._node_type) + additional_info
return txt

@ -231,7 +231,12 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
Return the function signature without the return values
"""
name, parameters, _ = self.signature
return ".".join([self.contract_declarer.name] + self._scope + [name]) + "(" + ",".join(parameters) + ")"
return (
".".join([self.contract_declarer.name] + self._scope + [name])
+ "("
+ ",".join(parameters)
+ ")"
)
@property
def contains_assembly(self) -> bool:
@ -1615,8 +1620,9 @@ class Function(ChildContract, ChildInheritance, SourceMapping):
return ir.rvalues[0] == ir.lvalue
def fix_phi(self, last_state_variables_instances, initial_state_variables_instances):
from slither.slithir.operations import (InternalCall, PhiCallback)
from slither.slithir.variables import (Constant, StateIRVariable)
from slither.slithir.operations import InternalCall, PhiCallback
from slither.slithir.variables import Constant, StateIRVariable
for node in self.nodes:
for ir in node.irs_ssa:
if node == self.entry_point:

@ -66,7 +66,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
# abi.decode returns an a list arbitrary types
"abi.decode()": [],
"type(address)": [],
"type()": [], # 0.6.8 changed type(address) to type()
"type()": [], # 0.6.8 changed type(address) to type()
}

@ -62,7 +62,6 @@ class SlitherCore(Context):
# If set to true, slither will not catch errors during parsing
self._disallow_partial: bool = False
###################################################################################
###################################################################################
# region Source code
@ -219,7 +218,6 @@ class SlitherCore(Context):
top_level_structures = [c.structures for c in self.contracts if c.is_top_level]
return [st for sublist in top_level_structures for st in sublist]
@property
def top_level_enums(self) -> List[Enum]:
top_level_enums = [c.enums for c in self.contracts if c.is_top_level]
@ -369,7 +367,6 @@ class SlitherCore(Context):
def contracts_with_missing_inheritance(self) -> Set:
return self._contract_with_missing_inheritance
@property
def disallow_partial(self) -> bool:
"""
@ -415,4 +412,5 @@ class SlitherCore(Context):
def storage_layout_of(self, contract, var) -> Tuple[int, int]:
return self._storage_layouts[contract.name][var.canonical_name]
# endregion

@ -174,7 +174,7 @@ class ElementaryType(Type):
@property
def storage_size(self) -> Tuple[int, bool]:
if self._type == 'string' or self._type == 'bytes':
if self._type == "string" or self._type == "bytes":
return 32, True
return int(self.size / 8), False

@ -3,7 +3,8 @@ from typing import Tuple
from slither.core.source_mapping.source_mapping import SourceMapping
class Type(SourceMapping,metaclass=abc.ABCMeta):
class Type(SourceMapping, metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def storage_size(self) -> Tuple[int, bool]:

@ -24,30 +24,30 @@ classification_colors = {
DetectorClassification.OPTIMIZATION: green,
DetectorClassification.LOW: green,
DetectorClassification.MEDIUM: yellow,
DetectorClassification.HIGH: red
DetectorClassification.HIGH: red,
}
classification_txt = {
DetectorClassification.INFORMATIONAL: 'Informational',
DetectorClassification.OPTIMIZATION: 'Optimization',
DetectorClassification.LOW: 'Low',
DetectorClassification.MEDIUM: 'Medium',
DetectorClassification.HIGH: 'High',
DetectorClassification.INFORMATIONAL: "Informational",
DetectorClassification.OPTIMIZATION: "Optimization",
DetectorClassification.LOW: "Low",
DetectorClassification.MEDIUM: "Medium",
DetectorClassification.HIGH: "High",
}
class AbstractDetector(metaclass=abc.ABCMeta):
ARGUMENT = '' # run the detector with slither.py --ARGUMENT
HELP = '' # help information
ARGUMENT = "" # run the detector with slither.py --ARGUMENT
HELP = "" # help information
IMPACT = None
CONFIDENCE = None
WIKI = ''
WIKI = ""
WIKI_TITLE = ''
WIKI_DESCRIPTION = ''
WIKI_EXPLOIT_SCENARIO = ''
WIKI_RECOMMENDATION = ''
WIKI_TITLE = ""
WIKI_DESCRIPTION = ""
WIKI_EXPLOIT_SCENARIO = ""
WIKI_RECOMMENDATION = ""
STANDARD_JSON = True
@ -58,43 +58,69 @@ class AbstractDetector(metaclass=abc.ABCMeta):
self.logger = logger
if not self.HELP:
raise IncorrectDetectorInitialization('HELP is not initialized {}'.format(self.__class__.__name__))
raise IncorrectDetectorInitialization(
"HELP is not initialized {}".format(self.__class__.__name__)
)
if not self.ARGUMENT:
raise IncorrectDetectorInitialization('ARGUMENT is not initialized {}'.format(self.__class__.__name__))
raise IncorrectDetectorInitialization(
"ARGUMENT is not initialized {}".format(self.__class__.__name__)
)
if not self.WIKI:
raise IncorrectDetectorInitialization('WIKI is not initialized {}'.format(self.__class__.__name__))
raise IncorrectDetectorInitialization(
"WIKI is not initialized {}".format(self.__class__.__name__)
)
if not self.WIKI_TITLE:
raise IncorrectDetectorInitialization('WIKI_TITLE is not initialized {}'.format(self.__class__.__name__))
raise IncorrectDetectorInitialization(
"WIKI_TITLE is not initialized {}".format(self.__class__.__name__)
)
if not self.WIKI_DESCRIPTION:
raise IncorrectDetectorInitialization('WIKI_DESCRIPTION is not initialized {}'.format(self.__class__.__name__))
if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [DetectorClassification.INFORMATIONAL,
DetectorClassification.OPTIMIZATION]:
raise IncorrectDetectorInitialization('WIKI_EXPLOIT_SCENARIO is not initialized {}'.format(self.__class__.__name__))
raise IncorrectDetectorInitialization(
"WIKI_DESCRIPTION is not initialized {}".format(self.__class__.__name__)
)
if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [
DetectorClassification.INFORMATIONAL,
DetectorClassification.OPTIMIZATION,
]:
raise IncorrectDetectorInitialization(
"WIKI_EXPLOIT_SCENARIO is not initialized {}".format(self.__class__.__name__)
)
if not self.WIKI_RECOMMENDATION:
raise IncorrectDetectorInitialization('WIKI_RECOMMENDATION is not initialized {}'.format(self.__class__.__name__))
if re.match('^[a-zA-Z0-9_-]*$', self.ARGUMENT) is None:
raise IncorrectDetectorInitialization('ARGUMENT has illegal character {}'.format(self.__class__.__name__))
if self.IMPACT not in [DetectorClassification.LOW,
DetectorClassification.MEDIUM,
DetectorClassification.HIGH,
DetectorClassification.INFORMATIONAL,
DetectorClassification.OPTIMIZATION]:
raise IncorrectDetectorInitialization('IMPACT is not initialized {}'.format(self.__class__.__name__))
if self.CONFIDENCE not in [DetectorClassification.LOW,
DetectorClassification.MEDIUM,
DetectorClassification.HIGH,
DetectorClassification.INFORMATIONAL,
DetectorClassification.OPTIMIZATION]:
raise IncorrectDetectorInitialization('CONFIDENCE is not initialized {}'.format(self.__class__.__name__))
raise IncorrectDetectorInitialization(
"WIKI_RECOMMENDATION is not initialized {}".format(self.__class__.__name__)
)
if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
raise IncorrectDetectorInitialization(
"ARGUMENT has illegal character {}".format(self.__class__.__name__)
)
if self.IMPACT not in [
DetectorClassification.LOW,
DetectorClassification.MEDIUM,
DetectorClassification.HIGH,
DetectorClassification.INFORMATIONAL,
DetectorClassification.OPTIMIZATION,
]:
raise IncorrectDetectorInitialization(
"IMPACT is not initialized {}".format(self.__class__.__name__)
)
if self.CONFIDENCE not in [
DetectorClassification.LOW,
DetectorClassification.MEDIUM,
DetectorClassification.HIGH,
DetectorClassification.INFORMATIONAL,
DetectorClassification.OPTIMIZATION,
]:
raise IncorrectDetectorInitialization(
"CONFIDENCE is not initialized {}".format(self.__class__.__name__)
)
def _log(self, info):
if self.logger:
@ -111,61 +137,76 @@ class AbstractDetector(metaclass=abc.ABCMeta):
all_results = [r.data for r in all_results]
results = []
# only keep valid result, and remove dupplicate
[results.append(r) for r in all_results if self.slither.valid_result(r) and r not in results]
[
results.append(r)
for r in all_results
if self.slither.valid_result(r) and r not in results
]
if results:
if self.logger:
info = '\n'
info = "\n"
for idx, result in enumerate(results):
if self.slither.triage_mode:
info += '{}: '.format(idx)
info += result['description']
info += 'Reference: {}'.format(self.WIKI)
info += "{}: ".format(idx)
info += result["description"]
info += "Reference: {}".format(self.WIKI)
self._log(info)
if self.slither.generate_patches:
for result in results:
try:
self._format(self.slither, result)
if not 'patches' in result:
if not "patches" in result:
continue
result['patches_diff'] = dict()
for file in result['patches']:
original_txt = self.slither.source_code[file].encode('utf8')
result["patches_diff"] = dict()
for file in result["patches"]:
original_txt = self.slither.source_code[file].encode("utf8")
patched_txt = original_txt
offset = 0
patches = result['patches'][file]
patches.sort(key=lambda x: x['start'])
if not all(patches[i]['end'] <= patches[i + 1]['end'] for i in range(len(patches) - 1)):
self._log(f'Impossible to generate patch; patches collisions: {patches}')
patches = result["patches"][file]
patches.sort(key=lambda x: x["start"])
if not all(
patches[i]["end"] <= patches[i + 1]["end"]
for i in range(len(patches) - 1)
):
self._log(
f"Impossible to generate patch; patches collisions: {patches}"
)
continue
for patch in patches:
patched_txt, offset = apply_patch(patched_txt, patch, offset)
diff = create_diff(self.slither, original_txt, patched_txt, file)
if not diff:
self._log(f'Impossible to generate patch; empty {result}')
self._log(f"Impossible to generate patch; empty {result}")
else:
result['patches_diff'][file] = diff
result["patches_diff"][file] = diff
except FormatImpossible as e:
self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{e}')
self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{e}')
if results and self.slither.triage_mode:
while True:
indexes = input('Results to hide during next runs: "0,1,...,{}" or "All" (enter to not hide results): '.format(len(results)))
if indexes == 'All':
indexes = input(
'Results to hide during next runs: "0,1,...,{}" or "All" (enter to not hide results): '.format(
len(results)
)
)
if indexes == "All":
self.slither.save_results_to_hide(results)
return []
if indexes == '':
if indexes == "":
return results
if indexes.startswith('['):
if indexes.startswith("["):
indexes = indexes[1:]
if indexes.endswith(']'):
if indexes.endswith("]"):
indexes = indexes[:-1]
try:
indexes = [int(i) for i in indexes.split(',')]
self.slither.save_results_to_hide([r for (idx, r) in enumerate(results) if idx in indexes])
indexes = [int(i) for i in indexes.split(",")]
self.slither.save_results_to_hide(
[r for (idx, r) in enumerate(results) if idx in indexes]
)
return [r for (idx, r) in enumerate(results) if idx not in indexes]
except ValueError:
self.logger.error(yellow('Malformed input. Example of valid input: 0,1,2,3'))
self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
return results
@property
@ -173,18 +214,20 @@ class AbstractDetector(metaclass=abc.ABCMeta):
return classification_colors[self.IMPACT]
def generate_result(self, info, additional_fields=None):
output = Output(info,
additional_fields,
standard_format=self.STANDARD_JSON,
markdown_root=self.slither.markdown_root)
output = Output(
info,
additional_fields,
standard_format=self.STANDARD_JSON,
markdown_root=self.slither.markdown_root,
)
output.data['check'] = self.ARGUMENT
output.data['impact'] = classification_txt[self.IMPACT]
output.data['confidence'] = classification_txt[self.CONFIDENCE]
output.data["check"] = self.ARGUMENT
output.data["impact"] = classification_txt[self.IMPACT]
output.data["confidence"] = classification_txt[self.CONFIDENCE]
return output
@staticmethod
def _format(slither, result):
"""Implement format"""
return
return

@ -7,7 +7,8 @@ from .attributes.incorrect_solc import IncorrectSolc
from .attributes.locked_ether import LockedEther
from .functions.arbitrary_send import ArbitrarySend
from .functions.suicidal import Suicidal
#from .functions.complex_function import ComplexFunction
# from .functions.complex_function import ComplexFunction
from .reentrancy.reentrancy_benign import ReentrancyBenign
from .reentrancy.reentrancy_read_before_write import ReentrancyReadBeforeWritten
from .reentrancy.reentrancy_eth import ReentrancyEth
@ -45,5 +46,6 @@ from .statements.boolean_constant_equality import BooleanEquality
from .statements.boolean_constant_misuse import BooleanConstantMisuse
from .statements.divide_before_multiply import DivideBeforeMultiply
from .slither.name_reused import NameReused
#
#

@ -11,23 +11,23 @@ class ConstantFunctionsAsm(AbstractDetector):
Constant function detector
"""
ARGUMENT = 'constant-function-asm' # run the detector with slither.py --ARGUMENT
HELP = 'Constant functions using assembly code' # help information
ARGUMENT = "constant-function-asm" # run the detector with slither.py --ARGUMENT
HELP = "Constant functions using assembly code" # help information
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code"
WIKI_TITLE = 'Constant functions using assembly code'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Constant functions using assembly code"
WIKI_DESCRIPTION = """
Functions declared as `constant`/`pure`/`view` using assembly code.
`constant`/`pure`/`view` was not enforced prior to Solidity 0.5.
Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification.
As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).'''
As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts)."""
WIKI_EXPLOIT_SCENARIO = '''
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Constant{
uint counter;
@ -38,9 +38,11 @@ contract Constant{
}
```
`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0.
All the calls to `get` revert, breaking Bob's smart contract execution.'''
All the calls to `get` revert, breaking Bob's smart contract execution."""
WIKI_RECOMMENDATION = 'Ensure the attributes of contracts compiled prior to Solidity 0.5.0 are correct.'
WIKI_RECOMMENDATION = (
"Ensure the attributes of contracts compiled prior to Solidity 0.5.0 are correct."
)
def _detect(self):
""" Detect the constant function using assembly code
@ -58,10 +60,10 @@ All the calls to `get` revert, breaking Bob's smart contract execution.'''
continue
if f.view or f.pure:
if f.contains_assembly:
attr = 'view' if f.view else 'pure'
attr = "view" if f.view else "pure"
info = [f, f' is declared {attr} but contains assembly code\n']
res = self.generate_result(info, {'contains_assembly': True})
info = [f, f" is declared {attr} but contains assembly code\n"]
res = self.generate_result(info, {"contains_assembly": True})
results.append(res)

@ -11,23 +11,23 @@ class ConstantFunctionsState(AbstractDetector):
Constant function detector
"""
ARGUMENT = 'constant-function-state' # run the detector with slither.py --ARGUMENT
HELP = 'Constant functions changing the state' # help information
ARGUMENT = "constant-function-state" # run the detector with slither.py --ARGUMENT
HELP = "Constant functions changing the state" # help information
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state"
WIKI_TITLE = 'Constant functions changing the state'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Constant functions changing the state"
WIKI_DESCRIPTION = """
Functions declared as `constant`/`pure`/`view` change the state.
`constant`/`pure`/`view` was not enforced prior to Solidity 0.5.
Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification.
As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts).'''
As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts)."""
WIKI_EXPLOIT_SCENARIO = '''
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Constant{
uint counter;
@ -38,9 +38,11 @@ contract Constant{
}
```
`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0.
All the calls to `get` revert, breaking Bob's smart contract execution.'''
All the calls to `get` revert, breaking Bob's smart contract execution."""
WIKI_RECOMMENDATION = 'Ensure that attributes of contracts compiled prior to Solidity 0.5.0 are correct.'
WIKI_RECOMMENDATION = (
"Ensure that attributes of contracts compiled prior to Solidity 0.5.0 are correct."
)
def _detect(self):
""" Detect the constant function changing the state
@ -59,14 +61,14 @@ All the calls to `get` revert, breaking Bob's smart contract execution.'''
if f.view or f.pure:
variables_written = f.all_state_variables_written()
if variables_written:
attr = 'view' if f.view else 'pure'
attr = "view" if f.view else "pure"
info = [f, f' is declared {attr} but changes state variables:\n']
info = [f, f" is declared {attr} but changes state variables:\n"]
for variable_written in variables_written:
info += ['\t- ', variable_written, '\n']
info += ["\t- ", variable_written, "\n"]
res = self.generate_result(info, {'contains_assembly': False})
res = self.generate_result(info, {"contains_assembly": False})
results.append(res)

@ -11,17 +11,16 @@ class ConstantPragma(AbstractDetector):
Check that the same pragma is used in all the files
"""
ARGUMENT = 'pragma'
HELP = 'If different pragma directives are used'
ARGUMENT = "pragma"
HELP = "If different pragma directives are used"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used"
WIKI_TITLE = 'Different pragma directives are used'
WIKI_DESCRIPTION = 'Detect whether different Solidity versions are used.'
WIKI_RECOMMENDATION = 'Use one Solidity version.'
WIKI_TITLE = "Different pragma directives are used"
WIKI_DESCRIPTION = "Detect whether different Solidity versions are used."
WIKI_RECOMMENDATION = "Use one Solidity version."
def _detect(self):
results = []

@ -13,7 +13,7 @@ from slither.formatters.attributes.incorrect_solc import format
# 3: version number
# 4: version number
PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)')
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
class IncorrectSolc(AbstractDetector):
@ -21,50 +21,71 @@ class IncorrectSolc(AbstractDetector):
Check if an old version of solc is used
"""
ARGUMENT = 'solc-version'
HELP = 'Incorrect Solidity version'
ARGUMENT = "solc-version"
HELP = "Incorrect Solidity version"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity"
WIKI_TITLE = 'Incorrect versions of Solidity'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Incorrect versions of Solidity"
WIKI_DESCRIPTION = """
`solc` frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks.
We also recommend avoiding complex `pragma` statement.'''
WIKI_RECOMMENDATION = '''
We also recommend avoiding complex `pragma` statement."""
WIKI_RECOMMENDATION = """
Deploy with any of the following Solidity versions:
- 0.5.11 - 0.5.13,
- 0.5.15 - 0.5.17,
- 0.6.8,
- 0.6.10 - 0.6.11.
Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing.'''
Consider using the latest version of Solidity for testing."""
COMPLEX_PRAGMA_TXT = "is too complex"
OLD_VERSION_TXT = "allows old versions"
LESS_THAN_TXT = "uses lesser than"
TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.11"
BUGGY_VERSION_TXT = "is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
TOO_RECENT_VERSION_TXT = (
"necessitates a version too recent to be trusted. Consider deploying with 0.6.11"
)
BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
)
# Indicates the allowed versions. Must be formatted in increasing order.
ALLOWED_VERSIONS = ["0.5.11", "0.5.12", "0.5.13", "0.5.15", "0.5.16", "0.5.17", "0.6.8", "0.6.10", "0.6.11"]
ALLOWED_VERSIONS = [
"0.5.11",
"0.5.12",
"0.5.13",
"0.5.15",
"0.5.16",
"0.5.17",
"0.6.8",
"0.6.10",
"0.6.11",
]
# Indicates the versions that should not be used.
BUGGY_VERSIONS = ["0.4.22", "^0.4.22",
"0.5.5", "^0.5.5",
"0.5.6", "^0.5.6",
"0.5.14", "^0.5.14",
"0.6.9", "^0.6.9"]
BUGGY_VERSIONS = [
"0.4.22",
"^0.4.22",
"0.5.5",
"^0.5.5",
"0.5.6",
"^0.5.6",
"0.5.14",
"^0.5.14",
"0.6.9",
"^0.6.9",
]
def _check_version(self, version):
op = version[0]
if op and op not in ['>', '>=', '^']:
if op and op not in [">", ">=", "^"]:
return self.LESS_THAN_TXT
version_number = '.'.join(version[2:])
version_number = ".".join(version[2:])
if version_number not in self.ALLOWED_VERSIONS:
if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split('.'))):
if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))):
return self.TOO_RECENT_VERSION_TXT
return self.OLD_VERSION_TXT
return None
@ -81,7 +102,11 @@ Consider using the latest version of Solidity for testing.'''
version_right = versions[1]
# Only allow two elements if the second one is
# <0.5.0 or <0.6.0
if version_right not in [('<', '', '0', '5', '0'), ('<', '', '0', '6', '0'), ('<', '', '0', '7', '0')]:
if version_right not in [
("<", "", "0", "5", "0"),
("<", "", "0", "6", "0"),
("<", "", "0", "7", "0"),
]:
return self.COMPLEX_PRAGMA_TXT
return self._check_version(version_left)
else:
@ -119,10 +144,15 @@ Consider using the latest version of Solidity for testing.'''
if self.slither.crytic_compile:
if self.slither.crytic_compile.compiler_version:
if self.slither.crytic_compile.compiler_version.version not in self.ALLOWED_VERSIONS:
info = ["solc-",
self.slither.crytic_compile.compiler_version.version,
" is not recommended for deployement\n"]
if (
self.slither.crytic_compile.compiler_version.version
not in self.ALLOWED_VERSIONS
):
info = [
"solc-",
self.slither.crytic_compile.compiler_version.version,
" is not recommended for deployement\n",
]
json = self.generate_result(info)

@ -2,27 +2,32 @@
Check if ethers are locked in the contract
"""
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.slithir.operations import (HighLevelCall, LowLevelCall, Send,
Transfer, NewContract, LibraryCall, InternalCall)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import (
HighLevelCall,
LowLevelCall,
Send,
Transfer,
NewContract,
LibraryCall,
InternalCall,
)
class LockedEther(AbstractDetector):
"""
"""
ARGUMENT = 'locked-ether'
ARGUMENT = "locked-ether"
HELP = "Contracts that lock ether"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether"
WIKI_TITLE = 'Contracts that lock Ether'
WIKI_DESCRIPTION = 'Contract with a `payable` function, but without a withdrawal capacity.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Contracts that lock Ether"
WIKI_DESCRIPTION = "Contract with a `payable` function, but without a withdrawal capacity."
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity 0.4.24;
contract Locked{
@ -30,9 +35,9 @@ contract Locked{
}
}
```
Every Ether sent to `Locked` will be lost.'''
Every Ether sent to `Locked` will be lost."""
WIKI_RECOMMENDATION = 'Remove the payable attribute or add a withdraw function.'
WIKI_RECOMMENDATION = "Remove the payable attribute or add a withdraw function."
@staticmethod
def do_no_send_ether(contract):
@ -45,15 +50,17 @@ Every Ether sent to `Locked` will be lost.'''
to_explore = []
for function in functions:
calls = [c.name for c in function.internal_calls]
if 'suicide(address)' in calls or 'selfdestruct(address)' in calls:
if "suicide(address)" in calls or "selfdestruct(address)" in calls:
return False
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, (Send, Transfer, HighLevelCall, LowLevelCall, NewContract)):
if isinstance(
ir, (Send, Transfer, HighLevelCall, LowLevelCall, NewContract)
):
if ir.call_value and ir.call_value != 0:
return False
if isinstance(ir, (LowLevelCall)):
if ir.function_name in ['delegatecall', 'callcode']:
if ir.function_name in ["delegatecall", "callcode"]:
return False
# If a new internal call or librarycall
# Add it to the list to explore
@ -64,7 +71,6 @@ Every Ether sent to `Locked` will be lost.'''
return True
def _detect(self):
results = []

@ -10,46 +10,56 @@ class IncorrectERC20InterfaceDetection(AbstractDetector):
Incorrect ERC20 Interface
"""
ARGUMENT = 'erc20-interface'
HELP = 'Incorrect ERC20 interfaces'
ARGUMENT = "erc20-interface"
HELP = "Incorrect ERC20 interfaces"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface"
WIKI_TITLE = 'Incorrect erc20 interface'
WIKI_DESCRIPTION = 'Incorrect return values for `ERC20` functions. A contract compiled with Solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Incorrect erc20 interface"
WIKI_DESCRIPTION = "Incorrect return values for `ERC20` functions. A contract compiled with Solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Token{
function transfer(address to, uint value) external;
//...
}
```
`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC20` interface implementation. Alice's contract is unable to interact with Bob's contract.'''
`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC20` interface implementation. Alice's contract is unable to interact with Bob's contract."""
WIKI_RECOMMENDATION = 'Set the appropriate return values and types for the defined `ERC20` functions.'
WIKI_RECOMMENDATION = (
"Set the appropriate return values and types for the defined `ERC20` functions."
)
@staticmethod
def incorrect_erc20_interface(signature):
(name, parameters, returnVars) = signature
if name == 'transfer' and parameters == ['address', 'uint256'] and returnVars != ['bool']:
if name == "transfer" and parameters == ["address", "uint256"] and returnVars != ["bool"]:
return True
if name == 'transferFrom' and parameters == ['address', 'address', 'uint256'] and returnVars != ['bool']:
if (
name == "transferFrom"
and parameters == ["address", "address", "uint256"]
and returnVars != ["bool"]
):
return True
if name == 'approve' and parameters == ['address', 'uint256'] and returnVars != ['bool']:
if name == "approve" and parameters == ["address", "uint256"] and returnVars != ["bool"]:
return True
if name == 'allowance' and parameters == ['address', 'address'] and returnVars != ['uint256']:
if (
name == "allowance"
and parameters == ["address", "address"]
and returnVars != ["uint256"]
):
return True
if name == 'balanceOf' and parameters == ['address'] and returnVars != ['uint256']:
if name == "balanceOf" and parameters == ["address"] and returnVars != ["uint256"]:
return True
if name == 'totalSupply' and parameters == [] and returnVars != ['uint256']:
if name == "totalSupply" and parameters == [] and returnVars != ["uint256"]:
return True
return False
@ -72,7 +82,11 @@ contract Token{
return []
funcs = contract.functions
functions = [f for f in funcs if IncorrectERC20InterfaceDetection.incorrect_erc20_interface(f.signature)]
functions = [
f
for f in funcs
if IncorrectERC20InterfaceDetection.incorrect_erc20_interface(f.signature)
]
return functions

@ -9,52 +9,72 @@ class IncorrectERC721InterfaceDetection(AbstractDetector):
Incorrect ERC721 Interface
"""
ARGUMENT = 'erc721-interface'
HELP = 'Incorrect ERC721 interfaces'
ARGUMENT = "erc721-interface"
HELP = "Incorrect ERC721 interfaces"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface"
)
WIKI_TITLE = 'Incorrect erc721 interface'
WIKI_DESCRIPTION = 'Incorrect return values for `ERC721` functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Incorrect erc721 interface"
WIKI_DESCRIPTION = "Incorrect return values for `ERC721` functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Token{
function ownerOf(uint256 _tokenId) external view returns (bool);
//...
}
```
`Token.ownerOf` does not return an address like `ERC721` expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC721` interface implementation. Alice's contract is unable to interact with Bob's contract.'''
`Token.ownerOf` does not return an address like `ERC721` expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC721` interface implementation. Alice's contract is unable to interact with Bob's contract."""
WIKI_RECOMMENDATION = 'Set the appropriate return values and vtypes for the defined `ERC721` functions.'
WIKI_RECOMMENDATION = (
"Set the appropriate return values and vtypes for the defined `ERC721` functions."
)
@staticmethod
def incorrect_erc721_interface(signature):
(name, parameters, returnVars) = signature
# ERC721
if name == 'balanceOf' and parameters == ['address'] and returnVars != ['uint256']:
if name == "balanceOf" and parameters == ["address"] and returnVars != ["uint256"]:
return True
if name == 'ownerOf' and parameters == ['uint256'] and returnVars != ['address']:
if name == "ownerOf" and parameters == ["uint256"] and returnVars != ["address"]:
return True
if name == 'safeTransferFrom' and parameters == ['address', 'address', 'uint256', 'bytes'] and returnVars != []:
if (
name == "safeTransferFrom"
and parameters == ["address", "address", "uint256", "bytes"]
and returnVars != []
):
return True
if name == 'safeTransferFrom' and parameters == ['address', 'address', 'uint256'] and returnVars != []:
if (
name == "safeTransferFrom"
and parameters == ["address", "address", "uint256"]
and returnVars != []
):
return True
if name == 'transferFrom' and parameters == ['address', 'address', 'uint256'] and returnVars != []:
if (
name == "transferFrom"
and parameters == ["address", "address", "uint256"]
and returnVars != []
):
return True
if name == 'approve' and parameters == ['address', 'uint256'] and returnVars != []:
if name == "approve" and parameters == ["address", "uint256"] and returnVars != []:
return True
if name == 'setApprovalForAll' and parameters == ['address', 'bool'] and returnVars != []:
if name == "setApprovalForAll" and parameters == ["address", "bool"] and returnVars != []:
return True
if name == 'getApproved' and parameters == ['uint256'] and returnVars != ['address']:
if name == "getApproved" and parameters == ["uint256"] and returnVars != ["address"]:
return True
if name == 'isApprovedForAll' and parameters == ['address', 'address'] and returnVars != ['bool']:
if (
name == "isApprovedForAll"
and parameters == ["address", "address"]
and returnVars != ["bool"]
):
return True
# ERC165 (dependency)
if name == 'supportsInterface' and parameters == ['bytes4'] and returnVars != ['bool']:
if name == "supportsInterface" and parameters == ["bytes4"] and returnVars != ["bool"]:
return True
return False
@ -72,7 +92,11 @@ contract Token{
return []
funcs = contract.functions
functions = [f for f in funcs if IncorrectERC721InterfaceDetection.incorrect_erc721_interface(f.signature)]
functions = [
f
for f in funcs
if IncorrectERC721InterfaceDetection.incorrect_erc721_interface(f.signature)
]
return functions
def _detect(self):

@ -9,16 +9,16 @@ class UnindexedERC20EventParameters(AbstractDetector):
Un-indexed ERC20 event parameters
"""
ARGUMENT = 'erc20-indexed'
HELP = 'Un-indexed ERC20 event parameters'
ARGUMENT = "erc20-indexed"
HELP = "Un-indexed ERC20 event parameters"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters"
WIKI_TITLE = 'Unindexed ERC20 event oarameters'
WIKI_DESCRIPTION = 'Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Unindexed ERC20 event oarameters"
WIKI_DESCRIPTION = "Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract ERC20Bad {
// ...
@ -29,9 +29,9 @@ contract ERC20Bad {
}
```
`Transfer` and `Approval` events should have the 'indexed' keyword on their two first parameters, as defined by the `ERC20` specification.
Failure to include these keywords will exclude the parameter data in the transaction/block's bloom filter, so external tooling searching for these parameters may overlook them and fail to index logs from this token contract.'''
Failure to include these keywords will exclude the parameter data in the transaction/block's bloom filter, so external tooling searching for these parameters may overlook them and fail to index logs from this token contract."""
WIKI_RECOMMENDATION = 'Add the `indexed` keyword to event parameters that should include it, according to the `ERC20` specification.'
WIKI_RECOMMENDATION = "Add the `indexed` keyword to event parameters that should include it, according to the `ERC20` specification."
STANDARD_JSON = False
@ -53,8 +53,10 @@ Failure to include these keywords will exclude the parameter data in the transac
for event in contract.events_declared:
# If this is transfer/approval events, expect the first two parameters to be indexed.
if event.full_name in ["Transfer(address,address,uint256)",
"Approval(address,address,uint256)"]:
if event.full_name in [
"Transfer(address,address,uint256)",
"Approval(address,address,uint256)",
]:
if not event.elems[0].indexed:
results.append((event, event.elems[0]))
if not event.elems[1].indexed:
@ -82,5 +84,4 @@ Failure to include these keywords will exclude the parameter data in the transac
res.add(event, {"parameter_name": parameter.name})
results.append(res)
return results

@ -6,17 +6,16 @@ class Backdoor(AbstractDetector):
Detect function named backdoor
"""
ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector
HELP = 'Function named backdoor (detector example)'
ARGUMENT = "backdoor" # slither will launch the detector with slither.py --mydetector
HELP = "Function named backdoor (detector example)"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/trailofbits/slither/wiki/Adding-a-new-detector'
WIKI_TITLE = 'Backdoor example'
WIKI_DESCRIPTION = 'Plugin example'
WIKI_EXPLOIT_SCENARIO = '..'
WIKI_RECOMMENDATION = '..'
WIKI = "https://github.com/trailofbits/slither/wiki/Adding-a-new-detector"
WIKI_TITLE = "Backdoor example"
WIKI_DESCRIPTION = "Plugin example"
WIKI_EXPLOIT_SCENARIO = ".."
WIKI_RECOMMENDATION = ".."
def _detect(self):
results = []
@ -24,9 +23,9 @@ class Backdoor(AbstractDetector):
for contract in self.slither.contracts_derived:
# Check if a function has 'backdoor' in its name
for f in contract.functions:
if 'backdoor' in f.name:
if "backdoor" in f.name:
# Info to be printed
info = ['Backdoor function found in ', f, '\n']
info = ["Backdoor function found in ", f, "\n"]
# Add the result in result
res = self.generate_result(info)

@ -11,28 +11,32 @@
"""
from slither.core.declarations import Function
from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent
from slither.core.declarations.solidity_variables import (SolidityFunction,
SolidityVariableComposed)
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.slithir.operations import (HighLevelCall, Index, LowLevelCall,
Send, SolidityCall, Transfer)
from slither.core.declarations.solidity_variables import SolidityFunction, SolidityVariableComposed
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import (
HighLevelCall,
Index,
LowLevelCall,
Send,
SolidityCall,
Transfer,
)
class ArbitrarySend(AbstractDetector):
"""
"""
ARGUMENT = 'arbitrary-send'
HELP = 'Functions that send Ether to arbitrary destinations'
ARGUMENT = "arbitrary-send"
HELP = "Functions that send Ether to arbitrary destinations"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations"
WIKI_TITLE = 'Functions that send Ether to arbitrary destinations'
WIKI_DESCRIPTION = 'Unprotected call to a function sending Ether to an arbitrary address.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Functions that send Ether to arbitrary destinations"
WIKI_DESCRIPTION = "Unprotected call to a function sending Ether to an arbitrary address."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract ArbitrarySend{
address destination;
@ -45,9 +49,9 @@ contract ArbitrarySend{
}
}
```
Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance.'''
Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance."""
WIKI_RECOMMENDATION = 'Ensure that an arbitrary user cannot withdraw unauthorized funds.'
WIKI_RECOMMENDATION = "Ensure that an arbitrary user cannot withdraw unauthorized funds."
def arbitrary_send(self, func):
"""
@ -59,32 +63,34 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract
for node in func.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall):
if ir.function == SolidityFunction('ecrecover(bytes32,uint8,bytes32,bytes32)'):
if ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"):
return False
if isinstance(ir, Index):
if ir.variable_right == SolidityVariableComposed('msg.sender'):
if ir.variable_right == SolidityVariableComposed("msg.sender"):
return False
if is_dependent(ir.variable_right, SolidityVariableComposed('msg.sender'), func.contract):
if is_dependent(
ir.variable_right, SolidityVariableComposed("msg.sender"), func.contract
):
return False
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)):
if isinstance(ir, (HighLevelCall)):
if isinstance(ir.function, Function):
if ir.function.full_name == 'transferFrom(address,address,uint256)':
if ir.function.full_name == "transferFrom(address,address,uint256)":
return False
if ir.call_value is None:
continue
if ir.call_value == SolidityVariableComposed('msg.value'):
if ir.call_value == SolidityVariableComposed("msg.value"):
continue
if is_dependent(ir.call_value, SolidityVariableComposed('msg.value'), func.contract):
if is_dependent(
ir.call_value, SolidityVariableComposed("msg.value"), func.contract
):
continue
if is_tainted(ir.destination, func.contract):
ret.append(node)
return ret
def detect_arbitrary_send(self, contract):
"""
Detect arbitrary send
@ -110,9 +116,9 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract
for (func, nodes) in arbitrary_send:
info = [func, " sends eth to arbitrary user\n"]
info += ['\tDangerous calls:\n']
info += ["\tDangerous calls:\n"]
for node in nodes:
info += ['\t- ', node, '\n']
info += ["\t- ", node, "\n"]
res = self.generate_result(info)

@ -1,10 +1,6 @@
from slither.core.declarations.solidity_variables import (SolidityFunction,
SolidityVariableComposed)
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.slithir.operations import (HighLevelCall,
LowLevelCall,
LibraryCall)
from slither.core.declarations.solidity_variables import SolidityFunction, SolidityVariableComposed
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import HighLevelCall, LowLevelCall, LibraryCall
from slither.utils.code_complexity import compute_cyclomatic_complexity
@ -17,9 +13,8 @@ class ComplexFunction(AbstractDetector):
- numerous external calls
"""
ARGUMENT = 'complex-function'
HELP = 'Complex functions'
ARGUMENT = "complex-function"
HELP = "Complex functions"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.MEDIUM
@ -42,10 +37,7 @@ class ComplexFunction(AbstractDetector):
code_complexity = compute_cyclomatic_complexity(func)
if code_complexity > ComplexFunction.MAX_CYCLOMATIC_COMPLEXITY:
result.append({
"func": func,
"cause": ComplexFunction.CAUSE_CYCLOMATIC
})
result.append({"func": func, "cause": ComplexFunction.CAUSE_CYCLOMATIC})
"""Detect the number of external calls in the func
shouldn't be greater than 5
@ -57,19 +49,13 @@ class ComplexFunction(AbstractDetector):
count += 1
if count > ComplexFunction.MAX_EXTERNAL_CALLS:
result.append({
"func": func,
"cause": ComplexFunction.CAUSE_EXTERNAL_CALL
})
result.append({"func": func, "cause": ComplexFunction.CAUSE_EXTERNAL_CALL})
"""Checks the number of the state variables written
shouldn't be greater than 10
"""
if len(func.state_variables_written) > ComplexFunction.MAX_STATE_VARIABLES:
result.append({
"func": func,
"cause": ComplexFunction.CAUSE_STATE_VARS
})
result.append({"func": func, "cause": ComplexFunction.CAUSE_STATE_VARS})
return result
@ -100,19 +86,20 @@ class ComplexFunction(AbstractDetector):
if cause == self.CAUSE_STATE_VARS:
txt += "\t- Reason: High number of modified state variables"
info = txt.format(func.canonical_name,
func.source_mapping_str)
info = txt.format(func.canonical_name, func.source_mapping_str)
info = info + "\n"
self.log(info)
res = self.generate_result(info)
res.add(func, {
'high_number_of_external_calls': cause == self.CAUSE_EXTERNAL_CALL,
'high_number_of_branches': cause == self.CAUSE_CYCLOMATIC,
'high_number_of_state_variables': cause == self.CAUSE_STATE_VARS
})
res.add(
func,
{
"high_number_of_external_calls": cause == self.CAUSE_EXTERNAL_CALL,
"high_number_of_branches": cause == self.CAUSE_CYCLOMATIC,
"high_number_of_state_variables": cause == self.CAUSE_STATE_VARS,
},
)
results.append(res)
return results

@ -1,7 +1,6 @@
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import SolidityCall
from slither.slithir.operations import (InternalCall, InternalDynamicCall)
from slither.slithir.operations import InternalCall, InternalDynamicCall
from slither.formatters.functions.external_function import format
@ -13,17 +12,18 @@ class ExternalFunction(AbstractDetector):
https://github.com/trailofbits/slither/pull/53#issuecomment-432809950
"""
ARGUMENT = 'external-function'
HELP = 'Public function that could be declared external'
ARGUMENT = "external-function"
HELP = "Public function that could be declared external"
IMPACT = DetectorClassification.OPTIMIZATION
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external"
WIKI_TITLE = 'Public function that could be declared external'
WIKI_DESCRIPTION = '`public` functions that are never called by the contract should be declared `external` to save gas.'
WIKI_RECOMMENDATION = 'Use the `external` attribute for functions never called from the contract.'
WIKI_TITLE = "Public function that could be declared external"
WIKI_DESCRIPTION = "`public` functions that are never called by the contract should be declared `external` to save gas."
WIKI_RECOMMENDATION = (
"Use the `external` attribute for functions never called from the contract."
)
@staticmethod
def detect_functions_called(contract):
@ -94,9 +94,12 @@ class ExternalFunction(AbstractDetector):
"""
# We assume the provided function is the base-most function, so we check all derived contracts
# for a redefinition
return [base_most_function] + [function for derived_contract in base_most_function.contract.derived_contracts
for function in derived_contract.functions
if function.full_name == base_most_function.full_name]
return [base_most_function] + [
function
for derived_contract in base_most_function.contract.derived_contracts
for function in derived_contract.functions
if function.full_name == base_most_function.full_name
]
@staticmethod
def function_parameters_written(function):
@ -140,25 +143,37 @@ class ExternalFunction(AbstractDetector):
# because parameters of external functions will be allocated in calldata region which is immutable
if self.function_parameters_written(function):
continue
# Get the base-most function to know our origin of this function.
base_most_function = self.get_base_most_function(function)
# Get all possible contracts which can call this function (or an override).
all_possible_sources = [base_most_function.contract] + base_most_function.contract.derived_contracts
all_possible_sources = [
base_most_function.contract
] + base_most_function.contract.derived_contracts
# Get all function signatures (overloaded and not), mark as completed and we process them now.
# Note: We mark all function definitions as the same, as they must all share visibility to override.
all_function_definitions = set(self.get_all_function_definitions(base_most_function))
all_function_definitions = set(
self.get_all_function_definitions(base_most_function)
)
completed_functions = completed_functions.union(all_function_definitions)
# Filter false-positives: Determine if any of these sources have dynamic calls, if so, flag all of these
# function definitions, and then flag all functions in all contracts that make dynamic calls.
sources_with_dynamic_calls = set(all_possible_sources) & dynamic_call_contracts
if sources_with_dynamic_calls:
functions_in_dynamic_call_sources = set([f for dyn_contract in sources_with_dynamic_calls
for f in dyn_contract.functions if not f.is_constructor])
completed_functions = completed_functions.union(functions_in_dynamic_call_sources)
functions_in_dynamic_call_sources = set(
[
f
for dyn_contract in sources_with_dynamic_calls
for f in dyn_contract.functions
if not f.is_constructor
]
)
completed_functions = completed_functions.union(
functions_in_dynamic_call_sources
)
continue
# Detect all functions called in each source, if any match our current signature, we skip
@ -176,8 +191,11 @@ class ExternalFunction(AbstractDetector):
# 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]
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:]

@ -12,17 +12,16 @@ class Suicidal(AbstractDetector):
Unprotected function detector
"""
ARGUMENT = 'suicidal'
HELP = 'Functions allowing anyone to destruct the contract'
ARGUMENT = "suicidal"
HELP = "Functions allowing anyone to destruct the contract"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal"
WIKI_TITLE = 'Suicidal'
WIKI_DESCRIPTION = 'Unprotected call to a function executing `selfdestruct`/`suicide`.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Suicidal"
WIKI_DESCRIPTION = "Unprotected call to a function executing `selfdestruct`/`suicide`."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Suicidal{
function kill() public{
@ -30,9 +29,9 @@ contract Suicidal{
}
}
```
Bob calls `kill` and destructs the contract.'''
Bob calls `kill` and destructs the contract."""
WIKI_RECOMMENDATION = 'Protect access to all sensitive functions.'
WIKI_RECOMMENDATION = "Protect access to all sensitive functions."
@staticmethod
def detect_suicidal_func(func):
@ -46,11 +45,11 @@ Bob calls `kill` and destructs the contract.'''
if func.is_constructor:
return False
if func.visibility not in ['public', 'external']:
if func.visibility not in ["public", "external"]:
return False
calls = [c.name for c in func.internal_calls]
if not ('suicide(address)' in calls or 'selfdestruct(address)' in calls):
if not ("suicide(address)" in calls or "selfdestruct(address)" in calls):
return False
if func.is_protected():

@ -14,45 +14,45 @@ class NamingConvention(AbstractDetector):
- Ignore echidna properties (functions with names starting 'echidna_' or 'crytic_'
"""
ARGUMENT = 'naming-convention'
HELP = 'Conformity to Solidity naming conventions'
ARGUMENT = "naming-convention"
HELP = "Conformity to Solidity naming conventions"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#conformity-to-solidity-naming-conventions'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#conformity-to-solidity-naming-conventions"
WIKI_TITLE = 'Conformance to Solidity naming conventions'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Conformance to Solidity naming conventions"
WIKI_DESCRIPTION = """
Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions) that should be followed.
#### Rule exceptions
- Allow constant variable name/symbol/decimals to be lowercase (`ERC20`).
- Allow `_` at the beginning of the `mixed_case` match for private variables and unused parameters.'''
- Allow `_` at the beginning of the `mixed_case` match for private variables and unused parameters."""
WIKI_RECOMMENDATION = 'Follow the Solidity [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions).'
WIKI_RECOMMENDATION = "Follow the Solidity [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions)."
STANDARD_JSON = False
@staticmethod
def is_cap_words(name):
return re.search('^[A-Z]([A-Za-z0-9]+)?_?$', name) is not None
return re.search("^[A-Z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_mixed_case(name):
return re.search('^[a-z]([A-Za-z0-9]+)?_?$', name) is not None
return re.search("^[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_mixed_case_with_underscore(name):
# Allow _ at the beginning to represent private variable
# or unused parameters
return re.search('^[_]?[a-z]([A-Za-z0-9]+)?_?$', name) is not None
return re.search("^[_]?[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_upper_case_with_underscores(name):
return re.search('^[A-Z0-9_]+_?$', name) is not None
return re.search("^[A-Z0-9_]+_?$", name) is not None
@staticmethod
def should_avoid_name(name):
return re.search('^[lOI]$', name) is not None
return re.search("^[lOI]$", name) is not None
def _detect(self):
@ -63,10 +63,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
info = ["Contract ", contract, " is not in CapWords\n"]
res = self.generate_result(info)
res.add(contract, {
"target": "contract",
"convention": "CapWords"
})
res.add(contract, {"target": "contract", "convention": "CapWords"})
results.append(res)
for struct in contract.structures_declared:
@ -74,10 +71,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
info = ["Struct ", struct, " is not in CapWords\n"]
res = self.generate_result(info)
res.add(struct, {
"target": "structure",
"convention": "CapWords"
})
res.add(struct, {"target": "structure", "convention": "CapWords"})
results.append(res)
for event in contract.events_declared:
@ -85,27 +79,24 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
info = ["Event ", event, " is not in CapWords\n"]
res = self.generate_result(info)
res.add(event, {
"target": "event",
"convention": "CapWords"
})
res.add(event, {"target": "event", "convention": "CapWords"})
results.append(res)
for func in contract.functions_declared:
if func.is_constructor:
continue
if not self.is_mixed_case(func.name):
if func.visibility in ['internal', 'private'] and self.is_mixed_case_with_underscore(func.name):
if func.visibility in [
"internal",
"private",
] and self.is_mixed_case_with_underscore(func.name):
continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"):
continue
info = ["Function ", func, " is not in mixedCase\n"]
res = self.generate_result(info)
res.add(func, {
"target": "function",
"convention": "mixedCase"
})
res.add(func, {"target": "function", "convention": "mixedCase"})
results.append(res)
for argument in func.parameters:
@ -120,41 +111,40 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
info = ["Parameter ", argument, " is not in mixedCase\n"]
res = self.generate_result(info)
res.add(argument, {
"target": "parameter",
"convention": "mixedCase"
})
res.add(argument, {"target": "parameter", "convention": "mixedCase"})
results.append(res)
for var in contract.state_variables_declared:
if self.should_avoid_name(var.name):
if not self.is_upper_case_with_underscores(var.name):
info = ["Variable ", var," used l, O, I, which should not be used\n"]
info = ["Variable ", var, " used l, O, I, which should not be used\n"]
res = self.generate_result(info)
res.add(var, {
"target": "variable",
"convention": "l_O_I_should_not_be_used"
})
res.add(
var, {"target": "variable", "convention": "l_O_I_should_not_be_used"}
)
results.append(res)
if var.is_constant is True:
# For ERC20 compatibility
if var.name in ['symbol', 'name', 'decimals']:
if var.name in ["symbol", "name", "decimals"]:
continue
if not self.is_upper_case_with_underscores(var.name):
info = ["Constant ", var," is not in UPPER_CASE_WITH_UNDERSCORES\n"]
info = ["Constant ", var, " is not in UPPER_CASE_WITH_UNDERSCORES\n"]
res = self.generate_result(info)
res.add(var, {
"target": "variable_constant",
"convention": "UPPER_CASE_WITH_UNDERSCORES"
})
res.add(
var,
{
"target": "variable_constant",
"convention": "UPPER_CASE_WITH_UNDERSCORES",
},
)
results.append(res)
else:
if var.visibility == 'private':
if var.visibility == "private":
correct_naming = self.is_mixed_case_with_underscore(var.name)
else:
correct_naming = self.is_mixed_case(var.name)
@ -162,10 +152,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
info = ["Variable ", var, " is not in mixedCase\n"]
res = self.generate_result(info)
res.add(var, {
"target": "variable",
"convention": "mixedCase"
})
res.add(var, {"target": "variable", "convention": "mixedCase"})
results.append(res)
for enum in contract.enums_declared:
@ -173,10 +160,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
info = ["Enum ", enum, " is not in CapWords\n"]
res = self.generate_result(info)
res.add(enum, {
"target": "enum",
"convention": "CapWords"
})
res.add(enum, {"target": "enum", "convention": "CapWords"})
results.append(res)
for modifier in contract.modifiers_declared:
@ -184,10 +168,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
info = ["Modifier ", modifier, " is not in mixedCase\n"]
res = self.generate_result(info)
res.add(modifier, {
"target": "modifier",
"convention": "mixedCase"
})
res.add(modifier, {"target": "modifier", "convention": "mixedCase"})
results.append(res)
return results

@ -7,9 +7,8 @@ from typing import List, Tuple
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
from slither.core.declarations.solidity_variables import (SolidityVariableComposed, SolidityVariable)
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import Binary, BinaryType
@ -18,16 +17,18 @@ def _timestamp(func: Function) -> List[Node]:
for node in func.nodes:
if node.contains_require_or_assert():
for var in node.variables_read:
if is_dependent(var, SolidityVariableComposed('block.timestamp'), func.contract):
if is_dependent(var, SolidityVariableComposed("block.timestamp"), func.contract):
ret.add(node)
if is_dependent(var, SolidityVariable('now'), func.contract):
if is_dependent(var, SolidityVariable("now"), func.contract):
ret.add(node)
for ir in node.irs:
if isinstance(ir, Binary) and BinaryType.return_bool(ir.type):
for var in ir.read:
if is_dependent(var, SolidityVariableComposed('block.timestamp'), func.contract):
if is_dependent(
var, SolidityVariableComposed("block.timestamp"), func.contract
):
ret.add(node)
if is_dependent(var, SolidityVariable('now'), func.contract):
if is_dependent(var, SolidityVariable("now"), func.contract):
ret.add(node)
return sorted(list(ret), key=lambda x: x.node_id)
@ -51,17 +52,19 @@ class Timestamp(AbstractDetector):
"""
"""
ARGUMENT = 'timestamp'
HELP = 'Dangerous usage of `block.timestamp`'
ARGUMENT = "timestamp"
HELP = "Dangerous usage of `block.timestamp`"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp"
WIKI_TITLE = 'Block timestamp'
WIKI_DESCRIPTION = 'Dangerous usage of `block.timestamp`. `block.timestamp` can be manipulated by miners.'
WIKI_EXPLOIT_SCENARIO = '''"Bob's contract relies on `block.timestamp` for its randomness. Eve is a miner and manipulates `block.timestamp` to exploit Bob's contract.'''
WIKI_RECOMMENDATION = 'Avoid relying on `block.timestamp`.'
WIKI_TITLE = "Block timestamp"
WIKI_DESCRIPTION = (
"Dangerous usage of `block.timestamp`. `block.timestamp` can be manipulated by miners."
)
WIKI_EXPLOIT_SCENARIO = """"Bob's contract relies on `block.timestamp` for its randomness. Eve is a miner and manipulates `block.timestamp` to exploit Bob's contract."""
WIKI_RECOMMENDATION = "Avoid relying on `block.timestamp`."
def _detect(self):
"""
@ -74,9 +77,9 @@ class Timestamp(AbstractDetector):
info = [func, " uses timestamp for comparisons\n"]
info += ['\tDangerous comparisons:\n']
info += ["\tDangerous comparisons:\n"]
for node in nodes:
info += ['\t- ', node, '\n']
info += ["\t- ", node, "\n"]
res = self.generate_result(info)

@ -11,16 +11,16 @@ class LowLevelCalls(AbstractDetector):
Detect usage of low level calls
"""
ARGUMENT = 'low-level-calls'
HELP = 'Low level calls'
ARGUMENT = "low-level-calls"
HELP = "Low level calls"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls"
WIKI_TITLE = 'Low-level calls'
WIKI_DESCRIPTION = 'The use of low-level calls is error-prone. Low-level calls do not check for [code existence](https://solidity.readthedocs.io/en/v0.4.25/control-structures.html#error-handling-assert-require-revert-and-exceptions) or call success.'
WIKI_RECOMMENDATION = 'Avoid low-level calls. Check the call success. If the call is meant for a contract, check for code existence.'
WIKI_TITLE = "Low-level calls"
WIKI_DESCRIPTION = "The use of low-level calls is error-prone. Low-level calls do not check for [code existence](https://solidity.readthedocs.io/en/v0.4.25/control-structures.html#error-handling-assert-require-revert-and-exceptions) or call success."
WIKI_RECOMMENDATION = "Avoid low-level calls. Check the call success. If the call is meant for a contract, check for code existence."
@staticmethod
def _contains_low_level_calls(node):
@ -35,8 +35,7 @@ class LowLevelCalls(AbstractDetector):
ret = []
for f in [f for f in contract.functions if contract == f.contract_declarer]:
nodes = f.nodes
assembly_nodes = [n for n in nodes if
self._contains_low_level_calls(n)]
assembly_nodes = [n for n in nodes if self._contains_low_level_calls(n)]
if assembly_nodes:
ret.append((f, assembly_nodes))
return ret
@ -48,10 +47,10 @@ class LowLevelCalls(AbstractDetector):
for c in self.contracts:
values = self.detect_low_level_calls(c)
for func, nodes in values:
info = ["Low level call in ", func,":\n"]
info = ["Low level call in ", func, ":\n"]
for node in nodes:
info += ['\t- ', node, '\n']
info += ["\t- ", node, "\n"]
res = self.generate_result(info)

@ -5,21 +5,22 @@ from slither.detectors.abstract_detector import DetectorClassification
from .unused_return_values import UnusedReturnValues
from slither.slithir.operations import LowLevelCall
class UncheckedLowLevel(UnusedReturnValues):
"""
If the return value of a send is not checked, it might lead to losing ether
"""
ARGUMENT = 'unchecked-lowlevel'
HELP = 'Unchecked low-level calls'
ARGUMENT = "unchecked-lowlevel"
HELP = "Unchecked low-level calls"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls"
WIKI_TITLE = 'Unchecked low-level calls'
WIKI_DESCRIPTION = 'The return value of a low-level call is not checked.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Unchecked low-level calls"
WIKI_DESCRIPTION = "The return value of a low-level call is not checked."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyConc{
function my_func(address payable dst) public payable{
@ -29,15 +30,11 @@ contract MyConc{
```
The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract.
If the low level is used to prevent blocking operations, consider logging failed calls.
'''
"""
WIKI_RECOMMENDATION = 'Ensure that the return value of a low-level call is checked or logged.'
WIKI_RECOMMENDATION = "Ensure that the return value of a low-level call is checked or logged."
_txt_description = "low-level calls"
def _is_instance(self, ir):
return isinstance(ir, LowLevelCall)

@ -6,21 +6,22 @@ from slither.detectors.abstract_detector import DetectorClassification
from .unused_return_values import UnusedReturnValues
from slither.slithir.operations import Send
class UncheckedSend(UnusedReturnValues):
"""
If the return value of a send is not checked, it might lead to losing ether
"""
ARGUMENT = 'unchecked-send'
HELP = 'Unchecked send'
ARGUMENT = "unchecked-send"
HELP = "Unchecked send"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send"
WIKI_TITLE = 'Unchecked Send'
WIKI_DESCRIPTION = 'The return value of a `send` is not checked.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Unchecked Send"
WIKI_DESCRIPTION = "The return value of a `send` is not checked."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyConc{
function my_func(address payable dst) public payable{
@ -30,11 +31,11 @@ contract MyConc{
```
The return value of `send` is not checked, so if the send fails, the Ether will be locked in the contract.
If `send` is used to prevent blocking operations, consider logging the failed `send`.
'''
"""
WIKI_RECOMMENDATION = 'Ensure that the return value of `send` is checked or logged.'
WIKI_RECOMMENDATION = "Ensure that the return value of `send` is checked or logged."
_txt_description = "send calls"
def _is_instance(self, ir):
return isinstance(ir, Send)
return isinstance(ir, Send)

@ -12,16 +12,18 @@ class UnusedReturnValues(AbstractDetector):
If the return value of a function is never used, it's likely to be bug
"""
ARGUMENT = 'unused-return'
HELP = 'Unused return values'
ARGUMENT = "unused-return"
HELP = "Unused return values"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return"
WIKI_TITLE = 'Unused return'
WIKI_DESCRIPTION = 'The return value of an external call is not stored in a local or state variable.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Unused return"
WIKI_DESCRIPTION = (
"The return value of an external call is not stored in a local or state variable."
)
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyConc{
using SafeMath for uint;
@ -30,9 +32,9 @@ contract MyConc{
}
}
```
`MyConc` calls `add` of `SafeMath`, but does not store the result in `a`. As a result, the computation has no effect.'''
`MyConc` calls `add` of `SafeMath`, but does not store the result in `a`. As a result, the computation has no effect."""
WIKI_RECOMMENDATION = 'Ensure that all the return values of the function calls are used.'
WIKI_RECOMMENDATION = "Ensure that all the return values of the function calls are used."
_txt_description = "external calls"
@ -81,4 +83,3 @@ contract MyConc{
results.append(res)
return results

@ -1,29 +1,27 @@
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import Nop
class VoidConstructor(AbstractDetector):
ARGUMENT = 'void-cst'
HELP = 'Constructor called not implemented'
ARGUMENT = "void-cst"
HELP = "Constructor called not implemented"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor"
WIKI_TITLE = 'Void constructor'
WIKI_DESCRIPTION = 'Detect the call to a constructor that is not implemented'
WIKI_RECOMMENDATION = 'Remove the constructor call.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Void constructor"
WIKI_DESCRIPTION = "Detect the call to a constructor that is not implemented"
WIKI_RECOMMENDATION = "Remove the constructor call."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A{}
contract B is A{
constructor() public A(){}
}
```
When reading `B`'s constructor definition, we might assume that `A()` initiates the contract, but no code is executed.'''
When reading `B`'s constructor definition, we might assume that `A()` initiates the contract, but no code is executed."""
def _detect(self):
"""

@ -26,7 +26,10 @@ def dict_are_equal(d1, d2):
return all(set(d1[k]) == set(d2[k]) for k in d1.keys())
def is_subset(new_info: Dict[Union[Variable, Node], Set[Node]], old_info: Dict[Union[Variable, Node], Set[Node]]):
def is_subset(
new_info: Dict[Union[Variable, Node], Set[Node]],
old_info: Dict[Union[Variable, Node], Set[Node]],
):
for k in new_info.keys():
if k not in old_info:
return False
@ -36,11 +39,13 @@ def is_subset(new_info: Dict[Union[Variable, Node], Set[Node]], old_info: Dict[U
def to_hashable(d: Dict[Node, Set[Node]]):
list_tuple = list(tuple((k, tuple(sorted(values, key=lambda x: x.node_id)))) for k, values in d.items())
list_tuple = list(
tuple((k, tuple(sorted(values, key=lambda x: x.node_id)))) for k, values in d.items()
)
return tuple(sorted(list_tuple, key=lambda x: x[0].node_id))
class AbstractState:
class AbstractState:
def __init__(self):
# send_eth returns the list of calls sending value
# calls returns the list of calls that can callback
@ -104,23 +109,36 @@ class AbstractState:
def merge_fathers(self, node, skip_father, detector):
for father in node.fathers:
if detector.KEY in father.context:
self._send_eth = union_dict(self._send_eth,
{key: values for key, values in father.context[detector.KEY].send_eth.items() if
key != skip_father})
self._calls = union_dict(self._calls,
{key: values for key, values in father.context[detector.KEY].calls.items() if
key != skip_father})
self._send_eth = union_dict(
self._send_eth,
{
key: values
for key, values in father.context[detector.KEY].send_eth.items()
if key != skip_father
},
)
self._calls = union_dict(
self._calls,
{
key: values
for key, values in father.context[detector.KEY].calls.items()
if key != skip_father
},
)
self._reads = union_dict(self._reads, father.context[detector.KEY].reads)
self._reads_prior_calls = union_dict(self.reads_prior_calls,
father.context[detector.KEY].reads_prior_calls)
self._reads_prior_calls = union_dict(
self.reads_prior_calls, father.context[detector.KEY].reads_prior_calls
)
def analyze_node(self, node, detector):
state_vars_read: Dict[Variable, Set[Node]] = defaultdict(set,
{v: {node} for v in node.state_variables_read})
state_vars_read: Dict[Variable, Set[Node]] = defaultdict(
set, {v: {node} for v in node.state_variables_read}
)
# All the state variables written
state_vars_written: Dict[Variable, Set[Node]] = defaultdict(set,
{v: {node} for v in node.state_variables_written})
state_vars_written: Dict[Variable, Set[Node]] = defaultdict(
set, {v: {node} for v in node.state_variables_written}
)
slithir_operations = []
# Add the state variables written in internal calls
for internal_call in node.internal_calls:
@ -139,9 +157,11 @@ class AbstractState:
for ir in node.irs + slithir_operations:
if detector.can_callback(ir):
self._calls[node] |= {ir.node}
self._reads_prior_calls[node] = set(self._reads_prior_calls.get(node, set()) |
set(node.context[detector.KEY].reads.keys()) |
set(state_vars_read.keys()))
self._reads_prior_calls[node] = set(
self._reads_prior_calls.get(node, set())
| set(node.context[detector.KEY].reads.keys())
| set(state_vars_read.keys())
)
contains_call = True
if detector.can_send_eth(ir):
@ -152,7 +172,6 @@ class AbstractState:
self._reads = union_dict(self._reads, state_vars_read)
return contains_call
def add(self, fathers):
@ -170,7 +189,7 @@ class AbstractState:
class Reentrancy(AbstractDetector):
KEY = 'REENTRANCY'
KEY = "REENTRANCY"
# can_callback and can_send_eth are static method
# allowing inherited classes to define different behaviors
@ -207,7 +226,10 @@ class Reentrancy(AbstractDetector):
This will work only on naive implementation
"""
return isinstance(node.expression, UnaryOperation) and node.expression.type == UnaryOperationType.BANG
return (
isinstance(node.expression, UnaryOperation)
and node.expression.type == UnaryOperationType.BANG
)
def _explore(self, node, visited, skip_father=None):
"""

@ -10,23 +10,25 @@ from typing import List
from slither.detectors.abstract_detector import DetectorClassification
from .reentrancy import Reentrancy, to_hashable
FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth'])
FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes'])
FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"])
FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"])
class ReentrancyBenign(Reentrancy):
ARGUMENT = 'reentrancy-benign'
HELP = 'Benign reentrancy vulnerabilities'
ARGUMENT = "reentrancy-benign"
HELP = "Benign reentrancy vulnerabilities"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2"
)
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Reentrancy vulnerabilities"
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentrancy-no-eth`).'''
WIKI_EXPLOIT_SCENARIO = '''
Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentrancy-no-eth`)."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
function callme(){
if( ! (msg.sender.call()() ) ){
@ -36,9 +38,9 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
}
```
`callme` contains a reentrancy. The reentrancy is benign because it's exploitation would have the same effect as two consecutive calls.'''
`callme` contains a reentrancy. The reentrancy is benign because it's exploitation would have the same effect as two consecutive calls."""
WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).'
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
STANDARD_JSON = False
@ -55,19 +57,25 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
continue
read_then_written = []
for c in node.context[self.KEY].calls:
read_then_written += [v for v in node.context[self.KEY].written
if v in node.context[self.KEY].reads_prior_calls[c]]
not_read_then_written = set([FindingValue(v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)))
for (v, nodes)
in node.context[self.KEY].written.items()
if v not in read_then_written])
read_then_written += [
v
for v in node.context[self.KEY].written
if v in node.context[self.KEY].reads_prior_calls[c]
]
not_read_then_written = set(
[
FindingValue(v, node, tuple(sorted(nodes, key=lambda x: x.node_id)))
for (v, nodes) in node.context[self.KEY].written.items()
if v not in read_then_written
]
)
if not_read_then_written:
# calls are ordered
finding_key = FindingKey(function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth))
finding_key = FindingKey(
function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth),
)
result[finding_key] |= not_read_then_written
return result
@ -87,27 +95,27 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id)
varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id))
info = ['Reentrancy in ', func, ':\n']
info = ["Reentrancy in ", func, ":\n"]
info += ['\tExternal calls:\n']
info += ["\tExternal calls:\n"]
for (call_info, calls_list) in calls:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ["\t\t- ", call_list_info, "\n"]
if calls != send_eth and send_eth:
info += ['\tExternal calls sending eth:\n']
info += ["\tExternal calls sending eth:\n"]
for (call_info, calls_list) in send_eth:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ['\tState variables written after the call(s):\n']
info += ["\t\t- ", call_list_info, "\n"]
info += ["\tState variables written after the call(s):\n"]
for finding_value in varsWritten:
info += ['\t- ', finding_value.node, '\n']
info += ["\t- ", finding_value.node, "\n"]
for other_node in finding_value.nodes:
if other_node != finding_value.node:
info += ['\t\t- ', other_node, '\n']
info += ["\t\t- ", other_node, "\n"]
# Create our JSON result
res = self.generate_result(info)
@ -117,41 +125,41 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
# Add all underlying calls in the function which are potentially problematic.
for (call_info, calls_list) in calls:
res.add(call_info, {
"underlying_type": "external_calls"
})
res.add(call_info, {"underlying_type": "external_calls"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"})
#
# If the calls are not the same ones that send eth, add the eth sending nodes.
if calls != send_eth:
for (call_info, calls_list) in calls:
res.add(call_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_info, {"underlying_type": "external_calls_sending_eth"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}
)
# Add all variables written via nodes which write them.
for finding_value in varsWritten:
res.add(finding_value.node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
finding_value.node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
for other_node in finding_value.nodes:
if other_node != finding_value.node:
res.add(other_node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
other_node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
# Append our result
results.append(res)

@ -10,23 +10,25 @@ from typing import List
from slither.detectors.abstract_detector import DetectorClassification
from .reentrancy import Reentrancy, to_hashable
FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth'])
FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes'])
FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"])
FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"])
class ReentrancyEth(Reentrancy):
ARGUMENT = 'reentrancy-eth'
HELP = 'Reentrancy vulnerabilities (theft of ethers)'
ARGUMENT = "reentrancy-eth"
HELP = "Reentrancy vulnerabilities (theft of ethers)"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities"
)
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Reentrancy vulnerabilities"
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)'''
WIKI_EXPLOIT_SCENARIO = '''
Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)"""
WIKI_EXPLOIT_SCENARIO = """
```solidity
function withdrawBalance(){
// send userBalance[msg.sender] Ether to msg.sender
@ -38,9 +40,9 @@ Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)'''
}
```
Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw more than its initial deposit to the contract.'''
Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw more than its initial deposit to the contract."""
WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions pattern`](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).'
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions pattern`](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
STANDARD_JSON = False
@ -59,18 +61,23 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
for c in node.context[self.KEY].calls:
if c == node:
continue
read_then_written |= set([FindingValue(v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)))
for (v, nodes)
in node.context[self.KEY].written.items()
if v in node.context[self.KEY].reads_prior_calls[c]])
read_then_written |= set(
[
FindingValue(
v, node, tuple(sorted(nodes, key=lambda x: x.node_id))
)
for (v, nodes) in node.context[self.KEY].written.items()
if v in node.context[self.KEY].reads_prior_calls[c]
]
)
if read_then_written:
# calls are ordered
finding_key = FindingKey(function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth))
finding_key = FindingKey(
function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth),
)
result[finding_key] |= set(read_then_written)
return result
@ -91,26 +98,26 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id)
varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id))
info = ['Reentrancy in ', func, ':\n']
info += ['\tExternal calls:\n']
info = ["Reentrancy in ", func, ":\n"]
info += ["\tExternal calls:\n"]
for (call_info, calls_list) in calls:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ["\t\t- ", call_list_info, "\n"]
if calls != send_eth and send_eth:
info += ['\tExternal calls sending eth:\n']
info += ["\tExternal calls sending eth:\n"]
for (call_info, calls_list) in send_eth:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ['\tState variables written after the call(s):\n']
info += ["\t\t- ", call_list_info, "\n"]
info += ["\tState variables written after the call(s):\n"]
for finding_value in varsWritten:
info += ['\t- ', finding_value.node, '\n']
info += ["\t- ", finding_value.node, "\n"]
for other_node in finding_value.nodes:
if other_node != finding_value.node:
info += ['\t\t- ', other_node, '\n']
info += ["\t\t- ", other_node, "\n"]
# Create our JSON result
res = self.generate_result(info)
@ -120,39 +127,39 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
# Add all underlying calls in the function which are potentially problematic.
for (call_info, calls_list) in calls:
res.add(call_info, {
"underlying_type": "external_calls"
})
res.add(call_info, {"underlying_type": "external_calls"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"})
# If the calls are not the same ones that send eth, add the eth sending nodes.
if calls != send_eth:
for (call_info, calls_list) in send_eth:
res.add(call_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_info, {"underlying_type": "external_calls_sending_eth"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}
)
# Add all variables written via nodes which write them.
for finding_value in varsWritten:
res.add(finding_value.node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
finding_value.node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
for other_node in finding_value.nodes:
if other_node != finding_value.node:
res.add(other_node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
other_node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
# Append our result
results.append(res)

@ -9,23 +9,25 @@ from collections import namedtuple, defaultdict
from slither.detectors.abstract_detector import DetectorClassification
from .reentrancy import Reentrancy, to_hashable
FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth'])
FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes'])
FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"])
FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"])
class ReentrancyEvent(Reentrancy):
ARGUMENT = 'reentrancy-events'
HELP = 'Reentrancy vulnerabilities leading to out-of-order Events'
ARGUMENT = "reentrancy-events"
HELP = "Reentrancy vulnerabilities leading to out-of-order Events"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3"
)
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Reentrancy vulnerabilities"
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancies leading to out-of-order events.'''
WIKI_EXPLOIT_SCENARIO = '''
Only report reentrancies leading to out-of-order events."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bug(Called d){
counter += 1;
@ -34,9 +36,9 @@ Only report reentrancies leading to out-of-order events.'''
}
```
If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties.'''
If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties."""
WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).'
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
STANDARD_JSON = False
@ -53,14 +55,19 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w
continue
# calls are ordered
finding_key = FindingKey(function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth))
finding_vars = set([FindingValue(e,
e.node,
tuple(sorted(nodes, key=lambda x: x.node_id)))
for (e, nodes)
in node.context[self.KEY].events.items()])
finding_key = FindingKey(
function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth),
)
finding_vars = set(
[
FindingValue(
e, e.node, tuple(sorted(nodes, key=lambda x: x.node_id))
)
for (e, nodes) in node.context[self.KEY].events.items()
]
)
if finding_vars:
result[finding_key] |= finding_vars
return result
@ -80,26 +87,26 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w
send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id)
events = sorted(events, key=lambda x: (str(x.variable.name), x.node.node_id))
info = ['Reentrancy in ', func, ':\n']
info += ['\tExternal calls:\n']
info = ["Reentrancy in ", func, ":\n"]
info += ["\tExternal calls:\n"]
for (call_info, calls_list) in calls:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ["\t\t- ", call_list_info, "\n"]
if calls != send_eth and send_eth:
info += ['\tExternal calls sending eth:\n']
info += ["\tExternal calls sending eth:\n"]
for (call_info, calls_list) in send_eth:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ['\tEvent emitted after the call(s):\n']
info += ["\t\t- ", call_list_info, "\n"]
info += ["\tEvent emitted after the call(s):\n"]
for finding_value in events:
info += ['\t- ', finding_value.node, '\n']
info += ["\t- ", finding_value.node, "\n"]
for other_node in finding_value.nodes:
if other_node != finding_value.node:
info += ['\t\t- ', other_node, '\n']
info += ["\t\t- ", other_node, "\n"]
# Create our JSON result
res = self.generate_result(info)
@ -109,38 +116,28 @@ If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, w
# Add all underlying calls in the function which are potentially problematic.
for (call_info, calls_list) in calls:
res.add(call_info, {
"underlying_type": "external_calls"
})
res.add(call_info, {"underlying_type": "external_calls"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"})
#
# If the calls are not the same ones that send eth, add the eth sending nodes.
if calls != send_eth:
for (call_info, calls_list) in send_eth:
res.add(call_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_info, {"underlying_type": "external_calls_sending_eth"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}
)
for finding_value in events:
res.add(finding_value.node, {
"underlying_type": "event"
})
res.add(finding_value.node, {"underlying_type": "event"})
for other_node in finding_value.nodes:
if other_node != finding_value.node:
res.add(other_node, {
"underlying_type": "event"
})
res.add(other_node, {"underlying_type": "event"})
# Append our result
results.append(res)

@ -11,25 +11,27 @@ from slither.detectors.abstract_detector import DetectorClassification
from slither.slithir.operations import Send, Transfer, EventCall
from .reentrancy import Reentrancy, to_hashable
FindingKey = namedtuple('FindingKey', ['function', 'calls', 'send_eth'])
FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes'])
FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"])
FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"])
class ReentrancyNoGas(Reentrancy):
KEY = 'REENTRANCY_NO_GAS'
KEY = "REENTRANCY_NO_GAS"
ARGUMENT = 'reentrancy-unlimited-gas'
HELP = 'Reentrancy vulnerabilities through send and transfer'
ARGUMENT = "reentrancy-unlimited-gas"
HELP = "Reentrancy vulnerabilities through send and transfer"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4"
)
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Reentrancy vulnerabilities"
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancy that is based on `transfer` or `send`.'''
WIKI_EXPLOIT_SCENARIO = '''
Only report reentrancy that is based on `transfer` or `send`."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
function callme(){
msg.sender.transfer(balances[msg.sender]):
@ -37,9 +39,9 @@ Only report reentrancy that is based on `transfer` or `send`.'''
}
```
`send` and `transfer` do not protect from reentrancies in case of gas price changes.'''
`send` and `transfer` do not protect from reentrancies in case of gas price changes."""
WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).'
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
@staticmethod
def can_callback(ir):
@ -64,19 +66,25 @@ Only report reentrancy that is based on `transfer` or `send`.'''
continue
# calls are ordered
finding_key = FindingKey(function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth))
finding_vars = set([FindingValue(v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)))
for (v, nodes)
in node.context[self.KEY].written.items()])
finding_vars |= set([FindingValue(e,
e.node,
tuple(sorted(nodes, key=lambda x: x.node_id)))
for (e, nodes)
in node.context[self.KEY].events.items()])
finding_key = FindingKey(
function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
send_eth=to_hashable(node.context[self.KEY].send_eth),
)
finding_vars = set(
[
FindingValue(v, node, tuple(sorted(nodes, key=lambda x: x.node_id)))
for (v, nodes) in node.context[self.KEY].written.items()
]
)
finding_vars |= set(
[
FindingValue(
e, e.node, tuple(sorted(nodes, key=lambda x: x.node_id))
)
for (e, nodes) in node.context[self.KEY].events.items()
]
)
if finding_vars:
result[finding_key] |= finding_vars
return result
@ -94,43 +102,49 @@ Only report reentrancy that is based on `transfer` or `send`.'''
for (func, calls, send_eth), varsWrittenOrEvent in result_sorted:
calls = sorted(list(set(calls)), key=lambda x: x[0].node_id)
send_eth = sorted(list(set(send_eth)), key=lambda x: x[0].node_id)
info = ['Reentrancy in ', func, ':\n']
info = ["Reentrancy in ", func, ":\n"]
info += ['\tExternal calls:\n']
info += ["\tExternal calls:\n"]
for (call_info, calls_list) in calls:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ["\t\t- ", call_list_info, "\n"]
if calls != send_eth and send_eth:
info += ['\tExternal calls sending eth:\n']
info += ["\tExternal calls sending eth:\n"]
for (call_info, calls_list) in send_eth:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += ["\t\t- ", call_list_info, "\n"]
varsWritten = [FindingValue(v, node, nodes) for (v, node, nodes)
in varsWrittenOrEvent if isinstance(v, Variable)]
varsWritten = [
FindingValue(v, node, nodes)
for (v, node, nodes) in varsWrittenOrEvent
if isinstance(v, Variable)
]
varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id))
if varsWritten:
info += ['\tState variables written after the call(s):\n']
info += ["\tState variables written after the call(s):\n"]
for finding_value in varsWritten:
info += ['\t- ', finding_value.node, '\n']
info += ["\t- ", finding_value.node, "\n"]
for other_node in finding_value.nodes:
if other_node != finding_value.node:
info += ['\t\t- ', other_node, '\n']
info += ["\t\t- ", other_node, "\n"]
events = [FindingValue(v, node, nodes) for (v, node, nodes)
in varsWrittenOrEvent if isinstance(v, EventCall)]
events = [
FindingValue(v, node, nodes)
for (v, node, nodes) in varsWrittenOrEvent
if isinstance(v, EventCall)
]
events = sorted(events, key=lambda x: (x.variable.name, x.node.node_id))
if events:
info += ['\tEvent emitted after the call(s):\n']
info += ["\tEvent emitted after the call(s):\n"]
for finding_value in events:
info += ['\t- ', finding_value.node, '\n']
info += ["\t- ", finding_value.node, "\n"]
for other_node in finding_value.nodes:
if other_node != finding_value.node:
info += ['\t\t- ', other_node, '\n']
info += ["\t\t- ", other_node, "\n"]
# Create our JSON result
res = self.generate_result(info)
@ -140,50 +154,46 @@ Only report reentrancy that is based on `transfer` or `send`.'''
# Add all underlying calls in the function which are potentially problematic.
for (call_info, calls_list) in calls:
res.add(call_info, {
"underlying_type": "external_calls"
})
res.add(call_info, {"underlying_type": "external_calls"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"})
#
# If the calls are not the same ones that send eth, add the eth sending nodes.
if calls != send_eth:
for (call_info, calls_list) in send_eth:
res.add(call_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_info, {"underlying_type": "external_calls_sending_eth"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(
call_list_info, {"underlying_type": "external_calls_sending_eth"}
)
# Add all variables written via nodes which write them.
for finding_value in varsWritten:
res.add(finding_value.node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
finding_value.node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
for other_node in finding_value.nodes:
if other_node != finding_value.node:
res.add(other_node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
other_node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
for finding_value in events:
res.add(finding_value.node, {
"underlying_type": "event"
})
res.add(finding_value.node, {"underlying_type": "event"})
for other_node in finding_value.nodes:
if other_node != finding_value.node:
res.add(other_node, {
"underlying_type": "event"
})
res.add(other_node, {"underlying_type": "event"})
# Append our result
results.append(res)

@ -9,25 +9,26 @@ from collections import namedtuple, defaultdict
from slither.detectors.abstract_detector import DetectorClassification
from .reentrancy import Reentrancy, to_hashable
FindingKey = namedtuple('FindingKey', ['function', 'calls'])
FindingValue = namedtuple('FindingValue', ['variable', 'node', 'nodes'])
FindingKey = namedtuple("FindingKey", ["function", "calls"])
FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"])
class ReentrancyReadBeforeWritten(Reentrancy):
ARGUMENT = 'reentrancy-no-eth'
HELP = 'Reentrancy vulnerabilities (no theft of ethers)'
ARGUMENT = "reentrancy-no-eth"
HELP = "Reentrancy vulnerabilities (no theft of ethers)"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1"
)
WIKI_TITLE = 'Reentrancy vulnerabilities'
WIKI_DESCRIPTION = '''
WIKI_TITLE = "Reentrancy vulnerabilities"
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Do not report reentrancies that involve Ether (see `reentrancy-eth`).'''
Do not report reentrancies that involve Ether (see `reentrancy-eth`)."""
WIKI_EXPLOIT_SCENARIO = '''
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bug(){
require(not_called);
@ -37,8 +38,8 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).'''
not_called = False;
}
```
'''
WIKI_RECOMMENDATION = 'Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy).'
"""
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
STANDARD_JSON = False
@ -55,18 +56,23 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).'''
for c in node.context[self.KEY].calls:
if c == node:
continue
read_then_written |= set([FindingValue(v,
node,
tuple(sorted(nodes, key=lambda x: x.node_id)))
for (v, nodes)
in node.context[self.KEY].written.items()
if v in node.context[self.KEY].reads_prior_calls[c]])
read_then_written |= set(
[
FindingValue(
v, node, tuple(sorted(nodes, key=lambda x: x.node_id))
)
for (v, nodes) in node.context[self.KEY].written.items()
if v in node.context[self.KEY].reads_prior_calls[c]
]
)
# We found a potential re-entrancy bug
if read_then_written:
# calls are ordered
finding_key = FindingKey(function=node.function,
calls=to_hashable(node.context[self.KEY].calls))
finding_key = FindingKey(
function=node.function,
calls=to_hashable(node.context[self.KEY].calls),
)
result[finding_key] |= read_then_written
return result
@ -84,20 +90,20 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).'''
calls = sorted(list(set(calls)), key=lambda x: x[0].node_id)
varsWritten = sorted(varsWritten, key=lambda x: (x.variable.name, x.node.node_id))
info = ['Reentrancy in ', func, ':\n']
info = ["Reentrancy in ", func, ":\n"]
info += ['\tExternal calls:\n']
info += ["\tExternal calls:\n"]
for (call_info, calls_list) in calls:
info += ['\t- ', call_info, '\n']
info += ["\t- ", call_info, "\n"]
for call_list_info in calls_list:
if call_list_info != call_info:
info += ['\t\t- ', call_list_info, '\n']
info += '\tState variables written after the call(s):\n'
info += ["\t\t- ", call_list_info, "\n"]
info += "\tState variables written after the call(s):\n"
for finding_value in varsWritten:
info += ['\t- ', finding_value.node, '\n']
info += ["\t- ", finding_value.node, "\n"]
for other_node in finding_value.nodes:
if other_node != finding_value.node:
info += ['\t\t- ', other_node, '\n']
info += ["\t\t- ", other_node, "\n"]
# Create our JSON result
res = self.generate_result(info)
@ -107,27 +113,29 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`).'''
# Add all underlying calls in the function which are potentially problematic.
for (call_info, calls_list) in calls:
res.add(call_info, {
"underlying_type": "external_calls"
})
res.add(call_info, {"underlying_type": "external_calls"})
for call_list_info in calls_list:
if call_list_info != call_info:
res.add(call_list_info, {
"underlying_type": "external_calls_sending_eth"
})
res.add(call_list_info, {"underlying_type": "external_calls_sending_eth"})
# Add all variables written via nodes which write them.
for finding_value in varsWritten:
res.add(finding_value.node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
finding_value.node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
for other_node in finding_value.nodes:
if other_node != finding_value.node:
res.add(other_node, {
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name
})
res.add(
other_node,
{
"underlying_type": "variables_written",
"variable_name": finding_value.variable.name,
},
)
# Append our result
results.append(res)

@ -11,17 +11,16 @@ class ShadowingAbstractDetection(AbstractDetector):
Shadowing detection
"""
ARGUMENT = 'shadowing-abstract'
HELP = 'State variables shadowing from abstract contracts'
ARGUMENT = "shadowing-abstract"
HELP = "State variables shadowing from abstract contracts"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts"
WIKI_TITLE = 'State variable shadowing from abstract contracts'
WIKI_DESCRIPTION = 'Detection of state variables shadowed from abstract contracts.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "State variable shadowing from abstract contracts"
WIKI_DESCRIPTION = "Detection of state variables shadowed from abstract contracts."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract BaseContract{
address owner;
@ -31,10 +30,9 @@ contract DerivedContract is BaseContract{
address owner;
}
```
`owner` of `BaseContract` is shadowed in `DerivedContract`.'''
WIKI_RECOMMENDATION = 'Remove the state variable shadowing.'
`owner` of `BaseContract` is shadowed in `DerivedContract`."""
WIKI_RECOMMENDATION = "Remove the state variable shadowing."
def detect_shadowing(self, contract):
ret = []
@ -49,7 +47,6 @@ contract DerivedContract is BaseContract{
ret.append([var] + shadow)
return ret
def _detect(self):
""" Detect shadowing
@ -65,7 +62,7 @@ contract DerivedContract is BaseContract{
for all_variables in shadowing:
shadow = all_variables[0]
variables = all_variables[1:]
info = [shadow, ' shadows:\n']
info = [shadow, " shadows:\n"]
for var in variables:
info += ["\t- ", var, "\n"]

@ -10,17 +10,16 @@ class BuiltinSymbolShadowing(AbstractDetector):
Built-in symbol shadowing
"""
ARGUMENT = 'shadowing-builtin'
HELP = 'Built-in symbol shadowing'
ARGUMENT = "shadowing-builtin"
HELP = "Built-in symbol shadowing"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing"
WIKI_TITLE = 'Builtin Symbol Shadowing'
WIKI_DESCRIPTION = 'Detection of shadowing built-in symbols using local variables, state variables, functions, modifiers, or events.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Builtin Symbol Shadowing"
WIKI_DESCRIPTION = "Detection of shadowing built-in symbols using local variables, state variables, functions, modifiers, or events."
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity ^0.4.24;
@ -36,9 +35,9 @@ contract Bug {
}
}
```
`now` is defined as a state variable, and shadows with the built-in symbol `now`. The function `assert` overshadows the built-in `assert` function. Any use of either of these built-in symbols may lead to unexpected results.'''
`now` is defined as a state variable, and shadows with the built-in symbol `now`. The function `assert` overshadows the built-in `assert` function. Any use of either of these built-in symbols may lead to unexpected results."""
WIKI_RECOMMENDATION = 'Rename the local variables, state variables, functions, modifiers, and events that shadow a builtin symbol.'
WIKI_RECOMMENDATION = "Rename the local variables, state variables, functions, modifiers, and events that shadow a builtin symbol."
SHADOWING_FUNCTION = "function"
SHADOWING_MODIFIER = "modifier"
@ -47,17 +46,70 @@ contract Bug {
SHADOWING_EVENT = "event"
# Reserved keywords reference: https://solidity.readthedocs.io/en/v0.5.2/units-and-global-variables.html
BUILTIN_SYMBOLS = ["assert", "require", "revert", "block", "blockhash",
"gasleft", "msg", "now", "tx", "this", "addmod", "mulmod",
"keccak256", "sha256", "sha3", "ripemd160", "ecrecover",
"selfdestruct", "suicide", "abi", "fallback", "receive"]
BUILTIN_SYMBOLS = [
"assert",
"require",
"revert",
"block",
"blockhash",
"gasleft",
"msg",
"now",
"tx",
"this",
"addmod",
"mulmod",
"keccak256",
"sha256",
"sha3",
"ripemd160",
"ecrecover",
"selfdestruct",
"suicide",
"abi",
"fallback",
"receive",
]
# https://solidity.readthedocs.io/en/v0.5.2/miscellaneous.html#reserved-keywords
RESERVED_KEYWORDS = ['abstract', 'after', 'alias', 'apply', 'auto', 'case', 'catch', 'copyof',
'default', 'define', 'final', 'immutable', 'implements', 'in', 'inline',
'let', 'macro', 'match', 'mutable', 'null', 'of', 'override', 'partial',
'promise', 'reference', 'relocatable', 'sealed', 'sizeof', 'static',
'supports', 'switch', 'try', 'type', 'typedef', 'typeof', 'unchecked']
RESERVED_KEYWORDS = [
"abstract",
"after",
"alias",
"apply",
"auto",
"case",
"catch",
"copyof",
"default",
"define",
"final",
"immutable",
"implements",
"in",
"inline",
"let",
"macro",
"match",
"mutable",
"null",
"of",
"override",
"partial",
"promise",
"reference",
"relocatable",
"sealed",
"sizeof",
"static",
"supports",
"switch",
"try",
"type",
"typedef",
"typeof",
"unchecked",
]
def is_builtin_symbol(self, word):
""" Detects if a given word is a built-in symbol.

@ -10,16 +10,16 @@ class LocalShadowing(AbstractDetector):
Local variable shadowing
"""
ARGUMENT = 'shadowing-local'
HELP = 'Local variables shadowing'
ARGUMENT = "shadowing-local"
HELP = "Local variables shadowing"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing"
WIKI_TITLE = 'Local variable shadowing'
WIKI_DESCRIPTION = 'Detection of shadowing using local variables.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Local variable shadowing"
WIKI_DESCRIPTION = "Detection of shadowing using local variables."
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity ^0.4.24;
@ -38,9 +38,9 @@ contract Bug {
}
}
```
`sensitive_function.owner` shadows `Bug.owner`. As a result, the use of `owner` in `sensitive_function` might be incorrect.'''
`sensitive_function.owner` shadows `Bug.owner`. As a result, the use of `owner` in `sensitive_function` might be incorrect."""
WIKI_RECOMMENDATION = 'Rename the local variables that shadow another component.'
WIKI_RECOMMENDATION = "Rename the local variables that shadow another component."
OVERSHADOWED_FUNCTION = "function"
OVERSHADOWED_MODIFIER = "modifier"
@ -80,7 +80,9 @@ contract Bug {
# Check state variables
for scope_state_variable in scope_contract.state_variables_declared:
if variable.name == scope_state_variable.name:
overshadowed.append((self.OVERSHADOWED_STATE_VARIABLE, scope_state_variable))
overshadowed.append(
(self.OVERSHADOWED_STATE_VARIABLE, scope_state_variable)
)
# If we have found any overshadowed objects, we'll want to add it to our result list.
if overshadowed:
@ -104,7 +106,7 @@ contract Bug {
for shadow in shadows:
local_variable = shadow[0]
overshadowed = shadow[1]
info = [local_variable, ' shadows:\n']
info = [local_variable, " shadows:\n"]
for overshadowed_entry in overshadowed:
info += ["\t- ", overshadowed_entry[1], f" ({overshadowed_entry[0]})\n"]

@ -10,16 +10,16 @@ class StateShadowing(AbstractDetector):
Shadowing of state variable
"""
ARGUMENT = 'shadowing-state'
HELP = 'State variables shadowing'
ARGUMENT = "shadowing-state"
HELP = "State variables shadowing"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing"
WIKI_TITLE = 'State variable shadowing'
WIKI_DESCRIPTION = 'Detection of state variables shadowed.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "State variable shadowing"
WIKI_DESCRIPTION = "Detection of state variables shadowed."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract BaseContract{
address owner;
@ -43,10 +43,9 @@ contract DerivedContract is BaseContract{
}
}
```
`owner` of `BaseContract` is never assigned and the modifier `isOwner` does not work.'''
WIKI_RECOMMENDATION = 'Remove the state variable shadowing.'
`owner` of `BaseContract` is never assigned and the modifier `isOwner` does not work."""
WIKI_RECOMMENDATION = "Remove the state variable shadowing."
def detect_shadowing(self, contract):
ret = []
@ -76,12 +75,11 @@ contract DerivedContract is BaseContract{
for all_variables in shadowing:
shadow = all_variables[0]
variables = all_variables[1:]
info = [shadow, ' shadows:\n']
info = [shadow, " shadows:\n"]
for var in variables:
info += ["\t- ", var, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -1,7 +1,6 @@
from collections import defaultdict
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
def _find_missing_inheritance(slither):
@ -26,22 +25,22 @@ def _find_missing_inheritance(slither):
class NameReused(AbstractDetector):
ARGUMENT = 'name-reused'
ARGUMENT = "name-reused"
HELP = "Contract's name reused"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused"
WIKI_TITLE = 'Name reused'
WIKI_DESCRIPTION = '''If a codebase has two contracts the similar names, the compilation artifacts
will not contain one of the contracts with the duplicate name.'''
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Name reused"
WIKI_DESCRIPTION = """If a codebase has two contracts the similar names, the compilation artifacts
will not contain one of the contracts with the duplicate name."""
WIKI_EXPLOIT_SCENARIO = """
Bob's `truffle` codebase has two contracts named `ERC20`.
When `truffle compile` runs, only one of the two contracts will generate artifacts in `build/contracts`.
As a result, the second contract cannot be analyzed.
'''
WIKI_RECOMMENDATION = 'Rename the contract.'
"""
WIKI_RECOMMENDATION = "Rename the contract."
def _detect(self):
results = []
@ -49,8 +48,9 @@ As a result, the second contract cannot be analyzed.
names_reused = self.slither.contract_name_collisions
# First show the contracts that we know are missing
incorrectly_constructed = [contract for contract in self.contracts
if contract.is_incorrectly_constructed]
incorrectly_constructed = [
contract for contract in self.contracts if contract.is_incorrectly_constructed
]
inheritance_corrupted = defaultdict(list)
for contract in incorrectly_constructed:
@ -58,17 +58,17 @@ As a result, the second contract cannot be analyzed.
inheritance_corrupted[father.name].append(contract)
for contract_name, files in names_reused.items():
info = [contract_name, ' is re-used:\n']
info = [contract_name, " is re-used:\n"]
for file in files:
if file is None:
info += ['\t- In an file not found, most likely in\n']
info += ["\t- In an file not found, most likely in\n"]
else:
info += ['\t- ', file, '\n']
info += ["\t- ", file, "\n"]
if contract_name in inheritance_corrupted:
info += ['\tAs a result, the inherited contracts are not correctly analyzed:\n']
info += ["\tAs a result, the inherited contracts are not correctly analyzed:\n"]
for corrupted in inheritance_corrupted[contract_name]:
info += ["\t\t- ", corrupted, '\n']
info += ["\t\t- ", corrupted, "\n"]
res = self.generate_result(info)
results.append(res)
@ -77,18 +77,18 @@ As a result, the second contract cannot be analyzed.
most_base_with_missing_inheritance = _find_missing_inheritance(self.slither)
for b in most_base_with_missing_inheritance:
info = [b, ' inherits from a contract for which the name is reused.\n']
info = [b, " inherits from a contract for which the name is reused.\n"]
if b.inheritance:
info += ['\t- Slither could not determine which contract has a duplicate name:\n']
info += ["\t- Slither could not determine which contract has a duplicate name:\n"]
for inheritance in b.inheritance:
info += ['\t\t-', inheritance, '\n']
info += ['\t- Check if:\n']
info += ['\t\t- A inherited contract is missing from this list,\n']
info += ['\t\t- The contract are imported from the correct files.\n']
info += ["\t\t-", inheritance, "\n"]
info += ["\t- Check if:\n"]
info += ["\t\t- A inherited contract is missing from this list,\n"]
info += ["\t\t- The contract are imported from the correct files.\n"]
if b.derived_contracts:
info += [f'\t- This issue impacts the contracts inheriting from {b.name}:\n']
info += [f"\t- This issue impacts the contracts inheriting from {b.name}:\n"]
for derived in b.derived_contracts:
info += ['\t\t-', derived, '\n']
info += ["\t\t-", derived, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -7,15 +7,15 @@ class RightToLeftOverride(AbstractDetector):
Detect the usage of a Right-To-Left-Override (U+202E) character
"""
ARGUMENT = 'rtlo'
HELP = 'Right-To-Left-Override control character is used'
ARGUMENT = "rtlo"
HELP = "Right-To-Left-Override control character is used"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character'
WIKI_TITLE = 'Right-to-Left-Override character'
WIKI_DESCRIPTION = 'An attacker can manipulate the logic of the contract by using a right-to-left-override character (`U+202E)`.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character"
WIKI_TITLE = "Right-to-Left-Override character"
WIKI_DESCRIPTION = "An attacker can manipulate the logic of the contract by using a right-to-left-override character (`U+202E)`."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Token
{
@ -42,19 +42,19 @@ contract Token
`Token` uses the right-to-left-override character when calling `_withdraw`. As a result, the fee is incorrectly sent to `msg.sender`, and the token balance is sent to the owner.
'''
WIKI_RECOMMENDATION = 'Special control characters must not be allowed.'
"""
WIKI_RECOMMENDATION = "Special control characters must not be allowed."
RTLO_CHARACTER_ENCODED = "\u202e".encode('utf-8')
RTLO_CHARACTER_ENCODED = "\u202e".encode("utf-8")
STANDARD_JSON = False
def _detect(self):
results = []
pattern = re.compile(".*\u202e.*".encode('utf-8'))
pattern = re.compile(".*\u202e.*".encode("utf-8"))
for filename, source in self.slither.source_code.items():
# Attempt to find all RTLO characters in this source file.
original_source_encoded = source.encode('utf-8')
original_source_encoded = source.encode("utf-8")
start_index = 0
# Keep searching all file contents for the character.
@ -68,7 +68,7 @@ contract Token
else:
# We found another instance of the character, define our output
idx = start_index + result_index
relative = self.slither.crytic_compile.filename_lookup(filename).relative
info = f"{relative} contains a unicode right-to-left-override character at byte offset {idx}:\n"
@ -76,9 +76,11 @@ contract Token
info += f"\t- {pattern.findall(source_encoded)[0]}\n"
res = self.generate_result(info)
res.add_other("rtlo-character",
(filename, idx, len(self.RTLO_CHARACTER_ENCODED)),
self.slither)
res.add_other(
"rtlo-character",
(filename, idx, len(self.RTLO_CHARACTER_ENCODED)),
self.slither,
)
results.append(res)
# Advance the start index for the next iteration

@ -11,17 +11,16 @@ class Assembly(AbstractDetector):
Detect usage of inline assembly
"""
ARGUMENT = 'assembly'
HELP = 'Assembly usage'
ARGUMENT = "assembly"
HELP = "Assembly usage"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage"
WIKI_TITLE = 'Assembly usage'
WIKI_DESCRIPTION = 'The use of assembly is error-prone and should be avoided.'
WIKI_RECOMMENDATION = 'Do not use `evm` assembly.'
WIKI_TITLE = "Assembly usage"
WIKI_DESCRIPTION = "The use of assembly is error-prone and should be avoided."
WIKI_RECOMMENDATION = "Do not use `evm` assembly."
@staticmethod
def _contains_inline_assembly_use(node):
@ -38,8 +37,7 @@ class Assembly(AbstractDetector):
if f.contract_declarer != contract:
continue
nodes = f.nodes
assembly_nodes = [n for n in nodes if
self._contains_inline_assembly_use(n)]
assembly_nodes = [n for n in nodes if self._contains_inline_assembly_use(n)]
if assembly_nodes:
ret.append((f, assembly_nodes))
return ret

@ -12,16 +12,16 @@ class BooleanEquality(AbstractDetector):
Boolean constant misuse
"""
ARGUMENT = 'boolean-equal'
HELP = 'Comparison to boolean constant'
ARGUMENT = "boolean-equal"
HELP = "Comparison to boolean constant"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality"
WIKI_TITLE = 'Boolean equality'
WIKI_DESCRIPTION = '''Detects the comparison to boolean constants.'''
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Boolean equality"
WIKI_DESCRIPTION = """Detects the comparison to boolean constants."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
function f(bool x) public {
@ -33,9 +33,9 @@ contract A {
}
}
```
Boolean constants can be used directly and do not need to be compare to `true` or `false`.'''
Boolean constants can be used directly and do not need to be compare to `true` or `false`."""
WIKI_RECOMMENDATION = '''Remove the equality to the boolean constant.'''
WIKI_RECOMMENDATION = """Remove the equality to the boolean constant."""
@staticmethod
def _detect_boolean_equality(contract):

@ -4,7 +4,15 @@ Module detecting misuse of Boolean constants
from slither.core.cfg.node import NodeType
from slither.core.solidity_types import ElementaryType
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import Assignment, Call, Return, InitArray, Binary, BinaryType, Condition
from slither.slithir.operations import (
Assignment,
Call,
Return,
InitArray,
Binary,
BinaryType,
Condition,
)
from slither.slithir.variables import Constant
@ -13,16 +21,18 @@ class BooleanConstantMisuse(AbstractDetector):
Boolean constant misuse
"""
ARGUMENT = 'boolean-cst'
HELP = 'Misuse of Boolean constant'
ARGUMENT = "boolean-cst"
HELP = "Misuse of Boolean constant"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant"
)
WIKI_TITLE = 'Misuse of a Boolean constant'
WIKI_DESCRIPTION = '''Detects the misuse of a Boolean constant.'''
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Misuse of a Boolean constant"
WIKI_DESCRIPTION = """Detects the misuse of a Boolean constant."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
function f(uint x) public {
@ -41,9 +51,9 @@ contract A {
}
```
Boolean constants in code have only a few legitimate uses.
Other uses (in complex expressions, as conditionals) indicate either an error or, most likely, the persistence of faulty code.'''
Other uses (in complex expressions, as conditionals) indicate either an error or, most likely, the persistence of faulty code."""
WIKI_RECOMMENDATION = '''Verify and simplify the condition.'''
WIKI_RECOMMENDATION = """Verify and simplify the condition."""
@staticmethod
def _detect_boolean_constant_misuses(contract):
@ -68,7 +78,9 @@ Other uses (in complex expressions, as conditionals) indicate either an error or
if node.irs:
if len(node.irs) == 1:
ir = node.irs[0]
if isinstance(ir, Condition) and ir.value == Constant('True', ElementaryType('bool')):
if isinstance(ir, Condition) and ir.value == Constant(
"True", ElementaryType("bool")
):
continue
for ir in node.irs:
@ -103,5 +115,5 @@ Other uses (in complex expressions, as conditionals) indicate either an error or
res = self.generate_result(info)
results.append(res)
return results

@ -1,27 +1,24 @@
"""
"""
from slither.core.cfg.node import NodeType
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.slithir.operations import (HighLevelCall, LibraryCall,
LowLevelCall, Send, Transfer)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import HighLevelCall, LibraryCall, LowLevelCall, Send, Transfer
class MultipleCallsInLoop(AbstractDetector):
"""
"""
ARGUMENT = 'calls-loop'
HELP = 'Multiple calls in a loop'
ARGUMENT = "calls-loop"
HELP = "Multiple calls in a loop"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop"
WIKI_TITLE = 'Calls inside a loop'
WIKI_DESCRIPTION = 'Calls inside a loop might lead to a denial-of-service attack.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Calls inside a loop"
WIKI_DESCRIPTION = "Calls inside a loop might lead to a denial-of-service attack."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract CallsInLoop{
@ -39,9 +36,9 @@ contract CallsInLoop{
}
```
If one of the destinations has a fallback function that reverts, `bad` will always revert.'''
If one of the destinations has a fallback function that reverts, `bad` will always revert."""
WIKI_RECOMMENDATION = 'Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls.'
WIKI_RECOMMENDATION = "Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls."
@staticmethod
def call_in_loop(node, in_loop, visited, ret):
@ -57,10 +54,7 @@ If one of the destinations has a fallback function that reverts, `bad` will alwa
if in_loop:
for ir in node.irs:
if isinstance(ir, (LowLevelCall,
HighLevelCall,
Send,
Transfer)):
if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)):
if isinstance(ir, LibraryCall):
continue
ret.append(node)
@ -73,8 +67,7 @@ If one of the destinations has a fallback function that reverts, `bad` will alwa
ret = []
for f in contract.functions + contract.modifiers:
if f.contract_declarer == contract and f.is_implemented:
MultipleCallsInLoop.call_in_loop(f.entry_point,
False, [], ret)
MultipleCallsInLoop.call_in_loop(f.entry_point, False, [], ret)
return ret

@ -7,17 +7,16 @@ class ControlledDelegateCall(AbstractDetector):
"""
"""
ARGUMENT = 'controlled-delegatecall'
HELP = 'Controlled delegatecall destination'
ARGUMENT = "controlled-delegatecall"
HELP = "Controlled delegatecall destination"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall"
WIKI_TITLE = 'Controlled Delegatecall'
WIKI_DESCRIPTION = '`Delegatecall` or `callcode` to an address controlled by the user.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Controlled Delegatecall"
WIKI_DESCRIPTION = "`Delegatecall` or `callcode` to an address controlled by the user."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Delegatecall{
function delegate(address to, bytes data){
@ -25,17 +24,19 @@ contract Delegatecall{
}
}
```
Bob calls `delegate` and delegates the execution to his malicious contract. As a result, Bob withdraws the funds of the contract and destructs it.'''
Bob calls `delegate` and delegates the execution to his malicious contract. As a result, Bob withdraws the funds of the contract and destructs it."""
WIKI_RECOMMENDATION = 'Avoid using `delegatecall`. Use only trusted destinations.'
WIKI_RECOMMENDATION = "Avoid using `delegatecall`. Use only trusted destinations."
def controlled_delegatecall(self, function):
ret = []
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, LowLevelCall) and ir.function_name in ['delegatecall', 'callcode']:
if is_tainted(ir.destination,
function.contract):
if isinstance(ir, LowLevelCall) and ir.function_name in [
"delegatecall",
"callcode",
]:
if is_tainted(ir.destination, function.contract):
ret.append(node)
return ret
@ -50,10 +51,10 @@ Bob calls `delegate` and delegates the execution to his malicious contract. As a
continue
nodes = self.controlled_delegatecall(f)
if nodes:
func_info = [f, ' uses delegatecall to a input-controlled function id\n']
func_info = [f, " uses delegatecall to a input-controlled function id\n"]
for node in nodes:
node_info = func_info + ['\t- ', node,'\n']
node_info = func_info + ["\t- ", node, "\n"]
res = self.generate_result(node_info)
results.append(res)

@ -15,16 +15,16 @@ class DeprecatedStandards(AbstractDetector):
Use of Deprecated Standards
"""
ARGUMENT = 'deprecated-standards'
HELP = 'Deprecated Solidity Standards'
ARGUMENT = "deprecated-standards"
HELP = "Deprecated Solidity Standards"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards"
WIKI_TITLE = 'Deprecated standards'
WIKI_DESCRIPTION = 'Detect the usage of deprecated standards.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Deprecated standards"
WIKI_DESCRIPTION = "Detect the usage of deprecated standards."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract ContractWithDeprecatedReferences {
// Deprecated: Change block.blockhash() -> blockhash()
@ -51,15 +51,19 @@ contract ContractWithDeprecatedReferences {
suicide(address(0));
}
}
```'''
```"""
WIKI_RECOMMENDATION = 'Replace all uses of deprecated symbols.'
WIKI_RECOMMENDATION = "Replace all uses of deprecated symbols."
# The format for the following deprecated lists is [(detecting_signature, original_text, recommended_text)]
DEPRECATED_SOLIDITY_VARIABLE = [("block.blockhash", "block.blockhash()", "blockhash()"),
("msg.gas", "msg.gas", "gasleft()")]
DEPRECATED_SOLIDITY_FUNCTIONS = [("suicide(address)", "suicide()", "selfdestruct()"),
("sha3()", "sha3()", "keccak256()")]
DEPRECATED_SOLIDITY_VARIABLE = [
("block.blockhash", "block.blockhash()", "blockhash()"),
("msg.gas", "msg.gas", "gasleft()"),
]
DEPRECATED_SOLIDITY_FUNCTIONS = [
("suicide(address)", "suicide()", "selfdestruct()"),
("sha3()", "sha3()", "keccak256()"),
]
DEPRECATED_NODE_TYPES = [(NodeType.THROW, "throw", "revert()")]
DEPRECATED_LOW_LEVEL_CALLS = [("callcode", "callcode", "delegatecall")]
@ -113,7 +117,9 @@ contract ContractWithDeprecatedReferences {
for state_variable in contract.state_variables_declared:
if state_variable.expression:
deprecated_results = self.detect_deprecation_in_expression(state_variable.expression)
deprecated_results = self.detect_deprecation_in_expression(
state_variable.expression
)
if deprecated_results:
results.append((state_variable, deprecated_results))
@ -152,10 +158,12 @@ contract ContractWithDeprecatedReferences {
for deprecated_reference in deprecated_references:
source_object = deprecated_reference[0]
deprecated_entries = deprecated_reference[1]
info = ['Deprecated standard detected ', source_object, ':\n']
info = ["Deprecated standard detected ", source_object, ":\n"]
for (dep_id, original_desc, recommended_disc) in deprecated_entries:
info += [f"\t- Usage of \"{original_desc}\" should be replaced with \"{recommended_disc}\"\n"]
info += [
f'\t- Usage of "{original_desc}" should be replaced with "{recommended_disc}"\n'
]
res = self.generate_result(info)
results.append(res)

@ -13,7 +13,10 @@ def is_division(ir):
return True
if isinstance(ir, LibraryCall):
if ir.function.name.lower() in ['div', 'safediv', ]:
if ir.function.name.lower() in [
"div",
"safediv",
]:
if len(ir.arguments) == 2:
if ir.lvalue:
return True
@ -26,7 +29,10 @@ def is_multiplication(ir):
return True
if isinstance(ir, LibraryCall):
if ir.function.name.lower() in ['mul', 'safemul', ]:
if ir.function.name.lower() in [
"mul",
"safemul",
]:
if len(ir.arguments) == 2:
if ir.lvalue:
return True
@ -49,17 +55,17 @@ class DivideBeforeMultiply(AbstractDetector):
Divide before multiply
"""
ARGUMENT = 'divide-before-multiply'
HELP = 'Imprecise arithmetic operations order'
ARGUMENT = "divide-before-multiply"
HELP = "Imprecise arithmetic operations order"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply"
WIKI_TITLE = 'Divide before multiply'
WIKI_DESCRIPTION = '''Solidity only supports integers, so division will often truncate; performing a multiply before a divison can sometimes avoid loss of precision.'''
WIKI_DESCRIPTION = '''Solidity integer division might truncate. As a result, performing multiplication before divison might reduce precision.'''
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Divide before multiply"
WIKI_DESCRIPTION = """Solidity only supports integers, so division will often truncate; performing a multiply before a divison can sometimes avoid loss of precision."""
WIKI_DESCRIPTION = """Solidity integer division might truncate. As a result, performing multiplication before divison might reduce precision."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
function f(uint n) public {
@ -69,9 +75,9 @@ contract A {
```
If `n` is greater than `oldSupply`, `coins` will be zero. For example, with `oldSupply = 5; n = 10, interest = 2`, coins will be zero.
If `(oldSupply * interest / n)` was used, `coins` would have been `1`.
In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous.'''
In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous."""
WIKI_RECOMMENDATION = '''Consider ordering multiplication before division.'''
WIKI_RECOMMENDATION = """Consider ordering multiplication before division."""
def _explore(self, node, explored, f_results, divisions):
if node in explored:

@ -5,10 +5,8 @@
from slither.analyses.data_dependency.data_dependency import is_dependent_ssa
from slither.core.declarations import Function
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification)
from slither.slithir.operations import (Assignment, Balance, Binary, BinaryType,
HighLevelCall)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import Assignment, Balance, Binary, BinaryType, HighLevelCall
from slither.core.solidity_types import MappingType, ElementaryType
@ -17,16 +15,18 @@ from slither.core.declarations.solidity_variables import SolidityVariable, Solid
class IncorrectStrictEquality(AbstractDetector):
ARGUMENT = 'incorrect-equality'
HELP = 'Dangerous strict equalities'
ARGUMENT = "incorrect-equality"
HELP = "Dangerous strict equalities"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities"
)
WIKI_TITLE = 'Dangerous strict equalities'
WIKI_DESCRIPTION = 'Use of strict equalities that can be easily manipulated by an attacker.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Dangerous strict equalities"
WIKI_DESCRIPTION = "Use of strict equalities that can be easily manipulated by an attacker."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Crowdsale{
function fund_reached() public returns(bool){
@ -34,13 +34,17 @@ contract Crowdsale{
}
```
`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens.
`Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends.'''
`Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends."""
WIKI_RECOMMENDATION = '''Don't use strict equality to determine if an account has enough Ether or tokens.'''
WIKI_RECOMMENDATION = (
"""Don't use strict equality to determine if an account has enough Ether or tokens."""
)
sources_taint = [SolidityVariable('now'),
SolidityVariableComposed('block.number'),
SolidityVariableComposed('block.timestamp')]
sources_taint = [
SolidityVariable("now"),
SolidityVariableComposed("block.number"),
SolidityVariableComposed("block.timestamp"),
]
@staticmethod
def is_direct_comparison(ir):
@ -48,7 +52,13 @@ contract Crowdsale{
@staticmethod
def is_any_tainted(variables, taints, function):
return any([is_dependent_ssa(var, taint, function.contract) for var in variables for taint in taints])
return any(
[
is_dependent_ssa(var, taint, function.contract)
for var in variables
for taint in taints
]
)
def taint_balance_equalities(self, functions):
taints = []
@ -58,16 +68,20 @@ contract Crowdsale{
if isinstance(ir, Balance):
taints.append(ir.lvalue)
if isinstance(ir, HighLevelCall):
#print(ir.function.full_name)
if isinstance(ir.function, Function) and\
ir.function.full_name == 'balanceOf(address)':
taints.append(ir.lvalue)
if isinstance(ir.function, StateVariable) and\
isinstance(ir.function.type, MappingType) and\
ir.function.name == 'balanceOf' and\
ir.function.type.type_from == ElementaryType('address') and\
ir.function.type.type_to == ElementaryType('uint256'):
taints.append(ir.lvalue)
# print(ir.function.full_name)
if (
isinstance(ir.function, Function)
and ir.function.full_name == "balanceOf(address)"
):
taints.append(ir.lvalue)
if (
isinstance(ir.function, StateVariable)
and isinstance(ir.function.type, MappingType)
and ir.function.name == "balanceOf"
and ir.function.type.type_from == ElementaryType("address")
and ir.function.type.type_to == ElementaryType("uint256")
):
taints.append(ir.lvalue)
if isinstance(ir, Assignment):
if ir.rvalue in self.sources_taint:
taints.append(ir.lvalue)
@ -109,7 +123,7 @@ contract Crowdsale{
ret = self.detect_strict_equality(c)
# sort ret to get deterministic results
ret = sorted(list(ret.items()), key=lambda x:x[0].name)
ret = sorted(list(ret.items()), key=lambda x: x[0].name)
for f, nodes in ret:
func_info = [f, " uses a dangerous strict equality:\n"]

@ -11,17 +11,17 @@ class TooManyDigits(AbstractDetector):
Detect numbers with too many digits
"""
ARGUMENT = 'too-many-digits'
HELP = 'Conformance to numeric notation best practices'
ARGUMENT = "too-many-digits"
HELP = "Conformance to numeric notation best practices"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits'
WIKI_TITLE = 'Too many digits'
WIKI_DESCRIPTION = '''
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits"
WIKI_TITLE = "Too many digits"
WIKI_DESCRIPTION = """
Literals with many digits are difficult to read and review.
'''
WIKI_EXPLOIT_SCENARIO = '''
"""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyContract{
uint 1_ether = 10000000000000000000;
@ -29,13 +29,13 @@ contract MyContract{
```
While `1_ether` looks like `1 ether`, it is `10 ether`. As a result, it's likely to be used incorrectly.
'''
WIKI_RECOMMENDATION = '''
"""
WIKI_RECOMMENDATION = """
Use:
- [Ether suffix](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#ether-units),
- [Time suffix](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#time-units), or
- [The scientific notation](https://solidity.readthedocs.io/en/latest/types.html#rational-and-integer-literals)
'''
"""
@staticmethod
def _detect_too_many_digits(f):
@ -49,7 +49,7 @@ Use:
if isinstance(read, Constant):
# read.value can return an int or a str. Convert it to str
value_as_str = read.original_value
if '00000' in value_as_str:
if "00000" in value_as_str:
# Info to be printed
ret.append(node)
return ret
@ -59,14 +59,14 @@ Use:
# iterate over all contracts
for contract in self.slither.contracts_derived:
# iterate over all functions
# iterate over all functions
for f in contract.functions:
# iterate over all the nodes
ret = self._detect_too_many_digits(f)
if ret:
func_info = [f, ' uses literals with too many digits:']
func_info = [f, " uses literals with too many digits:"]
for node in ret:
node_info = func_info + ['\n\t- ', node,'\n']
node_info = func_info + ["\n\t- ", node, "\n"]
# Add the result in result
res = self.generate_result(node_info)

@ -10,16 +10,18 @@ class TxOrigin(AbstractDetector):
Detect usage of tx.origin in a conditional node
"""
ARGUMENT = 'tx-origin'
HELP = 'Dangerous usage of `tx.origin`'
ARGUMENT = "tx-origin"
HELP = "Dangerous usage of `tx.origin`"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin"
)
WIKI_TITLE = 'Dangerous usage of `tx.origin`'
WIKI_DESCRIPTION = '`tx.origin`-based protection can be abused by a malicious contract if a legitimate user interacts with the malicious contract.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Dangerous usage of `tx.origin`"
WIKI_DESCRIPTION = "`tx.origin`-based protection can be abused by a malicious contract if a legitimate user interacts with the malicious contract."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract TxOrigin {
address owner = msg.sender;
@ -28,9 +30,9 @@ contract TxOrigin {
require(tx.origin == owner);
}
```
Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `TxOrigin` and bypasses the `tx.origin` protection.'''
Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `TxOrigin` and bypasses the `tx.origin` protection."""
WIKI_RECOMMENDATION = 'Do not use `tx.origin` for authorization.'
WIKI_RECOMMENDATION = "Do not use `tx.origin` for authorization."
@staticmethod
def _contains_incorrect_tx_origin_use(node):
@ -42,8 +44,9 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `
"""
solidity_var_read = node.solidity_variables_read
if solidity_var_read:
return any(v.name == 'tx.origin' for v in solidity_var_read) and\
all(v.name != 'msg.sender' for v in solidity_var_read)
return any(v.name == "tx.origin" for v in solidity_var_read) and all(
v.name != "msg.sender" for v in solidity_var_read
)
return False
def detect_tx_origin(self, contract):
@ -51,10 +54,12 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `
for f in contract.functions:
nodes = f.nodes
condtional_nodes = [n for n in nodes if n.contains_if() or
n.contains_require_or_assert()]
bad_tx_nodes = [n for n in condtional_nodes if
self._contains_incorrect_tx_origin_use(n)]
condtional_nodes = [
n for n in nodes if n.contains_if() or n.contains_require_or_assert()
]
bad_tx_nodes = [
n for n in condtional_nodes if self._contains_incorrect_tx_origin_use(n)
]
if bad_tx_nodes:
ret.append((f, bad_tx_nodes))
return ret

@ -7,21 +7,24 @@ from slither.slithir.operations import Binary, BinaryType
from slither.slithir.variables import Constant
from slither.core.solidity_types.elementary_type import Int, Uint
class TypeBasedTautology(AbstractDetector):
"""
Type-based tautology or contradiction
"""
ARGUMENT = 'tautology'
HELP = 'Tautology or contradiction'
ARGUMENT = "tautology"
HELP = "Tautology or contradiction"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction'
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction"
)
WIKI_TITLE = 'Tautology or contradiction'
WIKI_DESCRIPTION = '''Detects expressions that are tautologies or contradictions.'''
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Tautology or contradiction"
WIKI_DESCRIPTION = """Detects expressions that are tautologies or contradictions."""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
function f(uint x) public {
@ -41,19 +44,20 @@ contract A {
```
`x` is a `uint256`, so `x >= 0` will be always true.
`y` is a `uint8`, so `y <512` will be always true.
'''
"""
WIKI_RECOMMENDATION = '''Fix the incorrect comparison by changing the value type or the comparison.'''
WIKI_RECOMMENDATION = (
"""Fix the incorrect comparison by changing the value type or the comparison."""
)
def typeRange(self, t):
bits = int(t.split("int")[1])
if t in Uint:
return (0, (2**bits)-1)
return (0, (2 ** bits) - 1)
if t in Int:
v = (2**(bits-1))-1
v = (2 ** (bits - 1)) - 1
return (-v, v)
flip_table = {
BinaryType.GREATER: BinaryType.LESS,
BinaryType.GREATER_EQUAL: BinaryType.LESS_EQUAL,
@ -62,14 +66,14 @@ contract A {
}
def _detect_tautology_or_contradiction(self, low, high, cval, op):
'''
"""
Return true if "[low high] op cval " is always true or always false
:param low:
:param high:
:param cval:
:param op:
:return:
'''
"""
if op == BinaryType.LESS:
# a < cval
# its a tautology if
@ -124,7 +128,9 @@ contract A {
rtype = str(ir.variable_right.type)
if rtype in allInts:
(low, high) = self.typeRange(rtype)
if self._detect_tautology_or_contradiction(low, high, cval, self.flip_table[ir.type]):
if self._detect_tautology_or_contradiction(
low, high, cval, self.flip_table[ir.type]
):
f_results.add(node)
if isinstance(ir.variable_right, Constant):
@ -132,7 +138,9 @@ contract A {
ltype = str(ir.variable_left.type)
if ltype in allInts:
(low, high) = self.typeRange(ltype)
if self._detect_tautology_or_contradiction(low, high, cval, ir.type):
if self._detect_tautology_or_contradiction(
low, high, cval, ir.type
):
f_results.add(node)
results.append((function, f_results))

@ -18,32 +18,33 @@ class ConstCandidateStateVars(AbstractDetector):
Reference: https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables
"""
ARGUMENT = 'constable-states'
HELP = 'State variables that could be declared constant'
ARGUMENT = "constable-states"
HELP = "State variables that could be declared constant"
IMPACT = DetectorClassification.OPTIMIZATION
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant"
WIKI_TITLE = 'State variables that could be declared constant'
WIKI_DESCRIPTION = 'Constant state variables should be declared constant to save gas.'
WIKI_RECOMMENDATION = 'Add the `constant` attributes to state variables that never change.'
WIKI_TITLE = "State variables that could be declared constant"
WIKI_DESCRIPTION = "Constant state variables should be declared constant to save gas."
WIKI_RECOMMENDATION = "Add the `constant` attributes to state variables that never change."
@staticmethod
def _valid_candidate(v):
return isinstance(v.type, ElementaryType) and not v.is_constant
# https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables
valid_solidity_function = [SolidityFunction('keccak256()'),
SolidityFunction('keccak256(bytes)'),
SolidityFunction('sha256()'),
SolidityFunction('sha256(bytes)'),
SolidityFunction('ripemd160()'),
SolidityFunction('ripemd160(bytes)'),
SolidityFunction('ecrecover(bytes32,uint8,bytes32,bytes32)'),
SolidityFunction('addmod(uint256,uint256,uint256)'),
SolidityFunction('mulmod(uint256,uint256,uint256)')]
valid_solidity_function = [
SolidityFunction("keccak256()"),
SolidityFunction("keccak256(bytes)"),
SolidityFunction("sha256()"),
SolidityFunction("sha256(bytes)"),
SolidityFunction("ripemd160()"),
SolidityFunction("ripemd160(bytes)"),
SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"),
SolidityFunction("addmod(uint256,uint256,uint256)"),
SolidityFunction("mulmod(uint256,uint256,uint256)"),
]
@staticmethod
def _is_constant_var(v):
@ -59,12 +60,12 @@ class ConstCandidateStateVars(AbstractDetector):
values = export.result()
if not values:
return True
if all((val in self.valid_solidity_function or self._is_constant_var(val) for val in values)):
if all(
(val in self.valid_solidity_function or self._is_constant_var(val) for val in values)
):
return True
return False
def _detect(self):
""" Detect state variables that could be const
"""
@ -72,17 +73,23 @@ class ConstCandidateStateVars(AbstractDetector):
all_variables = [c.state_variables for c in self.slither.contracts]
all_variables = set([item for sublist in all_variables for item in sublist])
all_non_constant_elementary_variables = set([v for v in all_variables
if self._valid_candidate(v)])
all_non_constant_elementary_variables = set(
[v for v in all_variables if self._valid_candidate(v)]
)
all_functions = [c.all_functions_called for c in self.slither.contracts]
all_functions = list(set([item for sublist in all_functions for item in sublist]))
all_variables_written = [f.state_variables_written for f in all_functions if not f.is_constructor_variables]
all_variables_written = [
f.state_variables_written for f in all_functions if not f.is_constructor_variables
]
all_variables_written = set([item for sublist in all_variables_written for item in sublist])
constable_variables = [v for v in all_non_constant_elementary_variables
if (not v in all_variables_written) and self._constant_initial_expression(v)]
constable_variables = [
v
for v in all_non_constant_elementary_variables
if (not v in all_variables_written) and self._constant_initial_expression(v)
]
# Order for deterministic results
constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name)

@ -12,17 +12,16 @@ class UninitializedLocalVars(AbstractDetector):
"""
"""
ARGUMENT = 'uninitialized-local'
HELP = 'Uninitialized local variables'
ARGUMENT = "uninitialized-local"
HELP = "Uninitialized local variables"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables"
WIKI_TITLE = 'Uninitialized local variables'
WIKI_DESCRIPTION = 'Uninitialized local variables.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Uninitialized local variables"
WIKI_DESCRIPTION = "Uninitialized local variables."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Uninitialized is Owner{
function withdraw() payable public onlyOwner{
@ -31,9 +30,9 @@ contract Uninitialized is Owner{
}
}
```
Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is lost.'''
Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is lost."""
WIKI_RECOMMENDATION = 'Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero.'
WIKI_RECOMMENDATION = "Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero."
key = "UNINITIALIZEDLOCAL"
@ -73,7 +72,6 @@ Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is
for son in node.sons:
self._detect_uninitialized(function, son, visited)
def _detect(self):
""" Detect uninitialized local variables
@ -92,11 +90,13 @@ Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is
if function.contains_assembly:
continue
# dont consider storage variable, as they are detected by another detector
uninitialized_local_variables = [v for v in function.local_variables if not v.is_storage and v.uninitialized]
uninitialized_local_variables = [
v for v in function.local_variables if not v.is_storage and v.uninitialized
]
function.entry_point.context[self.key] = uninitialized_local_variables
self._detect_uninitialized(function, function.entry_point, [])
all_results = list(set(self.results))
for(function, uninitialized_local_variable) in all_results:
for (function, uninitialized_local_variable) in all_results:
info = [uninitialized_local_variable, " is a local variable never initialized\n"]
json = self.generate_result(info)

@ -19,16 +19,16 @@ class UninitializedStateVarsDetection(AbstractDetector):
Constant function detector
"""
ARGUMENT = 'uninitialized-state'
HELP = 'Uninitialized state variables'
ARGUMENT = "uninitialized-state"
HELP = "Uninitialized state variables"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables"
WIKI_TITLE = 'Uninitialized state variables'
WIKI_DESCRIPTION = 'Uninitialized state variables.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Uninitialized state variables"
WIKI_DESCRIPTION = "Uninitialized state variables."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Uninitialized{
address destination;
@ -39,10 +39,10 @@ contract Uninitialized{
}
```
Bob calls `transfer`. As a result, the Ether are sent to the address `0x0` and are lost.
'''
WIKI_RECOMMENDATION = '''
"""
WIKI_RECOMMENDATION = """
Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero.
'''
"""
@staticmethod
def _written_variables(contract):
@ -51,12 +51,11 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
for n in f.nodes:
ret += n.state_variables_written
for ir in n.irs:
if isinstance(ir, LibraryCall) \
or isinstance(ir, InternalCall):
if isinstance(ir, LibraryCall) or isinstance(ir, InternalCall):
idx = 0
if ir.function:
for param in ir.function.parameters:
if param.location == 'storage':
if param.location == "storage":
# If its a storage variable, add either the variable
# Or the variable it points to if its a reference
if isinstance(ir.arguments[idx], ReferenceVariable):
@ -69,7 +68,7 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
def _variable_written_in_proxy(self):
# Hack to memoize without having it define in the init
if hasattr(self, '__variables_written_in_proxy'):
if hasattr(self, "__variables_written_in_proxy"):
return self.__variables_written_in_proxy
variables_written_in_proxy = []
@ -85,7 +84,10 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
if contract.is_upgradeable:
variables_name_written_in_proxy = self._variable_written_in_proxy()
if variables_name_written_in_proxy:
variables_in_contract = [contract.get_state_variable_from_name(v) for v in variables_name_written_in_proxy]
variables_in_contract = [
contract.get_state_variable_from_name(v)
for v in variables_name_written_in_proxy
]
variables_in_contract = [v for v in variables_in_contract if v]
variables += variables_in_contract
return list(set(variables))
@ -101,10 +103,13 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
written_variables = self._written_variables(contract)
written_variables += self._written_variables_in_proxy(contract)
read_variables = self._read_variables(contract)
return [(variable, contract.get_functions_reading_from_variable(variable))
for variable in contract.state_variables if variable not in written_variables and \
not variable.expression and \
variable in read_variables]
return [
(variable, contract.get_functions_reading_from_variable(variable))
for variable in contract.state_variables
if variable not in written_variables
and not variable.expression
and variable in read_variables
]
def _detect(self):
""" Detect uninitialized state variables

@ -12,16 +12,16 @@ class UninitializedStorageVars(AbstractDetector):
"""
"""
ARGUMENT = 'uninitialized-storage'
HELP = 'Uninitialized storage variables'
ARGUMENT = "uninitialized-storage"
HELP = "Uninitialized storage variables"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables"
WIKI_TITLE = 'Uninitialized storage variables'
WIKI_DESCRIPTION = 'An uninitialized storage variable will act as a reference to the first state variable, and can override a critical variable.'
WIKI_EXPLOIT_SCENARIO = '''
WIKI_TITLE = "Uninitialized storage variables"
WIKI_DESCRIPTION = "An uninitialized storage variable will act as a reference to the first state variable, and can override a critical variable."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Uninitialized{
address owner = msg.sender;
@ -37,9 +37,9 @@ contract Uninitialized{
}
```
Bob calls `func`. As a result, `owner` is overridden to `0`.
'''
"""
WIKI_RECOMMENDATION = 'Initialize all storage variables.'
WIKI_RECOMMENDATION = "Initialize all storage variables."
# node.context[self.key] contains the uninitialized storage variables
key = "UNINITIALIZEDSTORAGE"
@ -80,7 +80,6 @@ Bob calls `func`. As a result, `owner` is overridden to `0`.
for son in node.sons:
self._detect_uninitialized(function, son, visited)
def _detect(self):
""" Detect uninitialized storage variables
@ -96,11 +95,13 @@ Bob calls `func`. As a result, `owner` is overridden to `0`.
for contract in self.slither.contracts:
for function in contract.functions:
if function.is_implemented:
uninitialized_storage_variables = [v for v in function.local_variables if v.is_storage and v.uninitialized]
uninitialized_storage_variables = [
v for v in function.local_variables if v.is_storage and v.uninitialized
]
function.entry_point.context[self.key] = uninitialized_storage_variables
self._detect_uninitialized(function, function.entry_point, [])
for(function, uninitialized_storage_variable) in self.results:
for (function, uninitialized_storage_variable) in self.results:
info = [uninitialized_storage_variable, " is a storage variable never initialized\n"]
json = self.generate_result(info)
results.append(json)

@ -8,49 +8,54 @@ from slither.visitors.expression.export_values import ExportValues
from slither.core.variables.state_variable import StateVariable
from slither.formatters.variables.unused_state_variables import format
class UnusedStateVars(AbstractDetector):
"""
Unused state variables detector
"""
ARGUMENT = 'unused-state'
HELP = 'Unused state variables'
ARGUMENT = "unused-state"
HELP = "Unused state variables"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables'
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables"
WIKI_TITLE = 'Unused state variable'
WIKI_DESCRIPTION = 'Unused state variable.'
WIKI_EXPLOIT_SCENARIO = ''
WIKI_RECOMMENDATION = 'Remove unused state variables.'
WIKI_TITLE = "Unused state variable"
WIKI_DESCRIPTION = "Unused state variable."
WIKI_EXPLOIT_SCENARIO = ""
WIKI_RECOMMENDATION = "Remove unused state variables."
def detect_unused(self, contract):
if contract.is_signature_only():
return None
# Get all the variables read in all the functions and modifiers
all_functions = (contract.all_functions_called + contract.modifiers)
all_functions = contract.all_functions_called + contract.modifiers
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]
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
array_candidates = [x.type.length for x in array_candidates if isinstance(x.type, ArrayType) and x.type.length]
array_candidates = [
x.type.length
for x in array_candidates
if isinstance(x.type, ArrayType) and x.type.length
]
array_candidates = [ExportValues(x).result() for x in array_candidates]
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']
return [
x for x in contract.variables if x not in variables_used and x.visibility != "public"
]
def _detect(self):
""" Detect unused state variables

@ -1,3 +1,6 @@
class SlitherException(Exception): pass
class SlitherException(Exception):
pass
class SlitherError(SlitherException): pass
class SlitherError(SlitherException):
pass

@ -2,35 +2,48 @@ import re
from slither.formatters.exceptions import FormatError
from slither.formatters.utils.patches import create_patch
def format(slither, result):
elements = result['elements']
elements = result["elements"]
for element in elements:
if element['type'] != "function":
if element["type"] != "function":
# Skip variable elements
continue
target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name'])
target_contract = slither.get_contract_from_name(
element["type_specific_fields"]["parent"]["name"]
)
if target_contract:
function = target_contract.get_function_from_signature(element['type_specific_fields']['signature'])
function = target_contract.get_function_from_signature(
element["type_specific_fields"]["signature"]
)
if function:
_patch(slither,
result,
element['source_mapping']['filename_absolute'],
int(function.parameters_src.source_mapping['start'] +
function.parameters_src.source_mapping['length']),
int(function.returns_src.source_mapping['start']))
_patch(
slither,
result,
element["source_mapping"]["filename_absolute"],
int(
function.parameters_src.source_mapping["start"]
+ function.parameters_src.source_mapping["length"]
),
int(function.returns_src.source_mapping["start"]),
)
def _patch(slither, result, in_file, modify_loc_start, modify_loc_end):
in_file_str = slither.source_code[in_file].encode('utf8')
in_file_str = slither.source_code[in_file].encode("utf8")
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end]
# Find the keywords view|pure|constant and remove them
m = re.search("(view|pure|constant)", old_str_of_interest.decode('utf-8'))
m = re.search("(view|pure|constant)", old_str_of_interest.decode("utf-8"))
if m:
create_patch(result,
in_file,
modify_loc_start + m.span()[0],
modify_loc_start + m.span()[1],
m.groups(0)[0], # this is view|pure|constant
"")
create_patch(
result,
in_file,
modify_loc_start + m.span()[0],
modify_loc_start + m.span()[1],
m.groups(0)[0], # this is view|pure|constant
"",
)
else:
raise FormatError("No view/pure/constant specifier exists. Regex failed to remove specifier!")
raise FormatError(
"No view/pure/constant specifier exists. Regex failed to remove specifier!"
)

@ -11,19 +11,24 @@ REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"]
# 2: version number
# 3: version number
# 4: version number
PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)')
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
def format(slither, result):
elements = result['elements']
elements = result["elements"]
versions_used = []
for element in elements:
versions_used.append(''.join(element['type_specific_fields']['directive'][1:]))
versions_used.append("".join(element["type_specific_fields"]["directive"][1:]))
solc_version_replace = _analyse_versions(versions_used)
for element in elements:
_patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace,
element['source_mapping']['start'],
element['source_mapping']['start'] + element['source_mapping']['length'])
_patch(
slither,
result,
element["source_mapping"]["filename_absolute"],
solc_version_replace,
element["source_mapping"]["start"],
element["source_mapping"]["start"] + element["source_mapping"]["length"],
)
def _analyse_versions(used_solc_versions):
@ -40,30 +45,27 @@ def _determine_solc_version_replacement(used_solc_version):
versions = PATTERN.findall(used_solc_version)
if len(versions) == 1:
version = versions[0]
minor_version = '.'.join(version[2:])[2]
if minor_version == '4':
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';'
elif minor_version == '5':
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';'
minor_version = ".".join(version[2:])[2]
if minor_version == "4":
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";"
elif minor_version == "5":
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";"
else:
raise FormatImpossible("Unknown version!")
elif len(versions) == 2:
version_right = versions[1]
minor_version_right = '.'.join(version_right[2:])[2]
if minor_version_right == '4':
minor_version_right = ".".join(version_right[2:])[2]
if minor_version_right == "4":
# Replace with 0.4.25
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';'
elif minor_version_right in ['5', '6']:
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";"
elif minor_version_right in ["5", "6"]:
# Replace with 0.5.3
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';'
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";"
def _patch(slither, result, in_file, pragma, modify_loc_start, modify_loc_end):
in_file_str = slither.source_code[in_file].encode('utf8')
in_file_str = slither.source_code[in_file].encode("utf8")
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end]
create_patch(result,
in_file,
int(modify_loc_start),
int(modify_loc_end),
old_str_of_interest,
pragma)
create_patch(
result, in_file, int(modify_loc_start), int(modify_loc_end), old_str_of_interest, pragma
)

@ -12,48 +12,58 @@ REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"]
# 2: version number
# 3: version number
# 4: version number
PATTERN = re.compile('(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)')
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
def format(slither, result):
elements = result['elements']
elements = result["elements"]
for element in elements:
solc_version_replace = _determine_solc_version_replacement(
''.join(element['type_specific_fields']['directive'][1:]))
"".join(element["type_specific_fields"]["directive"][1:])
)
_patch(
slither,
result,
element["source_mapping"]["filename_absolute"],
solc_version_replace,
element["source_mapping"]["start"],
element["source_mapping"]["start"] + element["source_mapping"]["length"],
)
_patch(slither, result, element['source_mapping']['filename_absolute'], solc_version_replace,
element['source_mapping']['start'], element['source_mapping']['start'] +
element['source_mapping']['length'])
def _determine_solc_version_replacement(used_solc_version):
versions = PATTERN.findall(used_solc_version)
if len(versions) == 1:
version = versions[0]
minor_version = '.'.join(version[2:])[2]
if minor_version == '4':
minor_version = ".".join(version[2:])[2]
if minor_version == "4":
# Replace with 0.4.25
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';'
elif minor_version == '5':
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";"
elif minor_version == "5":
# Replace with 0.5.3
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';'
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";"
else:
raise FormatImpossible(f"Unknown version {versions}")
elif len(versions) == 2:
version_right = versions[1]
minor_version_right = '.'.join(version_right[2:])[2]
if minor_version_right == '4':
minor_version_right = ".".join(version_right[2:])[2]
if minor_version_right == "4":
# Replace with 0.4.25
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ';'
elif minor_version_right in ['5','6']:
return "pragma solidity " + REPLACEMENT_VERSIONS[0] + ";"
elif minor_version_right in ["5", "6"]:
# Replace with 0.5.3
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ';'
return "pragma solidity " + REPLACEMENT_VERSIONS[1] + ";"
def _patch(slither, result, in_file, solc_version, modify_loc_start, modify_loc_end):
in_file_str = slither.source_code[in_file].encode('utf8')
in_file_str = slither.source_code[in_file].encode("utf8")
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end]
create_patch(result,
in_file,
int(modify_loc_start),
int(modify_loc_end),
old_str_of_interest,
solc_version)
create_patch(
result,
in_file,
int(modify_loc_start),
int(modify_loc_end),
old_str_of_interest,
solc_version,
)

@ -1,5 +1,9 @@
from slither.exceptions import SlitherException
class FormatImpossible(SlitherException): pass
class FormatError(SlitherException): pass
class FormatImpossible(SlitherException):
pass
class FormatError(SlitherException):
pass

@ -1,42 +1,53 @@
import re
from slither.formatters.utils.patches import create_patch
def format(slither, result):
elements = result['elements']
elements = result["elements"]
for element in elements:
target_contract = slither.get_contract_from_name(element['type_specific_fields']['parent']['name'])
target_contract = slither.get_contract_from_name(
element["type_specific_fields"]["parent"]["name"]
)
if target_contract:
function = target_contract.get_function_from_signature(element['type_specific_fields']['signature'])
function = target_contract.get_function_from_signature(
element["type_specific_fields"]["signature"]
)
if function:
_patch(slither,
result,
element['source_mapping']['filename_absolute'],
int(function.parameters_src.source_mapping['start']),
int(function.returns_src.source_mapping['start']))
_patch(
slither,
result,
element["source_mapping"]["filename_absolute"],
int(function.parameters_src.source_mapping["start"]),
int(function.returns_src.source_mapping["start"]),
)
def _patch(slither, result, in_file, modify_loc_start, modify_loc_end):
in_file_str = slither.source_code[in_file].encode('utf8')
in_file_str = slither.source_code[in_file].encode("utf8")
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end]
# Search for 'public' keyword which is in-between the function name and modifier name (if present)
# regex: 'public' could have spaces around or be at the end of the line
m = re.search(r'((\spublic)\s+)|(\spublic)$|(\)public)$', old_str_of_interest.decode('utf-8'))
m = re.search(r"((\spublic)\s+)|(\spublic)$|(\)public)$", old_str_of_interest.decode("utf-8"))
if m is None:
# No visibility specifier exists; public by default.
create_patch(result,
in_file,
# start after the function definition's closing paranthesis
modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1,
# end is same as start because we insert the keyword `external` at that location
modify_loc_start + len(old_str_of_interest.decode('utf-8').split(')')[0]) + 1,
"",
" external") # replace_text is `external`
create_patch(
result,
in_file,
# start after the function definition's closing paranthesis
modify_loc_start + len(old_str_of_interest.decode("utf-8").split(")")[0]) + 1,
# end is same as start because we insert the keyword `external` at that location
modify_loc_start + len(old_str_of_interest.decode("utf-8").split(")")[0]) + 1,
"",
" external",
) # replace_text is `external`
else:
create_patch(result,
in_file,
# start at the keyword `public`
modify_loc_start + m.span()[0] + 1,
# end after the keyword `public` = start + len('public'')
modify_loc_start + m.span()[0] + 1 + len('public'),
"public",
"external")
create_patch(
result,
in_file,
# start at the keyword `public`
modify_loc_start + m.span()[0] + 1,
# end after the keyword `public` = start + len('public'')
modify_loc_start + m.span()[0] + 1 + len("public"),
"public",
"external",
)

@ -1,7 +1,14 @@
import re
import logging
from slither.slithir.operations import Send, Transfer, OperationWithLValue, HighLevelCall, LowLevelCall, \
InternalCall, InternalDynamicCall
from slither.slithir.operations import (
Send,
Transfer,
OperationWithLValue,
HighLevelCall,
LowLevelCall,
InternalCall,
InternalDynamicCall,
)
from slither.core.declarations import Modifier
from slither.core.solidity_types import UserDefinedType, MappingType
from slither.core.declarations import Enum, Contract, Structure, Function
@ -11,22 +18,26 @@ from slither.formatters.exceptions import FormatError, FormatImpossible
from slither.formatters.utils.patches import create_patch
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('Slither.Format')
logger = logging.getLogger("Slither.Format")
def format(slither, result):
elements = result['elements']
elements = result["elements"]
for element in elements:
target = element['additional_fields']['target']
target = element["additional_fields"]["target"]
convention = element['additional_fields']['convention']
convention = element["additional_fields"]["convention"]
if convention == "l_O_I_should_not_be_used":
# l_O_I_should_not_be_used cannot be automatically patched
logger.info(f'The following naming convention cannot be patched: \n{result["description"]}')
logger.info(
f'The following naming convention cannot be patched: \n{result["description"]}'
)
continue
_patch(slither, result, element, target)
# endregion
###################################################################################
###################################################################################
@ -34,21 +45,82 @@ def format(slither, result):
###################################################################################
###################################################################################
KEY = 'ALL_NAMES_USED'
KEY = "ALL_NAMES_USED"
# https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#reserved-keywords
SOLIDITY_KEYWORDS = ['abstract', 'after', 'alias', 'apply', 'auto', 'case', 'catch', 'copyof', 'default', 'define',
'final', 'immutable', 'implements', 'in', 'inline', 'let', 'macro', 'match', 'mutable', 'null',
'of', 'override', 'partial', 'promise', 'reference', 'relocatable', 'sealed', 'sizeof', 'static',
'supports', 'switch', 'try', 'typedef', 'typeof', 'unchecked']
SOLIDITY_KEYWORDS = [
"abstract",
"after",
"alias",
"apply",
"auto",
"case",
"catch",
"copyof",
"default",
"define",
"final",
"immutable",
"implements",
"in",
"inline",
"let",
"macro",
"match",
"mutable",
"null",
"of",
"override",
"partial",
"promise",
"reference",
"relocatable",
"sealed",
"sizeof",
"static",
"supports",
"switch",
"try",
"typedef",
"typeof",
"unchecked",
]
# https://solidity.readthedocs.io/en/v0.5.11/miscellaneous.html#language-grammar
SOLIDITY_KEYWORDS += ['pragma', 'import', 'contract', 'library', 'contract', 'function', 'using', 'struct', 'enum',
'public', 'private', 'internal', 'external', 'calldata', 'memory', 'modifier', 'view', 'pure',
'constant', 'storage', 'for', 'if', 'while', 'break', 'return', 'throw', 'else', 'type']
SOLIDITY_KEYWORDS += [
"pragma",
"import",
"contract",
"library",
"contract",
"function",
"using",
"struct",
"enum",
"public",
"private",
"internal",
"external",
"calldata",
"memory",
"modifier",
"view",
"pure",
"constant",
"storage",
"for",
"if",
"while",
"break",
"return",
"throw",
"else",
"type",
]
SOLIDITY_KEYWORDS += ElementaryTypeName
def _name_already_use(slither, name):
# Do not convert to a name used somewhere else
if not KEY in slither.context:
@ -65,49 +137,53 @@ def _name_already_use(slither, name):
slither.context[KEY] = all_names
return name in slither.context[KEY]
def _convert_CapWords(original_name, slither):
name = original_name.capitalize()
while '_' in name:
offset = name.find('_')
while "_" in name:
offset = name.find("_")
if len(name) > offset:
name = name[0:offset] + name[offset+1].upper() + name[offset+1:]
name = name[0:offset] + name[offset + 1].upper() + name[offset + 1 :]
if _name_already_use(slither, name):
raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)')
raise FormatImpossible(f"{original_name} cannot be converted to {name} (already used)")
if name in SOLIDITY_KEYWORDS:
raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)')
raise FormatImpossible(f"{original_name} cannot be converted to {name} (Solidity keyword)")
return name
def _convert_mixedCase(original_name, slither):
name = original_name
if isinstance(name, bytes):
name = name.decode('utf8')
name = name.decode("utf8")
while '_' in name:
offset = name.find('_')
while "_" in name:
offset = name.find("_")
if len(name) > offset:
name = name[0:offset] + name[offset + 1].upper() + name[offset + 2:]
name = name[0:offset] + name[offset + 1].upper() + name[offset + 2 :]
name = name[0].lower() + name[1:]
if _name_already_use(slither, name):
raise FormatImpossible(f'{original_name} cannot be converted to {name} (already used)')
raise FormatImpossible(f"{original_name} cannot be converted to {name} (already used)")
if name in SOLIDITY_KEYWORDS:
raise FormatImpossible(f'{original_name} cannot be converted to {name} (Solidity keyword)')
raise FormatImpossible(f"{original_name} cannot be converted to {name} (Solidity keyword)")
return name
def _convert_UPPER_CASE_WITH_UNDERSCORES(name, slither):
if _name_already_use(slither, name.upper()):
raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (already used)')
raise FormatImpossible(f"{name} cannot be converted to {name.upper()} (already used)")
if name.upper() in SOLIDITY_KEYWORDS:
raise FormatImpossible(f'{name} cannot be converted to {name.upper()} (Solidity keyword)')
raise FormatImpossible(f"{name} cannot be converted to {name.upper()} (Solidity keyword)")
return name.upper()
conventions ={
"CapWords":_convert_CapWords,
"mixedCase":_convert_mixedCase,
"UPPER_CASE_WITH_UNDERSCORES":_convert_UPPER_CASE_WITH_UNDERSCORES
conventions = {
"CapWords": _convert_CapWords,
"mixedCase": _convert_mixedCase,
"UPPER_CASE_WITH_UNDERSCORES": _convert_UPPER_CASE_WITH_UNDERSCORES,
}
@ -118,11 +194,13 @@ conventions ={
###################################################################################
###################################################################################
def _get_from_contract(slither, element, name, getter):
contract_name = element['type_specific_fields']['parent']['name']
contract_name = element["type_specific_fields"]["parent"]["name"]
contract = slither.get_contract_from_name(contract_name)
return getattr(contract, getter)(name)
# endregion
###################################################################################
###################################################################################
@ -130,58 +208,70 @@ def _get_from_contract(slither, element, name, getter):
###################################################################################
###################################################################################
def _patch(slither, result, element, _target):
if _target == "contract":
target = slither.get_contract_from_name(element['name'])
target = slither.get_contract_from_name(element["name"])
elif _target == "structure":
target = _get_from_contract(slither, element, element['name'], 'get_structure_from_name')
target = _get_from_contract(slither, element, element["name"], "get_structure_from_name")
elif _target == "event":
target = _get_from_contract(slither, element, element['name'], 'get_event_from_name')
target = _get_from_contract(slither, element, element["name"], "get_event_from_name")
elif _target == "function":
# Avoid constructor (FP?)
if element['name'] != element['type_specific_fields']['parent']['name']:
function_sig = element['type_specific_fields']['signature']
target = _get_from_contract(slither, element, function_sig, 'get_function_from_signature')
if element["name"] != element["type_specific_fields"]["parent"]["name"]:
function_sig = element["type_specific_fields"]["signature"]
target = _get_from_contract(
slither, element, function_sig, "get_function_from_signature"
)
elif _target == "modifier":
modifier_sig = element['type_specific_fields']['signature']
target = _get_from_contract(slither, element, modifier_sig, 'get_modifier_from_signature')
modifier_sig = element["type_specific_fields"]["signature"]
target = _get_from_contract(slither, element, modifier_sig, "get_modifier_from_signature")
elif _target == "parameter":
contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name']
function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature']
param_name = element['name']
contract_name = element["type_specific_fields"]["parent"]["type_specific_fields"]["parent"][
"name"
]
function_sig = element["type_specific_fields"]["parent"]["type_specific_fields"][
"signature"
]
param_name = element["name"]
contract = slither.get_contract_from_name(contract_name)
function = contract.get_function_from_signature(function_sig)
target = function.get_local_variable_from_name(param_name)
elif _target in ["variable", "variable_constant"]:
# Local variable
if element['type_specific_fields']['parent'] == 'function':
contract_name = element['type_specific_fields']['parent']['type_specific_fields']['parent']['name']
function_sig = element['type_specific_fields']['parent']['type_specific_fields']['signature']
var_name = element['name']
if element["type_specific_fields"]["parent"] == "function":
contract_name = element["type_specific_fields"]["parent"]["type_specific_fields"][
"parent"
]["name"]
function_sig = element["type_specific_fields"]["parent"]["type_specific_fields"][
"signature"
]
var_name = element["name"]
contract = slither.get_contract_from_name(contract_name)
function = contract.get_function_from_signature(function_sig)
target = function.get_local_variable_from_name(var_name)
# State variable
else:
target = _get_from_contract(slither, element, element['name'], 'get_state_variable_from_name')
target = _get_from_contract(
slither, element, element["name"], "get_state_variable_from_name"
)
elif _target == "enum":
target = _get_from_contract(slither, element, element['name'], 'get_enum_from_canonical_name')
target = _get_from_contract(
slither, element, element["name"], "get_enum_from_canonical_name"
)
else:
raise FormatError("Unknown naming convention! " + _target)
_explore(slither,
result,
target,
conventions[element['additional_fields']['convention']])
_explore(slither, result, target, conventions[element["additional_fields"]["convention"]])
# endregion
@ -194,22 +284,24 @@ def _patch(slither, result, element, _target):
# group 1: beginning of the from type
# group 2: beginning of the to type
# nested mapping are within the group 1
#RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)'
RE_MAPPING_FROM = b'([a-zA-Z0-9\._\[\]]*)'
RE_MAPPING_TO = b'([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)'
RE_MAPPING = b'[ ]*mapping[ ]*\([ ]*' + RE_MAPPING_FROM + b'[ ]*' + b'=>' + b'[ ]*'+ RE_MAPPING_TO + b'\)'
# RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)'
RE_MAPPING_FROM = b"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = b"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING = (
b"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + b"\)"
)
def _is_var_declaration(slither, filename, start):
'''
"""
Detect usage of 'var ' for Solidity < 0.5
:param slither:
:param filename:
:param start:
:return:
'''
v = 'var '
return slither.source_code[filename][start:start + len(v)] == v
"""
v = "var "
return slither.source_code[filename][start : start + len(v)] == v
def _explore_type(slither, result, target, convert, type, filename_source_code, start, end):
@ -222,17 +314,11 @@ def _explore_type(slither, result, target, convert, type, filename_source_code,
loc_start = start
if _is_var_declaration(slither, filename_source_code, start):
loc_end = loc_start + len('var')
loc_end = loc_start + len("var")
else:
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
else:
# Patch type based on structure
@ -243,24 +329,17 @@ def _explore_type(slither, result, target, convert, type, filename_source_code,
loc_start = start
if _is_var_declaration(slither, filename_source_code, start):
loc_end = loc_start + len('var')
loc_end = loc_start + len("var")
else:
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
# Structure contain a list of elements, that might need patching
# .elems return a list of VariableStructure
_explore_variables_declaration(slither,
type.type.elems.values(),
result,
target,
convert)
_explore_variables_declaration(
slither, type.type.elems.values(), result, target, convert
)
if isinstance(type, MappingType):
# Mapping has three steps:
@ -271,11 +350,16 @@ def _explore_type(slither, result, target, convert, type, filename_source_code,
# Do the comparison twice, so we can factor together the re matching
# mapping can only have elementary type in type_from
if isinstance(type.type_to, (UserDefinedType, MappingType)) or target in [type.type_from, type.type_to]:
if isinstance(type.type_to, (UserDefinedType, MappingType)) or target in [
type.type_from,
type.type_to,
]:
full_txt_start = start
full_txt_end = end
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end]
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
re_match = re.match(RE_MAPPING, full_txt)
assert re_match
@ -286,12 +370,7 @@ def _explore_type(slither, result, target, convert, type, filename_source_code,
loc_start = start + re_match.start(1)
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
if type.type_to == target:
@ -301,97 +380,98 @@ def _explore_type(slither, result, target, convert, type, filename_source_code,
loc_start = start + re_match.start(2)
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
if isinstance(type.type_to, (UserDefinedType, MappingType)):
loc_start = start + re_match.start(2)
loc_end = start + re_match.end(2)
_explore_type(slither,
result,
target,
convert,
type.type_to,
filename_source_code,
loc_start,
loc_end)
def _explore_variables_declaration(slither, variables, result, target, convert, patch_comment=False):
_explore_type(
slither,
result,
target,
convert,
type.type_to,
filename_source_code,
loc_start,
loc_end,
)
def _explore_variables_declaration(
slither, variables, result, target, convert, patch_comment=False
):
for variable in variables:
# First explore the type of the variable
filename_source_code = variable.source_mapping['filename_absolute']
full_txt_start = variable.source_mapping['start']
full_txt_end = full_txt_start + variable.source_mapping['length']
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end]
_explore_type(slither,
result,
target,
convert,
variable.type,
filename_source_code,
full_txt_start,
variable.source_mapping['start'] + variable.source_mapping['length'])
filename_source_code = variable.source_mapping["filename_absolute"]
full_txt_start = variable.source_mapping["start"]
full_txt_end = full_txt_start + variable.source_mapping["length"]
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
_explore_type(
slither,
result,
target,
convert,
variable.type,
filename_source_code,
full_txt_start,
variable.source_mapping["start"] + variable.source_mapping["length"],
)
# If the variable is the target
if variable == target:
old_str = variable.name
new_str = convert(old_str, slither)
loc_start = full_txt_start + full_txt.find(old_str.encode('utf8'))
loc_start = full_txt_start + full_txt.find(old_str.encode("utf8"))
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
# Patch comment only makes sense for local variable declaration in the parameter list
if patch_comment and isinstance(variable, LocalVariable):
if 'lines' in variable.source_mapping and variable.source_mapping['lines']:
if "lines" in variable.source_mapping and variable.source_mapping["lines"]:
func = variable.function
end_line = func.source_mapping['lines'][0]
end_line = func.source_mapping["lines"][0]
if variable in func.parameters:
idx = len(func.parameters) - func.parameters.index(variable) + 1
first_line = end_line - idx - 2
potential_comments = slither.source_code[filename_source_code].encode('utf8')
potential_comments = potential_comments.splitlines(keepends=True)[first_line:end_line-1]
potential_comments = slither.source_code[filename_source_code].encode(
"utf8"
)
potential_comments = potential_comments.splitlines(keepends=True)[
first_line : end_line - 1
]
idx_beginning = func.source_mapping['start']
idx_beginning += - func.source_mapping['starting_column'] + 1
idx_beginning += - sum([len(c) for c in potential_comments])
idx_beginning = func.source_mapping["start"]
idx_beginning += -func.source_mapping["starting_column"] + 1
idx_beginning += -sum([len(c) for c in potential_comments])
old_comment = f'@param {old_str}'.encode('utf8')
old_comment = f"@param {old_str}".encode("utf8")
for line in potential_comments:
idx = line.find(old_comment)
if idx >=0:
if idx >= 0:
loc_start = idx + idx_beginning
loc_end = loc_start + len(old_comment)
new_comment = f'@param {new_str}'.encode('utf8')
new_comment = f"@param {new_str}".encode("utf8")
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_comment,
new_comment)
create_patch(
result,
filename_source_code,
loc_start,
loc_end,
old_comment,
new_comment,
)
break
idx_beginning += len(line)
def _explore_structures_declaration(slither, structures, result, target, convert):
for st in structures:
# Explore the variable declared within the structure (VariableStructure)
@ -402,23 +482,20 @@ def _explore_structures_declaration(slither, structures, result, target, convert
old_str = st.name
new_str = convert(old_str, slither)
filename_source_code = st.source_mapping['filename_absolute']
full_txt_start = st.source_mapping['start']
full_txt_end = full_txt_start + st.source_mapping['length']
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end]
filename_source_code = st.source_mapping["filename_absolute"]
full_txt_start = st.source_mapping["start"]
full_txt_end = full_txt_start + st.source_mapping["length"]
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
# The name is after the space
matches = re.finditer(b'struct[ ]*', full_txt)
matches = re.finditer(b"struct[ ]*", full_txt)
# Look for the end offset of the largest list of ' '
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end()
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
def _explore_events_declaration(slither, events, result, target, convert):
@ -428,20 +505,16 @@ def _explore_events_declaration(slither, events, result, target, convert):
# If the event is the target
if event == target:
filename_source_code = event.source_mapping['filename_absolute']
filename_source_code = event.source_mapping["filename_absolute"]
old_str = event.name
new_str = convert(old_str, slither)
loc_start = event.source_mapping['start']
loc_start = event.source_mapping["start"]
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
def get_ir_variables(ir):
vars = ir.read
@ -460,24 +533,29 @@ def get_ir_variables(ir):
return [v for v in vars if v]
def _explore_irs(slither, irs, result, target, convert):
if irs is None:
return
for ir in irs:
for v in get_ir_variables(ir):
if target == v or (
isinstance(target, Function) and isinstance(v, Function) and
v.canonical_name == target.canonical_name):
isinstance(target, Function)
and isinstance(v, Function)
and v.canonical_name == target.canonical_name
):
source_mapping = ir.expression.source_mapping
filename_source_code = source_mapping['filename_absolute']
full_txt_start = source_mapping['start']
full_txt_end = full_txt_start + source_mapping['length']
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end]
filename_source_code = source_mapping["filename_absolute"]
full_txt_start = source_mapping["start"]
full_txt_end = full_txt_start + source_mapping["length"]
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
if not target.name.encode('utf8') in full_txt:
raise FormatError(f'{target} not found in {full_txt} ({source_mapping}')
if not target.name.encode("utf8") in full_txt:
raise FormatError(f"{target} not found in {full_txt} ({source_mapping}")
old_str = target.name.encode('utf8')
old_str = target.name.encode("utf8")
new_str = convert(old_str, slither)
counter = 0
@ -487,18 +565,13 @@ def _explore_irs(slither, irs, result, target, convert):
target_found_at = full_txt.find((old_str))
full_txt = full_txt[target_found_at+1:]
full_txt = full_txt[target_found_at + 1 :]
counter += target_found_at
loc_start = full_txt_start + counter
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
def _explore_functions(slither, functions, result, target, convert):
@ -510,26 +583,24 @@ def _explore_functions(slither, functions, result, target, convert):
old_str = function.name
new_str = convert(old_str, slither)
filename_source_code = function.source_mapping['filename_absolute']
full_txt_start = function.source_mapping['start']
full_txt_end = full_txt_start + function.source_mapping['length']
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end]
filename_source_code = function.source_mapping["filename_absolute"]
full_txt_start = function.source_mapping["start"]
full_txt_end = full_txt_start + function.source_mapping["length"]
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
# The name is after the space
if isinstance(target, Modifier):
matches = re.finditer(b'modifier([ ]*)', full_txt)
matches = re.finditer(b"modifier([ ]*)", full_txt)
else:
matches = re.finditer(b'function([ ]*)', full_txt)
matches = re.finditer(b"function([ ]*)", full_txt)
# Look for the end offset of the largest list of ' '
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end()
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
def _explore_enums(slither, enums, result, target, convert):
for enum in enums:
@ -537,23 +608,20 @@ def _explore_enums(slither, enums, result, target, convert):
old_str = enum.name
new_str = convert(old_str, slither)
filename_source_code = enum.source_mapping['filename_absolute']
full_txt_start = enum.source_mapping['start']
full_txt_end = full_txt_start + enum.source_mapping['length']
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end]
filename_source_code = enum.source_mapping["filename_absolute"]
full_txt_start = enum.source_mapping["start"]
full_txt_end = full_txt_start + enum.source_mapping["length"]
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
# The name is after the space
matches = re.finditer(b'enum([ ]*)', full_txt)
matches = re.finditer(b"enum([ ]*)", full_txt)
# Look for the end offset of the largest list of ' '
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end()
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
def _explore_contract(slither, contract, result, target, convert):
@ -563,27 +631,24 @@ def _explore_contract(slither, contract, result, target, convert):
_explore_enums(slither, contract.enums, result, target, convert)
if contract == target:
filename_source_code = contract.source_mapping['filename_absolute']
full_txt_start = contract.source_mapping['start']
full_txt_end = full_txt_start + contract.source_mapping['length']
full_txt = slither.source_code[filename_source_code].encode('utf8')[full_txt_start:full_txt_end]
filename_source_code = contract.source_mapping["filename_absolute"]
full_txt_start = contract.source_mapping["start"]
full_txt_end = full_txt_start + contract.source_mapping["length"]
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
old_str = contract.name
new_str = convert(old_str, slither)
# The name is after the space
matches = re.finditer(b'contract[ ]*', full_txt)
matches = re.finditer(b"contract[ ]*", full_txt)
# Look for the end offset of the largest list of ' '
loc_start = full_txt_start + max(matches, key=lambda x: len(x.group())).end()
loc_end = loc_start + len(old_str)
create_patch(result,
filename_source_code,
loc_start,
loc_end,
old_str,
new_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
def _explore(slither, result, target, convert):
@ -591,8 +656,4 @@ def _explore(slither, result, target, convert):
_explore_contract(slither, contract, result, target, convert)
# endregion

@ -2,42 +2,41 @@ import os
import difflib
from collections import defaultdict
def create_patch(result, file, start, end, old_str, new_str):
if isinstance(old_str, bytes):
old_str = old_str.decode('utf8')
old_str = old_str.decode("utf8")
if isinstance(new_str, bytes):
new_str = new_str.decode('utf8')
p = {"start": start,
"end": end,
"old_string": old_str,
"new_string": new_str
}
if 'patches' not in result:
result['patches'] = defaultdict(list)
if p not in result['patches'][file]:
result['patches'][file].append(p)
new_str = new_str.decode("utf8")
p = {"start": start, "end": end, "old_string": old_str, "new_string": new_str}
if "patches" not in result:
result["patches"] = defaultdict(list)
if p not in result["patches"][file]:
result["patches"][file].append(p)
def apply_patch(original_txt, patch, offset):
patched_txt = original_txt[:int(patch['start'] + offset)]
patched_txt += patch['new_string'].encode('utf8')
patched_txt += original_txt[int(patch['end'] + offset):]
patched_txt = original_txt[: int(patch["start"] + offset)]
patched_txt += patch["new_string"].encode("utf8")
patched_txt += original_txt[int(patch["end"] + offset) :]
# Keep the diff of text added or sub, in case of multiple patches
patch_length_diff = len(patch['new_string']) - (patch['end'] - patch['start'])
patch_length_diff = len(patch["new_string"]) - (patch["end"] - patch["start"])
return patched_txt, patch_length_diff + offset
def create_diff(slither, original_txt, patched_txt, filename):
if slither.crytic_compile:
relative_path = slither.crytic_compile.filename_lookup(filename).relative
relative_path = os.path.join('.', relative_path)
relative_path = os.path.join(".", relative_path)
else:
relative_path = filename
diff = difflib.unified_diff(original_txt.decode('utf8').splitlines(False),
patched_txt.decode('utf8').splitlines(False),
fromfile=relative_path,
tofile=relative_path,
lineterm='')
diff = difflib.unified_diff(
original_txt.decode("utf8").splitlines(False),
patched_txt.decode("utf8").splitlines(False),
fromfile=relative_path,
tofile=relative_path,
lineterm="",
)
return '\n'.join(list(diff)) + '\n'
return "\n".join(list(diff)) + "\n"

@ -2,37 +2,45 @@ import re
from slither.formatters.exceptions import FormatError, FormatImpossible
from slither.formatters.utils.patches import create_patch
def format(slither, result):
elements = result['elements']
elements = result["elements"]
for element in elements:
# TODO: decide if this should be changed in the constant detector
contract_name = element['type_specific_fields']['parent']['name']
contract_name = element["type_specific_fields"]["parent"]["name"]
contract = slither.get_contract_from_name(contract_name)
var = contract.get_state_variable_from_name(element['name'])
var = contract.get_state_variable_from_name(element["name"])
if not var.expression:
raise FormatImpossible(f'{var.name} is uninitialized and cannot become constant.')
raise FormatImpossible(f"{var.name} is uninitialized and cannot become constant.")
_patch(slither, result, element['source_mapping']['filename_absolute'],
element['name'],
"constant " + element['name'],
element['source_mapping']['start'],
element['source_mapping']['start'] + element['source_mapping']['length'])
_patch(
slither,
result,
element["source_mapping"]["filename_absolute"],
element["name"],
"constant " + element["name"],
element["source_mapping"]["start"],
element["source_mapping"]["start"] + element["source_mapping"]["length"],
)
def _patch(slither, result, in_file, match_text, replace_text, modify_loc_start, modify_loc_end):
in_file_str = slither.source_code[in_file].encode('utf8')
in_file_str = slither.source_code[in_file].encode("utf8")
old_str_of_interest = in_file_str[modify_loc_start:modify_loc_end]
# Add keyword `constant` before the variable name
(new_str_of_interest, num_repl) = re.subn(match_text, replace_text, old_str_of_interest.decode('utf-8'), 1)
(new_str_of_interest, num_repl) = re.subn(
match_text, replace_text, old_str_of_interest.decode("utf-8"), 1
)
if num_repl != 0:
create_patch(result,
in_file,
modify_loc_start,
modify_loc_end,
old_str_of_interest,
new_str_of_interest)
create_patch(
result,
in_file,
modify_loc_start,
modify_loc_end,
old_str_of_interest,
new_str_of_interest,
)
else:
raise FormatError("State variable not found?!")

@ -2,27 +2,31 @@ from slither.formatters.utils.patches import create_patch
def format(slither, result):
elements = result['elements']
elements = result["elements"]
for element in elements:
if element['type'] == "variable":
_patch(slither,
result,
element['source_mapping']['filename_absolute'],
element['source_mapping']['start'])
if element["type"] == "variable":
_patch(
slither,
result,
element["source_mapping"]["filename_absolute"],
element["source_mapping"]["start"],
)
def _patch(slither, result, in_file, modify_loc_start):
in_file_str = slither.source_code[in_file].encode('utf8')
in_file_str = slither.source_code[in_file].encode("utf8")
old_str_of_interest = in_file_str[modify_loc_start:]
old_str = old_str_of_interest.decode('utf-8').partition(';')[0]\
+ old_str_of_interest.decode('utf-8').partition(';')[1]
create_patch(result,
in_file,
int(modify_loc_start),
# Remove the entire declaration until the semicolon
int(modify_loc_start + len(old_str_of_interest.decode('utf-8').partition(';')[0]) + 1),
old_str,
"")
old_str = (
old_str_of_interest.decode("utf-8").partition(";")[0]
+ old_str_of_interest.decode("utf-8").partition(";")[1]
)
create_patch(
result,
in_file,
int(modify_loc_start),
# Remove the entire declaration until the semicolon
int(modify_loc_start + len(old_str_of_interest.decode("utf-8").partition(";")[0]) + 1),
old_str,
"",
)

@ -8,10 +8,10 @@ class IncorrectPrinterInitialization(Exception):
class AbstractPrinter(metaclass=abc.ABCMeta):
ARGUMENT = '' # run the printer with slither.py --ARGUMENT
HELP = '' # help information
ARGUMENT = "" # run the printer with slither.py --ARGUMENT
HELP = "" # help information
WIKI = ''
WIKI = ""
def __init__(self, slither, logger):
self.slither = slither
@ -20,24 +20,29 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
self.logger = logger
if not self.HELP:
raise IncorrectPrinterInitialization('HELP is not initialized {}'.format(self.__class__.__name__))
raise IncorrectPrinterInitialization(
"HELP is not initialized {}".format(self.__class__.__name__)
)
if not self.ARGUMENT:
raise IncorrectPrinterInitialization('ARGUMENT is not initialized {}'.format(self.__class__.__name__))
raise IncorrectPrinterInitialization(
"ARGUMENT is not initialized {}".format(self.__class__.__name__)
)
if not self.WIKI:
raise IncorrectPrinterInitialization('WIKI is not initialized {}'.format(self.__class__.__name__))
raise IncorrectPrinterInitialization(
"WIKI is not initialized {}".format(self.__class__.__name__)
)
def info(self, info):
if self.logger:
self.logger.info(info)
def generate_output(self, info, additional_fields=None):
if additional_fields is None:
additional_fields = {}
d = output.Output(info, additional_fields)
d.data['printer'] = self.ARGUMENT
d.data["printer"] = self.ARGUMENT
return d

@ -15,4 +15,4 @@ from .summary.modifier_calls import Modifiers
from .summary.require_calls import RequireOrAssert
from .summary.constructor_calls import ConstructorPrinter
from .guidance.echidna import Echidna
from .summary.evm import PrinterEVM
from .summary.evm import PrinterEVM

@ -12,61 +12,66 @@ from slither.core.declarations.function import Function
from slither.core.variables.variable import Variable
def _contract_subgraph(contract):
return f'cluster_{contract.id}_{contract.name}'
return f"cluster_{contract.id}_{contract.name}"
# return unique id for contract function to use as node name
def _function_node(contract, function):
return f'{contract.id}_{function.name}'
return f"{contract.id}_{function.name}"
# return unique id for solidity function to use as node name
def _solidity_function_node(solidity_function):
return f'{solidity_function.name}'
return f"{solidity_function.name}"
# return dot language string to add graph edge
def _edge(from_node, to_node):
return f'"{from_node}" -> "{to_node}"'
# return dot language string to add graph node (with optional label)
def _node(node, label=None):
return ' '.join((
f'"{node}"',
f'[label="{label}"]' if label is not None else '',
))
return " ".join((f'"{node}"', f'[label="{label}"]' if label is not None else "",))
class PrinterCallGraph(AbstractPrinter):
ARGUMENT = 'call-graph'
HELP = 'Export the call-graph of the contracts to a dot file'
ARGUMENT = "call-graph"
HELP = "Export the call-graph of the contracts to a dot file"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph"
def _process_functions(self, functions):
contract_functions = defaultdict(set) # contract -> contract functions nodes
contract_calls = defaultdict(set) # contract -> contract calls edges
contract_functions = defaultdict(set) # contract -> contract functions nodes
contract_calls = defaultdict(set) # contract -> contract calls edges
solidity_functions = set() # solidity function nodes
solidity_calls = set() # solidity calls edges
external_calls = set() # external calls edges
solidity_functions = set() # solidity function nodes
solidity_calls = set() # solidity calls edges
external_calls = set() # external calls edges
all_contracts = set()
for function in functions:
all_contracts.add(function.contract_declarer)
for function in functions:
self._process_function(function.contract_declarer,
function,
contract_functions,
contract_calls,
solidity_functions,
solidity_calls,
external_calls,
all_contracts)
render_internal_calls = ''
self._process_function(
function.contract_declarer,
function,
contract_functions,
contract_calls,
solidity_functions,
solidity_calls,
external_calls,
all_contracts,
)
render_internal_calls = ""
for contract in all_contracts:
render_internal_calls += self._render_internal_calls(contract, contract_functions, contract_calls)
render_internal_calls += self._render_internal_calls(
contract, contract_functions, contract_calls
)
render_solidity_calls = self._render_solidity_calls(solidity_functions, solidity_calls)
@ -74,32 +79,49 @@ class PrinterCallGraph(AbstractPrinter):
return render_internal_calls + render_solidity_calls + render_external_calls
def _process_function(self, contract, function, contract_functions, contract_calls, solidity_functions, solidity_calls, external_calls, all_contracts):
contract_functions[contract].add(
_node(_function_node(contract, function), function.name),
)
def _process_function(
self,
contract,
function,
contract_functions,
contract_calls,
solidity_functions,
solidity_calls,
external_calls,
all_contracts,
):
contract_functions[contract].add(_node(_function_node(contract, function), function.name),)
for internal_call in function.internal_calls:
self._process_internal_call(contract, function, internal_call, contract_calls, solidity_functions, solidity_calls)
self._process_internal_call(
contract,
function,
internal_call,
contract_calls,
solidity_functions,
solidity_calls,
)
for external_call in function.high_level_calls:
self._process_external_call(contract, function, external_call, contract_functions, external_calls, all_contracts)
self._process_external_call(
contract, function, external_call, contract_functions, external_calls, all_contracts
)
def _process_internal_call(self, contract, function, internal_call, contract_calls, solidity_functions, solidity_calls):
def _process_internal_call(
self, contract, function, internal_call, contract_calls, solidity_functions, solidity_calls
):
if isinstance(internal_call, (Function)):
contract_calls[contract].add(_edge(
_function_node(contract, function),
_function_node(contract, internal_call),
))
contract_calls[contract].add(
_edge(_function_node(contract, function), _function_node(contract, internal_call),)
)
elif isinstance(internal_call, (SolidityFunction)):
solidity_functions.add(
_node(_solidity_function_node(internal_call)),
solidity_functions.add(_node(_solidity_function_node(internal_call)),)
solidity_calls.add(
_edge(_function_node(contract, function), _solidity_function_node(internal_call),)
)
solidity_calls.add(_edge(
_function_node(contract, function),
_solidity_function_node(internal_call),
))
def _process_external_call(self, contract, function, external_call, contract_functions, external_calls, all_contracts):
def _process_external_call(
self, contract, function, external_call, contract_functions, external_calls, all_contracts
):
external_contract, external_function = external_call
if not external_contract in all_contracts:
@ -107,46 +129,45 @@ class PrinterCallGraph(AbstractPrinter):
# add variable as node to respective contract
if isinstance(external_function, (Variable)):
contract_functions[external_contract].add(_node(
_function_node(external_contract, external_function),
external_function.name
))
contract_functions[external_contract].add(
_node(_function_node(external_contract, external_function), external_function.name)
)
external_calls.add(_edge(
_function_node(contract, function),
_function_node(external_contract, external_function),
))
external_calls.add(
_edge(
_function_node(contract, function),
_function_node(external_contract, external_function),
)
)
def _render_internal_calls(self, contract, contract_functions, contract_calls):
lines = []
lines.append(f'subgraph {_contract_subgraph(contract)} {{')
lines.append(f"subgraph {_contract_subgraph(contract)} {{")
lines.append(f'label = "{contract.name}"')
lines.extend(contract_functions[contract])
lines.extend(contract_calls[contract])
lines.append('}')
lines.append("}")
return '\n'.join(lines)
return "\n".join(lines)
def _render_solidity_calls(self, solidity_functions, solidity_calls):
lines = []
lines.append('subgraph cluster_solidity {')
lines.append("subgraph cluster_solidity {")
lines.append('label = "[Solidity]"')
lines.extend(solidity_functions)
lines.extend(solidity_calls)
lines.append('}')
lines.append("}")
return '\n'.join(lines)
return "\n".join(lines)
def _render_external_calls(self, external_calls):
return '\n'.join(external_calls)
return "\n".join(external_calls)
def output(self, filename):
"""
@ -155,23 +176,29 @@ class PrinterCallGraph(AbstractPrinter):
filename(string)
"""
if not filename.endswith('.dot'):
filename += '.dot'
if not filename.endswith(".dot"):
filename += ".dot"
if filename == ".dot":
filename = "all_contracts.dot"
info = ''
info = ""
results = []
with open(filename, 'w', encoding='utf8') as f:
info += f'Call Graph: {filename}\n'
content = '\n'.join(['strict digraph {'] + [self._process_functions(self.slither.functions)] + ['}'])
with open(filename, "w", encoding="utf8") as f:
info += f"Call Graph: {filename}\n"
content = "\n".join(
["strict digraph {"] + [self._process_functions(self.slither.functions)] + ["}"]
)
f.write(content)
results.append((filename, content))
for derived_contract in self.slither.contracts_derived:
with open(f'{derived_contract.name}.dot', 'w', encoding='utf8') as f:
info += f'Call Graph: {derived_contract.name}.dot\n'
content = '\n'.join(['strict digraph {'] + [self._process_functions(derived_contract.functions)] + ['}'])
with open(f"{derived_contract.name}.dot", "w", encoding="utf8") as f:
info += f"Call Graph: {derived_contract.name}.dot\n"
content = "\n".join(
["strict digraph {"]
+ [self._process_functions(derived_contract.functions)]
+ ["}"]
)
f.write(content)
results.append((filename, content))
@ -181,4 +208,3 @@ class PrinterCallGraph(AbstractPrinter):
res.add_file(filename, content)
return res

@ -9,10 +9,10 @@ from slither.utils.myprettytable import MyPrettyTable
class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
ARGUMENT = 'vars-and-auth'
HELP = 'Print the state variables written and the authorization of the functions'
ARGUMENT = "vars-and-auth"
HELP = "Print the state variables written and the authorization of the functions"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#variables-written-and-authorization"
@staticmethod
def get_msg_sender_checks(function):
@ -21,10 +21,14 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
all_nodes = [f.nodes for f in all_functions if isinstance(f, Function)]
all_nodes = [item for sublist in all_nodes for item in sublist]
all_conditional_nodes = [n for n in all_nodes if\
n.contains_if() or n.contains_require_or_assert()]
all_conditional_nodes_on_msg_sender = [str(n.expression) for n in all_conditional_nodes if\
'msg.sender' in [v.name for v in n.solidity_variables_read]]
all_conditional_nodes = [
n for n in all_nodes if n.contains_if() or n.contains_require_or_assert()
]
all_conditional_nodes_on_msg_sender = [
str(n.expression)
for n in all_conditional_nodes
if "msg.sender" in [v.name for v in n.solidity_variables_read]
]
return all_conditional_nodes_on_msg_sender
def output(self, _filename):
@ -34,24 +38,28 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
_filename(string)
"""
txt = ''
txt = ""
all_tables = []
for contract in self.contracts:
if contract.is_top_level:
continue
txt += "\nContract %s\n"%contract.name
table = MyPrettyTable(["Function", "State variables written", "Conditions on msg.sender"])
txt += "\nContract %s\n" % contract.name
table = MyPrettyTable(
["Function", "State variables written", "Conditions on msg.sender"]
)
for function in contract.functions:
state_variables_written = [v.name for v in function.all_state_variables_written()]
msg_sender_condition = self.get_msg_sender_checks(function)
table.add_row([function.name, str(state_variables_written), str(msg_sender_condition)])
table.add_row(
[function.name, str(state_variables_written), str(msg_sender_condition)]
)
all_tables.append((contract.name, table))
txt += str(table) + '\n'
txt += str(table) + "\n"
self.info(txt)
res = self.generate_output(txt)
for name, table in all_tables:
res.add_pretty_table(table, name)
return res
return res

@ -6,10 +6,10 @@ from slither.printers.abstract_printer import AbstractPrinter
class CFG(AbstractPrinter):
ARGUMENT = 'cfg'
HELP = 'Export the CFG of each functions'
ARGUMENT = "cfg"
HELP = "Export the CFG of each functions"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg"
def output(self, original_filename):
"""
@ -18,19 +18,21 @@ class CFG(AbstractPrinter):
_filename(string)
"""
info = ''
info = ""
all_files = []
for contract in self.contracts:
if contract.is_top_level:
continue
for function in contract.functions + contract.modifiers:
if original_filename:
filename = "{}-{}-{}.dot".format(original_filename, contract.name, function.full_name)
filename = "{}-{}-{}.dot".format(
original_filename, contract.name, function.full_name
)
else:
filename = "{}-{}.dot".format(contract.name, function.full_name)
info += 'Export {}\n'.format(filename)
info += "Export {}\n".format(filename)
content = function.slithir_cfg_to_dot_str()
with open(filename, 'w', encoding='utf8') as f:
with open(filename, "w", encoding="utf8") as f:
f.write(content)
all_files.append((filename, content))
@ -39,4 +41,4 @@ class CFG(AbstractPrinter):
res = self.generate_output(info)
for filename, content in all_files:
res.add_file(filename, content)
return res
return res

@ -8,21 +8,37 @@ from typing import Dict, List, Set, Tuple, Union, NamedTuple
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.cfg.node import Node
from slither.core.declarations import Function
from slither.core.declarations.solidity_variables import SolidityVariableComposed, SolidityFunction, SolidityVariable
from slither.core.declarations.solidity_variables import (
SolidityVariableComposed,
SolidityFunction,
SolidityVariable,
)
from slither.core.expressions import NewContract
from slither.core.slither_core import SlitherCore
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.variable import Variable
from slither.printers.abstract_printer import AbstractPrinter
from slither.slithir.operations import Member, Operation, SolidityCall, LowLevelCall, HighLevelCall, EventCall, Send, \
Transfer, InternalDynamicCall, InternalCall, TypeConversion, Balance
from slither.slithir.operations import (
Member,
Operation,
SolidityCall,
LowLevelCall,
HighLevelCall,
EventCall,
Send,
Transfer,
InternalDynamicCall,
InternalCall,
TypeConversion,
Balance,
)
from slither.slithir.operations.binary import Binary, BinaryType
from slither.slithir.variables import Constant
def _get_name(f: Function) -> str:
if f.is_fallback or f.is_receive:
return f'()'
return f"()"
return f.solidity_signature
@ -35,7 +51,9 @@ def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _extract_solidity_variable_usage(slither: SlitherCore, sol_var: SolidityVariable) -> Dict[str, List[str]]:
def _extract_solidity_variable_usage(
slither: SlitherCore, sol_var: SolidityVariable
) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
functions_using_sol_var = []
@ -61,7 +79,7 @@ def _is_constant(f: Function) -> bool:
:return:
"""
if f.view or f.pure:
if not f.contract.slither.crytic_compile.compiler_version.version.startswith('0.4'):
if not f.contract.slither.crytic_compile.compiler_version.version.startswith("0.4"):
return True
if f.payable:
return False
@ -76,13 +94,15 @@ def _is_constant(f: Function) -> bool:
return False
if isinstance(ir, (EventCall, NewContract, LowLevelCall, Send, Transfer)):
return False
if isinstance(ir, SolidityCall) and ir.function in [SolidityFunction('selfdestruct(address)'),
SolidityFunction('suicide(address)')]:
if isinstance(ir, SolidityCall) and ir.function in [
SolidityFunction("selfdestruct(address)"),
SolidityFunction("suicide(address)"),
]:
return False
if isinstance(ir, HighLevelCall):
if isinstance(ir.function, Variable) or ir.function.view or ir.function.pure:
# External call to constant functions are ensured to be constant only for solidity >= 0.5
if f.contract.slither.crytic_compile.compiler_version.version.startswith('0.4'):
if f.contract.slither.crytic_compile.compiler_version.version.startswith("0.4"):
return False
else:
return False
@ -97,7 +117,9 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)]
cst_functions += [v.function_name for v in contract.state_variables if v.visibility in ['public']]
cst_functions += [
v.function_name for v in contract.state_variables if v.visibility in ["public"]
]
if cst_functions:
ret[contract.name] = cst_functions
return ret
@ -109,7 +131,7 @@ def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]:
functions_using_assert = []
for f in contract.functions_entry_points:
for v in f.all_solidity_calls():
if v == SolidityFunction('assert(bool)'):
if v == SolidityFunction("assert(bool)"):
functions_using_assert.append(_get_name(f))
break
if functions_using_assert:
@ -120,9 +142,7 @@ def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]:
# Create a named tuple that is serialization in json
def json_serializable(cls):
def as_dict(self):
yield {name: value for name, value in zip(
self._fields,
iter(super(cls, self).__iter__()))}
yield {name: value for name, value in zip(self._fields, iter(super(cls, self).__iter__()))}
cls.__iter__ = as_dict
return cls
@ -137,15 +157,19 @@ class ConstantValue(NamedTuple):
type: str
def _extract_constants_from_irs(irs: List[Operation],
all_cst_used: List[ConstantValue],
all_cst_used_in_binary: Dict[str, List[ConstantValue]],
context_explored: Set[Node]):
def _extract_constants_from_irs(
irs: List[Operation],
all_cst_used: List[ConstantValue],
all_cst_used_in_binary: Dict[str, List[ConstantValue]],
context_explored: Set[Node],
):
for ir in irs:
if isinstance(ir, Binary):
for r in ir.read:
if isinstance(r, Constant):
all_cst_used_in_binary[str(ir.type)].append(ConstantValue(str(r.value), str(r.type)))
all_cst_used_in_binary[str(ir.type)].append(
ConstantValue(str(r.value), str(r.type))
)
if isinstance(ir, TypeConversion):
if isinstance(ir.variable, Constant):
all_cst_used.append(ConstantValue(str(ir.variable.value), str(ir.type)))
@ -163,13 +187,17 @@ def _extract_constants_from_irs(irs: List[Operation],
continue
else:
context_explored.add(r.node_initialization)
_extract_constants_from_irs(r.node_initialization.irs,
all_cst_used,
all_cst_used_in_binary,
context_explored)
_extract_constants_from_irs(
r.node_initialization.irs,
all_cst_used,
all_cst_used_in_binary,
context_explored,
)
def _extract_constants(slither: SlitherCore) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]:
def _extract_constants(
slither: SlitherCore,
) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]:
# contract -> function -> [ {"value": value, "type": type} ]
ret_cst_used: Dict[str, Dict[str, List[ConstantValue]]] = defaultdict(dict)
# contract -> function -> binary_operand -> [ {"value": value, "type": type ]
@ -181,18 +209,21 @@ def _extract_constants(slither: SlitherCore) -> Tuple[Dict[str, Dict[str, List]]
context_explored = set()
context_explored.add(function)
_extract_constants_from_irs(function.all_slithir_operations(),
all_cst_used,
all_cst_used_in_binary,
context_explored)
_extract_constants_from_irs(
function.all_slithir_operations(),
all_cst_used,
all_cst_used_in_binary,
context_explored,
)
# Note: use list(set()) instead of set
# As this is meant to be serialized in JSON, and JSON does not support set
if all_cst_used:
ret_cst_used[contract.name][_get_name(function)] = list(set(all_cst_used))
if all_cst_used_in_binary:
ret_cst_used_in_binary[contract.name][_get_name(function)] = {k: list(set(v)) for k, v in
all_cst_used_in_binary.items()}
ret_cst_used_in_binary[contract.name][_get_name(function)] = {
k: list(set(v)) for k, v in all_cst_used_in_binary.items()
}
return ret_cst_used, ret_cst_used_in_binary
@ -201,13 +232,16 @@ def _extract_function_relations(slither: SlitherCore) -> Dict[str, Dict[str, Dic
ret: Dict[str, Dict[str, Dict[str, List[str]]]] = defaultdict(dict)
for contract in slither.contracts:
ret[contract.name] = defaultdict(dict)
written = {_get_name(function): function.all_state_variables_written()
for function in contract.functions_entry_points}
read = {_get_name(function): function.all_state_variables_read()
for function in contract.functions_entry_points}
written = {
_get_name(function): function.all_state_variables_written()
for function in contract.functions_entry_points
}
read = {
_get_name(function): function.all_state_variables_read()
for function in contract.functions_entry_points
}
for function in contract.functions_entry_points:
ret[contract.name][_get_name(function)] = {"impacts": [],
"is_impacted_by": []}
ret[contract.name][_get_name(function)] = {"impacts": [], "is_impacted_by": []}
for candidate, varsWritten in written.items():
if any((r in varsWritten for r in function.all_state_variables_read())):
ret[contract.name][_get_name(function)]["is_impacted_by"].append(candidate)
@ -264,27 +298,31 @@ def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
if isinstance(ir, HighLevelCall):
for idx, parameter in enumerate(function.parameters):
if is_dependent(ir.destination, parameter, function):
ret[contract.name].append({
"function": _get_name(function),
"parameter_idx": idx,
"signature": _get_name(ir.function)
})
ret[contract.name].append(
{
"function": _get_name(function),
"parameter_idx": idx,
"signature": _get_name(ir.function),
}
)
if isinstance(ir, LowLevelCall):
for idx, parameter in enumerate(function.parameters):
if is_dependent(ir.destination, parameter, function):
ret[contract.name].append({
"function": _get_name(function),
"parameter_idx": idx,
"signature": None
})
ret[contract.name].append(
{
"function": _get_name(function),
"parameter_idx": idx,
"signature": None,
}
)
return ret
class Echidna(AbstractPrinter):
ARGUMENT = 'echidna'
HELP = 'Export Echidna guiding information'
ARGUMENT = "echidna"
HELP = "Export Echidna guiding information"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna"
def output(self, filename):
"""
@ -296,22 +334,29 @@ class Echidna(AbstractPrinter):
"""
payable = _extract_payable(self.slither)
timestamp = _extract_solidity_variable_usage(self.slither,
SolidityVariableComposed('block.timestamp'))
block_number = _extract_solidity_variable_usage(self.slither,
SolidityVariableComposed('block.number'))
msg_sender = _extract_solidity_variable_usage(self.slither,
SolidityVariableComposed('msg.sender'))
msg_gas = _extract_solidity_variable_usage(self.slither,
SolidityVariableComposed('msg.gas'))
timestamp = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.timestamp")
)
block_number = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.number")
)
msg_sender = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.sender")
)
msg_gas = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.gas")
)
assert_usage = _extract_assert(self.slither)
cst_functions = _extract_constant_functions(self.slither)
(cst_used, cst_used_in_binary) = _extract_constants(self.slither)
functions_relations = _extract_function_relations(self.slither)
constructors = {contract.name: contract.constructor.full_name
for contract in self.slither.contracts if contract.constructor}
constructors = {
contract.name: contract.constructor.full_name
for contract in self.slither.contracts
if contract.constructor
}
external_calls = _have_external_calls(self.slither)
@ -319,20 +364,22 @@ class Echidna(AbstractPrinter):
use_balance = _use_balance(self.slither)
d = {'payable': payable,
'timestamp': timestamp,
'block_number': block_number,
'msg_sender': msg_sender,
'msg_gas': msg_gas,
'assert': assert_usage,
'constant_functions': cst_functions,
'constants_used': cst_used,
'constants_used_in_binary': cst_used_in_binary,
'functions_relations': functions_relations,
'constructors': constructors,
'have_external_calls': external_calls,
'call_a_parameter': call_parameters,
'use_balance': use_balance}
d = {
"payable": payable,
"timestamp": timestamp,
"block_number": block_number,
"msg_sender": msg_sender,
"msg_gas": msg_gas,
"assert": assert_usage,
"constant_functions": cst_functions,
"constants_used": cst_used,
"constants_used_in_binary": cst_used_in_binary,
"functions_relations": functions_relations,
"constructors": constructors,
"have_external_calls": external_calls,
"call_a_parameter": call_parameters,
"use_balance": use_balance,
}
self.info(json.dumps(d, indent=4))

@ -9,10 +9,10 @@ from slither.utils.colors import blue, green
class PrinterInheritance(AbstractPrinter):
ARGUMENT = 'inheritance'
HELP = 'Print the inheritance relations between contracts'
ARGUMENT = "inheritance"
HELP = "Print the inheritance relations between contracts"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance"
def _get_child_contracts(self, base):
# Generate function to get all child contracts of a base contract
@ -28,54 +28,54 @@ class PrinterInheritance(AbstractPrinter):
Args:
_filename(string)
"""
info = 'Inheritance\n'
info = "Inheritance\n"
if not self.contracts:
return
info += blue('Child_Contract -> ') + green('Immediate_Base_Contracts')
info += green(' [Not_Immediate_Base_Contracts]')
info += blue("Child_Contract -> ") + green("Immediate_Base_Contracts")
info += green(" [Not_Immediate_Base_Contracts]")
result = {'child_to_base': {}}
result = {"child_to_base": {}}
for child in self.contracts:
if child.is_top_level:
continue
info += blue(f'\n+ {child.name}\n')
result['child_to_base'][child.name] = {'immediate': [],
'not_immediate': []}
info += blue(f"\n+ {child.name}\n")
result["child_to_base"][child.name] = {"immediate": [], "not_immediate": []}
if child.inheritance:
immediate = child.immediate_inheritance
not_immediate = [i for i in child.inheritance if i not in immediate]
info += ' -> ' + green(", ".join(map(str, immediate))) + '\n'
result['child_to_base'][child.name]['immediate'] = list(map(str, immediate))
info += " -> " + green(", ".join(map(str, immediate))) + "\n"
result["child_to_base"][child.name]["immediate"] = list(map(str, immediate))
if not_immediate:
info += ", ["+ green(", ".join(map(str, not_immediate))) + "]\n"
result['child_to_base'][child.name]['not_immediate'] = list(map(str, not_immediate))
info += ", [" + green(", ".join(map(str, not_immediate))) + "]\n"
result["child_to_base"][child.name]["not_immediate"] = list(
map(str, not_immediate)
)
info += green('\n\nBase_Contract -> ') + blue('Immediate_Child_Contracts') + '\n'
info += blue(' [Not_Immediate_Child_Contracts]') + '\n'
info += green("\n\nBase_Contract -> ") + blue("Immediate_Child_Contracts") + "\n"
info += blue(" [Not_Immediate_Child_Contracts]") + "\n"
result['base_to_child'] = {}
result["base_to_child"] = {}
for base in self.contracts:
if base.is_top_level:
continue
info += green(f'\n+ {base.name}') + '\n'
info += green(f"\n+ {base.name}") + "\n"
children = list(self._get_child_contracts(base))
result['base_to_child'][base.name] = {'immediate': [],
'not_immediate': []}
result["base_to_child"][base.name] = {"immediate": [], "not_immediate": []}
if children:
immediate = [child for child in children if base in child.immediate_inheritance]
not_immediate = [child for child in children if not child in immediate]
info += ' -> ' + blue(", ".join(map(str, immediate))) + '\n'
result['base_to_child'][base.name]['immediate'] = list(map(str, immediate))
info += " -> " + blue(", ".join(map(str, immediate))) + "\n"
result["base_to_child"][base.name]["immediate"] = list(map(str, immediate))
if not_immediate:
info += ', [' + blue(", ".join(map(str, not_immediate))) + ']' + '\n'
result['base_to_child'][base.name]['not_immediate'] = list(map(str, immediate))
info += ", [" + blue(", ".join(map(str, not_immediate))) + "]" + "\n"
result["base_to_child"][base.name]["not_immediate"] = list(map(str, immediate))
self.info(info)
res = self.generate_output(info, additional_fields=result)

@ -9,15 +9,17 @@
from slither.core.declarations.contract import Contract
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.inheritance_analysis import (detect_c3_function_shadowing,
detect_state_variable_shadowing)
from slither.utils.inheritance_analysis import (
detect_c3_function_shadowing,
detect_state_variable_shadowing,
)
class PrinterInheritanceGraph(AbstractPrinter):
ARGUMENT = 'inheritance-graph'
HELP = 'Export the inheritance graph of each contract to a dot file'
ARGUMENT = "inheritance-graph"
HELP = "Export the inheritance graph of each contract to a dot file"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph"
def __init__(self, slither, logger):
super(PrinterInheritanceGraph, self).__init__(slither, logger)
@ -49,7 +51,9 @@ class PrinterInheritanceGraph(AbstractPrinter):
# Html pattern, each line is a row in a table
var_name = var.name
pattern = '<TR><TD align="left"> %s</TD></TR>'
pattern_contract = '<TR><TD align="left"> %s<font color="blue" POINT-SIZE="10"> (%s)</font></TD></TR>'
pattern_contract = (
'<TR><TD align="left"> %s<font color="blue" POINT-SIZE="10"> (%s)</font></TD></TR>'
)
pattern_shadow = '<TR><TD align="left"><font color="red"> %s</font></TD></TR>'
pattern_contract_shadow = '<TR><TD align="left"><font color="red"> %s</font><font color="blue" POINT-SIZE="10"> (%s)</font></TD></TR>'
@ -76,10 +80,14 @@ class PrinterInheritanceGraph(AbstractPrinter):
result = []
indirect_shadows = detect_c3_function_shadowing(contract)
for winner, colliding_functions in indirect_shadows.items():
collision_steps = ', '.join([f.contract_declarer.name
for f in colliding_functions] + [winner.contract_declarer.name])
result.append(f"'{winner.full_name}' collides in inherited contracts {collision_steps} where {winner.contract_declarer.name} is chosen.")
return '\n'.join(result)
collision_steps = ", ".join(
[f.contract_declarer.name for f in colliding_functions]
+ [winner.contract_declarer.name]
)
result.append(
f"'{winner.full_name}' collides in inherited contracts {collision_steps} where {winner.contract_declarer.name} is chosen."
)
return "\n".join(result)
def _get_port_id(self, var, contract):
return "%s%s" % (var.name, contract.name)
@ -88,38 +96,62 @@ class PrinterInheritanceGraph(AbstractPrinter):
"""
Build summary using HTML
"""
ret = ''
ret = ""
# Add arrows (number them if there is more than one path so we know order of declaration for inheritance).
if len(contract.immediate_inheritance) == 1:
ret += '%s -> %s;\n' % (contract.name, contract.immediate_inheritance[0])
ret += "%s -> %s;\n" % (contract.name, contract.immediate_inheritance[0])
else:
for i in range(0, len(contract.immediate_inheritance)):
ret += '%s -> %s [ label="%s" ];\n' % (contract.name, contract.immediate_inheritance[i], i + 1)
ret += '%s -> %s [ label="%s" ];\n' % (
contract.name,
contract.immediate_inheritance[i],
i + 1,
)
# Functions
visibilities = ['public', 'external']
public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if
not f.is_constructor and not f.is_constructor_variables
and f.contract_declarer == contract and f.visibility in visibilities]
public_functions = ''.join(public_functions)
private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if
not f.is_constructor and not f.is_constructor_variables
and f.contract_declarer == contract and f.visibility not in visibilities]
private_functions = ''.join(private_functions)
visibilities = ["public", "external"]
public_functions = [
self._get_pattern_func(f, contract)
for f in contract.functions
if not f.is_constructor
and not f.is_constructor_variables
and f.contract_declarer == contract
and f.visibility in visibilities
]
public_functions = "".join(public_functions)
private_functions = [
self._get_pattern_func(f, contract)
for f in contract.functions
if not f.is_constructor
and not f.is_constructor_variables
and f.contract_declarer == contract
and f.visibility not in visibilities
]
private_functions = "".join(private_functions)
# Modifiers
modifiers = [self._get_pattern_func(m, contract) for m in contract.modifiers if m.contract_declarer == contract]
modifiers = ''.join(modifiers)
modifiers = [
self._get_pattern_func(m, contract)
for m in contract.modifiers
if m.contract_declarer == contract
]
modifiers = "".join(modifiers)
# Public variables
public_variables = [self._get_pattern_var(v, contract) for v in contract.state_variables_declared
if v.visibility in visibilities]
public_variables = ''.join(public_variables)
private_variables = [self._get_pattern_var(v, contract) for v in contract.state_variables_declared
if v.visibility not in visibilities]
private_variables = ''.join(private_variables)
public_variables = [
self._get_pattern_var(v, contract)
for v in contract.state_variables_declared
if v.visibility in visibilities
]
public_variables = "".join(public_variables)
private_variables = [
self._get_pattern_var(v, contract)
for v in contract.state_variables_declared
if v.visibility not in visibilities
]
private_variables = "".join(private_variables)
# Obtain any indirect shadowing information for this node.
indirect_shadowing_information = self._get_indirect_shadowing_information(contract)
@ -130,23 +162,26 @@ class PrinterInheritanceGraph(AbstractPrinter):
ret += '<TR><TD align="center"><B>%s</B></TD></TR>' % contract.name
if public_functions:
ret += '<TR><TD align="left"><I>Public Functions:</I></TD></TR>'
ret += '%s' % public_functions
ret += "%s" % public_functions
if private_functions:
ret += '<TR><TD align="left"><I>Private Functions:</I></TD></TR>'
ret += '%s' % private_functions
ret += "%s" % private_functions
if modifiers:
ret += '<TR><TD align="left"><I>Modifiers:</I></TD></TR>'
ret += '%s' % modifiers
ret += "%s" % modifiers
if public_variables:
ret += '<TR><TD align="left"><I>Public Variables:</I></TD></TR>'
ret += '%s' % public_variables
ret += "%s" % public_variables
if private_variables:
ret += '<TR><TD align="left"><I>Private Variables:</I></TD></TR>'
ret += '%s' % private_variables
ret += "%s" % private_variables
if indirect_shadowing_information:
ret += '<TR><TD><BR/></TD></TR><TR><TD align="left" border="1"><font color="#777777" point-size="10">%s</font></TD></TR>' % indirect_shadowing_information.replace('\n', '<BR/>')
ret += '</TABLE> >];\n'
ret += (
'<TR><TD><BR/></TD></TR><TR><TD align="left" border="1"><font color="#777777" point-size="10">%s</font></TD></TR>'
% indirect_shadowing_information.replace("\n", "<BR/>")
)
ret += "</TABLE> >];\n"
return ret
@ -157,24 +192,24 @@ class PrinterInheritanceGraph(AbstractPrinter):
filename(string)
"""
if filename == '':
filename = 'contracts.dot'
if not filename.endswith('.dot'):
if filename == "":
filename = "contracts.dot"
if not filename.endswith(".dot"):
filename += ".dot"
info = 'Inheritance Graph: ' + filename + '\n'
info = "Inheritance Graph: " + filename + "\n"
self.info(info)
content = 'digraph "" {\n'
for c in self.contracts:
if c.is_top_level:
continue
content += self._summary(c) + '\n'
content += '}'
content += self._summary(c) + "\n"
content += "}"
with open(filename, 'w', encoding='utf8') as f:
with open(filename, "w", encoding="utf8") as f:
f.write(content)
res = self.generate_output(info)
res.add_file(filename, content)
return res
return res

@ -6,20 +6,20 @@ from slither.utils import output
class ConstructorPrinter(AbstractPrinter):
WIKI = 'https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls'
ARGUMENT = 'constructor-calls'
HELP = 'Print the constructors executed'
WIKI = "https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls"
ARGUMENT = "constructor-calls"
HELP = "Print the constructors executed"
def _get_soruce_code(self, cst):
src_mapping = cst.source_mapping
content = self.slither.source_code[src_mapping['filename_absolute']]
start = src_mapping['start']
end = src_mapping['start'] + src_mapping['length']
initial_space = src_mapping['starting_column']
return ' ' * initial_space + content[start:end]
content = self.slither.source_code[src_mapping["filename_absolute"]]
start = src_mapping["start"]
end = src_mapping["start"] + src_mapping["length"]
initial_space = src_mapping["starting_column"]
return " " * initial_space + content[start:end]
def output(self, _filename):
info = ''
info = ""
for contract in self.slither.contracts_derived:
stack_name = []
stack_definition = []
@ -35,18 +35,18 @@ class ConstructorPrinter(AbstractPrinter):
if len(stack_name) > 0:
info += '\n########' + "#" * len(contract.name) + "########\n"
info += "\n########" + "#" * len(contract.name) + "########\n"
info += "####### " + contract.name + " #######\n"
info += '########' + "#" * len(contract.name) + "########\n\n"
info += "## Constructor Call Sequence" + '\n'
info += "########" + "#" * len(contract.name) + "########\n\n"
info += "## Constructor Call Sequence" + "\n"
for name in stack_name[::-1]:
info += "\t- " + name + '\n'
info += "\n## Constructor Definitions" + '\n'
info += "\t- " + name + "\n"
info += "\n## Constructor Definitions" + "\n"
count = len(stack_definition) - 1
while count >= 0:
info += "\n### " + stack_name[count] + '\n'
info += "\n" + str(stack_definition[count]) + '\n'
info += "\n### " + stack_name[count] + "\n"
info += "\n" + str(stack_definition[count]) + "\n"
count = count - 1
self.info(info)

@ -8,10 +8,10 @@ from slither.utils.colors import blue, green, magenta
class ContractSummary(AbstractPrinter):
ARGUMENT = 'contract-summary'
HELP = 'Print a summary of the contracts'
ARGUMENT = "contract-summary"
HELP = "Print a summary of the contracts"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary"
def output(self, _filename):
"""
@ -30,28 +30,34 @@ class ContractSummary(AbstractPrinter):
is_upgradeable_proxy = c.is_upgradeable_proxy
is_upgradeable = c.is_upgradeable
additional_txt_info = ''
additional_txt_info = ""
if is_upgradeable_proxy:
additional_txt_info += ' (Upgradeable Proxy)'
additional_txt_info += " (Upgradeable Proxy)"
if is_upgradeable:
additional_txt_info += ' (Upgradeable)'
additional_txt_info += " (Upgradeable)"
if c in self.slither.contracts_derived:
additional_txt_info += ' (Most derived contract)'
additional_txt_info += " (Most derived contract)"
txt += blue(f"\n+ Contract {c.name}{additional_txt_info}\n")
additional_fields = output.Output('', additional_fields={
'is_upgradeable_proxy': is_upgradeable_proxy,
'is_upgradeable': is_upgradeable,
'is_most_derived': c in self.slither.contracts_derived
})
additional_fields = output.Output(
"",
additional_fields={
"is_upgradeable_proxy": is_upgradeable_proxy,
"is_upgradeable": is_upgradeable,
"is_most_derived": c in self.slither.contracts_derived,
},
)
# Order the function with
# contract_declarer -> list_functions
public = [(f.contract_declarer.name, f) for f in c.functions if (not f.is_shadowed and
not f.is_constructor_variables)]
public = [
(f.contract_declarer.name, f)
for f in c.functions
if (not f.is_shadowed and not f.is_constructor_variables)
]
collect = collections.defaultdict(list)
for a, b in public:
collect[a].append(b)
@ -63,15 +69,20 @@ class ContractSummary(AbstractPrinter):
functions = sorted(functions, key=lambda f: f.full_name)
for function in functions:
if function.visibility in ['external', 'public']:
txt += green(" - {} ({})\n".format(function.full_name, function.visibility))
if function.visibility in ['internal', 'private']:
txt += magenta(" - {} ({})\n".format(function.full_name, function.visibility))
if function.visibility not in ['external', 'public', 'internal', 'private']:
if function.visibility in ["external", "public"]:
txt += green(
" - {} ({})\n".format(function.full_name, function.visibility)
)
if function.visibility in ["internal", "private"]:
txt += magenta(
" - {} ({})\n".format(function.full_name, function.visibility)
)
if function.visibility not in ["external", "public", "internal", "private"]:
txt += " - {}  ({})\n".format(function.full_name, function.visibility)
additional_fields.add(function, additional_fields={"visibility":
function.visibility})
additional_fields.add(
function, additional_fields={"visibility": function.visibility}
)
all_contracts.append((c, additional_fields.data))

@ -9,15 +9,23 @@ from slither.utils.myprettytable import MyPrettyTable
def _get(v, c):
return list(set([d.name for d in get_dependencies(v, c) if not isinstance(d, (TemporaryVariable,
ReferenceVariable))]))
return list(
set(
[
d.name
for d in get_dependencies(v, c)
if not isinstance(d, (TemporaryVariable, ReferenceVariable))
]
)
)
class DataDependency(AbstractPrinter):
ARGUMENT = 'data-dependency'
HELP = 'Print the data dependencies of the variables'
ARGUMENT = "data-dependency"
HELP = "Print the data dependencies of the variables"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#data-dependencies'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#data-dependencies"
def output(self, _filename):
"""
@ -27,14 +35,14 @@ class DataDependency(AbstractPrinter):
"""
all_tables = []
all_txt = ''
all_txt = ""
txt = ''
txt = ""
for c in self.contracts:
if c.is_top_level:
continue
txt += "\nContract %s\n"%c.name
table = MyPrettyTable(['Variable', 'Dependencies'])
txt += "\nContract %s\n" % c.name
table = MyPrettyTable(["Variable", "Dependencies"])
for v in c.state_variables:
table.add_row([v.name, _get(v, c)])
@ -42,8 +50,8 @@ class DataDependency(AbstractPrinter):
txt += "\n"
for f in c.functions_and_modifiers_declared:
txt += "\nFunction %s\n"%f.full_name
table = MyPrettyTable(['Variable', 'Dependencies'])
txt += "\nFunction %s\n" % f.full_name
table = MyPrettyTable(["Variable", "Dependencies"])
for v in f.variables:
table.add_row([v.name, _get(v, f)])
for v in c.state_variables:

@ -7,10 +7,10 @@ from slither.utils.colors import blue, green, magenta, red
class PrinterEVM(AbstractPrinter):
ARGUMENT = 'evm'
HELP = 'Print the evm instructions of nodes in functions'
ARGUMENT = "evm"
HELP = "Print the evm instructions of nodes in functions"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#evm'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#evm"
def output(self, _filename):
"""
@ -22,53 +22,77 @@ class PrinterEVM(AbstractPrinter):
txt = ""
if not self.slither.crytic_compile:
txt = 'The EVM printer requires to compile with crytic-compile'
txt = "The EVM printer requires to compile with crytic-compile"
self.info(red(txt))
res = self.generate_output(txt)
return res
evm_info = self._extract_evm_info(self.slither)
for contract in self.slither.contracts_derived:
txt += blue('Contract {}\n'.format(contract.name))
txt += blue("Contract {}\n".format(contract.name))
contract_file = self.slither.source_code[contract.source_mapping['filename_absolute']].encode('utf-8')
contract_file_lines = open(contract.source_mapping['filename_absolute'], 'r').readlines()
contract_file = self.slither.source_code[
contract.source_mapping["filename_absolute"]
].encode("utf-8")
contract_file_lines = open(
contract.source_mapping["filename_absolute"], "r"
).readlines()
contract_pcs = {}
contract_cfg = {}
for function in contract.functions:
txt += blue(f'\tFunction {function.canonical_name}\n')
txt += blue(f"\tFunction {function.canonical_name}\n")
# CFG and source mapping depend on function being constructor or not
if function.is_constructor:
contract_cfg = evm_info['cfg_init', contract.name]
contract_pcs = evm_info['mapping_init', contract.name]
contract_cfg = evm_info["cfg_init", contract.name]
contract_pcs = evm_info["mapping_init", contract.name]
else:
contract_cfg = evm_info['cfg', contract.name]
contract_pcs = evm_info['mapping', contract.name]
contract_cfg = evm_info["cfg", contract.name]
contract_pcs = evm_info["mapping", contract.name]
for node in function.nodes:
txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = contract_file[0:node.source_mapping['start']].count("\n".encode("utf-8")) + 1
txt += green('\t\tSource line {}: {}\n'.format(node_source_line,
contract_file_lines[node_source_line - 1].rstrip()))
txt += magenta('\t\tEVM Instructions:\n')
node_source_line = (
contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8"))
+ 1
)
txt += green(
"\t\tSource line {}: {}\n".format(
node_source_line, contract_file_lines[node_source_line - 1].rstrip()
)
)
txt += magenta("\t\tEVM Instructions:\n")
node_pcs = contract_pcs.get(node_source_line, [])
for pc in node_pcs:
txt += magenta('\t\t\t0x{:x}: {}\n'.format(int(pc), contract_cfg.get_instruction_at(pc)))
txt += magenta(
"\t\t\t0x{:x}: {}\n".format(
int(pc), contract_cfg.get_instruction_at(pc)
)
)
for modifier in contract.modifiers:
txt += blue(f'\tModifier {modifier.canonical_name}\n')
txt += blue(f"\tModifier {modifier.canonical_name}\n")
for node in modifier.nodes:
txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = contract_file[0:node.source_mapping['start']].count("\n".encode("utf-8")) + 1
txt += green('\t\tSource line {}: {}\n'.format(node_source_line,
contract_file_lines[node_source_line - 1].rstrip()))
txt += magenta('\t\tEVM Instructions:\n')
node_source_line = (
contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8"))
+ 1
)
txt += green(
"\t\tSource line {}: {}\n".format(
node_source_line, contract_file_lines[node_source_line - 1].rstrip()
)
)
txt += magenta("\t\tEVM Instructions:\n")
node_pcs = contract_pcs.get(node_source_line, [])
for pc in node_pcs:
txt += magenta('\t\t\t0x{:x}: {}\n'.format(int(pc), contract_cfg.get_instruction_at(pc)))
txt += magenta(
"\t\t\t0x{:x}: {}\n".format(
int(pc), contract_cfg.get_instruction_at(pc)
)
)
self.info(txt)
res = self.generate_output(txt)
@ -89,21 +113,24 @@ class PrinterEVM(AbstractPrinter):
contract_bytecode_runtime = slither.crytic_compile.bytecode_runtime(contract.name)
contract_srcmap_runtime = slither.crytic_compile.srcmap_runtime(contract.name)
cfg = CFG(contract_bytecode_runtime)
evm_info['cfg', contract.name] = cfg
evm_info['mapping', contract.name] = generate_source_to_evm_ins_mapping(
evm_info["cfg", contract.name] = cfg
evm_info["mapping", contract.name] = generate_source_to_evm_ins_mapping(
cfg.instructions,
contract_srcmap_runtime,
slither,
contract.source_mapping['filename_absolute'])
contract.source_mapping["filename_absolute"],
)
contract_bytecode_init = slither.crytic_compile.bytecode_init(contract.name)
contract_srcmap_init = slither.crytic_compile.srcmap_init(contract.name)
cfg_init = CFG(contract_bytecode_init)
evm_info['cfg_init', contract.name] = cfg_init
evm_info['mapping_init', contract.name] = generate_source_to_evm_ins_mapping(
evm_info["cfg_init", contract.name] = cfg_init
evm_info["mapping_init", contract.name] = generate_source_to_evm_ins_mapping(
cfg_init.instructions,
contract_srcmap_init, slither,
contract.source_mapping['filename_absolute'])
contract_srcmap_init,
slither,
contract.source_mapping["filename_absolute"],
)
return evm_info

@ -8,16 +8,16 @@ from slither.utils.myprettytable import MyPrettyTable
class FunctionSummary(AbstractPrinter):
ARGUMENT = 'function-summary'
HELP = 'Print a summary of the functions'
ARGUMENT = "function-summary"
HELP = "Print a summary of the functions"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary"
@staticmethod
def _convert(l):
if l:
n = 2
l = [l[i:i + n] for i in range(0, len(l), n)]
l = [l[i : i + n] for i in range(0, len(l), n)]
l = [str(x) for x in l]
return "\n".join(l)
return str(l)
@ -30,42 +30,63 @@ class FunctionSummary(AbstractPrinter):
"""
all_tables = []
all_txt = ''
all_txt = ""
for c in self.contracts:
if c.is_top_level:
continue
(name, inheritance, var, func_summaries, modif_summaries) = c.get_summary()
txt = "\nContract %s"%name
txt += '\nContract vars: '+str(var)
txt += '\nInheritance:: '+str(inheritance)
table = MyPrettyTable(["Function",
"Visibility",
"Modifiers",
"Read",
"Write",
"Internal Calls",
"External Calls"])
for (_c_name, f_name, visi, modifiers, read, write, internal_calls, external_calls) in func_summaries:
txt = "\nContract %s" % name
txt += "\nContract vars: " + str(var)
txt += "\nInheritance:: " + str(inheritance)
table = MyPrettyTable(
[
"Function",
"Visibility",
"Modifiers",
"Read",
"Write",
"Internal Calls",
"External Calls",
]
)
for (
_c_name,
f_name,
visi,
modifiers,
read,
write,
internal_calls,
external_calls,
) in func_summaries:
read = self._convert(read)
write = self._convert(write)
internal_calls = self._convert(internal_calls)
external_calls = self._convert(external_calls)
table.add_row([f_name, visi, modifiers, read, write, internal_calls, external_calls])
txt += "\n \n"+str(table)
table = MyPrettyTable(["Modifiers",
"Visibility",
"Read",
"Write",
"Internal Calls",
"External Calls"])
for (_c_name, f_name, visi, _, read, write, internal_calls, external_calls) in modif_summaries:
table.add_row(
[f_name, visi, modifiers, read, write, internal_calls, external_calls]
)
txt += "\n \n" + str(table)
table = MyPrettyTable(
["Modifiers", "Visibility", "Read", "Write", "Internal Calls", "External Calls"]
)
for (
_c_name,
f_name,
visi,
_,
read,
write,
internal_calls,
external_calls,
) in modif_summaries:
read = self._convert(read)
write = self._convert(write)
internal_calls = self._convert(internal_calls)
external_calls = self._convert(external_calls)
table.add_row([f_name, visi, read, write, internal_calls, external_calls])
txt += "\n\n"+str(table)
txt += "\n\n" + str(table)
txt += "\n"
self.info(txt)

@ -8,10 +8,10 @@ from slither.utils.myprettytable import MyPrettyTable
class FunctionIds(AbstractPrinter):
ARGUMENT = 'function-id'
HELP = 'Print the keccack256 signature of the functions'
ARGUMENT = "function-id"
HELP = "Print the keccack256 signature of the functions"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id"
def output(self, _filename):
"""
@ -20,21 +20,21 @@ class FunctionIds(AbstractPrinter):
_filename(string)
"""
txt = ''
txt = ""
all_tables = []
for contract in self.slither.contracts_derived:
txt += '\n{}:\n'.format(contract.name)
table = MyPrettyTable(['Name', 'ID'])
txt += "\n{}:\n".format(contract.name)
table = MyPrettyTable(["Name", "ID"])
for function in contract.functions:
if function.visibility in ['public', 'external']:
if function.visibility in ["public", "external"]:
function_id = get_function_id(function.solidity_signature)
table.add_row([function.solidity_signature, f"{function_id:#0{10}x}"])
for variable in contract.state_variables:
if variable.visibility in ['public']:
if variable.visibility in ["public"]:
sig = variable.function_name
function_id = get_function_id(sig)
table.add_row([sig, f"{function_id:#0{10}x}"])
txt += str(table) + '\n'
txt += str(table) + "\n"
all_tables.append((contract.name, table))
self.info(txt)
@ -43,4 +43,4 @@ class FunctionIds(AbstractPrinter):
for name, table in all_tables:
res.add_pretty_table(table, name)
return res
return res

@ -19,10 +19,10 @@ from slither.utils.tests_pattern import is_test_file
class PrinterHumanSummary(AbstractPrinter):
ARGUMENT = 'human-summary'
HELP = 'Print a human-readable summary of the contracts'
ARGUMENT = "human-summary"
HELP = "Print a human-readable summary of the contracts"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary"
@staticmethod
def _get_summary_erc20(contract):
@ -30,23 +30,24 @@ class PrinterHumanSummary(AbstractPrinter):
functions_name = [f.name for f in contract.functions]
state_variables = [v.name for v in contract.state_variables]
pause = 'pause' in functions_name
pause = "pause" in functions_name
if 'mint' in functions_name:
if 'mintingFinished' in state_variables:
if "mint" in functions_name:
if "mintingFinished" in state_variables:
mint_unlimited = False
else:
mint_unlimited = True
else:
mint_unlimited = None # no minting
race_condition_mitigated = 'increaseApproval' in functions_name or \
'safeIncreaseAllowance' in functions_name
race_condition_mitigated = (
"increaseApproval" in functions_name or "safeIncreaseAllowance" in functions_name
)
return pause, mint_unlimited, race_condition_mitigated
def get_summary_erc20(self, contract):
txt = ''
txt = ""
pause, mint_unlimited, race_condition_mitigated = self._get_summary_erc20(contract)
@ -66,9 +67,9 @@ class PrinterHumanSummary(AbstractPrinter):
return txt
def _get_detectors_result(self) -> Tuple[List[Dict],int, int, int, int, int]:
def _get_detectors_result(self) -> Tuple[List[Dict], int, int, int, int, int]:
# disable detectors logger
logger = logging.getLogger('Detectors')
logger = logging.getLogger("Detectors")
logger.setLevel(logging.ERROR)
checks_optimization = self.slither.detectors_optimization
@ -97,16 +98,20 @@ class PrinterHumanSummary(AbstractPrinter):
issues_high = [c for c in issues_high if c]
issues_high = [item for sublist in issues_high for item in sublist]
all_results = issues_optimization + issues_informational + issues_low + issues_medium + issues_high
all_results = (
issues_optimization + issues_informational + issues_low + issues_medium + issues_high
)
return (all_results,
len(issues_optimization),
len(issues_informational),
len(issues_low),
len(issues_medium),
len(issues_high))
return (
all_results,
len(issues_optimization),
len(issues_informational),
len(issues_low),
len(issues_medium),
len(issues_high),
)
def get_detectors_result(self) -> Tuple[str, List[Dict],int, int, int, int, int]:
def get_detectors_result(self) -> Tuple[str, List[Dict], int, int, int, int, int]:
all_results, optimization, informational, low, medium, high = self._get_detectors_result()
txt = "Number of optimization issues: {}\n".format(green(optimization))
txt += "Number of informational issues: {}\n".format(green(informational))
@ -140,7 +145,7 @@ class PrinterHumanSummary(AbstractPrinter):
is_complex = self._is_complex_code(contract)
result = red('Yes') if is_complex else green('No')
result = red("Yes") if is_complex else green("No")
return result
@staticmethod
@ -181,8 +186,8 @@ class PrinterHumanSummary(AbstractPrinter):
def _compilation_type(self):
if self.slither.crytic_compile is None:
return 'Compilation non standard\n'
return f'Compiled with {str(self.slither.crytic_compile.type)}\n'
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:
@ -221,7 +226,10 @@ class PrinterHumanSummary(AbstractPrinter):
use_abi_encoder = False
for pragma in self.slither.pragma_directives:
if pragma.source_mapping["filename_absolute"] == contract.source_mapping["filename_absolute"]:
if (
pragma.source_mapping["filename_absolute"]
== contract.source_mapping["filename_absolute"]
):
if pragma.is_abi_encoder_v2:
use_abi_encoder = True
@ -235,16 +243,25 @@ class PrinterHumanSummary(AbstractPrinter):
for ir in function.slithir_operations:
if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)) and ir.call_value:
can_send_eth = True
if isinstance(ir, SolidityCall) and ir.function in [SolidityFunction("suicide(address)"),
SolidityFunction("selfdestruct(address)")]:
if isinstance(ir, SolidityCall) and ir.function in [
SolidityFunction("suicide(address)"),
SolidityFunction("selfdestruct(address)"),
]:
can_selfdestruct = True
if (isinstance(ir, SolidityCall) and
ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)")):
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"ecrecover(bytes32,uint8,bytes32,bytes32)"
):
has_ecrecover = True
if isinstance(ir, LowLevelCall) and ir.function_name in ["delegatecall", "callcode"]:
if isinstance(ir, LowLevelCall) and ir.function_name in [
"delegatecall",
"callcode",
]:
can_delegatecall = True
if isinstance(ir, HighLevelCall):
if isinstance(ir.function, (Function, StateVariable)) and ir.function.contract.is_possible_token:
if (
isinstance(ir.function, (Function, StateVariable))
and ir.function.contract.is_possible_token
):
has_token_interaction = True
return {
@ -271,54 +288,62 @@ class PrinterHumanSummary(AbstractPrinter):
txt += self._compilation_type()
results = {
'contracts': {
"elements": []
},
'number_lines': 0,
'number_lines_in_dependencies': 0,
'number_lines_assembly': 0,
'standard_libraries': [],
'ercs': [],
'number_findings': dict(),
'detectors': []
"contracts": {"elements": []},
"number_lines": 0,
"number_lines_in_dependencies": 0,
"number_lines_assembly": 0,
"standard_libraries": [],
"ercs": [],
"number_findings": dict(),
"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
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
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_detectors, detectors_results, optimization, info, low, medium, high = self.get_detectors_result()
txt += f"Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n"
(
txt_detectors,
detectors_results,
optimization,
info,
low,
medium,
high,
) = self.get_detectors_result()
txt += txt_detectors
results['number_findings'] = {
'optimization_issues': optimization,
'informational_issues': info,
'low_issues': low,
'medium_issues': medium,
'high_issues': high
results["number_findings"] = {
"optimization_issues": optimization,
"informational_issues": info,
"low_issues": low,
"medium_issues": medium,
"high_issues": high,
}
results['detectors'] = detectors_results
results["detectors"] = detectors_results
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(l) for l in libs]
ercs = self._ercs()
if ercs:
txt += f'ERCs: {", ".join(ercs)}\n'
results['ercs'] = [str(e) for e in ercs]
results["ercs"] = [str(e) for e in ercs]
table = MyPrettyTable(["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"])
table = MyPrettyTable(
["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"]
)
for contract in self.slither.contracts_derived:
if contract.is_from_dependency() or contract.is_test:
@ -326,40 +351,46 @@ class PrinterHumanSummary(AbstractPrinter):
is_complex = self.is_complex_code(contract)
number_functions = self._number_functions(contract)
ercs = ','.join(contract.ercs())
ercs = ",".join(contract.ercs())
is_erc20 = contract.is_erc20()
erc20_info = ''
erc20_info = ""
if is_erc20:
erc20_info += self.get_summary_erc20(contract)
features = "\n".join([name for name, to_print in self._get_features(contract).items() if to_print])
features = "\n".join(
[name for name, to_print in self._get_features(contract).items() if to_print]
)
table.add_row([contract.name, number_functions, ercs, erc20_info, is_complex, features])
self.info(txt + '\n' + str(table))
self.info(txt + "\n" + str(table))
results_contract = output.Output('')
results_contract = output.Output("")
for contract in self.slither.contracts_derived:
if contract.is_test or contract.is_from_dependency():
continue
contract_d = {'contract_name': contract.name,
'is_complex_code': self._is_complex_code(contract),
'is_erc20': contract.is_erc20(),
'number_functions': self._number_functions(contract),
'features': [name for name, to_print in self._get_features(contract).items() if to_print]}
if contract_d['is_erc20']:
contract_d = {
"contract_name": contract.name,
"is_complex_code": self._is_complex_code(contract),
"is_erc20": contract.is_erc20(),
"number_functions": self._number_functions(contract),
"features": [
name for name, to_print in self._get_features(contract).items() if to_print
],
}
if contract_d["is_erc20"]:
pause, mint_limited, race_condition_mitigated = self._get_summary_erc20(contract)
contract_d['erc20_pause'] = pause
contract_d["erc20_pause"] = pause
if mint_limited is not None:
contract_d['erc20_can_mint'] = True
contract_d['erc20_mint_limited'] = mint_limited
contract_d["erc20_can_mint"] = True
contract_d["erc20_mint_limited"] = mint_limited
else:
contract_d['erc20_can_mint'] = False
contract_d['erc20_race_condition_mitigated'] = race_condition_mitigated
contract_d["erc20_can_mint"] = False
contract_d["erc20_race_condition_mitigated"] = race_condition_mitigated
results_contract.add_contract(contract, additional_fields=contract_d)
results['contracts']['elements'] = results_contract.elements
results["contracts"]["elements"] = results_contract.elements
json = self.generate_output(txt, additional_fields=results)

@ -9,10 +9,10 @@ from slither.utils.myprettytable import MyPrettyTable
class Modifiers(AbstractPrinter):
ARGUMENT = 'modifiers'
HELP = 'Print the modifiers called by each function'
ARGUMENT = "modifiers"
HELP = "Print the modifiers called by each function"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#modifiers'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#modifiers"
def output(self, _filename):
"""
@ -21,13 +21,12 @@ class Modifiers(AbstractPrinter):
_filename(string)
"""
all_txt = ''
all_txt = ""
all_tables = []
for contract in self.slither.contracts_derived:
txt = "\nContract %s"%contract.name
table = MyPrettyTable(["Function",
"Modifiers"])
txt = "\nContract %s" % contract.name
table = MyPrettyTable(["Function", "Modifiers"])
for function in contract.functions:
modifiers = function.modifiers
for call in function.all_internal_calls():
@ -37,11 +36,11 @@ class Modifiers(AbstractPrinter):
if isinstance(call, Function):
modifiers += call.modifiers
table.add_row([function.name, [m.name for m in set(modifiers)]])
txt += "\n"+str(table)
txt += "\n" + str(table)
self.info(txt)
res = self.generate_output(all_txt)
for name, table in all_tables:
res.add_pretty_table(table, name)
return res
return res

@ -7,16 +7,19 @@ from slither.printers.abstract_printer import AbstractPrinter
from slither.slithir.operations import SolidityCall
from slither.utils.myprettytable import MyPrettyTable
require_or_assert = [SolidityFunction("assert(bool)"),
SolidityFunction("require(bool)"),
SolidityFunction("require(bool,string)")]
require_or_assert = [
SolidityFunction("assert(bool)"),
SolidityFunction("require(bool)"),
SolidityFunction("require(bool,string)"),
]
class RequireOrAssert(AbstractPrinter):
ARGUMENT = 'require'
HELP = 'Print the require and assert calls of each function'
ARGUMENT = "require"
HELP = "Print the require and assert calls of each function"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#require'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#require"
@staticmethod
def _convert(l):
@ -30,17 +33,22 @@ class RequireOrAssert(AbstractPrinter):
"""
all_tables = []
all_txt = ''
all_txt = ""
for contract in self.slither.contracts_derived:
txt = "\nContract %s"%contract.name
table = MyPrettyTable(["Function",
"require or assert"])
txt = "\nContract %s" % contract.name
table = MyPrettyTable(["Function", "require or assert"])
for function in contract.functions:
require = function.all_slithir_operations()
require = [ir for ir in require if isinstance(ir, SolidityCall) and ir.function in require_or_assert]
require = [
ir
for ir in require
if isinstance(ir, SolidityCall) and ir.function in require_or_assert
]
require = [ir.node for ir in require]
table.add_row([function.name, self._convert([str(m.expression) for m in set(require)])])
txt += "\n"+str(table)
table.add_row(
[function.name, self._convert([str(m.expression) for m in set(require)])]
)
txt += "\n" + str(table)
self.info(txt)
all_tables.append((contract.name, table))
all_txt += txt

@ -6,10 +6,10 @@ from slither.printers.abstract_printer import AbstractPrinter
class PrinterSlithIR(AbstractPrinter):
ARGUMENT = 'slithir'
HELP = 'Print the slithIR representation of the functions'
ARGUMENT = "slithir"
HELP = "Print the slithIR representation of the functions"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir"
def output(self, _filename):
"""
@ -22,28 +22,28 @@ class PrinterSlithIR(AbstractPrinter):
for contract in self.contracts:
if contract.is_top_level:
continue
txt += 'Contract {}\n'.format(contract.name)
txt += "Contract {}\n".format(contract.name)
for function in contract.functions:
txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}\n'
for node in function.nodes:
if node.expression:
txt += '\t\tExpression: {}\n'.format(node.expression)
txt += '\t\tIRs:\n'
txt += "\t\tExpression: {}\n".format(node.expression)
txt += "\t\tIRs:\n"
for ir in node.irs:
txt += '\t\t\t{}\n'.format(ir)
txt += "\t\t\t{}\n".format(ir)
elif node.irs:
txt += '\t\tIRs:\n'
txt += "\t\tIRs:\n"
for ir in node.irs:
txt += '\t\t\t{}\n'.format(ir)
txt += "\t\t\t{}\n".format(ir)
for modifier in contract.modifiers:
txt += '\tModifier {}\n'.format(modifier.canonical_name)
txt += "\tModifier {}\n".format(modifier.canonical_name)
for node in modifier.nodes:
txt += str(node)
if node.expression:
txt += '\t\tExpression: {}\n'.format(node.expression)
txt += '\t\tIRs:\n'
txt += "\t\tExpression: {}\n".format(node.expression)
txt += "\t\tIRs:\n"
for ir in node.irs:
txt += '\t\t\t{}\n'.format(ir)
txt += "\t\t\t{}\n".format(ir)
self.info(txt)
res = self.generate_output(txt)
return res

@ -7,10 +7,10 @@ from slither.printers.abstract_printer import AbstractPrinter
class PrinterSlithIRSSA(AbstractPrinter):
ARGUMENT = 'slithir-ssa'
HELP = 'Print the slithIR representation of the functions'
ARGUMENT = "slithir-ssa"
HELP = "Print the slithIR representation of the functions"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir-ssa'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#slithir-ssa"
def output(self, _filename):
"""
@ -23,26 +23,26 @@ class PrinterSlithIRSSA(AbstractPrinter):
for contract in self.contracts:
if contract.is_top_level:
continue
txt += 'Contract {}'.format(contract.name) + '\n'
txt += "Contract {}".format(contract.name) + "\n"
for function in contract.functions:
txt += '\tFunction {}'.format(function.canonical_name) + '\n'
txt += "\tFunction {}".format(function.canonical_name) + "\n"
for node in function.nodes:
if node.expression:
txt += '\t\tExpression: {}'.format(node.expression) + '\n'
txt += "\t\tExpression: {}".format(node.expression) + "\n"
if node.irs_ssa:
txt += '\t\tIRs:' + '\n'
txt += "\t\tIRs:" + "\n"
for ir in node.irs_ssa:
txt += '\t\t\t{}'.format(ir) + '\n'
txt += "\t\t\t{}".format(ir) + "\n"
for modifier in contract.modifiers:
txt += '\tModifier {}'.format(modifier.canonical_name) + '\n'
txt += "\tModifier {}".format(modifier.canonical_name) + "\n"
for node in modifier.nodes:
txt += str(node) + '\n'
txt += str(node) + "\n"
if node.expression:
txt += '\t\tExpression: {}'.format(node.expression) + '\n'
txt += "\t\tExpression: {}".format(node.expression) + "\n"
if node.irs_ssa:
txt += '\t\tIRs:' + '\n'
txt += "\t\tIRs:" + "\n"
for ir in node.irs_ssa:
txt += '\t\t\t{}'.format(ir) + '\n'
txt += "\t\t\t{}".format(ir) + "\n"
self.info(txt)
res = self.generate_output(txt)
return res

@ -8,10 +8,10 @@ from slither.utils.myprettytable import MyPrettyTable
class VariableOrder(AbstractPrinter):
ARGUMENT = 'variable-order'
HELP = 'Print the storage order of the state variables'
ARGUMENT = "variable-order"
HELP = "Print the storage order of the state variables"
WIKI = 'https://github.com/trailofbits/slither/wiki/Printer-documentation#variable-order'
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#variable-order"
def output(self, _filename):
"""
@ -20,24 +20,24 @@ class VariableOrder(AbstractPrinter):
_filename(string)
"""
txt = ''
txt = ""
all_tables = []
for contract in self.slither.contracts_derived:
txt += '\n{}:\n'.format(contract.name)
table = MyPrettyTable(['Name', 'Type', 'Slot', 'Offset'])
txt += "\n{}:\n".format(contract.name)
table = MyPrettyTable(["Name", "Type", "Slot", "Offset"])
for variable in contract.state_variables_ordered:
if not variable.is_constant:
slot, offset = self.slither.storage_layout_of(contract, variable)
table.add_row([variable.canonical_name, str(variable.type), slot, offset])
all_tables.append((contract.name, table))
txt += str(table) + '\n'
txt += str(table) + "\n"
self.info(txt)
res = self.generate_output(txt)
for name, table in all_tables:
res.add_pretty_table(table, name)
return res
return res

@ -44,7 +44,6 @@ class Slither(SlitherCore):
super().__init__()
self._parser: SlitherSolc # This could be another parser, like SlitherVyper, interface needs to be determined
self._disallow_partial: bool = kwargs.get("disallow_partial", False)
# list of files provided (see --splitted option)

@ -1,13 +1,25 @@
import logging
from typing import List
from slither.core.declarations import (Contract, Enum, Event, Function,
SolidityFunction, SolidityVariable,
SolidityVariableComposed, Structure)
from slither.core.declarations import (
Contract,
Enum,
Event,
Function,
SolidityFunction,
SolidityVariable,
SolidityVariableComposed,
Structure,
)
from slither.core.expressions import Identifier, Literal
from slither.core.solidity_types import (ArrayType, ElementaryType,
FunctionType, MappingType,
UserDefinedType, TypeInformation)
from slither.core.solidity_types import (
ArrayType,
ElementaryType,
FunctionType,
MappingType,
UserDefinedType,
TypeInformation,
)
from slither.core.solidity_types.elementary_type import Int as ElementaryTypeInt
from slither.core.solidity_types.type import Type
from slither.core.variables.function_type_variable import FunctionTypeVariable
@ -15,32 +27,52 @@ from slither.core.variables.variable import Variable
from slither.core.variables.state_variable import StateVariable
from slither.slithir.operations.codesize import CodeSize
from slither.slithir.variables import TupleVariable
from slither.slithir.operations import (Assignment, Balance, Binary,
BinaryType, Call, Condition, Delete,
EventCall, HighLevelCall, Index,
InitArray, InternalCall,
InternalDynamicCall, Length,
LibraryCall, LowLevelCall, Member,
NewArray, NewContract,
NewElementaryType, NewStructure,
OperationWithLValue, Push, Return,
Send, SolidityCall, Transfer,
TypeConversion, Unary, Unpack, Nop)
from slither.slithir.operations import (
Assignment,
Balance,
Binary,
BinaryType,
Call,
Condition,
Delete,
EventCall,
HighLevelCall,
Index,
InitArray,
InternalCall,
InternalDynamicCall,
Length,
LibraryCall,
LowLevelCall,
Member,
NewArray,
NewContract,
NewElementaryType,
NewStructure,
OperationWithLValue,
Push,
Return,
Send,
SolidityCall,
Transfer,
TypeConversion,
Unary,
Unpack,
Nop,
)
from slither.slithir.tmp_operations.argument import Argument, ArgumentType
from slither.slithir.tmp_operations.tmp_call import TmpCall
from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray
from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract
from slither.slithir.tmp_operations.tmp_new_elementary_type import \
TmpNewElementaryType
from slither.slithir.tmp_operations.tmp_new_elementary_type import TmpNewElementaryType
from slither.slithir.tmp_operations.tmp_new_structure import TmpNewStructure
from slither.slithir.variables import (Constant, ReferenceVariable,
TemporaryVariable)
from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVariable
from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR
from slither.utils.function import get_function_id
from slither.utils.type import export_nested_types_from_variable
from slither.slithir.exceptions import SlithIRError
logger = logging.getLogger('ConvertToIR')
logger = logging.getLogger("ConvertToIR")
def convert_expression(expression, node):
@ -91,10 +123,11 @@ def convert_expression(expression, node):
###################################################################################
###################################################################################
def is_value(ins):
if isinstance(ins, TmpCall):
if isinstance(ins.ori, Member):
if ins.ori.variable_right == 'value':
if ins.ori.variable_right == "value":
return True
return False
@ -102,64 +135,64 @@ def is_value(ins):
def is_gas(ins):
if isinstance(ins, TmpCall):
if isinstance(ins.ori, Member):
if ins.ori.variable_right == 'gas':
if ins.ori.variable_right == "gas":
return True
return False
def get_sig(ir, name):
'''
"""
Return a list of potential signature
It is a list, as Constant variables can be converted to int256
Args:
ir (slithIR.operation)
Returns:
list(str)
'''
sig = '{}({})'
"""
sig = "{}({})"
# list of list of arguments
argss = convert_arguments(ir.arguments)
return [sig.format(name, ','.join(args)) for args in argss]
return [sig.format(name, ",".join(args)) for args in argss]
def get_canonical_names(ir, function_name, contract_name):
'''
"""
Return a list of potential signature
It is a list, as Constant variables can be converted to int256
Args:
ir (slithIR.operation)
Returns:
list(str)
'''
sig = '{}({})'
"""
sig = "{}({})"
# list of list of arguments
argss = convert_arguments(ir.arguments)
return [sig.format(f'{contract_name}.{function_name}', ','.join(args)) for args in argss]
return [sig.format(f"{contract_name}.{function_name}", ",".join(args)) for args in argss]
def convert_arguments(arguments):
argss = [[]]
for arg in arguments:
if isinstance(arg, (list,)):
type_arg = '{}[{}]'.format(get_type(arg[0].type), len(arg))
type_arg = "{}[{}]".format(get_type(arg[0].type), len(arg))
elif isinstance(arg, Function):
type_arg = arg.signature_str
else:
type_arg = get_type(arg.type)
if isinstance(arg, Constant) and arg.type == ElementaryType('uint256'):
if isinstance(arg, Constant) and arg.type == ElementaryType("uint256"):
# If it is a constant
# We dupplicate the existing list
# And we add uint256 and int256 cases
# There is no potential collision, as the compiler
# Prevent it with a
# Prevent it with a
# "not unique after argument-dependent loopkup" issue
argss_new = [list(args) for args in argss]
for args in argss:
args.append(str(ElementaryType('uint256')))
args.append(str(ElementaryType("uint256")))
for args in argss_new:
args.append(str(ElementaryType('int256')))
args.append(str(ElementaryType("int256")))
argss = argss + argss_new
else:
for args in argss:
@ -168,11 +201,9 @@ def convert_arguments(arguments):
def is_temporary(ins):
return isinstance(ins, (Argument,
TmpNewElementaryType,
TmpNewContract,
TmpNewArray,
TmpNewStructure))
return isinstance(
ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure)
)
# endregion
@ -182,10 +213,11 @@ def is_temporary(ins):
###################################################################################
###################################################################################
def integrate_value_gas(result):
'''
"""
Integrate value and gas temporary arguments to call instruction
'''
"""
was_changed = True
calls = []
@ -230,7 +262,7 @@ def integrate_value_gas(result):
# Remove the call to value/gas instruction
result = [i for i in result if not i in to_remove]
# update the real call
# update the real call
for ins in result:
if isinstance(ins, TmpCall):
# use of while if there redirections
@ -261,10 +293,11 @@ def integrate_value_gas(result):
###################################################################################
###################################################################################
def propagate_type_and_convert_call(result, node):
'''
"""
Propagate the types variables and convert tmp call to real call operation
'''
"""
calls_value = {}
calls_gas = {}
@ -354,57 +387,55 @@ def _convert_type_contract(ir, slither):
assert isinstance(ir.variable_left.type, TypeInformation)
contract = ir.variable_left.type.type
if ir.variable_right == 'creationCode':
if ir.variable_right == "creationCode":
if slither.crytic_compile:
bytecode = slither.crytic_compile.bytecode_init(contract.name)
else:
logger.info(
'The codebase uses type(x).creationCode, but crytic-compile was not used. As a result, the bytecode cannot be found')
"The codebase uses type(x).creationCode, but crytic-compile was not used. As a result, the bytecode cannot be found"
)
bytecode = "MISSING_BYTECODE"
assignment = Assignment(ir.lvalue,
Constant(str(bytecode)),
ElementaryType('bytes'))
assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes"))
assignment.set_expression(ir.expression)
assignment.set_node(ir.node)
assignment.lvalue.set_type(ElementaryType('bytes'))
assignment.lvalue.set_type(ElementaryType("bytes"))
return assignment
if ir.variable_right == 'runtimeCode':
if ir.variable_right == "runtimeCode":
if slither.crytic_compile:
bytecode = slither.crytic_compile.bytecode_runtime(contract.name)
else:
logger.info(
'The codebase uses type(x).runtimeCode, but crytic-compile was not used. As a result, the bytecode cannot be found')
"The codebase uses type(x).runtimeCode, but crytic-compile was not used. As a result, the bytecode cannot be found"
)
bytecode = "MISSING_BYTECODE"
assignment = Assignment(ir.lvalue,
Constant(str(bytecode)),
ElementaryType('bytes'))
assignment = Assignment(ir.lvalue, Constant(str(bytecode)), ElementaryType("bytes"))
assignment.set_expression(ir.expression)
assignment.set_node(ir.node)
assignment.lvalue.set_type(ElementaryType('bytes'))
assignment.lvalue.set_type(ElementaryType("bytes"))
return assignment
if ir.variable_right == 'interfaceId':
if ir.variable_right == "interfaceId":
entry_points = contract.functions_entry_points
interfaceId = 0
for entry_point in entry_points:
interfaceId = interfaceId ^ get_function_id(entry_point.full_name)
assignment = Assignment(ir.lvalue,
Constant(str(interfaceId), type=ElementaryType('bytes4')),
ElementaryType('bytes4'))
assignment = Assignment(
ir.lvalue,
Constant(str(interfaceId), type=ElementaryType("bytes4")),
ElementaryType("bytes4"),
)
assignment.set_expression(ir.expression)
assignment.set_node(ir.node)
assignment.lvalue.set_type(ElementaryType('bytes4'))
assignment.lvalue.set_type(ElementaryType("bytes4"))
return assignment
if ir.variable_right == 'name':
assignment = Assignment(ir.lvalue,
Constant(contract.name),
ElementaryType('string'))
if ir.variable_right == "name":
assignment = Assignment(ir.lvalue, Constant(contract.name), ElementaryType("string"))
assignment.set_expression(ir.expression)
assignment.set_node(ir.node)
assignment.lvalue.set_type(ElementaryType('string'))
assignment.lvalue.set_type(ElementaryType("string"))
return assignment
raise SlithIRError(f'type({contract.name}).{ir.variable_right} is unknown')
raise SlithIRError(f"type({contract.name}).{ir.variable_right} is unknown")
def propagate_types(ir, node):
@ -417,7 +448,7 @@ def propagate_types(ir, node):
ir.lvalue.set_type(ir.rvalue.type)
elif isinstance(ir, Binary):
if BinaryType.return_bool(ir.type):
ir.lvalue.set_type(ElementaryType('bool'))
ir.lvalue.set_type(ElementaryType("bool"))
else:
ir.lvalue.set_type(ir.variable_left.type)
elif isinstance(ir, Delete):
@ -432,12 +463,12 @@ def propagate_types(ir, node):
if t is None:
return
if isinstance(t, ElementaryType) and t.name == 'address':
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:
if t in using_for or "*" in using_for:
new_ir = convert_to_library(ir, node, using_for)
if new_ir:
return new_ir
@ -450,19 +481,23 @@ def propagate_types(ir, node):
return convert_type_of_high_and_internal_level_call(ir, contract)
# Convert HighLevelCall to LowLevelCall
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)
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
)
if can_be_low_level(ir):
return convert_to_low_level(ir)
# Convert push operations
# May need to insert a new operation
# Which leads to return a list of operation
if isinstance(t, ArrayType) or (isinstance(t, ElementaryType) and t.type == 'bytes'):
if ir.function_name == 'push' and len(ir.arguments) == 1:
if isinstance(t, ArrayType) or (
isinstance(t, ElementaryType) and t.type == "bytes"
):
if ir.function_name == "push" and len(ir.arguments) == 1:
return convert_to_push(ir, node)
if ir.function_name == 'pop' and len(ir.arguments) == 0:
if ir.function_name == "pop" and len(ir.arguments) == 0:
return convert_to_pop(ir, node)
elif isinstance(ir, Index):
@ -503,49 +538,67 @@ def propagate_types(ir, node):
assert False
elif isinstance(ir, Member):
# TODO we should convert the reference to a temporary if the member is a length or a balance
if ir.variable_right == 'length' and not isinstance(ir.variable_left, Contract) and isinstance(
ir.variable_left.type, (ElementaryType, ArrayType)):
if (
ir.variable_right == "length"
and not isinstance(ir.variable_left, Contract)
and isinstance(ir.variable_left.type, (ElementaryType, ArrayType))
):
length = Length(ir.variable_left, ir.lvalue)
length.set_expression(ir.expression)
length.lvalue.points_to = ir.variable_left
length.set_node(ir.node)
return length
if ir.variable_right == 'balance' and not isinstance(ir.variable_left, Contract) and isinstance(
ir.variable_left.type, ElementaryType):
if (
ir.variable_right == "balance"
and not isinstance(ir.variable_left, Contract)
and isinstance(ir.variable_left.type, ElementaryType)
):
b = Balance(ir.variable_left, ir.lvalue)
b.set_expression(ir.expression)
b.set_node(ir.node)
return b
if ir.variable_right == 'codesize' and not isinstance(ir.variable_left, Contract) and isinstance(
ir.variable_left.type, ElementaryType):
if (
ir.variable_right == "codesize"
and not isinstance(ir.variable_left, Contract)
and isinstance(ir.variable_left.type, ElementaryType)
):
b = CodeSize(ir.variable_left, ir.lvalue)
b.set_expression(ir.expression)
b.set_node(ir.node)
return b
if ir.variable_right == 'selector' and isinstance(ir.variable_left.type, Function):
assignment = Assignment(ir.lvalue,
Constant(str(get_function_id(ir.variable_left.type.full_name))),
ElementaryType('bytes4'))
if ir.variable_right == "selector" and isinstance(ir.variable_left.type, Function):
assignment = Assignment(
ir.lvalue,
Constant(str(get_function_id(ir.variable_left.type.full_name))),
ElementaryType("bytes4"),
)
assignment.set_expression(ir.expression)
assignment.set_node(ir.node)
assignment.lvalue.set_type(ElementaryType('bytes4'))
assignment.lvalue.set_type(ElementaryType("bytes4"))
return assignment
if isinstance(ir.variable_left, TemporaryVariable) and isinstance(ir.variable_left.type,
TypeInformation):
if isinstance(ir.variable_left, TemporaryVariable) and isinstance(
ir.variable_left.type, TypeInformation
):
return _convert_type_contract(ir, node.function.slither)
left = ir.variable_left
t = None
# Handling of this.function_name usage
if (left == SolidityVariable("this") and
isinstance(ir.variable_right, Constant) and
str(ir.variable_right) in [x.name for x in ir.function.contract.functions]
if (
left == SolidityVariable("this")
and isinstance(ir.variable_right, Constant)
and str(ir.variable_right) in [x.name for x in ir.function.contract.functions]
):
# Assumption that this.function_name can only compile if
# And the contract does not have two functions starting with function_name
# Otherwise solc raises:
# Error: Member "f" not unique after argument-dependent lookup in contract
targeted_function = next((x for x in ir.function.contract.functions if
x.name == str(ir.variable_right)))
targeted_function = next(
(
x
for x in ir.function.contract.functions
if x.name == str(ir.variable_right)
)
)
parameters = []
returns = []
for parameter in targeted_function.parameters:
@ -581,13 +634,22 @@ def propagate_types(ir, node):
# This allows to track the selector keyword
# We dont need to check for function collision, as solc prevents the use of selector
# if there are multiple functions with the same name
f = next((f for f in type_t.functions if f.name == ir.variable_right), None)
f = next(
(f for f in type_t.functions if f.name == ir.variable_right), None
)
if f:
ir.lvalue.set_type(f)
else:
# Allow propgation for variable access through contract's nale
# like Base_contract.my_variable
v = next((v for v in type_t.state_variables if v.name == ir.variable_right), None)
v = next(
(
v
for v in type_t.state_variables
if v.name == ir.variable_right
),
None,
)
if v:
ir.lvalue.set_type(v.type)
elif isinstance(ir, NewArray):
@ -603,9 +665,9 @@ def propagate_types(ir, node):
# No change required
pass
elif isinstance(ir, Send):
ir.lvalue.set_type(ElementaryType('bool'))
ir.lvalue.set_type(ElementaryType("bool"))
elif isinstance(ir, SolidityCall):
if ir.function.name in ['type(address)', 'type()']:
if ir.function.name in ["type(address)", "type()"]:
ir.function.return_type = [TypeInformation(ir.arguments[0])]
return_type = ir.function.return_type
if len(return_type) == 1:
@ -621,12 +683,21 @@ def propagate_types(ir, node):
idx = ir.index
t = types[idx]
ir.lvalue.set_type(t)
elif isinstance(ir,
(Argument, TmpCall, TmpNewArray, TmpNewContract, TmpNewStructure, TmpNewElementaryType)):
elif isinstance(
ir,
(
Argument,
TmpCall,
TmpNewArray,
TmpNewContract,
TmpNewStructure,
TmpNewElementaryType,
),
):
# temporary operation; they will be removed
pass
else:
raise SlithIRError('Not handling {} during type propgation'.format(type(ir)))
raise SlithIRError("Not handling {} during type propgation".format(type(ir)))
def extract_tmp_call(ins, contract):
@ -641,8 +712,12 @@ def extract_tmp_call(ins, contract):
# If there is a call on an inherited contract, it is an internal call or an event
if ins.ori.variable_left in contract.inheritance + [contract]:
if str(ins.ori.variable_right) in [f.name for f in contract.functions]:
internalcall = InternalCall((ins.ori.variable_right, ins.ori.variable_left.name), ins.nbr_arguments,
ins.lvalue, ins.type_call)
internalcall = InternalCall(
(ins.ori.variable_right, ins.ori.variable_left.name),
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
)
internalcall.set_expression(ins.expression)
internalcall.call_id = ins.call_id
return internalcall
@ -658,13 +733,23 @@ def extract_tmp_call(ins, contract):
op.set_expression(ins.expression)
op.call_id = ins.call_id
return op
libcall = LibraryCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue,
ins.type_call)
libcall = LibraryCall(
ins.ori.variable_left,
ins.ori.variable_right,
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
)
libcall.set_expression(ins.expression)
libcall.call_id = ins.call_id
return libcall
msgcall = HighLevelCall(ins.ori.variable_left, ins.ori.variable_right, ins.nbr_arguments, ins.lvalue,
ins.type_call)
msgcall = HighLevelCall(
ins.ori.variable_left,
ins.ori.variable_right,
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
)
msgcall.call_id = ins.call_id
if ins.call_gas:
@ -680,10 +765,12 @@ def extract_tmp_call(ins, contract):
r.set_node(ins.node)
return r
if isinstance(ins.called, SolidityVariableComposed):
if str(ins.called) == 'block.blockhash':
ins.called = SolidityFunction('blockhash(uint256)')
elif str(ins.called) == 'this.balance':
s = SolidityCall(SolidityFunction('this.balance()'), ins.nbr_arguments, ins.lvalue, ins.type_call)
if str(ins.called) == "block.blockhash":
ins.called = SolidityFunction("blockhash(uint256)")
elif str(ins.called) == "this.balance":
s = SolidityCall(
SolidityFunction("this.balance()"), ins.nbr_arguments, ins.lvalue, ins.type_call
)
s.set_expression(ins.expression)
return s
@ -736,13 +823,14 @@ def extract_tmp_call(ins, contract):
# Ideally we should compare here for the parameters types too
if len(ins.called.constructor.parameters) != ins.nbr_arguments:
return Nop()
internalcall = InternalCall(ins.called.constructor, ins.nbr_arguments, ins.lvalue,
ins.type_call)
internalcall = InternalCall(
ins.called.constructor, ins.nbr_arguments, ins.lvalue, ins.type_call
)
internalcall.call_id = ins.call_id
internalcall.set_expression(ins.expression)
return internalcall
raise Exception('Not extracted {} {}'.format(type(ins.called), ins))
raise Exception("Not extracted {} {}".format(type(ins.called), ins))
# endregion
@ -752,13 +840,16 @@ def extract_tmp_call(ins, contract):
###################################################################################
###################################################################################
def can_be_low_level(ir):
return ir.function_name in ['transfer',
'send',
'call',
'delegatecall',
'callcode',
'staticcall']
return ir.function_name in [
"transfer",
"send",
"call",
"delegatecall",
"callcode",
"staticcall",
]
def convert_to_low_level(ir):
@ -769,49 +860,46 @@ def convert_to_low_level(ir):
Must be called after can_be_low_level
"""
if ir.function_name == 'transfer':
if ir.function_name == "transfer":
assert len(ir.arguments) == 1
prev_ir = ir
ir = Transfer(ir.destination, ir.arguments[0])
ir.set_expression(prev_ir.expression)
ir.set_node(prev_ir.node)
return ir
elif ir.function_name == 'send':
elif ir.function_name == "send":
assert len(ir.arguments) == 1
prev_ir = ir
ir = Send(ir.destination, ir.arguments[0], ir.lvalue)
ir.set_expression(prev_ir.expression)
ir.set_node(prev_ir.node)
ir.lvalue.set_type(ElementaryType('bool'))
ir.lvalue.set_type(ElementaryType("bool"))
return ir
elif ir.function_name in ['call',
'delegatecall',
'callcode',
'staticcall']:
new_ir = LowLevelCall(ir.destination,
ir.function_name,
ir.nbr_arguments,
ir.lvalue,
ir.type_call)
elif ir.function_name in ["call", "delegatecall", "callcode", "staticcall"]:
new_ir = LowLevelCall(
ir.destination, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call
)
new_ir.call_gas = ir.call_gas
new_ir.call_value = ir.call_value
new_ir.arguments = ir.arguments
if ir.slither.solc_version >= "0.5":
new_ir.lvalue.set_type([ElementaryType('bool'), ElementaryType('bytes')])
new_ir.lvalue.set_type([ElementaryType("bool"), ElementaryType("bytes")])
else:
new_ir.lvalue.set_type(ElementaryType('bool'))
new_ir.lvalue.set_type(ElementaryType("bool"))
new_ir.set_expression(ir.expression)
new_ir.set_node(ir.node)
return new_ir
raise SlithIRError('Incorrect conversion to low level {}'.format(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']
return ir.destination.name == "abi" and ir.function_name in [
"encode",
"encodePacked",
"encodeWithSelector",
"encodeWithSignature",
"decode",
]
def convert_to_solidity_func(ir):
@ -820,17 +908,19 @@ def convert_to_solidity_func(ir):
:param ir:
:return:
"""
call = SolidityFunction('abi.{}()'.format(ir.function_name))
call = SolidityFunction("abi.{}()".format(ir.function_name))
new_ir = SolidityCall(call, ir.nbr_arguments, ir.lvalue, ir.type_call)
new_ir.arguments = ir.arguments
new_ir.set_expression(ir.expression)
new_ir.set_node(ir.node)
if isinstance(call.return_type, list) and len(call.return_type) == 1:
new_ir.lvalue.set_type(call.return_type[0])
elif (isinstance(new_ir.lvalue, TupleVariable) and
call == SolidityFunction("abi.decode()") and
len(new_ir.arguments) == 2 and
isinstance(new_ir.arguments[1], list)):
elif (
isinstance(new_ir.lvalue, TupleVariable)
and call == SolidityFunction("abi.decode()")
and len(new_ir.arguments) == 2
and isinstance(new_ir.arguments[1], list)
):
types = [x for x in new_ir.arguments[1]]
new_ir.lvalue.set_type(types)
# abi.decode where the type to decode is a singleton
@ -877,7 +967,7 @@ def convert_to_push(ir, node):
ir.set_expression(prev_ir.expression)
ir.set_node(prev_ir.node)
length = Literal(len(operation.init_values), 'uint256')
length = Literal(len(operation.init_values), "uint256")
t = operation.init_values[0].type
ir.lvalue.set_type(ArrayType(t, length))
@ -921,7 +1011,7 @@ def convert_to_pop(ir, node):
arr = ir.destination
length = ReferenceVariable(node)
length.set_type(ElementaryType('uint256'))
length.set_type(ElementaryType("uint256"))
ir_length = Length(arr, length)
ir_length.set_expression(ir.expression)
@ -931,15 +1021,15 @@ def convert_to_pop(ir, node):
val = TemporaryVariable(node)
ir_sub_1 = Binary(val, length, Constant("1", ElementaryType('uint256')), BinaryType.SUBTRACTION)
ir_sub_1 = Binary(val, length, Constant("1", ElementaryType("uint256")), BinaryType.SUBTRACTION)
ir_sub_1.set_expression(ir.expression)
ir_sub_1.set_node(ir.node)
ret.append(ir_sub_1)
element_to_delete = ReferenceVariable(node)
ir_assign_element_to_delete = Index(element_to_delete, arr, val, ElementaryType('uint256'))
ir_assign_element_to_delete = Index(element_to_delete, arr, val, ElementaryType("uint256"))
ir_length.lvalue.points_to = arr
element_to_delete.set_type(ElementaryType('uint256'))
element_to_delete.set_type(ElementaryType("uint256"))
ir_assign_element_to_delete.set_expression(ir.expression)
ir_assign_element_to_delete.set_node(ir.node)
ret.append(ir_assign_element_to_delete)
@ -950,14 +1040,14 @@ def convert_to_pop(ir, node):
ret.append(ir_delete)
length_to_assign = ReferenceVariable(node)
length_to_assign.set_type(ElementaryType('uint256'))
length_to_assign.set_type(ElementaryType("uint256"))
ir_length = Length(arr, length_to_assign)
ir_length.set_expression(ir.expression)
ir_length.lvalue.points_to = arr
ir_length.set_node(ir.node)
ret.append(ir_length)
ir_assign_length = Assignment(length_to_assign, val, ElementaryType('uint256'))
ir_assign_length = Assignment(length_to_assign, val, ElementaryType("uint256"))
ir_assign_length.set_expression(ir.expression)
ir_assign_length.set_node(ir.node)
ret.append(ir_assign_length)
@ -969,11 +1059,9 @@ def look_for_library(contract, ir, node, using_for, t):
for destination in using_for[t]:
lib_contract = contract.slither.get_contract_from_name(str(destination))
if lib_contract:
lib_call = LibraryCall(lib_contract,
ir.function_name,
ir.nbr_arguments,
ir.lvalue,
ir.type_call)
lib_call = LibraryCall(
lib_contract, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call
)
lib_call.set_expression(ir.expression)
lib_call.set_node(ir.node)
lib_call.call_gas = ir.call_gas
@ -996,8 +1084,8 @@ def convert_to_library(ir, node, using_for):
if new_ir:
return new_ir
if '*' in using_for:
new_ir = look_for_library(contract, ir, node, using_for, '*')
if "*" in using_for:
new_ir = look_for_library(contract, ir, node, using_for, "*")
if new_ir:
return new_ir
@ -1011,7 +1099,7 @@ def get_type(t):
"""
if isinstance(t, UserDefinedType):
if isinstance(t.type, Contract):
return 'address'
return "address"
return str(t)
@ -1129,7 +1217,7 @@ def convert_type_of_high_and_internal_level_call(ir, contract):
if can_be_solidity_func(ir):
return convert_to_solidity_func(ir)
if not func:
logger.error('Function not found {}'.format(sig))
logger.error("Function not found {}".format(sig))
ir.function = func
if isinstance(func, Function):
return_type = func.return_type
@ -1159,9 +1247,11 @@ def convert_type_of_high_and_internal_level_call(ir, contract):
# If the return type is a structure, but the lvalue is a tuple
# We convert the type of the structure to a list of element
# TODO: explore to replace all tuple variables by structures
if (isinstance(ir.lvalue, TupleVariable) and
isinstance(return_type, UserDefinedType) and
isinstance(return_type.type, Structure)):
if (
isinstance(ir.lvalue, TupleVariable)
and isinstance(return_type, UserDefinedType)
and isinstance(return_type.type, Structure)
):
return_type = _convert_to_structure_to_list(return_type)
ir.lvalue.set_type(return_type)
@ -1178,6 +1268,7 @@ def convert_type_of_high_and_internal_level_call(ir, contract):
###################################################################################
###################################################################################
def find_references_origin(irs):
"""
Make lvalue of each Index, Member operation
@ -1195,12 +1286,15 @@ def find_references_origin(irs):
###################################################################################
###################################################################################
def remove_temporary(result):
result = [ins for ins in result if not isinstance(ins, (Argument,
TmpNewElementaryType,
TmpNewContract,
TmpNewArray,
TmpNewStructure))]
result = [
ins
for ins in result
if not isinstance(
ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure)
)
]
return result
@ -1245,6 +1339,7 @@ def remove_unused(result):
###################################################################################
###################################################################################
def convert_constant_types(irs):
"""
late conversion of uint -> type for constant (Literal)
@ -1265,15 +1360,15 @@ def convert_constant_types(irs):
# TODO: fix missing Unpack conversion
continue
else:
if ir.rvalue.type.type != 'int256':
ir.rvalue.set_type(ElementaryType('int256'))
if ir.rvalue.type.type != "int256":
ir.rvalue.set_type(ElementaryType("int256"))
was_changed = True
if isinstance(ir, Binary):
if isinstance(ir.lvalue.type, ElementaryType):
if ir.lvalue.type.type in ElementaryTypeInt:
for r in ir.read:
if r.type.type != 'int256':
r.set_type(ElementaryType('int256'))
if r.type.type != "int256":
r.set_type(ElementaryType("int256"))
was_changed = True
if isinstance(ir, (HighLevelCall, InternalCall)):
func = ir.function
@ -1288,8 +1383,8 @@ def convert_constant_types(irs):
t = types[idx]
if isinstance(t, ElementaryType):
if t.type in ElementaryTypeInt:
if arg.type.type != 'int256':
arg.set_type(ElementaryType('int256'))
if arg.type.type != "int256":
arg.set_type(ElementaryType("int256"))
was_changed = True
if isinstance(ir, NewStructure):
st = ir.structure
@ -1297,16 +1392,16 @@ def convert_constant_types(irs):
e = st.elems_ordered[idx]
if isinstance(e.type, ElementaryType):
if e.type.type in ElementaryTypeInt:
if arg.type.type != 'int256':
arg.set_type(ElementaryType('int256'))
if arg.type.type != "int256":
arg.set_type(ElementaryType("int256"))
was_changed = True
if isinstance(ir, InitArray):
if isinstance(ir.lvalue.type, ArrayType):
if isinstance(ir.lvalue.type.type, ElementaryType):
if ir.lvalue.type.type.type in ElementaryTypeInt:
for r in ir.read:
if r.type.type != 'int256':
r.set_type(ElementaryType('int256'))
if r.type.type != "int256":
r.set_type(ElementaryType("int256"))
was_changed = True
@ -1317,6 +1412,7 @@ def convert_constant_types(irs):
###################################################################################
###################################################################################
def convert_delete(irs):
"""
Convert the lvalue of the Delete to point to the variable removed
@ -1337,6 +1433,7 @@ def convert_delete(irs):
###################################################################################
###################################################################################
def apply_ir_heuristics(irs, node):
"""
Apply a set of heuristic to improve slithIR

@ -1,3 +1,5 @@
from slither.exceptions import SlitherException
class SlithIRError(SlitherException): pass
class SlithIRError(SlitherException):
pass

@ -7,11 +7,13 @@ from slither.slithir.variables import TupleVariable, ReferenceVariable
logger = logging.getLogger("AssignmentOperationIR")
class Assignment(OperationWithLValue):
class Assignment(OperationWithLValue):
def __init__(self, left_variable, right_variable, variable_return_type):
assert is_valid_lvalue(left_variable)
assert is_valid_rvalue(right_variable) or isinstance(right_variable, (Function, TupleVariable))
assert is_valid_rvalue(right_variable) or isinstance(
right_variable, (Function, TupleVariable)
)
super(Assignment, self).__init__()
self._variables = [left_variable, right_variable]
self._lvalue = left_variable
@ -39,5 +41,7 @@ class Assignment(OperationWithLValue):
points = self.lvalue.points_to
while isinstance(points, ReferenceVariable):
points = points.points_to
return '{} (->{}) := {}({})'.format(self.lvalue, points, self.rvalue, self.rvalue.type)
return '{}({}) := {}({})'.format(self.lvalue, self.lvalue.type, self.rvalue, self.rvalue.type)
return "{} (->{}) := {}({})".format(self.lvalue, points, self.rvalue, self.rvalue.type)
return "{}({}) := {}({})".format(
self.lvalue, self.lvalue.type, self.rvalue, self.rvalue.type
)

@ -4,13 +4,12 @@ from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
class Balance(OperationWithLValue):
def __init__(self, value, lvalue):
assert is_valid_rvalue(value)
assert is_valid_lvalue(lvalue)
self._value = value
self._lvalue = lvalue
lvalue.set_type(ElementaryType('uint256'))
lvalue.set_type(ElementaryType("uint256"))
@property
def read(self):

@ -33,57 +33,59 @@ class BinaryType(Enum):
@staticmethod
def return_bool(operation_type):
return operation_type in [BinaryType.OROR,
BinaryType.ANDAND,
BinaryType.LESS,
BinaryType.GREATER,
BinaryType.LESS_EQUAL,
BinaryType.GREATER_EQUAL,
BinaryType.EQUAL,
BinaryType.NOT_EQUAL]
return operation_type in [
BinaryType.OROR,
BinaryType.ANDAND,
BinaryType.LESS,
BinaryType.GREATER,
BinaryType.LESS_EQUAL,
BinaryType.GREATER_EQUAL,
BinaryType.EQUAL,
BinaryType.NOT_EQUAL,
]
@staticmethod
def get_type(operation_type):
if operation_type == '**':
if operation_type == "**":
return BinaryType.POWER
if operation_type == '*':
if operation_type == "*":
return BinaryType.MULTIPLICATION
if operation_type == '/':
if operation_type == "/":
return BinaryType.DIVISION
if operation_type == '%':
if operation_type == "%":
return BinaryType.MODULO
if operation_type == '+':
if operation_type == "+":
return BinaryType.ADDITION
if operation_type == '-':
if operation_type == "-":
return BinaryType.SUBTRACTION
if operation_type == '<<':
if operation_type == "<<":
return BinaryType.LEFT_SHIFT
if operation_type == '>>':
if operation_type == ">>":
return BinaryType.RIGHT_SHIFT
if operation_type == '&':
if operation_type == "&":
return BinaryType.AND
if operation_type == '^':
if operation_type == "^":
return BinaryType.CARET
if operation_type == '|':
if operation_type == "|":
return BinaryType.OR
if operation_type == '<':
if operation_type == "<":
return BinaryType.LESS
if operation_type == '>':
if operation_type == ">":
return BinaryType.GREATER
if operation_type == '<=':
if operation_type == "<=":
return BinaryType.LESS_EQUAL
if operation_type == '>=':
if operation_type == ">=":
return BinaryType.GREATER_EQUAL
if operation_type == '==':
if operation_type == "==":
return BinaryType.EQUAL
if operation_type == '!=':
if operation_type == "!=":
return BinaryType.NOT_EQUAL
if operation_type == '&&':
if operation_type == "&&":
return BinaryType.ANDAND
if operation_type == '||':
if operation_type == "||":
return BinaryType.OROR
raise SlithIRError('get_type: Unknown operation type {})'.format(operation_type))
raise SlithIRError("get_type: Unknown operation type {})".format(operation_type))
def __str__(self):
if self == BinaryType.POWER:
@ -128,7 +130,6 @@ class BinaryType(Enum):
class Binary(OperationWithLValue):
def __init__(self, result, left_variable, right_variable, operation_type):
assert is_valid_rvalue(left_variable)
assert is_valid_rvalue(right_variable)
@ -139,7 +140,7 @@ class Binary(OperationWithLValue):
self._type = operation_type
self._lvalue = result
if BinaryType.return_bool(operation_type):
result.set_type(ElementaryType('bool'))
result.set_type(ElementaryType("bool"))
else:
result.set_type(left_variable.type)
@ -172,13 +173,13 @@ class Binary(OperationWithLValue):
points = self.lvalue.points_to
while isinstance(points, ReferenceVariable):
points = points.points_to
return '{}(-> {}) = {} {} {}'.format(str(self.lvalue),
points,
self.variable_left,
self.type_str,
self.variable_right)
return '{}({}) = {} {} {}'.format(str(self.lvalue),
self.lvalue.type,
self.variable_left,
self.type_str,
self.variable_right)
return "{}(-> {}) = {} {} {}".format(
str(self.lvalue), points, self.variable_left, self.type_str, self.variable_right
)
return "{}({}) = {} {} {}".format(
str(self.lvalue),
self.lvalue.type,
self.variable_left,
self.type_str,
self.variable_right,
)

@ -2,7 +2,6 @@ from slither.slithir.operations.operation import Operation
class Call(Operation):
def __init__(self):
super(Call, self).__init__()
self._arguments = []
@ -16,15 +15,15 @@ class Call(Operation):
self._arguments = v
def can_reenter(self, callstack=None):
'''
"""
Must be called after slithIR analysis pass
:return: bool
'''
"""
return False
def can_send_eth(self):
'''
"""
Must be called after slithIR analysis pass
:return: bool
'''
"""
return False

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save