Repository: crytic/slither

Issue:
Printer: Inheritance printing on stdout
Hey,

It would be cool to be able to print inheritance to stdout (to quickly analyze things), examples:
* see what inherits from BaseContract: `BaseContract childrens: A, B, C, D, E, ...` (it should also list the contracts that derives from A, B, C, D, E)
* see all base classes for given contract: `Children inherits from: BaseContract1, BaseContract2, BaseBaseContract, SuperBaseBaseBaseContract`
 


Discussion:
This can be implemented as a new printer (and maybe rename the original `--printer-inheritance` to `--printer-inheritance-graph`)
Issue Status: 1. **Open** 2. Started 3. Submitted 4. Done 

<hr>

__This issue now has a funding of 150.0 DAI (150.0 USD @ $1.0/DAI)  attached to it as part of the Ethereum Community Fund via ECF Web 3.0 Infrastructure Fund fund.__

 * If you would like to work on this issue you can 'start work' [on the Gitcoin Issue Details page](https://gitcoin.co/issue/trailofbits/slither/23/1517).
* Want to chip in? Add your own contribution [here](https://gitcoin.co/issue/trailofbits/slither/23/1517).
* Questions? Checkout <a href='https://gitcoin.co/help'>Gitcoin Help</a> or the <a href='https://gitcoin.co/slack'>Gitcoin Slack</a>
* $48,005.92 more funded OSS Work available on the [Gitcoin Issue Explorer](https://gitcoin.co/explorer)


Resolving patch:
diff --git a/slither/__main__.py b/slither/__main__.py
--- a/slither/__main__.py
+++ b/slither/__main__.py
@@ -116,12 +116,14 @@ def main():
     from slither.printers.summary.summary import PrinterSummary
     from slither.printers.summary.quick_summary import PrinterQuickSummary
     from slither.printers.inheritance.inheritance import PrinterInheritance
+    from slither.printers.inheritance.inheritance_graph import PrinterInheritanceGraph
     from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization
     from slither.printers.summary.slithir import PrinterSlithIR
 
     printers = [PrinterSummary,
                 PrinterQuickSummary,
                 PrinterInheritance,
+                PrinterInheritanceGraph,
                 PrinterWrittenVariablesAndAuthorization,
                 PrinterSlithIR]
 
diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py
--- a/slither/printers/inheritance/inheritance.py
+++ b/slither/printers/inheritance/inheritance.py
@@ -1,123 +1,46 @@
 """
-    Module printing the inheritance graph
+    Module printing the inheritance relation
 
-    The inheritance graph shows the relation between the contracts
-    and their functions/modifiers/public variables.
-    The output is a dot file named filename.dot
+    The inheritance shows the relation between the contracts
 """
 
-from slither.core.declarations.contract import Contract
-from slither.detectors.shadowing.shadowing_functions import ShadowingFunctionsDetection
 from slither.printers.abstract_printer import AbstractPrinter
+from slither.utils.colors import blue, green
 
 
 class PrinterInheritance(AbstractPrinter):
     ARGUMENT = 'inheritance'
-    HELP = 'the inheritance graph'
+    HELP = 'the inheritance relation between contracts'
 
-    def __init__(self, slither, logger):
-        super(PrinterInheritance, self).__init__(slither, logger)
-
-        inheritance = [x.inheritance for x in slither.contracts]
-        self.inheritance = set([item for sublist in inheritance for item in sublist])
-
-        shadow = ShadowingFunctionsDetection(slither, None)
-        ret = shadow.detect()
-        functions_shadowed = {}
-        for s in ret:
-            if s['contractShadower'] not in functions_shadowed:
-                functions_shadowed[s['contractShadower']] = []
-            functions_shadowed[s['contractShadower']] += s['funcs']
-        self.functions_shadowed = functions_shadowed
-
-    def _get_pattern_func(self, func, contract):
-        # Html pattern, each line is a row in a table
-        func_name = func.full_name
-        pattern = '<TR><TD align="left">    %s</TD></TR>'
-        pattern_shadow = '<TR><TD align="left"><font color="#FFA500">    %s</font></TD></TR>'
-        if contract.name in self.functions_shadowed:
-            if func_name in self.functions_shadowed[contract.name]:
-                return pattern_shadow % func_name
-        return pattern % func_name
-
-    def _get_pattern_var(self, var, contract):
-        # 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_arrow  = '<TR><TD align="left" PORT="%s"><font color="blue">    %s</font></TD></TR>'
-        if isinstance(var.type, Contract):
-            return pattern_contract % (var_name, str(var.type))
-            # return pattern_arrow%(self._get_port_id(var, contract), var_name)
-        return pattern % var_name
-
-    def _get_port_id(self, var, contract):
-        return "%s%s" % (var.name, contract.name)
-
-    def _summary(self, contract):
-        """
-            Build summary using HTML
-        """
-        ret = ''
-        # Add arrows
-        for i in contract.inheritance:
-            ret += '%s -> %s;\n' % (contract.name, i)
-
-        # Functions
-        visibilities = ['public', 'external']
-        public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if
-                            not f.is_constructor and f.contract == 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 f.contract == 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 == contract]
-        modifiers = ''.join(modifiers)
-        # Public variables
-        public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if
-                            v.visibility in visibilities]
-        public_variables = ''.join(public_variables)
-
-        private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if
-                             not v.visibility in visibilities]
-        private_variables = ''.join(private_variables)
-
-        # Build the node label
-        ret += '%s[shape="box"' % contract.name
-        ret += 'label=< <TABLE border="0">'
-        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
-        if private_functions:
-            ret += '<TR><TD align="left"><I>Private Functions:</I></TD></TR>'
-            ret += '%s' % private_functions
-        if modifiers:
-            ret += '<TR><TD align="left"><I>Modifiers:</I></TD></TR>'
-            ret += '%s' % modifiers
-        if public_variables:
-            ret += '<TR><TD align="left"><I>Public Variables:</I></TD></TR>'
-            ret += '%s' % public_variables
-        if private_variables:
-            ret += '<TR><TD align="left"><I>Private Variables:</I></TD></TR>'
-            ret += '%s' % private_variables
-        ret += '</TABLE> >];\n'
-
-        return ret
+    def _get_child_contracts(self, base):
+        # Generate function to get all child contracts of a base contract
+        for child in self.contracts:
+            if base in child.inheritance:
+                yield child
 
     def output(self, filename):
         """
-            Output the graph in filename
+            Output the inheritance relation
+
+            _filename is not used
             Args:
-                filename(string)
+                _filename(string)
         """
