// --- FILE: /audit/AUDIT_PROMPT.md --- # 100-Point Solidity Audit & Optimization Prompt Production-ready, local CI (Drone / Woodpecker) friendly audit bundle for Hardhat + Foundry + Dockerized security toolchains. ## Purpose Use this prompt as the single source-of-truth for automated LLM agents, CI pipeline steps, or manual auditors. It describes the 100-point scoring rubric, how to run the tests locally in Docker, and how to produce the final scored `summary.md` and `summary.json`. --- ## Quick start (local) Prerequisites: - Docker & Docker Compose - Drone or Woodpecker (server + runner) installed locally, or use the `drone-runner-docker` image - Node 20+, Foundry (forge), Python 3.10+ Run locally (example using docker-compose): ```bash # build and run analyzer containers (optional) docker compose up --build --detach # run foundry tests docker run --rm -v $(pwd):/src -w /src ghcr.io/foundry-rs/foundry:latest forge test --gas-report --ffi --json > reports/forge-gas.json # run hardhat tests docker run --rm -v $(pwd):/src -w /src node:20-alpine sh -c "npm ci && npx hardhat test --network hardhat --json > reports/hardhat-test.json" # run slither (example) docker run --rm -v $(pwd):/src -w /src trailofbits/eth-security-toolbox:latest slither . --json reports/slither.json # run echidna (example) docker run --rm -v $(pwd):/src -w /src trailofbits/echidna:latest echidna-test contracts/ --config echidna.yaml --json > reports/echidna.json # then merge and score python3 scripts/merge_reports.py --reports reports --out reports/merged.json python3 scripts/score_audit.py --input reports/merged.json --out summary.md --json summary.json ``` --- ## 100-Point Checklist (short) (Full checklist is intentionally compacted here — the scoring script uses the same criteria.) A. Architecture & Design (10) B. Security Vulnerability Analysis (25) C. Gas & Performance Optimization (20) D. Testing & Coverage (15) E. Tool-Based Analysis (20) F. Documentation & Clarity (5) G. CI/CD & Automation (5) H. Foundry + Hardhat Parity Validation (5) I. Code Quality & Readability (5) J. Advanced Protocol-Specific Checks (10) K. Deployment & Production Readiness (10) (See repo `README` or `scripts/score_audit.py` for the detailed mapping of checks → points.) --- ## CI Integration (Drone / Woodpecker local) This repo provides `ci/.drone.yml` and `ci/.woodpecker.yml` for local CI runners. Both files execute the same pipeline: compile, tests (foundry/hardhat), slither, echidna, collect reports, merge, score, and upload artifacts. --- ## Output - `summary.md` — human readable scored audit with fixes and references - `summary.json` — structured audit with per-check boolean/status and weight - `reports/*` — raw tool outputs --- ## Notes & Best Practices - Pin Docker images in your private registry for reproducibility - Use `--json` outputs where supported so `merge_reports.py` can parse them - Consider running `forge snapshot` and `npx hardhat node --fork` for mainnet fork tests // --- FILE: /ci/.drone.yml --- kind: pipeline type: docker name: solidity-audit steps: - name: prepare image: alpine:3.18 commands: - apk add --no-cache python3 py3-pip git jq - mkdir -p reports - pip install --no-cache-dir -r requirements.txt || true - name: foundry-test image: ghcr.io/foundry-rs/foundry:latest commands: - forge test --gas-report --ffi --json > reports/forge-gas.json || true - cp out/test-results.json reports/foundry-tests.json || true - name: hardhat-test image: node:20-alpine commands: - apk add --no-cache git python3 py3-pip - npm ci - npx hardhat test --network hardhat --show-stack-traces --json > reports/hardhat-test.json || true - npx hardhat coverage --reporter json > reports/hardhat-coverage.json || true - name: slither image: trailofbits/eth-security-toolbox:latest commands: - slither . --json reports/slither.json || true - name: echidna image: trailofbits/echidna:latest commands: - echidna-test contracts/ --config echidna.yaml --json > reports/echidna.json || true - name: merge-and-score image: python:3.12 commands: - python3 scripts/merge_reports.py --reports reports --out reports/merged.json - python3 scripts/score_audit.py --input reports/merged.json --out summary.md --json summary.json - name: artifact image: alpine:3.18 commands: - tar -czf audit-artifacts.tgz summary.md summary.json reports || true trigger: event: - push - pull_request // --- FILE: /ci/.woodpecker.yml --- pipeline: prepare: image: alpine:3.18 commands: - apk add --no-cache python3 py3-pip git jq - mkdir -p reports - pip install --no-cache-dir -r requirements.txt || true foundry-test: image: ghcr.io/foundry-rs/foundry:latest commands: - forge test --gas-report --ffi --json > reports/forge-gas.json || true hardhat-test: image: node:20-alpine commands: - apk add --no-cache git python3 py3-pip - npm ci - npx hardhat test --network hardhat --show-stack-traces --json > reports/hardhat-test.json || true - npx hardhat coverage --reporter json > reports/hardhat-coverage.json || true slither: image: trailofbits/eth-security-toolbox:latest commands: - slither . --json reports/slither.json || true echidna: image: trailofbits/echidna:latest commands: - echidna-test contracts/ --config echidna.yaml --json > reports/echidna.json || true merge-and-score: image: python:3.12 commands: - python3 scripts/merge_reports.py --reports reports --out reports/merged.json - python3 scripts/score_audit.py --input reports/merged.json --out summary.md --json summary.json artifact: image: alpine:3.18 commands: - tar -czf audit-artifacts.tgz summary.md summary.json reports || true // --- FILE: /scripts/merge_reports.py --- #!/usr/bin/env python3 """ merge_reports.py Collect common JSON outputs from various auditing tools into a single unified JSON file. """ import argparse import json import os from pathlib import Path DEFAULT_KEYS = [ "slither.json", "echidna.json", "forge-gas.json", "hardhat-test.json", "hardhat-coverage.json", "foundry-tests.json", ] def load_json_if_exists(p: Path): if p.exists(): try: return json.loads(p.read_text()) except Exception: # try to load line-delimited JSON try: lines = [l for l in p.read_text().splitlines() if l.strip()] if len(lines) == 1: return json.loads(lines[0]) except Exception: return {"raw": p.read_text()} return None def main(): parser = argparse.ArgumentParser() parser.add_argument("--reports", required=True, help="reports dir") parser.add_argument("--out", required=True, help="output file") args = parser.parse_args() rdir = Path(args.reports) aggregated = {"tools": {}, "meta": {"cwd": str(Path.cwd())}} for key in DEFAULT_KEYS: p = rdir / key data = load_json_if_exists(p) aggregated["tools"][key] = data # add any other json files in the reports directory for p in rdir.glob('*.json'): if p.name in DEFAULT_KEYS: continue data = load_json_if_exists(p) aggregated["tools"][p.name] = data Path(args.out).write_text(json.dumps(aggregated, indent=2)) print(f"Merged reports written to {args.out}") if __name__ == '__main__': main() // --- FILE: /scripts/score_audit.py --- #!/usr/bin/env python3 """ score_audit.py Basic scoring engine that reads merged reports JSON and maps findings to the 100-point checklist. This is intentionally conservative — a human review is recommended to confirm final scores. """ import argparse import json from pathlib import Path # scoring mapping: check_id -> (points, human_description) CHECKS = { "A1": (1, "Contract separation and minimal responsibility"), "A2": (1, "Interfaces are abstracted"), "A3": (1, "Inheritance and virtual/override usage"), "A4": (1, "Upgradeability patterns validated"), "A5": (1, "Diamond/facets isolation"), "A6": (1, "Access control consistency"), "A7": (1, "Event coverage for state changes"), "A8": (1, "No circular dependencies"), "A9": (1, "Fallback/receive functions secured"), "A10": (1, "Storage layout & gaps for upgrades"), # Security (25 pts) — condensed checks, group scanning "B1": (5, "Reentrancy and checks-effects-interactions"), "B2": (4, "Delegatecall & low-level call scrutiny"), "B3": (4, "Oracle & time-manipulation mitigations"), "B4": (4, "Signature/EIP-712 & replay protections"), "B5": (4, "Flash loan & flash swap resilience"), "B6": (4, "Denial-of-service / access paths"), # Gas & Perf (20) "C1": (4, "Struct packing & storage optimizations"), "C2": (4, "Min SLOAD/SSTORE & calldata usage"), "C3": (4, "Immutable/constant usage"), "C4": (4, "Unchecked blocks & safe micro-optimizations"), "C5": (4, "Solidity optimizer settings validated"), # Testing & Coverage (15) "D1": (5, "Foundry tests & gas report"), "D2": (5, "Hardhat tests & coverage"), "D3": (5, "Fuzzing/property tests (echidna)"), # Tool-based (20) "E1": (5, "Slither scan"), "E2": (5, "Mythril / symbolic execution"), "E3": (5, "Echidna fuzzing present and passing"), "E4": (5, "Crytic / aggregated reporting present"), # Docs & CI small buckets "F1": (2, "Natspec & function docs"), "F2": (1, "README & deployment notes"), "F3": (2, "State variable documentation"), "G1": (2, "CI pipeline exists"), "G2": (1, "Artifacts produced"), "G3": (2, "Pinned analyzer docker images"), "H1": (2, "Foundry/Hardhat parity checks"), "H2": (3, "ABI/metadata parity"), "I1": (2, "Linting & solhint/prettier"), "I2": (3, "Import paths & naming conventions"), "J1": (2, "DEX math & invariants"), "J2": (4, "Flash swap & repay logic"), "J3": (4, "Oracle & TWAP validations"), "K1": (3, "Deployment scripts dry-run"), "K2": (3, "Mainnet fork tests"), "K3": (4, "Upgrade/rollback procedure"), } def score_from_merged(merged: dict) -> dict: """Produce a best-effort score mapping. The function inspects merged tool outputs and marks checks as pass/fail/unknown.""" tools = merged.get("tools", {}) results = {} # Helper flags has_slither = bool(tools.get("slither.json")) has_echidna = bool(tools.get("echidna.json")) has_forge = bool(tools.get("forge-gas.json") or tools.get("foundry-tests.json")) has_hh = bool(tools.get("hardhat-test.json") or tools.get("hardhat-coverage.json")) # Simple heuristics — these can be extended for more sophisticated parsing results["A1"] = {"score": CHECKS["A1"][0], "notes": "Manual review recommended"} results["A2"] = {"score": CHECKS["A2"][0], "notes": "Check for I* interfaces in contracts/"} results["A3"] = {"score": CHECKS["A3"][0], "notes": "Verify virtual/override where inheritance exists"} results["A4"] = {"score": CHECKS["A4"][0], "notes": "If proxies found, confirm EIP-1967/EIP-2535"} results["A5"] = {"score": CHECKS["A5"][0], "notes": "Diamond pattern needs human verification"} results["A6"] = {"score": CHECKS["A6"][0], "notes": "Ensure AccessControl usage"} results["A7"] = {"score": CHECKS["A7"][0], "notes": "Events present for mutative functions"} results["A8"] = {"score": CHECKS["A8"][0], "notes": "Static analysis required"} results["A9"] = {"score": CHECKS["A9"][0], "notes": "Check fallback/receive implementation"} results["A10"] = {"score": CHECKS["A10"][0], "notes": "Storage gap pattern detected?"} # Security results["B1"] = {"score": CHECKS["B1"][0], "notes": "Slither may show reentrancy issues" if has_slither else "Run slither to confirm"} results["B2"] = {"score": CHECKS["B2"][0], "notes": "Look for delegatecall usage"} results["B3"] = {"score": CHECKS["B3"][0], "notes": "Oracle access patterns require review"} results["B4"] = {"score": CHECKS["B4"][0], "notes": "Check EIP-712 and signature handling"} results["B5"] = {"score": CHECKS["B5"][0], "notes": "Flash loan logic present? run fuzzers"} results["B6"] = {"score": CHECKS["B6"][0], "notes": "DOS vectors require manual review"} # Gas results["C1"] = {"score": CHECKS["C1"][0], "notes": "Static and gas reports help here"} results["C2"] = {"score": CHECKS["C2"][0], "notes": "Check for excessive storage ops"} results["C3"] = {"score": CHECKS["C3"][0], "notes": "Immutable/constant detection"} results["C4"] = {"score": CHECKS["C4"][0], "notes": "Use unchecked where safe"} results["C5"] = {"score": CHECKS["C5"][0], "notes": "Compare optimizer settings between frameworks"} # Testing results["D1"] = {"score": CHECKS["D1"][0] if has_forge else 0, "notes": "Foundry tests present" if has_forge else "Foundry tests not found"} results["D2"] = {"score": CHECKS["D2"][0] if has_hh else 0, "notes": "Hardhat tests present" if has_hh else "Hardhat tests not found"} results["D3"] = {"score": CHECKS["D3"][0] if has_echidna else 0, "notes": "Echidna fuzzing present" if has_echidna else "Echidna not found"} # Tool-based results["E1"] = {"score": CHECKS["E1"][0] if has_slither else 0, "notes": "Slither run" if has_slither else "Slither not found"} # Mythril detection is best-effort results["E2"] = {"score": CHECKS["E2"][0], "notes": "Run Mythril manually (not auto-detected)"} results["E3"] = {"score": CHECKS["E3"][0] if has_echidna else 0, "notes": "Echidna report present" if has_echidna else "Echidna missing"} results["E4"] = {"score": CHECKS["E4"][0], "notes": "Crytic recommended for aggregated CI"} # Docs & CI results["F1"] = {"score": CHECKS["F1"][0], "notes": "Natspec presence check recommended"} results["F2"] = {"score": CHECKS["F2"][0], "notes": "README presence"} results["F3"] = {"score": CHECKS["F3"][0], "notes": "State vars documented?"} results["G1"] = {"score": CHECKS["G1"][0], "notes": "CI pipeline file present"} results["G2"] = {"score": CHECKS["G2"][0], "notes": "Artifacts generation"} results["G3"] = {"score": CHECKS["G3"][0], "notes": "Pin analyzer docker images"} results["H1"] = {"score": CHECKS["H1"][0], "notes": "Parity checks should be executed"} results["H2"] = {"score": CHECKS["H2"][0], "notes": "Metadata ABI differences"} results["I1"] = {"score": CHECKS["I1"][0], "notes": "Run solhint/prettier"} results["I2"] = {"score": CHECKS["I2"][0], "notes": "Naming & imports"} results["J1"] = {"score": CHECKS["J1"][0], "notes": "DEX math tests recommended"} results["J2"] = {"score": CHECKS["J2"][0], "notes": "Flash swap repay checks"} results["J3"] = {"score": CHECKS["J3"][0], "notes": "TWAP/oracle checks"} results["K1"] = {"score": CHECKS["K1"][0], "notes": "Dry run scripts present"} results["K2"] = {"score": CHECKS["K2"][0], "notes": "Mainnet fork tests"} results["K3"] = {"score": CHECKS["K3"][0], "notes": "Upgrade/rollback steps documented"} # compute totals total_possible = sum(p for p, _ in CHECKS.values()) total_scored = sum(v["score"] for v in results.values()) return { "checks": results, "summary": { "possible": total_possible, "score": total_scored, "percentage": round(100.0 * total_scored / total_possible, 2) } } def pretty_markdown(scored: dict) -> str: lines = [] lines.append(f"# Audit Summary — {scored['summary']['score']}/{scored['summary']['possible']} ({scored['summary']['percentage']}%)\n") lines.append("## Individual Checks\n") for k, v in scored["checks"].items(): points, desc = CHECKS.get(k, (0, "Unknown check")) lines.append(f"- **{k}** — {desc} — **{v['score']} / {points}** — {v.get('notes','')}") return "\n".join(lines) def main(): parser = argparse.ArgumentParser() parser.add_argument("--input", required=True, help="merged json input") parser.add_argument("--out", required=True, help="markdown output path") parser.add_argument("--json", help="json output path", default=None) args = parser.parse_args() merged = json.loads(Path(args.input).read_text()) scored = score_from_merged(merged) md = pretty_markdown(scored) Path(args.out).write_text(md) print(f"Written summary markdown to {args.out}") if args.json: Path(args.json).write_text(json.dumps(scored, indent=2)) print(f"Written summary json to {args.json}") if __name__ == '__main__': main() // --- FILE: /requirements.txt --- jsonschema // --- FILE: /README.md --- # Solidity Audit Bundle (Drone + Woodpecker local) This bundle contains a production-ready audit prompt, CI configs, and helper scripts to run a 100-point audit locally using Dockerized analyzers and CI runners (Drone or Woodpecker). Files: - `/audit/AUDIT_PROMPT.md` - user-facing prompt & quick-start - `/ci/.drone.yml` - Drone pipeline for auditing - `/ci/.woodpecker.yml` - Woodpecker pipeline for auditing - `/scripts/merge_reports.py` - collect and merge JSON reports - `/scripts/score_audit.py` - scoring engine to map checks into a 100-pt score - `/requirements.txt` - python deps // --- END OF DOCUMENT ---