From f18f8ee260e4cba2cdba49b4d217424c0a6616d1 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sat, 8 Sep 2018 07:21:05 +0100 Subject: [PATCH 01/11] Fix typo in contract helpers --- slither/core/declarations/contract.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 09d58e3cd..9c935a15e 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -245,7 +245,7 @@ class Contract(ChildSlither, SourceMapping): Returns: Structure """ - return next((st for st in self.structure if st.name == structure_name), None) + return next((st for st in self.structures if st.name == structure_name), None) def get_structure_from_canonical_name(self, structure_name): """ @@ -255,7 +255,7 @@ class Contract(ChildSlither, SourceMapping): Returns: Structure """ - return next((st for st in self.structure if st.canonical_name == structure_name), None) + return next((st for st in self.structures if st.canonical_name == structure_name), None) def get_event_from_name(self, event_name): """ @@ -275,7 +275,7 @@ class Contract(ChildSlither, SourceMapping): Returns: Enum """ - return next((e for e in self.enum if e.name == event_name), None) + return next((e for e in self.enums if e.name == enum_name), None) def get_enum_from_canonical_name(self, enum_name): """ @@ -285,7 +285,7 @@ class Contract(ChildSlither, SourceMapping): Returns: Enum """ - return next((e for e in self.enum if e.canonical_name == event_name), None) + return next((e for e in self.enums if e.canonical_name == enum_name), None) def get_summary(self): """ Return the function summary From 07de8cb9efa2d172f71e82c608e422bb7cad6427 Mon Sep 17 00:00:00 2001 From: Josselin Date: Mon, 10 Sep 2018 11:34:32 +0100 Subject: [PATCH 02/11] Add human readable printer (WIP) --- slither/core/declarations/contract.py | 12 ++ slither/printers/printers.py | 1 + .../printers/summary/printer_human_summary.py | 107 ++++++++++++++++++ slither/utils/code_complexity.py | 75 ++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 slither/printers/summary/printer_human_summary.py create mode 100644 slither/utils/code_complexity.py diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 9c935a15e..0e85b2e68 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -287,6 +287,18 @@ class Contract(ChildSlither, SourceMapping): """ return next((e for e in self.enums if e.canonical_name == enum_name), None) + def is_erc20(self): + """ + Check if the contract is a erc20 token + Note: it does not check for correct return values + Returns: + bool + """ + full_names = [f.full_name for f in self.functions] + return 'transfer(address,uint256)' in full_names and\ + 'transferFrom(address,address,uint256)' in full_names and\ + 'approve(address,uint256)' in full_names + def get_summary(self): """ Return the function summary diff --git a/slither/printers/printers.py b/slither/printers/printers.py index 79fe0cf31..341512aae 100644 --- a/slither/printers/printers.py +++ b/slither/printers/printers.py @@ -6,6 +6,7 @@ from slither.printers.abstractPrinter import AbstractPrinter # Printer must be imported here from slither.printers.summary.printerSummary import PrinterSummary from slither.printers.summary.printerQuickSummary import PrinterQuickSummary +from slither.printers.summary.printer_human_summary import PrinterHumanSummary from slither.printers.inheritance.printerInheritance import PrinterInheritance from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization diff --git a/slither/printers/summary/printer_human_summary.py b/slither/printers/summary/printer_human_summary.py new file mode 100644 index 000000000..baaa14684 --- /dev/null +++ b/slither/printers/summary/printer_human_summary.py @@ -0,0 +1,107 @@ +""" + Module printing summary of the contract +""" +import logging + +from slither.printers.abstractPrinter import AbstractPrinter +from slither.utils.colors import blue, green, magenta, red, yellow + +from slither.utils.code_complexity import compute_cyclomatic_complexity + +from slither.detectors.detectors import Detectors + +class PrinterHumanSummary(AbstractPrinter): + + ARGUMENT = 'print-human-summary' + HELP = 'Print an 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 'paused' in functions_name or '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: + 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): + detectors = Detectors() + + # disable detectors logger + logger = logging.getLogger('Detectors') + logger.setLevel(logging.ERROR) + + checks_low = detectors.low + checks_medium = detectors.medium + checks_high = detectors.high + + issues_low = [detectors.run_detector(self.slither, c) for c in checks_low] + issues_low = [c for c in issues_low if c] + issues_medium = [detectors.run_detector(self.slither, c) for c in checks_medium] + issues_medium = [c for c in issues_medium if c] + issues_high = [detectors.run_detector(self.slither, c) for c in checks_high] + issues_high = [c for c in issues_high if c] + + txt = "Number of low issue: {}\n".format(green(str(len(issues_low)))) + txt += "Number of medium issue: {}\n".format(yellow(str(len(issues_medium)))) + txt += "Number of high issue: {}\n".format(red(str(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 + + if is_complex: + txt = "\tComplex code? {}\n".format(red('Yes')) + else: + txt = "\tComplex code? {}\n".format(green('No')) + + return txt + + 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 += "\tIs ERC20 token: {}\n".format(contract.is_erc20()) + if is_erc20: + txt += self.get_summary_erc20(contract) + + self.info(txt) diff --git a/slither/utils/code_complexity.py b/slither/utils/code_complexity.py new file mode 100644 index 000000000..54dddcf29 --- /dev/null +++ b/slither/utils/code_complexity.py @@ -0,0 +1,75 @@ +# Funciton computing the code complexity + +def compute_number_edges(function): + """ + Compute the number of edges of the CFG + Args: + function (core.declarations.function.Function) + Returns: + int + """ + n = 0 + for node in function.nodes: + n += len(node.sons) + return n + + +def compute_strongly_connected_components(function): + """ + Compute strongly connected components + Based on Kosaraju algo + Implem follows wikipedia algo: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm#The_algorithm + Args: + function (core.declarations.function.Function) + Returns: + list(list(nodes)) + """ + visited = {n:False for n in function.nodes} + assigned = {n:False for n in function.nodes} + components = [] + l = [] + + def visit(node): + if not visited[node]: + visited[node] = True + for son in node.sons: + visit(son) + l.append(node) + + for n in function.nodes: + visit(n) + + def assign(node, root): + if not assigned[node]: + assigned[node] = True + root.append(node) + for father in node.fathers: + assign(father, root) + + for n in l: + component = [] + assign(n, component) + if component: + components.append(component) + + return components + +def compute_cyclomatic_complexity(function): + """ + Compute the cyclomatic complexity of a function + Args: + function (core.declarations.function.Function) + Returns: + int + """ + # from https://en.wikipedia.org/wiki/Cyclomatic_complexity + # M = E - N + 2P + # where M is the complexity + # E number of edges + # N number of nodes + # P number of connected components + + E = compute_number_edges(function) + N = len(function.nodes) + P = len(compute_strongly_connected_components(function)) + return E - N + 2 * P From 431b435f99d0e4e881ff3db17d17bbeda7c3bbed Mon Sep 17 00:00:00 2001 From: disconnect3d Date: Mon, 10 Sep 2018 21:31:34 +0200 Subject: [PATCH 03/11] Refactor printer-human-readable and colors --- .../printers/summary/printer_human_summary.py | 27 ++++++++----------- slither/utils/colors.py | 22 ++++++++------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/slither/printers/summary/printer_human_summary.py b/slither/printers/summary/printer_human_summary.py index baaa14684..1b0911075 100644 --- a/slither/printers/summary/printer_human_summary.py +++ b/slither/printers/summary/printer_human_summary.py @@ -1,17 +1,15 @@ """ - Module printing summary of the contract +Module printing summary of the contract """ import logging +from slither.detectors.detectors import Detectors from slither.printers.abstractPrinter import AbstractPrinter -from slither.utils.colors import blue, green, magenta, red, yellow - from slither.utils.code_complexity import compute_cyclomatic_complexity +from slither.utils.colors import green, red, yellow -from slither.detectors.detectors import Detectors class PrinterHumanSummary(AbstractPrinter): - ARGUMENT = 'print-human-summary' HELP = 'Print an human readable summary of the contracts' @@ -26,7 +24,7 @@ class PrinterHumanSummary(AbstractPrinter): else: txt += "\t\t Can be paused? : {}\n".format(green('No')) - if 'mint' in functions_name: + if 'mint' in functions_name: if not 'mintingFinished' in state_variables: txt += "\t\t Minting restriction? : {}\n".format(red('None')) else: @@ -54,14 +52,14 @@ class PrinterHumanSummary(AbstractPrinter): issues_low = [detectors.run_detector(self.slither, c) for c in checks_low] issues_low = [c for c in issues_low if c] - issues_medium = [detectors.run_detector(self.slither, c) for c in checks_medium] + issues_medium = (detectors.run_detector(self.slither, c) for c in checks_medium) issues_medium = [c for c in issues_medium if c] issues_high = [detectors.run_detector(self.slither, c) for c in checks_high] issues_high = [c for c in issues_high if c] - txt = "Number of low issue: {}\n".format(green(str(len(issues_low)))) - txt += "Number of medium issue: {}\n".format(yellow(str(len(issues_medium)))) - txt += "Number of high issue: {}\n".format(red(str(len(issues_high)))) + 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 @@ -79,13 +77,10 @@ class PrinterHumanSummary(AbstractPrinter): for f in contract.functions: if compute_cyclomatic_complexity(f) > 7: is_complex = True - - if is_complex: - txt = "\tComplex code? {}\n".format(red('Yes')) - else: - txt = "\tComplex code? {}\n".format(green('No')) - return txt + result = red('Yes') if is_complex else green('No') + + return "\tComplex code? {}\n".format(result) def output(self, _filename): """ 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) From 40c9999b10778dd8d09dc1ef66db11dcca8ed72d Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Wed, 7 Nov 2018 16:46:58 +0100 Subject: [PATCH 04/11] Update slither/printers/summary/printer_human_summary.py Co-Authored-By: montyly --- slither/printers/summary/printer_human_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/summary/printer_human_summary.py b/slither/printers/summary/printer_human_summary.py index 1b0911075..f2e6e5b70 100644 --- a/slither/printers/summary/printer_human_summary.py +++ b/slither/printers/summary/printer_human_summary.py @@ -11,7 +11,7 @@ from slither.utils.colors import green, red, yellow class PrinterHumanSummary(AbstractPrinter): ARGUMENT = 'print-human-summary' - HELP = 'Print an human readable summary of the contracts' + HELP = 'Print a human readable summary of the contracts' @staticmethod def get_summary_erc20(contract): From 0ca1865f9b56e025f518d153b323d63ad6258222 Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Wed, 7 Nov 2018 16:47:03 +0100 Subject: [PATCH 05/11] Update slither/utils/code_complexity.py Co-Authored-By: montyly --- slither/utils/code_complexity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/utils/code_complexity.py b/slither/utils/code_complexity.py index 54dddcf29..40f255278 100644 --- a/slither/utils/code_complexity.py +++ b/slither/utils/code_complexity.py @@ -2,7 +2,7 @@ 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: From acfafee4feb54e3d8ad11c6253c810d120d79fb3 Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Wed, 7 Nov 2018 16:47:08 +0100 Subject: [PATCH 06/11] Update slither/printers/summary/printer_human_summary.py Co-Authored-By: montyly --- slither/printers/summary/printer_human_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/summary/printer_human_summary.py b/slither/printers/summary/printer_human_summary.py index f2e6e5b70..cf5644ea8 100644 --- a/slither/printers/summary/printer_human_summary.py +++ b/slither/printers/summary/printer_human_summary.py @@ -84,7 +84,7 @@ class PrinterHumanSummary(AbstractPrinter): def output(self, _filename): """ - _filename is not used + _filename is not used Args: _filename(string) """ From f3df6752b9a820c9178e00e657663cec71923482 Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Wed, 7 Nov 2018 16:47:26 +0100 Subject: [PATCH 07/11] Update slither/printers/summary/printer_human_summary.py Co-Authored-By: montyly --- slither/printers/summary/printer_human_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/summary/printer_human_summary.py b/slither/printers/summary/printer_human_summary.py index cf5644ea8..50b68c30a 100644 --- a/slither/printers/summary/printer_human_summary.py +++ b/slither/printers/summary/printer_human_summary.py @@ -19,7 +19,7 @@ class PrinterHumanSummary(AbstractPrinter): functions_name = [f.name for f in contract.functions] state_variables = [v.name for v in contract.state_variables] - if 'paused' in functions_name or 'pause' in functions_name: + 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')) From c4fcf4ffb9dede9f3dd1787960db57783d86e313 Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Wed, 7 Nov 2018 16:47:42 +0100 Subject: [PATCH 08/11] Update slither/printers/summary/printer_human_summary.py Co-Authored-By: montyly --- slither/printers/summary/printer_human_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/summary/printer_human_summary.py b/slither/printers/summary/printer_human_summary.py index 50b68c30a..ca299c114 100644 --- a/slither/printers/summary/printer_human_summary.py +++ b/slither/printers/summary/printer_human_summary.py @@ -17,7 +17,7 @@ class PrinterHumanSummary(AbstractPrinter): 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] + state_variables = [v.name.lower() for v in contract.state_variables] if 'pause' in functions_name: txt += "\t\t Can be paused? : {}\n".format(yellow('Yes')) From fff9db469afe1307284c93e7a5617cec700ec9d6 Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Wed, 7 Nov 2018 16:48:01 +0100 Subject: [PATCH 09/11] Update slither/printers/summary/printer_human_summary.py Co-Authored-By: montyly --- slither/printers/summary/printer_human_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/printers/summary/printer_human_summary.py b/slither/printers/summary/printer_human_summary.py index ca299c114..1f5e14092 100644 --- a/slither/printers/summary/printer_human_summary.py +++ b/slither/printers/summary/printer_human_summary.py @@ -16,7 +16,7 @@ class PrinterHumanSummary(AbstractPrinter): @staticmethod def get_summary_erc20(contract): txt = '' - functions_name = [f.name for f in contract.functions] + functions_name = [f.name.lower() for f in contract.functions] state_variables = [v.name.lower() for v in contract.state_variables] if 'pause' in functions_name: From fb1abacfdc81187a0538cacc3cf77e673dbfcddb Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Wed, 7 Nov 2018 16:48:22 +0100 Subject: [PATCH 10/11] Update slither/core/declarations/contract.py Co-Authored-By: montyly --- slither/core/declarations/contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index cbb4003df..fe270d47a 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -288,7 +288,7 @@ 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 From 4f1970351e797f2a2c26ea05cfa238983badccb6 Mon Sep 17 00:00:00 2001 From: Ben Stewart Date: Wed, 7 Nov 2018 16:48:27 +0100 Subject: [PATCH 11/11] Update slither/utils/code_complexity.py Co-Authored-By: montyly --- slither/utils/code_complexity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/utils/code_complexity.py b/slither/utils/code_complexity.py index 40f255278..6d533ab42 100644 --- a/slither/utils/code_complexity.py +++ b/slither/utils/code_complexity.py @@ -1,4 +1,4 @@ -# Funciton computing the code complexity +# Function computing the code complexity def compute_number_edges(function): """