-        if not filename.endswith('.dot'):
-            filename += ".dot"
-        info = 'Inheritance Graph: ' + filename
+        info = 'Inheritance\n'
+
+        if not self.contracts:
+            return
+
+        info += blue('Child_Contract -> ') + green('Base_Contracts')
+        for child in self.contracts:
+            info += blue(f'\n+ {child.name}')
+            if child.inheritance:
+                info += ' -> ' + green(", ".join(map(str, child.inheritance)))
+
+        info += green('\n\nBase_Contract -> ') + blue('Child_Contracts')
+        for base in self.contracts:
+            info += green(f'\n+ {base.name}')
+            children = list(self._get_child_contracts(base))
+            if children:
+                info += ' -> ' + blue(", ".join(map(str, children)))
         self.info(info)
-        with open(filename, 'w') as f:
-            f.write('digraph{\n')
-            for c in self.contracts:
-                f.write(self._summary(c))
-            f.write('}')
diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py
new file mode 100644
--- /dev/null
+++ b/slither/printers/inheritance/inheritance_graph.py
@@ -0,0 +1,122 @@
+"""
+    Module printing the inheritance graph
+
+    The inheritance graph shows the relation between the contracts
+    and their functions/modifiers/public variables.
+    The output is a dot file named filename.dot
+"""
+
+from slither.core.declarations.contract import Contract
+from slither.detectors.shadowing.shadowing_functions import ShadowingFunctionsDetection
+from slither.printers.abstract_printer import AbstractPrinter
+
+class PrinterInheritanceGraph(AbstractPrinter):
+    ARGUMENT = 'inheritance-graph'
+    HELP = 'the inheritance graph'
+
+    def __init__(self, slither, logger):
+        super(PrinterInheritanceGraph, self).__init__(slither, logger)
+
+        inheritance = [x.inheritance for x in slither.contracts]
+        self.inheritance = set([item for sublist in inheritance for item in sublist])
+
+        shadow = ShadowingFunctionsDetection(slither, None)
+        ret = shadow.detect()
+        functions_shadowed = {}
+        for s in ret:
+            if s['contractShadower'] not in functions_shadowed:
+                functions_shadowed[s['contractShadower']] = []
+            functions_shadowed[s['contractShadower']] += s['funcs']
+        self.functions_shadowed = functions_shadowed
+
+    def _get_pattern_func(self, func, contract):
+        # Html pattern, each line is a row in a table
+        func_name = func.full_name
+        pattern = '<TR><TD align="left">    %s</TD></TR>'
+        pattern_shadow = '<TR><TD align="left"><font color="#FFA500">    %s</font></TD></TR>'
+        if contract.name in self.functions_shadowed:
+            if func_name in self.functions_shadowed[contract.name]:
+                return pattern_shadow % func_name
+        return pattern % func_name
+
+    def _get_pattern_var(self, var, contract):
+        # 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_arrow  = '<TR><TD align="left" PORT="%s"><font color="blue">    %s</font></TD></TR>'
+        if isinstance(var.type, Contract):
+            return pattern_contract % (var_name, str(var.type))
+            # return pattern_arrow%(self._get_port_id(var, contract), var_name)
+        return pattern % var_name
+
+    def _get_port_id(self, var, contract):
+        return "%s%s" % (var.name, contract.name)
+
+    def _summary(self, contract):
+        """
+            Build summary using HTML
+        """
+        ret = ''
+        # Add arrows
+        for i in contract.inheritance:
+            ret += '%s -> %s;\n' % (contract.name, i)
+
+        # Functions
+        visibilities = ['public', 'external']
+        public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if
+                            not f.is_constructor and f.contract == 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 f.contract == 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 == contract]
+        modifiers = ''.join(modifiers)
+        # Public variables
+        public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if
+                            v.visibility in visibilities]
+        public_variables = ''.join(public_variables)
+
+        private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if
+                             not v.visibility in visibilities]
+        private_variables = ''.join(private_variables)
+
+        # Build the node label
+        ret += '%s[shape="box"' % contract.name
+        ret += 'label=< <TABLE border="0">'
+        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
+        if private_functions:
+            ret += '<TR><TD align="left"><I>Private Functions:</I></TD></TR>'
+            ret += '%s' % private_functions
+        if modifiers:
+            ret += '<TR><TD align="left"><I>Modifiers:</I></TD></TR>'
+            ret += '%s' % modifiers
+        if public_variables:
+            ret += '<TR><TD align="left"><I>Public Variables:</I></TD></TR>'
+            ret += '%s' % public_variables
+        if private_variables:
+            ret += '<TR><TD align="left"><I>Private Variables:</I></TD></TR>'
+            ret += '%s' % private_variables
+        ret += '</TABLE> >];\n'
+
+        return ret
+
+    def output(self, filename):
+        """
+            Output the graph in filename
+            Args:
+                filename(string)
+        """
+        if not filename.endswith('.dot'):
+            filename += ".dot"
+        info = 'Inheritance Graph: ' + filename
+        self.info(info)
+        with open(filename, 'w') as f:
+            f.write('digraph{\n')
+            for c in self.contracts:
+                f.write(self._summary(c))
+            f.write('}')


Test patch: