test
Automated pre-audit checklist for Solidity smart contracts. Runs SWC registry scan, OpenZeppelin pattern validation, gas optimization suggestions, and common...
Like a lobster shell, security has layers — review code before you run it.
License
SKILL.md
Solidity Audit Pre-Check Skill
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.
When to Use
- Before submitting contracts to a paid auditor (Trail of Bits, Certik, Spearbit, etc.)
- After writing a new contract feature or major refactor
- During internal review of a PR that touches contract code
- When onboarding a new contract codebase to understand risk surface
- Pre-deployment to mainnet or any public testnet
When NOT to Use
- As a replacement for a manual audit. This is a pre-filter, not a security guarantee.
- For non-Solidity contracts (Rust/Anchor, Cairo, Stylus — use language-specific tools)
- For already-deployed and immutable contracts (nothing actionable)
- For frontend/UI code or off-chain scripts
- As a compliance certification — automated tools have known false negative rates
Toolchain Setup
Install required CLI tools:
# 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:
slither --version
myth version
forge --version
solhint --version
aderyn --version
Pre-Check Workflow
Step 1 — Inventory the Codebase
# 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:
- How many contracts? How many are deployed vs inherited?
- What OpenZeppelin version? (
lib/openzeppelin-contracts/package.json) - External protocols used? (Uniswap, Aave, Chainlink, etc.)
- Upgradeability pattern? (UUPS, Transparent Proxy, Beacon, none)
Step 2 — Slither Static Analysis
# 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:
| Level | Action |
|---|---|
| High | Block deployment — fix before audit |
| Medium | Must address — provide justification if not fixed |
| Low | Fix or document rationale |
| Informational | Style/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 auth
Step 3 — Mythril Symbolic Execution
# 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:
- Integer overflow/underflow (pre-0.8.x contracts)
- Reentrancy via symbolic paths
- Delegatecall to user-supplied addresses
- Unprotected selfdestruct
- Dependence on predictable values (block.timestamp, blockhash)
Step 4 — Aderyn AST Analysis
# 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:
- Centralization risk (single owner controls)
- Missing events on state changes
- Unsafe ERC20 operations
- Weak access control patterns
- Incorrect inheritance order
Step 5 — Solhint Linting
# Init config
solhint --init
# Lint all contracts
solhint 'contracts/**/*.sol'
# Use security ruleset
solhint --config .solhint.json 'contracts/**/*.sol'
Recommended .solhint.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"
}
}
Step 6 — OpenZeppelin Pattern Validation
Manual checklist — verify each applies:
# 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:
-
Ownable2Stepused instead ofOwnable(prevents accidental ownership transfer) -
AccessControlroles defined withkeccak256constants, not inline strings -
DEFAULT_ADMIN_ROLEnot held by an EOA in production (use multisig) -
renounceRoleandrevokeRoletested for edge cases
Tokens (ERC20/ERC721):
-
SafeERC20.safeTransferused for all external token transfers -
_safeMintused in ERC721 (not_mint) - Callback hooks (
onERC721Received) handled - Reentrancy guard on any function that calls external contracts after state change
Upgradeability:
- Initializers called correctly (no constructor logic in upgradeable contracts)
-
_disableInitializers()called in implementation constructor - Storage gaps defined in all base contracts
- ERC-7201 namespaced storage used (OZ v5+)
- No storage collisions between proxy and implementation
Pausability:
-
Pausableintegrated withwhenNotPausedon critical functions -
pause()protected by role (not public) - Emergency unpause path defined and tested
Step 7 — Gas Optimization Audit
# 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:
| Pattern | Issue | Fix |
|---|---|---|
uint256 i = 0 in loop | Wastes gas | uint256 i (zero default) |
storage variable read in loop | SLOAD per iteration | Cache in memory first |
require(condition, "long string") | String costs gas | Use custom errors |
| Multiple mappings vs struct | Extra SLOADs | Pack into struct |
public on internal functions | Extra ABI overhead | internal or private |
| Unbounded loops | DoS risk + gas | Add maxIterations param |
transfer() / send() | 2300 gas limit | Use call{value: x}("") |
Custom error pattern (saves ~50 gas per revert):
// Bad
require(balance >= amount, "Insufficient balance");
// Good
error InsufficientBalance(uint256 balance, uint256 required);
if (balance < amount) revert InsufficientBalance(balance, amount);
Step 8 — SWC Registry Cross-Reference
Cross-check findings against SWC Registry:
| SWC | Name | Slither Detector | Severity |
|---|---|---|---|
| SWC-100 | Function Default Visibility | suicidal, controlled-selfdestruct | High |
| SWC-101 | Integer Overflow/Underflow | integer-overflow | High (pre-0.8.x) |
| SWC-104 | Unchecked Call Return Value | unchecked-transfer | High |
| SWC-105 | Unprotected Ether Withdrawal | arbitrary-send-eth | High |
| SWC-106 | Unprotected SELFDESTRUCT | suicidal | High |
| SWC-107 | Reentrancy | reentrancy-eth, reentrancy-no-eth | High/Med |
| SWC-111 | Use of Deprecated Functions | deprecated-standards | Med |
| SWC-112 | Delegate Call to Untrusted Callee | controlled-delegatecall | High |
| SWC-115 | Authorization via tx.origin | tx-origin | High |
| SWC-116 | Block values as time proxy | weak-prng, block-timestamp | Low/Med |
| SWC-120 | Weak Sources of Randomness | weak-prng | High |
| SWC-128 | DoS With Block Gas Limit | costly-loop | Med |
Step 9 — Manual Review Checklist
Items automated tools miss — must be done by human:
Business Logic:
- Economic invariants hold (balances sum to total supply, etc.)
- State machine transitions are complete and non-overlapping
- Off-by-one errors in fee/slippage calculations
- Rounding direction favors protocol, not attacker (round down for user, up for protocol)
- Flash loan attack surface: any function callable within a single tx that can drain funds?
Access Control Logic:
- Every privileged function has a role check
- Role assignments tested for separation of duties
- Time locks on critical parameter changes (fee changes, oracle updates)
Oracle / Price Feed:
- Chainlink feeds: stale data check (
updatedAt+ heartbeat) - Chainlink feeds: min/max circuit breakers handled
- TWAP used instead of spot price for large positions
- No single-source price oracle
Proxy Patterns:
- Proxy admin is a multisig, not an EOA
- Implementation cannot be initialized by attacker
-
selfdestructin implementation would brick proxy — is it present?
Step 10 — Pre-Audit Report Template
Generate a structured handoff document for the audit team:
# 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]
Example: Full Pre-Check Run
#!/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'"
Integration with Other Skills
- develop-secure-contracts — Use to integrate OpenZeppelin patterns that this skill validates
- upgrade-solidity-contracts — Pre-check upgradeable proxies before upgrade
- ethskills — General Ethereum dev context and deployment patterns
- erc20-tokenomics-builder — Pre-check token contracts built with this skill
Severity Thresholds for Deployment Go/No-Go
| Severity | Count | Decision |
|---|---|---|
| Critical/High | Any | Block — do not deploy |
| Medium | > 3 | Block — audit required |
| Medium | 1-3 | Mitigate or justify in writing |
| Low | Any | Document and accept or fix |
| Informational | Any | Fix 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%.
Files
1 totalComments
Loading comments…
