From 5876ef14f15ebba8aad84fcfddb1f26ccaa86918 Mon Sep 17 00:00:00 2001 From: nisedo <73041332+nisedo@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:25:21 +0100 Subject: [PATCH 1/4] Add entry_points printer to all_printers.py list --- slither/printers/all_printers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/slither/printers/all_printers.py b/slither/printers/all_printers.py index 034578f52..d3e836d71 100644 --- a/slither/printers/all_printers.py +++ b/slither/printers/all_printers.py @@ -25,3 +25,4 @@ from .functions.dominator import Dominator from .summary.martin import Martin from .summary.cheatcodes import CheatcodePrinter +from .summary.entry_points import PrinterEntryPoints From 2ca5f2dbfed70f4475c3e70482d4d49f3460d7ab Mon Sep 17 00:00:00 2001 From: nisedo <73041332+nisedo@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:25:41 +0100 Subject: [PATCH 2/4] Update README to add entry-points printer --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9968d6350..8422756a3 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ For more information, see * `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph) * `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary) * `loc`: [Count the total number lines of code (LOC), source lines of code (SLOC), and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).](https://github.com/trailofbits/slither/wiki/Printer-documentation#loc) +* `entry-points`: [Print the entry points of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#entry-points) ### In-Depth Review Printers From 3aef721aec4843c54e8549281933e8c14a156526 Mon Sep 17 00:00:00 2001 From: nisedo <73041332+nisedo@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:27:36 +0100 Subject: [PATCH 3/4] Add entry_points.py printer --- slither/printers/summary/entry_points.py | 130 +++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 slither/printers/summary/entry_points.py diff --git a/slither/printers/summary/entry_points.py b/slither/printers/summary/entry_points.py new file mode 100644 index 000000000..4319c5ff0 --- /dev/null +++ b/slither/printers/summary/entry_points.py @@ -0,0 +1,130 @@ +""" +Module printing all the entry points of the contracts + +This printer identifies and displays all externally accessible functions (entry points) +of smart contracts, excluding view/pure functions and special functions like constructors. +It helps in security analysis by providing a clear overview of possible external interactions. +""" +from slither.printers.abstract_printer import AbstractPrinter +from slither.core.declarations.function_contract import FunctionContract +from slither.core.declarations.contract import Contract +from slither.utils.colors import Colors +from slither.utils.output import Output + +class PrinterEntryPoints(AbstractPrinter): + ARGUMENT = "entry-points" + HELP = "Print the entry points of the contracts" + + WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#entry-points" + + def _get_contract_type(self, contract: Contract) -> str: + """ + Returns a string describing the contract type. + + Args: + contract (Contract): The contract to analyze + + Returns: + str: Contract type description ("Interface", "Library", "Abstract Contract", or "Contract") + """ + if contract.is_interface: + return "Interface" + if contract.is_library: + return "Library" + if contract.is_abstract: + return "Abstract Contract" + return "Contract" + + def output(self, filename: str) -> Output: + """ + Generates a formatted output of all contract entry points. + + This method processes all contracts to: + 1. Filter out interfaces and contracts from utility/testing directories + 2. Sort contracts by type and name + 3. Identify and format public/external functions + 4. Include inheritance and modifier information + + Args: + filename (str): The filename to save the results (not used in current implementation) + + Returns: + Output: Formatted data containing entry points for each contract + """ + all_contracts = [] + + # Filter out interfaces and contracts from utility/testing directories + non_interface_contracts = [ + c for c in self.contracts + if not c.is_interface and + 'lib/' not in c.source_mapping.filename.absolute and + 'node_modules/' not in c.source_mapping.filename.absolute and + 'mock/' not in c.source_mapping.filename.absolute + ] + + # Sort contracts with priority: regular contracts > abstract contracts > libraries + sorted_contracts = sorted( + non_interface_contracts, + key=lambda x: ( + not x.is_library, # Libraries last + not x.is_abstract, # Abstract contracts second + x.name # Alphabetical within each category + ) + ) + + for contract in sorted_contracts: + # Identify entry points: public/external functions excluding: + # - Constructors, fallback, receive functions + # - View/pure functions + # - Interface functions + entry_points = [f for f in contract.functions if + (f.visibility in ['public', 'external'] and + isinstance(f, FunctionContract) and + not f.is_constructor and + not f.is_fallback and + not f.is_receive and + not f.view and + not f.pure and + not f.contract_declarer.is_interface)] + + # Skip contract if no entry points + if not entry_points: + continue + + contract_info = [] + contract_type = self._get_contract_type(contract) + source_file = contract.source_mapping.filename.short + + # Combine contract type, name, and source into one line + contract_info.append(f"\n{contract_type} {Colors.BOLD}{Colors.BLUE}{contract.name}{Colors.END} ({source_file})") + + # Add inheritance information if any + if contract.inheritance: + inheritance_str = ", ".join(c.name for c in contract.inheritance) + contract_info.append(f"Inherits from: {inheritance_str}") + + # Sort functions prioritizing external over public visibility + entry_points.sort(key=lambda x: (x.visibility != 'external', x.visibility != 'public', x.full_name)) + + for f in entry_points: + # Collect and format modifier information + modifiers = [m.name for m in f.modifiers] if f.modifiers else [] + modifier_str = f" [{', '.join(modifiers)}]" if modifiers else "" + + # Identify inherited functions and their origin + inherited_str = "" + if f.contract_declarer != contract: + inherited_str = f" (from {f.contract_declarer.name})" + + # Extract just the function name without parameters + function_name = f.name + + contract_info.append(f" - {Colors.BOLD}{Colors.RED}{function_name}{Colors.END}{modifier_str}{inherited_str}") + + all_contracts.append("\n".join(contract_info)) + + # Generate final output + info = "\n".join(all_contracts) if all_contracts else "" + self.info(info) + + return self.generate_output(info) \ No newline at end of file From 91230d3228dbadaab35e73b9fa10a05f1b173768 Mon Sep 17 00:00:00 2001 From: nisedo <73041332+nisedo@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:59:34 +0100 Subject: [PATCH 4/4] Refactor entry_points.py for linters and correct README --- README.md | 2 +- slither/printers/summary/entry_points.py | 199 +++++++++++++---------- 2 files changed, 113 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 8422756a3..986f44190 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ For more information, see * `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph) * `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary) * `loc`: [Count the total number lines of code (LOC), source lines of code (SLOC), and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).](https://github.com/trailofbits/slither/wiki/Printer-documentation#loc) -* `entry-points`: [Print the entry points of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#entry-points) +* `entry-points`: [Print all the state-changing entry point functions of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#entry-points) ### In-Depth Review Printers diff --git a/slither/printers/summary/entry_points.py b/slither/printers/summary/entry_points.py index 4319c5ff0..01fae3318 100644 --- a/slither/printers/summary/entry_points.py +++ b/slither/printers/summary/entry_points.py @@ -1,130 +1,155 @@ """ -Module printing all the entry points of the contracts - -This printer identifies and displays all externally accessible functions (entry points) -of smart contracts, excluding view/pure functions and special functions like constructors. -It helps in security analysis by providing a clear overview of possible external interactions. +Module printing all the state-changing entry point functions of the contracts """ + from slither.printers.abstract_printer import AbstractPrinter from slither.core.declarations.function_contract import FunctionContract from slither.core.declarations.contract import Contract from slither.utils.colors import Colors from slither.utils.output import Output + class PrinterEntryPoints(AbstractPrinter): + ARGUMENT = "entry-points" - HELP = "Print the entry points of the contracts" + HELP = "Print all the state-changing entry point functions of the contracts" WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#entry-points" - def _get_contract_type(self, contract: Contract) -> str: + def _get_entry_points(self, contract: Contract) -> list: """ - Returns a string describing the contract type. - + Get filtered entry point functions for a contract. + + Filters out: + - Non-public/external functions + - Constructors + - Fallback functions + - Receive functions + - View/Pure functions + - Interface/Library functions + Args: contract (Contract): The contract to analyze - + Returns: - str: Contract type description ("Interface", "Library", "Abstract Contract", or "Contract") + list: List of functions that are state-changing entry points """ - if contract.is_interface: - return "Interface" - if contract.is_library: - return "Library" - if contract.is_abstract: - return "Abstract Contract" - return "Contract" + return [ + f + for f in contract.functions + if ( + f.visibility in ["public", "external"] + and isinstance(f, FunctionContract) + and not f.is_constructor + and not f.is_fallback + and not f.is_receive + and not f.view + and not f.pure + and not f.contract_declarer.is_interface + and not f.contract_declarer.is_library + ) + ] + + def _format_function_info(self, function: FunctionContract, contract: Contract) -> str: + """ + Format function information including modifiers and inheritance details. + + Args: + function (FunctionContract): The function to format + contract (Contract): The contract containing the function + + Returns: + str: Formatted string containing function name, modifiers, and inheritance info + """ + # Get list of modifier names if any exist + modifiers = [m.name for m in function.modifiers] if function.modifiers else [] + modifier_str = f" [{', '.join(modifiers)}]" if modifiers else "" + + # Add inheritance information if function is inherited + inherited_str = "" + if function.contract_declarer != contract: + inherited_str = f" (from {function.contract_declarer.name})" + return f" - {Colors.BOLD}{Colors.RED}{function.name}{Colors.END}{modifier_str}{inherited_str}" def output(self, filename: str) -> Output: """ - Generates a formatted output of all contract entry points. - - This method processes all contracts to: - 1. Filter out interfaces and contracts from utility/testing directories - 2. Sort contracts by type and name - 3. Identify and format public/external functions - 4. Include inheritance and modifier information - + Generates a formatted output of all contract entry point functions. + + The output is organized by contract and includes: + - Contract type (Abstract/Regular) + - Contract name and source file + - Inheritance information + - List of entry point functions with their modifiers and inheritance details + + Contracts are filtered to exclude: + - Interfaces and libraries + - Contracts from library directories + - Contracts from node_modules + - Mock contracts + Args: - filename (str): The filename to save the results (not used in current implementation) - + filename (str): The output filename (unused but required by interface) + Returns: - Output: Formatted data containing entry points for each contract + Output: Formatted output containing all entry point functions information """ all_contracts = [] - # Filter out interfaces and contracts from utility/testing directories - non_interface_contracts = [ - c for c in self.contracts - if not c.is_interface and - 'lib/' not in c.source_mapping.filename.absolute and - 'node_modules/' not in c.source_mapping.filename.absolute and - 'mock/' not in c.source_mapping.filename.absolute + # Filter out interfaces, libraries, and contracts from common dependency paths + filtered_contracts = [ + c + for c in self.contracts + if not c.is_interface + and not c.is_library + and "lib/" not in c.source_mapping.filename.absolute + and "node_modules/" not in c.source_mapping.filename.absolute + and not any( + mock in c.source_mapping.filename.absolute.lower() for mock in ["mock", "mocks"] + ) ] - - # Sort contracts with priority: regular contracts > abstract contracts > libraries + + # Sort contracts: non-abstract first, then by name sorted_contracts = sorted( - non_interface_contracts, + filtered_contracts, key=lambda x: ( - not x.is_library, # Libraries last - not x.is_abstract, # Abstract contracts second - x.name # Alphabetical within each category - ) + not x.is_abstract, + x.name, + ), ) for contract in sorted_contracts: - # Identify entry points: public/external functions excluding: - # - Constructors, fallback, receive functions - # - View/pure functions - # - Interface functions - entry_points = [f for f in contract.functions if - (f.visibility in ['public', 'external'] and - isinstance(f, FunctionContract) and - not f.is_constructor and - not f.is_fallback and - not f.is_receive and - not f.view and - not f.pure and - not f.contract_declarer.is_interface)] - - # Skip contract if no entry points + entry_points = self._get_entry_points(contract) if not entry_points: continue - + contract_info = [] - contract_type = self._get_contract_type(contract) + # Determine contract type and format source information + contract_type = "Abstract Contract" if contract.is_abstract else "Contract" source_file = contract.source_mapping.filename.short - - # Combine contract type, name, and source into one line - contract_info.append(f"\n{contract_type} {Colors.BOLD}{Colors.BLUE}{contract.name}{Colors.END} ({source_file})") - - # Add inheritance information if any + + # Add contract header with type, name, and source file + contract_info.append( + f"\n{contract_type} {Colors.BOLD}{Colors.BLUE}{contract.name}{Colors.END} ({source_file})" + ) + + # Add inheritance information if present if contract.inheritance: inheritance_str = ", ".join(c.name for c in contract.inheritance) contract_info.append(f"Inherits from: {inheritance_str}") - - # Sort functions prioritizing external over public visibility - entry_points.sort(key=lambda x: (x.visibility != 'external', x.visibility != 'public', x.full_name)) - + + # Sort entry point functions by visibility and name + entry_points.sort( + key=lambda x: (x.visibility != "external", x.visibility != "public", x.full_name) + ) + + # Add formatted function information for each entry point for f in entry_points: - # Collect and format modifier information - modifiers = [m.name for m in f.modifiers] if f.modifiers else [] - modifier_str = f" [{', '.join(modifiers)}]" if modifiers else "" - - # Identify inherited functions and their origin - inherited_str = "" - if f.contract_declarer != contract: - inherited_str = f" (from {f.contract_declarer.name})" - - # Extract just the function name without parameters - function_name = f.name - - contract_info.append(f" - {Colors.BOLD}{Colors.RED}{function_name}{Colors.END}{modifier_str}{inherited_str}") - + contract_info.append(self._format_function_info(f, contract)) + all_contracts.append("\n".join(contract_info)) - - # Generate final output + + # Combine all contract information or return empty string if no contracts found info = "\n".join(all_contracts) if all_contracts else "" self.info(info) - - return self.generate_output(info) \ No newline at end of file + + return self.generate_output(info)