Skip to content

Commit

Permalink
Refactor entry_points.py for linters and correct README
Browse files Browse the repository at this point in the history
  • Loading branch information
nisedo committed Dec 13, 2024
1 parent 3aef721 commit 91230d3
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 88 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
199 changes: 112 additions & 87 deletions slither/printers/summary/entry_points.py
Original file line number Diff line number Diff line change
@@ -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)

return self.generate_output(info)

0 comments on commit 91230d3

Please sign in to comment.