diff --git a/slither/__main__.py b/slither/__main__.py index 8c4c51eca..f39376f6b 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -46,11 +46,12 @@ def _process(slither, detector_classes, printer_classes): results = [] - detector_results = slither.run_detectors() - detector_results = [x for x in detector_results if x] # remove empty results - detector_results = [item for sublist in detector_results for item in sublist] # flatten + if not printer_classes: + detector_results = slither.run_detectors() + detector_results = [x for x in detector_results if x] # remove empty results + detector_results = [item for sublist in detector_results for item in sublist] # flatten - results.extend(detector_results) + results.extend(detector_results) slither.run_printers() # Currently printers does not return results @@ -137,6 +138,7 @@ def get_detectors_and_printers(): from slither.printers.call.call_graph import PrinterCallGraph from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization from slither.printers.summary.slithir import PrinterSlithIR + from slither.printers.summary.human_summary import PrinterHumanSummary printers = [FunctionSummary, ContractSummary, @@ -144,7 +146,8 @@ def get_detectors_and_printers(): PrinterInheritanceGraph, PrinterCallGraph, PrinterWrittenVariablesAndAuthorization, - PrinterSlithIR] + PrinterSlithIR, + PrinterHumanSummary] # Handle plugins! for entry_point in iter_entry_points(group='slither_analyzer.plugin', name=None): @@ -178,10 +181,7 @@ def main_impl(all_detector_classes, all_printer_classes): args = parse_args(all_detector_classes, all_printer_classes) printer_classes = choose_printers(args, all_printer_classes) - if printer_classes: - detector_classes = [] - else: - detector_classes = choose_detectors(args, all_detector_classes) + detector_classes = choose_detectors(args, all_detector_classes) default_log = logging.INFO if not args.debug else logging.DEBUG diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 59bef5a93..f1e27d16f 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -302,7 +302,8 @@ class Contract(ChildSlither, SourceMapping): def is_erc20(self): """ - Check if the contract is a erc20 token + Check if the contract is an erc20 token + Note: it does not check for correct return values Returns: bool diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py new file mode 100644 index 000000000..cadd41948 --- /dev/null +++ b/slither/printers/summary/human_summary.py @@ -0,0 +1,105 @@ +""" +Module printing summary of the contract +""" +import logging + +from slither.printers.abstract_printer import AbstractPrinter +from slither.utils.code_complexity import compute_cyclomatic_complexity +from slither.utils.colors import green, red, yellow + + +class PrinterHumanSummary(AbstractPrinter): + ARGUMENT = 'human-summary' + HELP = 'Print a human readable summary of the contracts' + + @staticmethod + def get_summary_erc20(contract): + txt = '' + functions_name = [f.name for f in contract.functions] + state_variables = [v.name for v in contract.state_variables] + + if 'pause' in functions_name: + txt += "\t\t Can be paused? : {}\n".format(yellow('Yes')) + else: + txt += "\t\t Can be paused? : {}\n".format(green('No')) + + if 'mint' in functions_name: + if not 'mintingFinished' in state_variables: + txt += "\t\t Minting restriction? : {}\n".format(red('None')) + else: + txt += "\t\t Minting restriction? : {}\n".format(yellow('Yes')) + else: + txt += "\t\t Minting restriction? : {}\n".format(green('No Minting')) + + if 'increaseApproval' in functions_name or 'safeIncreaseAllowance' in functions_name: + txt += "\t\t ERC20 race condition mitigation: {}\n".format(green('Yes')) + else: + txt += "\t\t ERC20 race condition mitigation: {}\n".format(red('No')) + + return txt + + def get_detectors_result(self): + + # disable detectors logger + logger = logging.getLogger('Detectors') + logger.setLevel(logging.ERROR) + + checks_informational = self.slither.detectors_informational + checks_low = self.slither.detectors_low + checks_medium = self.slither.detectors_medium + checks_high = self.slither.detectors_high + + issues_informational = [c.detect() for c in checks_informational] + issues_informational = [item for sublist in issues_informational for item in sublist] + issues_low = [c.detect() for c in checks_low] + issues_low = [c for c in issues_low if c] + issues_medium = (c.detect() for c in checks_medium) + issues_medium = [c for c in issues_medium if c] + issues_high = [c.detect() for c in checks_high] + issues_high = [c for c in issues_high if c] + + txt = "Number of informational issues: {}\n".format(green(len(issues_informational))) + txt += "Number of low issues: {}\n".format(green(len(issues_low))) + txt += "Number of medium issues: {}\n".format(yellow(len(issues_medium))) + txt += "Number of high issues: {}\n".format(red(len(issues_high))) + + return txt + + @staticmethod + def is_complex_code(contract): + """ + Check if the code is complex + Heuristic, the code is complex if: + - One function has a cyclomatic complexity > 7 + Args: + contract + """ + is_complex = False + + for f in contract.functions: + if compute_cyclomatic_complexity(f) > 7: + is_complex = True + + result = red('Yes') if is_complex else green('No') + + return "\tComplex code? {}\n".format(result) + + def output(self, _filename): + """ + _filename is not used + Args: + _filename(string) + """ + + txt = "Analyze of {}\n".format(self.slither.filename) + txt += self.get_detectors_result() + for contract in self.slither.contracts_derived: + txt += "\nContract {}\n".format(contract.name) + txt += self.is_complex_code(contract) + is_erc20 = contract.is_erc20() + txt += '\tNumber of functions:{}'.format(len(contract.functions)) + txt += "\tIs ERC20 token: {}\n".format(contract.is_erc20()) + if is_erc20: + txt += self.get_summary_erc20(contract) + + self.info(txt) diff --git a/slither/slither.py b/slither/slither.py index 463eebd80..d056faa1b 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -3,7 +3,7 @@ import os import subprocess import sys -from slither.detectors.abstract_detector import AbstractDetector +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.printers.abstract_printer import AbstractPrinter from .solc_parsing.slitherSolc import SlitherSolc from .utils.colors import red @@ -36,6 +36,26 @@ class Slither(SlitherSolc): self._analyze_contracts() + @property + def detectors(self): + return self._detectors + + @property + def detectors_high(self): + return [d for d in self.detectors if d.IMPACT == DetectorClassification.HIGH] + + @property + def detectors_medium(self): + return [d for d in self.detectors if d.IMPACT == DetectorClassification.MEDIUM] + + @property + def detectors_low(self): + return [d for d in self.detectors if d.IMPACT == DetectorClassification.LOW] + + @property + def detectors_informational(self): + return [d for d in self.detectors if d.IMPACT == DetectorClassification.INFORMATIONAL] + def register_detector(self, detector_class): """ :param detector_class: Class inheriting from `AbstractDetector`. diff --git a/slither/utils/code_complexity.py b/slither/utils/code_complexity.py index efe94648b..6d533ab42 100644 --- a/slither/utils/code_complexity.py +++ b/slither/utils/code_complexity.py @@ -1,8 +1,8 @@ -# Funciton computing the code complexity +# Function computing the code complexity def compute_number_edges(function): """ - Compute the number of edges of the CFG + Compute the number of edges of the CFG Args: function (core.declarations.function.Function) Returns: @@ -72,4 +72,4 @@ def compute_cyclomatic_complexity(function): E = compute_number_edges(function) N = len(function.nodes) P = len(compute_strongly_connected_components(function)) - return E - N + 2 * P \ No newline at end of file + return E - N + 2 * P diff --git a/slither/utils/colors.py b/slither/utils/colors.py index ce7f209bb..b34ac68d2 100644 --- a/slither/utils/colors.py +++ b/slither/utils/colors.py @@ -1,3 +1,6 @@ +from functools import partial + + class Colors: RED = '\033[91m' GREEN = '\033[92m' @@ -6,14 +9,13 @@ class Colors: MAGENTA = '\033[95m' END = '\033[0m' -def green(txt): - return Colors.GREEN + txt + Colors.END -def yellow(txt): - return Colors.YELLOW + txt + Colors.END -def red(txt): - return Colors.RED + txt + Colors.END -def blue(txt): - return Colors.BLUE + txt + Colors.END -def magenta(txt): - return Colors.MAGENTA + txt + Colors.END +def colorize(color, txt): + return '{}{}{}'.format(color, txt, Colors.END) + + +green = partial(colorize, Colors.GREEN) +yellow = partial(colorize, Colors.YELLOW) +red = partial(colorize, Colors.RED) +blue = partial(colorize, Colors.BLUE) +magenta = partial(colorize, Colors.MAGENTA)