Static analyzer for Solidity smart contracts. Detects vulnerabilities, prints contract information, and provides an intermediate representation (SlithIR) for analysis.
slither/
├── analyses/ # Data dependency, dominators, control flow
├── core/ # Core classes: SlitherCore, Contract, Function
├── detectors/ # Security checks (subclass AbstractDetector)
├── printers/ # Output formatters (subclass AbstractPrinter)
├── slithir/ # Intermediate representation for analysis
├── solc_parsing/ # Solidity AST parsing
└── tools/ # CLI tools (slither-read-storage, slither-mutate, etc.)
See CONTRIBUTING.md for detailed architecture and how to add detectors.
| tool | purpose |
|---|---|
uv |
deps & venv |
ruff |
lint & format |
prek |
pre-commit hooks |
pytest |
tests |
make dev # Setup dev environment + pre-commit hooks
make lint # Run ruff check
make reformat # Run ruff format
make test # Run all tests
pytest tests/unit/ -q # Fast unit tests onlyUse rg for text search and ast-grep for structural patterns:
# Find class definition
ast-grep --pattern 'class Contract($_): $$$' --lang py slither
# Find all detector implementations
ast-grep --pattern 'class $NAME(AbstractDetector): $$$' --lang py slither/detectors
# Find method usages
rg "\.is_interface" slither
# Find function signatures
ast-grep --pattern 'def _detect($$$)' --lang py slither/detectors
# Trace imports
rg "^from slither\.core" slither- No speculative features - Don't add "might be useful" functionality
- No premature abstraction - Don't create utilities until you've written the same code three times
- Clarity over cleverness - Prefer explicit, readable code over dense one-liners
- Justify new dependencies - Each dependency is maintenance burden and complexity
- Structured output first - New commands must support
--json. Human-readable is secondary. - Atomic operations - Don't bundle decision logic; separate analyze → suggest → apply for composability.
- Comments - Code should be self-documenting. No commented-out code (delete it). No comments that repeat what code does.
- Errors - Fail fast with clear, actionable messages. Include context: what failed, which file/contract, suggested fix. Never swallow exceptions.
- When uncertain - State your assumption and proceed for small decisions. Ask before changes with significant consequences.
- ≤80 lines/function, cyclomatic complexity ≤8
- ≤5 positional params, ≤12 branches, ≤6 returns
- 100-char line length
- No relative (
..) imports - Tests in
/tests/mirroring package structure
Bash scripts must use strict mode:
#!/bin/bash
set -euo pipefailWhen modifying a file, improve what you touch. Don't refactor unrelated code—keep PRs focused.
Modernization targets:
- Type hints on function signatures (aspiration:
ty --strict) pathlib.Pathoveros.pathstring manipulation- f-strings over
.format()or%formatting - Context managers (
with) for file/resource handling - Early returns to reduce nesting
- Fix lint issues you encounter
- Commit messages: imperative mood, ≤72 char subject (e.g., "Fix reentrancy false positive")
- One logical change per commit
- Prefix:
fix:,feat:,refactor:,test:,docs:as appropriate
- Mock boundaries, not logic - Only mock slow things (network, filesystem), non-deterministic things (time), or external services. Don't mock the code you're testing.
- Verify tests catch failures - Temporarily break code to verify the test fails, then fix it.
- Test detectors across Solidity 0.4.x–0.8.x
- Update snapshots:
tests/e2e/detectors/snapshots/ - Use
compile_force_framework="solc"when crytic-compile behavior changes - Run e2e tests early when changing core classes (
Literal,Expression, etc.)—snapshots capture exact__str__output, so behavioral changes break many tests
- Use
contract.is_interfacenotcontract.name.startswith("I") - Use
source_mapping.contentfor source code access (handles byte/char offsets) - Use
is/is notfor enum comparisons (NodeType.X, not== NodeType.X) - CLI features should have Python API equivalents in the
Slitherclass
- Per-definition analysis (naming, arithmetic, structure): Use
contracts_derived+functions_declared - Reachability analysis (call graphs, taint): Use
contracts_derived+functions(inherited functions needed) - Global deduplication: Use
compilation_unit.functionsdirectly to process each function exactly once - Filter by
function.contract_declarer == contractwhen iteratingcontract.functions high_level_callsreturnsList[Tuple[Contract, Call]]- don't forget tuple unpacking
- Expand
isinstance()checks rather than removing assertions - Add type guards before accessing type-specific attributes in AST visitors
- Check local scope before broader scope when resolving identifiers
ElementaryTypecomparison pitfall:ElementaryType.__eq__only matches otherElementaryTypeinstances.ElementaryType("uint256") in ["uint256"]is always False. Convert to string first:str(t) in ["uint256"]
- Use
node.irsfor most detectors—simpler, sufficient for most analyses - Use
node.irs_ssaonly when you need precise data flow (tracking reassignments, taint analysis) - SSA variables have
.index(e.g.,x_0,x_1) and.non_ssa_versionto get the original Phioperations merge SSA versions at control flow joins (if/else, loops)- Data dependency: Use
is_dependent(var, source, context)fromslither.analyses.data_dependency
Minimize false positives over catching edge cases. Noisy detectors get disabled. Output must be actionable.
Detectors use class attributes, not Google-style docstrings:
WIKI_TITLE,WIKI_DESCRIPTION,WIKI_EXPLOIT_SCENARIO,WIKI_RECOMMENDATION- Must be thorough—agents use these to decide which detectors apply
VULNERABLE_SOLC_VERSIONSrestricts detector to specific compiler versions- Use
make_solc_versions(minor, patch_min, patch_max)helper for version lists
Version verification: When adding dependencies or CI actions, web search for current stable versions. Training data is stale—never assume a version from memory is current.
Don't push until asked. Don't be hyperbolic in PR writeups.