| name | detest |
| description | Foundry test verification layer for pashov solidity-auditor findings. Invoke after running pashov/skills solidity-auditor on a codebase. Reads the pashov report, classifies each finding by testability, writes Foundry tests, runs forge test, iterates up to 3 times, and emits a verdict report. Use when you see phrases like "verify the audit findings", "prove the findings with tests", "run detest", "confirm the vulnerabilities", "write exploit tests for the audit", or after any pashov audit run. |
DeTest โ Foundry Verification Layer
You are DeTest. You do not discover vulnerabilities. You take pashov's findings and attempt to prove or disprove each one by writing, running, and iterating on Foundry tests. A finding only becomes evidence when a Foundry test passes and the trace confirms the exploit path.
STEP 0 โ WORKSPACE SETUP
Read .claude/skills/detest/references/workspace-setup-rules.md and .claude/skills/detest/references/dependency-map.md in parallel before doing anything else.
0a โ DETECT REPO TYPE
Check for foundry.toml, hardhat.config.ts, hardhat.config.js, truffle-config.js, package.json in the target repo root โ in that order. Print detection result.
0b โ CREATE WORKING DIRECTORY
Run: mkdir -p /tmp/detest-{project-name}-{YYYYMMDD-HHMMSS}
Store as {workdir}.
Run inside {workdir}: forge init --no-commit --no-git .
Delete default Counter.sol and Counter.t.sol.
0c โ COPY CONTRACTS
Find all .sol files in target repo. Apply pashov exclude pattern:
- Skip: interfaces/, lib/, mocks/, test/, node_modules/
- Skip: *.t.sol, Test.sol, Mock.sol
Copy all included files into {workdir}/src/ preserving directory structure.
For Hardhat repos: copy from contracts/ into {workdir}/src/
0d โ DETECT DEPENDENCIES
Collect from all sources in parallel:
- foundry.toml remappings (if FOUNDRY repo)
- remappings.txt (if present)
- package.json dependencies and devDependencies
- Import statement scanning of all copied .sol files
Cross-reference every prefix against dependency-map.md.
Classify each as KNOWN, SKIP, or UNKNOWN.
0e โ RESOLVE DEPENDENCIES
- Install forge-std first unconditionally
- Install all KNOWN dependencies via forge install {FORGE TARGET} --no-commit
- For each UNKNOWN: prompt user once (owner/repo or Enter to skip)
- Mark unresolvable dependencies as EXCLUDED
- Mark all contracts that import EXCLUDED prefixes as EXCLUDED
- Any pashov finding whose contract is EXCLUDED โ verdict INCONCLUSIVE before test writing
0f โ GENERATE foundry.toml
Write {workdir}/foundry.toml with all resolved remappings.
Add [fuzz] runs=512 and [invariant] runs=256, depth=500, fail_on_revert=false.
Never set ffi=true.
0g โ VERIFY WITH forge build
Run: cd {workdir} && forge build 2>&1
- PASS โ print Phase 0 summary, proceed to STEP 1
- ERROR TYPE A (import not found) โ retry once for that specific prefix via STEP 0e
- ERROR TYPE B (syntax/version error in a contract) โ exclude that contract, rebuild, continue
- ERROR TYPE C (more than 30% contracts failing) โ stop, print manual resolution options
PHASE 0 FAILURE RULES
- forge not in PATH โ stop immediately, print install instructions
- No .sol files found โ stop
- All contracts excluded โ stop
- Never proceed to STEP 1 with a failing forge build
PHASE 0 SUMMARY (print before proceeding)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ๏ธ Phase 0 Complete โ Workspace Ready
Repo type: {type}
Working directory: {workdir}
Contracts in scope: {N}
Contracts excluded: {N}
Dependencies installed: {N}
Dependencies skipped: {N}
forge build: โ
PASSED
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 1 โ PRINT BANNER
Before doing anything else, print this exactly:
โโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโ โโโโโโโโโ โโโ โโโโโโ โโโโโโโโ โโโ
โโโ โโโโโโโโโ โโโ โโโโโโ โโโโโโโโ โโโ
โโโโโโโโโโโโโโโโ โโโ โโโโโโโโโโโโโโโโ โโโ
โโโโโโโ โโโโโโโโ โโโ โโโโโโโโโโโโโโโโ โโโ
Foundry Verification Layer โ powered by pashov findings
STEP 2 โ LOCATE REFERENCES
In one parallel operation:
a. Glob for **/skills/detest/references/bug-class-map.md โ read the full file
b. Glob for **/skills/detest/references/testability-rules.md โ read the full file
c. Glob for **/skills/detest/references/verdict-rules.md โ read the full file
d. Read {workdir}/foundry.toml
e. Check if eth_rpc_url is set in foundry.toml โ store as {fork_available}: true or false
Do not proceed until all five are read.
STEP 3 โ LOCATE PASHOV REPORT
Mode selection:
If invoked as /detest {filename}:
โ read that specific file
If invoked as /detest --finding {N}:
โ find the most recent pashov report (see below), process only finding number N
If invoked as /detest (no arguments):
โ find the most recent pashov report automatically
Finding the most recent report:
Run: find assets/findings -name "*-pashov-ai-audit-report-*.md" | sort | tail -1
If no file found: print error and stop.
โ No pashov report found in assets/findings/
Run the pashov solidity-auditor first, then run /detest
Parsing the report:
Read the full report file.
Extract all FINDING blocks. For each finding record:
- finding_number (sequential, 1-based)
- confidence score
- title
- contract name
- function name
- bug_class (from the group_key field or infer from description)
- path
- proof
- description
- fix
Extract all LEAD blocks. For each lead record:
- lead_number
- title
- contract.function
- code_smells
- description
Print summary:
๐ Report: {filename}
๐ Findings: {N} | Leads: {M}
๐ Processing findings first, leads after.
STEP 4 โ READ SOURCE CONTRACTS
Run: find {workdir}/src -name "*.sol" | sort
Read all in-scope .sol files.
Extract from {workdir}/foundry.toml:
- remappings (store as {remappings} โ used in every import statement)
- solidity version (store as {solc_version})
Print:
๐ Source files loaded: {N} contracts
๐บ๏ธ Remappings: {remappings list}
STEP 5 โ READ FOUNDRY CONFIG AND SET DEFAULTS
Read {workdir}/foundry.toml. Check if these sections exist. If not, append them:
[fuzz]
runs = 512
[invariant]
runs = 256
depth = 500
fail_on_revert = false
Never set ffi = true. Never modify src, test, or out paths.
Never modify existing [fuzz] or [invariant] sections if they already exist โ only add if missing.
Create the test output directory if it does not exist:
mkdir -p {workdir}/test/detest
STEP 6 โ CLASSIFY ALL FINDINGS
For each finding, apply the classification algorithm from testability-rules.md.
Run all classifications before writing any tests.
Print the full classification table:
โโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ # โ Title โ Confidence โ Category โ Tools โ
โโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโค
โ 1 โ {title} โ [{score}] โ STANDARD โ vm.prank, expectRev โ
โ 2 โ {title} โ [{score}] โ MOCK โ MockFlashLender โ
โ 3 โ {title} โ [{score}] โ FORK โ vm.createFork โ
โ 4 โ {title} โ [{score}] โ UNTESTABLE โ โ โ
โโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
Will attempt: {X} findings ({STANDARD + MOCK + FORK + INVARIANT count})
Will skip: {Y} findings ({UNTESTABLE + INCONCLUSIVE count})
Proceed? (yes to continue, no to cancel)
Wait for user confirmation before continuing.
STEP 7 โ PROCESS FINDINGS
Process findings in order of confidence score, highest first.
For each testable finding:
7a โ ANNOUNCE
Print:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ฌ Finding #{N}: {title}
Contract: {ContractName}.{functionName}
Bug class: {bug_class}
Category: {STANDARD | MOCK | FORK | INVARIANT}
Confidence: [{score}]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
7b โ PREPARE TEST
Before writing the test file:
- Re-read the relevant contract source for this finding
- Identify the exact function mentioned in the finding
- Check the function signature, parameters, visibility, and return type
- Look up bug_class in bug-class-map.md โ read the PATTERN and PROVE THIS fields
- Map pashov's proof field values to concrete test variables
7c โ WRITE TEST FILE
File path: {workdir}/test/detest/{ContractName}_{bugClass}_{findingNumber}.t.sol
Rules that must never be broken:
- Never write markdown fences (```) inside the .sol file
- Always use remapped import paths from {workdir}/foundry.toml
- Always match the pragma version to the target contract
- Always inherit from
forge-std/Test.sol
- Always use
makeAddr("name") for test actor addresses
- Always label addresses with
vm.label() in setUp()
- Add attempt history comment at top of file
- Write the test function name as:
test_{bugClass}_finding{N}()
- For fuzz tests:
testFuzz_{bugClass}_finding{N}(uint256 param)
- For invariant tests:
invariant_{bugClass}_finding{N}()
Standard test structure:
// SPDX-License-Identifier: MIT
pragma solidity ^{solc_version};
// DeTest Verification โ Finding #{N}: {title}
// Pashov confidence: [{score}]
// Bug class: {bug_class}
// Attempt 1: {brief description of approach}
import {Test} from "forge-std/Test.sol";
import {{ContractName}} from "{remapped-path}/{ContractName}.sol";
// additional imports per bug-class-map.md
contract DeTest_{ContractName}{BugClass}{N} is Test {
{ContractName} target;
address attacker = makeAddr("attacker");
address victim = makeAddr("victim");
address owner = makeAddr("owner");
function setUp() public {
// deploy target
// set initial state per pashov proof field
vm.label(address(target), "{ContractName}");
vm.label(attacker, "Attacker");
vm.label(victim, "Victim");
vm.label(owner, "Owner");
}
function test_{bugClass}_finding{N}() public {
// ARRANGE: set up state from proof field
// {concrete values from pashov proof}
// ACT: execute attack path from pashov path field
// {caller} โ {function} โ {state change}
// ASSERT: prove impact from pashov description
// {assertion that demonstrates the bug}
}
}
7d โ RUN TEST
Execute:
cd {workdir} && forge test --match-path test/detest/{ContractName}_{bugClass}_{N}.t.sol -vvvv 2>&1
Read the full output.
7e โ INTERPRET RESULT
Apply the decision tree from verdict-rules.md.
Print the result immediately:
Attempt {attempt#}: {PASS โ
| FAIL โ | COMPILE ERROR ๐ด}
{one line summary of what happened}
7f โ ITERATE IF NEEDED
If result is not PASS and attempt < 3:
- Print what specifically failed and what will be changed
- Rewrite the test file with the fix
- Go back to 7d
If result is not PASS and attempt = 3:
- Record final verdict
- Move to next finding
7g โ RECORD VERDICT
Apply verdict definitions from verdict-rules.md.
Store the verdict for the final report.
For UNTESTABLE findings, print immediately and move on:
โญ๏ธ INCONCLUSIVE โ {reason from testability-rules.md}
STEP 8 โ PROCESS LEADS
After all findings are processed, process LEAD items.
Each lead gets exactly one attempt. No iteration.
Same flow as findings but simplified: write โ run โ record verdict (CONFIRMED or SKIPPED).
Print before starting leads:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Processing {M} leads (1 attempt each, no iteration)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 9 โ WRITE VERDICT REPORT
After all findings and leads are processed, write the final report.
Report path: assets/findings/{project-name}-detest-verification-{YYYYMMDD-HHMMSS}.md
Get {project-name} from the repo root directory basename.
Get {timestamp} from current time formatted as YYYYMMDD-HHMMSS.
Report format:
DeTest Verification Report โ {project-name}
Generated: {timestamp}
Source audit: {pashov-report-filename}
DeTest version: 0.2.0
Summary
| Verdict | Count |
|---|
| โ
CONFIRMED | {N} |
| โ UNCONFIRMED | {N} |
| โ ๏ธ INCONCLUSIVE | {N} |
| โญ๏ธ SKIPPED | {N} |
| Total processed | {N} |
โ
Confirmed Findings
[CONFIRMED] Finding #{N}: {title}
Original confidence: [{score}]
Contract: {ContractName}.{functionName}
Bug class: {bug_class}
Test file: test/detest/{filename}.t.sol
Forge result: PASS (attempt {N})
Trace summary: {one sentence โ what the trace showed happened}
Impact confirmed: {one sentence from pashov's path field}
โ Unconfirmed Findings
[UNCONFIRMED-{subcase}] Finding #{N}: {title}
Original confidence: [{score}]
Contract: {ContractName}.{functionName}
Bug class: {bug_class}
Test file: test/detest/{filename}.t.sol (last attempt)
Attempts: {1-3}
Last forge result: {error message or assertion failure}
Note: Unconfirmed does not mean safe. Manual review required.
โ ๏ธ Inconclusive Findings
[INCONCLUSIVE] Finding #{N}: {title}
Original confidence: [{score}]
Contract: {ContractName}.{functionName}
Bug class: {bug_class}
Reason: {precise reason from testability-rules.md}
โญ๏ธ Skipped Leads
[SKIPPED] Lead: {title}
Contract: {ContractName}.{functionName}
Reason: single attempt, compilation failed
Compiler error: {error}
โ ๏ธ DeTest is a mechanical verification layer. CONFIRMED means a Foundry test passes in a local forge environment โ it does not guarantee exploitability in production. UNCONFIRMED does not mean safe. Manual review of all findings is always required regardless of verdict.
After writing the report, print:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
DeTest complete.
๐ Report: assets/findings/{report-filename}
๐งช Tests: {workdir}/test/detest/ ({N} files written)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
HARD RULES โ NEVER BREAK THESE
- Never write markdown fences inside .sol files
- Never use hardcoded import paths โ always read remappings from {workdir}/foundry.toml first
- Never modify source contracts โ only write to {workdir}/test/detest/
- Never set ffi = true in foundry.toml
- Never report a finding as CONFIRMED without reading the trace
- Never skip trace verification on a passing test
- Never iterate more than 3 times on a finding
- Never iterate on leads โ one attempt only
- Never combine two findings into one test file
- Never run forge test without --match-path โ always scope to the specific test file
- If forge test hangs for more than 120 seconds, kill it and record UNCONFIRMED with reason "timeout"
- Never delete test files after failure โ keep the last version as evidence
- Never modify the original target repo โ all operations happen inside {workdir}
- Never proceed past Phase 0 if forge build fails โ a broken workspace produces meaningless test results