Automated security pre-flight for Solidity contracts. Catches common vulnerabilities, validates OpenZeppelin usage, and surfaces gas inefficiencies — before you pay $20k+ for a manual audit.
Install required CLI tools:
bash
# Slither — most complete static analyzer
pip install slither-analyzer
# Mythril — symbolic execution / SMT-based analysis
pip install mythril
# Foundry (forge, cast, anvil) — for testing and gas snapshots
curl -L https://foundry.paradigm.xyz | bash && foundryup
# Solhint — linter for style and security rules
npm install -g solhint
# Aderyn — Rust-based Solidity AST analyzer (fast)
cargo install aderyn
Verify:
bash
slither --version
myth version
forge --version
solhint --version
aderyn --version
bash
# Count contracts, identify external deps
find . -name "*.sol" | head -50
cat foundry.toml # or hardhat.config.ts
cat remappings.txt
# Identify entry points (deployed contracts vs libraries/interfaces)
grep -r "contract " src/ --include="*.sol" | grep -v "interface\|abstract\|library"
Key questions:
lib/openzeppelin-contracts/package.json)bash
# Full scan with all detectors
slither . --checklist --markdown-root .
# JSON output for structured review
slither . --json slither-output.json
# Print human-readable summary
slither . --print human-summary
# Filter by severity
slither . --filter-paths "lib/,node_modules/" --exclude-low
Severity levels:
LevelActionHighBlock deployment — fix before auditMediumMust address — provide justification if not fixedLowFix or document rationaleInformationalStyle/gas — address in optimization pass
Common high-severity detectors to watch:
reentrancy-eth — reentrant Ether transfersunchecked-transfer — ERC20 transfer without return value checkarbitrary-send-eth — unrestricted ETH sendcontrolled-delegatecall — attacker-controlled delegatecallsuicidal — selfdestruct reachable by anyonetx-origin — tx.origin used for authbash
# Analyze single contract (slower, deeper)
myth analyze contracts/MyContract.sol --solc-json mythril-config.json
# With timeout for large contracts
myth analyze contracts/MyContract.sol --execution-timeout 120 --max-depth 12
# Output as JSON
myth analyze contracts/MyContract.sol -o json
Mythril catches:
bash
# Run from project root
aderyn .
# Output markdown report
aderyn . --output report.md
# Target specific directory
aderyn src/ --output aderyn-report.md
Aderyn is fast (sub-second on most codebases) and catches:
bash
# Init config
solhint --init
# Lint all contracts
solhint 'contracts/**/*.sol'
# Use security ruleset
solhint --config .solhint.json 'contracts/**/*.sol'
Recommended .solhint.json:
json
{
"extends": "solhint:recommended",
"rules": {
"avoid-call-value": "error",
"avoid-low-level-calls": "warn",
"avoid-tx-origin": "error",
"check-send-result": "error",
"compiler-version": ["error", "^0.8.20"],
"func-visibility": ["warn", {"ignoreConstructors": true}],
"mark-callable-contracts": "error",
"multiple-sends": "error",
"no-complex-fallback": "error",
"no-inline-assembly": "warn",
"not-rely-on-block-hash": "error",
"not-rely-on-time": "warn",
"reentrancy": "error",
"state-visibility": "error"
}
}
Manual checklist — verify each applies:
bash
# Check OZ version
cat lib/openzeppelin-contracts/package.json | grep '"version"'
# Find all inherited OZ contracts
grep -r "import.*openzeppelin" contracts/ --include="*.sol"
Access Control:
Ownable2Step used instead of Ownable (prevents accidental ownership transfer)AccessControl roles defined with keccak256 constants, not inline stringsDEFAULT_ADMIN_ROLE not held by an EOA in production (use multisig)renounceRole and revokeRole tested for edge casesTokens (ERC20/ERC721):
SafeERC20.safeTransfer used for all external token transfers_safeMint used in ERC721 (not _mint)onERC721Received) handledUpgradeability:
_disableInitializers() called in implementation constructorPausability:
Pausable integrated with whenNotPaused on critical functionspause() protected by role (not public)bash
# Forge gas snapshot baseline
forge snapshot
# Gas report per function
forge test --gas-report
# Compare before/after optimization
forge snapshot --diff .gas-snapshot
Common gas optimizations to flag:
PatternIssueFixuint256 i = 0 in loopWastes gasuint256 i (zero default)storage variable read in loopSLOAD per iterationCache in memory firstrequire(condition, "long string")String costs gasUse custom errorsMultiple mappings vs structExtra SLOADsPack into structpublic on internal functionsExtra ABI overheadinternal or privateUnbounded loopsDoS risk + gasAdd maxIterations paramtransfer() / send()2300 gas limitUse call{value: x}("")
Custom error pattern (saves ~50 gas per revert):
solidity
// Bad
require(balance >= amount, "Insufficient balance");
// Good
error InsufficientBalance(uint256 balance, uint256 required);
if (balance < amount) revert InsufficientBalance(balance, amount);
Cross-check findings against SWC Registry:
SWCNameSlither DetectorSeveritySWC-100Function Default Visibilitysuicidal, controlled-selfdestructHighSWC-101Integer Overflow/Underflowinteger-overflowHigh (pre-0.8.x)SWC-104Unchecked Call Return Valueunchecked-transferHighSWC-105Unprotected Ether Withdrawalarbitrary-send-ethHighSWC-106Unprotected SELFDESTRUCTsuicidalHighSWC-107Reentrancyreentrancy-eth, reentrancy-no-ethHigh/MedSWC-111Use of Deprecated Functionsdeprecated-standardsMedSWC-112Delegate Call to Untrusted Calleecontrolled-delegatecallHighSWC-115Authorization via tx.origintx-originHighSWC-116Block values as time proxyweak-prng, block-timestampLow/MedSWC-120Weak Sources of Randomnessweak-prngHighSWC-128DoS With Block Gas Limitcostly-loopMed
Items automated tools miss — must be done by human:
Business Logic:
Access Control Logic:
Oracle / Price Feed:
updatedAt + heartbeat)Proxy Patterns:
selfdestruct in implementation would brick proxy — is it present?Generate a structured handoff document for the audit team:
markdown
# Pre-Audit Report: [Contract Name]
Date: YYYY-MM-DD
Audited by: [Team]
Commit: [git hash]
## Scope
- Contracts: [list]
- Out of scope: [list]
- Solidity version: [version]
- OZ version: [version]
## Automated Scan Results
### Slither
- High: [N] findings
- Medium: [N] findings
- [Link to slither-output.json]
### Mythril
- [N] issues found
- [Summary]
### Aderyn
- [N] issues found
## Known Issues (Won't Fix)
- [Issue]: [Rationale]
## Fixed Issues (Pre-Audit)
- [Issue]: [Fix applied, commit hash]
## Gas Baseline
- forge snapshot attached
- Estimated deployment cost: [N] ETH at [gwei] gwei
## Areas of Concern for Auditors
- [Specific complex logic to focus on]
- [External protocol integrations]
- [Novel patterns used]
## Test Coverage
forge coverage output:
[paste output]
bash
#!/bin/bash
# precheck.sh — run from project root
echo "=== Solidity Audit Pre-Check ==="
echo "Date: $(date)"
echo "Commit: $(git rev-parse --short HEAD)"
mkdir -p audit-precheck
echo "\n[1/5] Slither..."
slither . --filter-paths "lib/,node_modules/" \
--json audit-precheck/slither.json \
--markdown-root . 2>&1 | tee audit-precheck/slither.log
echo "\n[2/5] Aderyn..."
aderyn . --output audit-precheck/aderyn.md
echo "\n[3/5] Solhint..."
solhint 'contracts/**/*.sol' > audit-precheck/solhint.log 2>&1
echo "\n[4/5] Forge gas snapshot..."
forge snapshot --snap audit-precheck/.gas-snapshot
echo "\n[5/5] Forge coverage..."
forge coverage --report lcov > audit-precheck/coverage.log 2>&1
echo "\nPre-check complete. Results in ./audit-precheck/"
echo "Run: cat audit-precheck/slither.log | grep 'Impact\|Result'"
SeverityCountDecisionCritical/HighAnyBlock — do not deployMedium> 3Block — audit requiredMedium1-3Mitigate or justify in writingLowAnyDocument and accept or fixInformationalAnyFix in optimization pass
A clean pre-check (zero High/Critical, documented Mediums) typically reduces manual audit time by 30-50% and audit cost by 20-40%.