Stateful Invariant Testing
aviggiano/security
Build metric-driven Chimera/create-chimera-app stateful invariant testing campaigns for Solidity projects.
Generate Echidna/Medusa-compatible Solidity fuzz suites from Foundry or Hardhat projects.
$ npx skills add pashov/skills --skill fizz -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install pashov/skills fizz --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/pashov/skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/fizz .claude/skills/fizz && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "fizz" agent skill from https://github.com/pashov/skills/tree/main/fizz into .claude/skills/fizz/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fizz", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/pashov/skills/tree/main/fizzType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add pashov/skills --skill fizz -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install pashov/skills fizz --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pashov/skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/fizz .agents/skills/fizz && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "fizz" agent skill from https://github.com/pashov/skills/tree/main/fizz into .agents/skills/fizz/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fizz", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pashov/skills --skill fizz -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install pashov/skills fizz --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pashov/skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/fizz .cursor/skills/fizz && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "fizz" agent skill from https://github.com/pashov/skills/tree/main/fizz into .cursor/skills/fizz/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fizz", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/pashov/skills.git --path fizz--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add pashov/skills --skill fizz -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install pashov/skills fizz --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pashov/skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/fizz .gemini/skills/fizz && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "fizz" agent skill from https://github.com/pashov/skills/tree/main/fizz into .gemini/skills/fizz/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fizz", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install pashov/skills fizzInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add pashov/skills --skill fizz -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/pashov/skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/fizz .github/skills/fizz && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "fizz" agent skill from https://github.com/pashov/skills/tree/main/fizz into .github/skills/fizz/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fizz", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add pashov/skills --skill fizz -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install pashov/skills fizz --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/pashov/skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/fizz .opencode/skills/fizz && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "fizz" agent skill from https://github.com/pashov/skills/tree/main/fizz into .opencode/skills/fizz/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "fizz", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
fizzGenerate Echidna/Medusa-compatible Solidity fuzz suites from Foundry or Hardhat projects.
Fizz is an agent skill from pashov/skills. Generate Echidna/Medusa-compatible Solidity fuzz suites from Foundry or Hardhat projects. Trigger on "fizz", "generate fuzz suite", "build fuzz harness", "stateful fuzzing", "fuzzing harness", "property testing", and "invariant suite".
Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. The skill folder holds 60 other files, including scripts and reference files (for example `README.md`, `agents/implementers/global-property-implementer.md` and `agents/implementers/specific-property-implementer.md`).
It sits in Security, covering Fuzzing, Smart contracts and Smart contract auditing. It works with Solidity. The repository describes itself as: Pashov Audit Group Skills. The licence is MIT.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 92b0ea6. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 1 file in scripts/, which the agent can run.
Shell commands in SKILL.md call:
nodebashgitFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
secure-contracts.comgetfoundry.shFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Fizz loads about 11k tokens when it runs, and up to ~18k if it reads all its reference files. Until then it costs about 60 tokens; SKILL.md has 5,432 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from pashov/skills at commit 92b0ea6, republished under its MIT licence (© pashov). 5,432 words, ~11,015 tokens.
.claude/skills/fizz/SKILL.md (or your agent's skills folder). This skill also uses 55 other files; get the full folder from GitHub.Generate a stateful Solidity fuzz suite under {SUITE_DIR} (default: test/fizz/), with metadata and fuzzer runtime files under {META_DIR} (default: fizz_data/).
Use Echidna and Medusa for invariant campaigns. Use Foundry for compilation, smoke testing, and quick debugging.
test/fizz/ and the metadata/runtime files under fizz_data/ unless the user explicitly asks for different paths.PROJECT_ROOT: user-provided path, otherwise the current working directory.SKILL_PATH: the directory containing this SKILL.md.SUITE_DIR: test/fizz relative to PROJECT_ROOT. Pass --suite-dir to suite-generation steps.META_DIR: fizz_data relative to PROJECT_ROOT. Pass --meta-dir to metadata steps.--no-invariants skips Step 9 only.--max (or --opus, or "max quality") upgrades every subagent in this run from Sonnet to Opus. See "Subagent Model" below.--guided / --automatic selects the run mode. See "Run Mode" below.The skill runs in one of two modes, resolved once at the start of the run and reused for every checkpoint below:
{MODE} = "guided" — the parent agent pauses for user input at key checkpoints: Step 3 (additional docs), Step 4 (interactive function picker UI), Step 4.5 (cost confirmation), Step 6 (setup review), Step 8 (per-cycle coverage decision), Step 9c (property review), Step 10 (fuzzer choice).{MODE} = "automatic" — the parent agent never pauses. Step 4 runs with --auto, Step 8 loops up to 3 coverage cycles then proceeds, Step 10 defaults to Medusa, and the cost estimate from Step 4.5 is printed but not gated on user confirmation.{MODE}--guided / "guided mode" / "walk me through" / "let me review" → {MODE} = "guided".--automatic / --auto / "unguided" / "run the whole thing" / "no prompts" → {MODE} = "automatic".{MODE} unresolved; Step 0 asks for it via the selection prompt after printing the banner.Every subsequent instruction referencing {MODE} must substitute the resolved value. Do NOT switch modes mid-run.
All subagents spawned by this skill (Step 3 fallback Protocol Analyzer, the 5 Step 9b discovery agents, the Step 9c Synthesizer, the 2 Step 9d Implementers, and the Step 11 Report Writer) default to Sonnet for cost and latency.
The parent agent orchestrating the pipeline is whatever model the user's Claude Code session is running (this skill does not control it). Only the delegated subagents are covered by {AGENT_MODEL}.
{AGENT_MODEL}Resolve once at the start of the run and reuse it for every spawn below:
--max / --opus / "max quality" / "run on opus" / similar → {AGENT_MODEL} = "opus".--sonnet / "use sonnet" / "default model" → {AGENT_MODEL} = "sonnet".{AGENT_MODEL} unresolved; Step 0 asks for it via the selection prompt after printing the banner.Every subsequent spawn instruction below references {AGENT_MODEL} — substitute the resolved value when making the actual tool call. Do NOT mix tiers within a single run.
At the start of every skill run, first print this ASCII banner once before any other output — including any selection prompt:
██████╗ █████╗ ███████╗██╗ ██╗ ██████╗ ██╗ ██╗ ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗
██╔══██╗██╔══██╗██╔════╝██║ ██║██╔═══██╗██║ ██║ ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝
██████╔╝███████║███████╗███████║██║ ██║██║ ██║ ███████╗█████╔╝ ██║██║ ██║ ███████╗
██╔═══╝ ██╔══██║╚════██║██╔══██║██║ ██║╚██╗ ██╔╝ ╚════██║██╔═██╗ ██║██║ ██║ ╚════██║
██║ ██║ ██║███████║██║ ██║╚██████╔╝ ╚████╔╝ ███████║██║ ██╗██║███████╗███████╗███████║
╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝
After the banner, resolve {MODE} per the "Run Mode" section and {AGENT_MODEL} per the "Subagent Model" section. For any value still unresolved from invocation flags, ask the user via a single AskUserQuestion tool call containing only the unresolved questions (skip the call entirely if both were resolved from flags).
Output discipline (mandatory): Between the banner block and the AskUserQuestion invocation, emit no user-facing text whatsoever — no "I'll ask about…", no "loading the tool…", no acknowledgement that flags were missing. If AskUserQuestion's schema needs to be fetched via ToolSearch first, do that silently as well. The user should see banner → selection UI → resolved-values lines, with nothing in between. This overrides the default behavior of narrating intent before tool calls.
{MODE} — header: "Run mode", question: "How should I run?", options:label: "Automatic (Recommended)", description: "Run end-to-end with no prompts."label: "Guided", description: "Pause at 7 checkpoints: extra docs, entry-point picker (browser UI), cost confirm, setup review, per-cycle coverage decision, property review, fuzzer choice."{AGENT_MODEL} — header: "Subagent model", question: "Which model should drive the subagents?", options:label: "Sonnet (Recommended)", description: "Default. Faster and cheaper for the 5 discovery agents, synthesizer, and 2 implementers."label: "Opus", description: "Higher quality but ~10× the cost. Equivalent to passing --max / --opus."Map the user's selections back to {MODE} (automatic / guided) and {AGENT_MODEL} (sonnet / opus), then print these two lines so the resolved values are visible in transcript:
Mode: guided or Mode: automatic — the resolved {MODE}.Subagent model: sonnet (default) or Subagent model: opus (--max) (if --max / --opus / "max quality" / "use opus" / Opus selection was requested).Run sequentially:
forge --version.forge --version fails, tell the user that Foundry is missing and suggest installing it using the official documentation:
Foundry install guide: https://www.getfoundry.sh/introduction/installationforge --version fails, stop here. Foundry is required before proceeding with the rest of the workflow.bash {SKILL_PATH}/scripts/ensure_foundry.sh {PROJECT_ROOT}.foundry.toml is missing, allow ensure_foundry.sh to create one. If it fails, stop and report the error.medusa --version.echidna --version.https://secure-contracts.com/program-analysis/medusa/docs/src/getting_started/installation.html
Echidna install guide: https://secure-contracts.com/program-analysis/echidna/introduction/installation.htmlmedusa --version fails, stop here. Medusa is required before proceeding with the rest of the workflow.echidna --version fails but Foundry and Medusa are installed, you may continue, but keep the installation recommendation in the user-facing summary because Echidna is still expected for the full workflow.Run sequentially:
{PROJECT_ROOT}/foundry.toml.cd {PROJECT_ROOT} && forge build.node {SKILL_PATH}/scripts/extract_abis.js {PROJECT_ROOT} --meta-dir {META_DIR}.This step exists to drive setup, handler selection, and invariant generation quality.
If {MODE} = "guided", before touching any analysis source first ask the user: "Any additional docs, links, whitepapers, spec files, or prior-audit notes I should consider? (paste paths or URLs, or reply 'none')". If the user provides anything, write the raw list to {PROJECT_ROOT}/{META_DIR}/additional-context.md (one entry per line, include URLs verbatim). Later sub-steps of this step — and Step 9a — must read that file if it exists and fold it into the protocol-understanding context.
Start by checking whether {PROJECT_ROOT}/x-ray/ exists and contains x-ray.md. x-ray.md is REQUIRED — without it, x-ray output is considered unavailable regardless of which other files are present.
If {PROJECT_ROOT}/x-ray/x-ray.md exists, read it first as the primary project-understanding source. Then also read any of these supplementary files present in {PROJECT_ROOT}/x-ray/:
If {PROJECT_ROOT}/x-ray/x-ray.md does NOT exist, you MUST run the x-ray Acquisition Protocol below. The Protocol Analyzer fallback (Attempt 4) is FORBIDDEN until Attempts 1–3 have each been executed and their outcomes recorded in /tmp/x-ray-attempts.md. "I think x-ray isn't available" is NOT a valid skip — only the recorded output of an actual tool/command counts.
Before Attempt 1, delete /tmp/x-ray-attempts.md if it exists (rm -f /tmp/x-ray-attempts.md) — stale entries from a previous run would falsely satisfy the Attempt 4 gate. Then create a fresh /tmp/x-ray-attempts.md and append one entry per attempt: timestamp, attempt name, command/tool invoked, exact output (or "no output"), outcome (SUCCESS / FAILED: {reason} / SKIPPED: {reason}). Attempt 4 requires the file to contain exactly 3 entries (one per Attempt 1, 2, 3) — SKIPPED entries count toward this total.
Attempt 1 — invoke the skill. Call the x-ray skill via the Skill tool with args="{PROJECT_ROOT}". Do NOT pre-judge availability — invoke it. Only a runtime error of the form "skill not found" / "unknown skill" counts as unavailable. If it runs, wait for completion, then verify {PROJECT_ROOT}/x-ray/x-ray.md was written. If yes → SUCCESS, exit Protocol.
Attempt 2 — install from the official source and re-invoke. Run:
git clone --depth 1 https://github.com/pashov/skills.git /tmp/pashov-skills-xray-install \
&& mkdir -p ~/.claude/skills \
&& cp -r /tmp/pashov-skills-xray-install/x-ray ~/.claude/skills/x-rayThen re-invoke Skill('x-ray', args="{PROJECT_ROOT}"). If {PROJECT_ROOT}/x-ray/x-ray.md is produced → SUCCESS, exit Protocol. If the re-invocation still returns "skill not found" / "unknown skill" (auto-discovery did not pick up the freshly installed skill mid-session), do NOT mark this attempt failed yet — instead read ~/.claude/skills/x-ray/SKILL.md (or /tmp/pashov-skills-xray-install/x-ray/SKILL.md) and execute its instructions inline against {PROJECT_ROOT}. If that produces {PROJECT_ROOT}/x-ray/x-ray.md → SUCCESS, exit Protocol. Only if ALL of (Skill re-invocation, inline execution) fail does this attempt count as FAILED.
Attempt 3 — guided-mode user gate (guided only). If {MODE} = "guided" AND Attempts 1–2 both failed, ASK the user: "Could not obtain x-ray automatically (logs in /tmp/x-ray-attempts.md). Options: (a) paste an x-ray.md path, (b) authorize Protocol Analyzer fallback, (c) abort. Choose a/b/c." Record their answer. If (a) and the file exists → copy to {PROJECT_ROOT}/x-ray/x-ray.md, SUCCESS. If (c) → halt the skill. Only (b) — explicit user authorization — permits Attempt 4. In {MODE} = "automatic", skip this attempt and record SKIPPED: automatic mode.
Attempt 4 — Protocol Analyzer fallback. Permitted ONLY after Attempts 1–3 are recorded in /tmp/x-ray-attempts.md (with status FAILED, SKIPPED, or — for Attempt 3 only — (b) authorized). Before spawning, confirm the file exists and contains 3 entries; if not, GO BACK to the missing attempt — do not proceed.
Fallback: Read {SKILL_PATH}/agents/protocol-analyzer.md, replace {SKILL_PATH} with the actual {SKILL_PATH}, {PROJECT_ROOT} with the actual {PROJECT_ROOT}, and {META_DIR} with the actual {META_DIR}, then spawn as a general-purpose agent with model: "{AGENT_MODEL}". The agent reads the source files, then writes the analysis to {PROJECT_ROOT}/{META_DIR}/protocol-understanding.md so that later steps can read it back instead of relying on conversation context.
From the x-ray documentation or protocol-understanding.md infer and summarize:
If something is still ambiguous after those reads, keep going with a conservative assumption and leave a targeted TODO later instead of guessing broadly.
Do not plan full ghost-variable layouts, snapshot structs, or final implementation details here. Do record the likely invariants clearly so Step 9 can reuse them from {PROJECT_ROOT}/x-ray/ or {PROJECT_ROOT}/{META_DIR}/protocol-understanding.md as its starting point.
Read selection-policy.md.
Create {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json as a filtered copy of {PROJECT_ROOT}/{META_DIR}/contracts.json that keeps the functions most likely to produce useful state transitions.
Build the preselection from the protocol understanding gathered in Step 3 — primarily the x-ray entry-point map (if available) and source-level access control observations. If Step 3 produced an entry-point map with caller or access annotations, use that as the primary filter: exclude functions marked as internal-caller-only or contract-to-contract plumbing. Use {PROJECT_ROOT}/{META_DIR}/contracts.json only as the structural template for the output JSON format, not to decide which functions to include.
Then run:
{MODE} = "automatic":
node {SKILL_PATH}/scripts/select_functions.js {PROJECT_ROOT} --contracts {PROJECT_ROOT}/{META_DIR}/contracts.json --selection {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json --meta-dir {META_DIR} --auto{MODE} = "guided":
node {SKILL_PATH}/scripts/select_functions.js {PROJECT_ROOT} --contracts {PROJECT_ROOT}/{META_DIR}/contracts.json --selection {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json --meta-dir {META_DIR}The --auto flag accepts the inferred selection and exits immediately. Without it, the script opens a browser UI with the inferred selection pre-checked so the user can adjust and confirm. Both paths write entry-point-selection.json.
After the script completes, read {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json.
If the script exits before writing entry-point-selection.json, stop and report that failure.
Print a short summary:
After reading the selection, classify the selected functions into two tiers:
Write this classification to {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json by adding a "tier": "primary" or "tier": "secondary" field to each function entry.
In Step 7, secondary-tier functions will be wrapped in a dispatcher handler that groups them behind a single entry point with an enum selector. This reduces call frequency naturally without excluding them entirely — the fuzzer picks a random selector value, so secondary functions get exercised occasionally but don't dominate the call sequence.
If the user already excluded a function during selection, it stays excluded. The dispatcher is only for functions the user chose to keep but that should be deprioritized.
{PROJECT_ROOT}/{META_DIR}/entry-point-selection.json limits handler generation only. It does not limit the setup dependency graph.
Run:
node {SKILL_PATH}/scripts/estimate_cost.js {PROJECT_ROOT} --meta-dir {META_DIR} --model {AGENT_MODEL} --mode {MODE}
The script reads entry-point-selection.json, applies a size bucket based on selected-function count, and writes {PROJECT_ROOT}/{META_DIR}/cost-estimate.md with a per-stage breakdown plus a total and an expected range. The numbers are Anthropic list-price ballparks; actual cost varies with coverage cycles, re-runs, and prompt-cache hit rate.
Print the cost estimate table to the user. Then:
{MODE} = "automatic": continue to Step 5 without pausing.{MODE} = "guided": ask the user "Proceed with this estimate, or abort?" and wait for confirmation before continuing. If the user aborts, stop the run and report where the artifacts so far were written.Run:
node {SKILL_PATH}/scripts/generate_suite.js {PROJECT_ROOT} --suite-dir {SUITE_DIR} --meta-dir {META_DIR}
This copies the full template scaffold into {PROJECT_ROOT}/{SUITE_DIR}/, including core harness files and utility files such as utils/MockERC20.sol. It also writes the fuzzer config files (echidna.yaml, medusa.json) into {PROJECT_ROOT}/.
It also reads {PROJECT_ROOT}/{META_DIR}/entry-point-selection.json and generates one stub handler file per selected contract under {PROJECT_ROOT}/{SUITE_DIR}/handlers/. Those stubs include the clamped and unclamped section headers but no sample handler functions. Handlers.sol is scaffolded to import and inherit from all generated handler stubs.
Treat the copied files as the starting point only. The next steps must modify them to fit the target protocol.
Read setup-playbook.md and template-map.md.
Modify the scaffolded core files under {PROJECT_ROOT}/{SUITE_DIR}/. Use template-map.md as the source of truth for the inheritance chain, file roles, and which scaffolded files are expected to be refined in this step versus later steps.
Use setup-playbook.md as the source of truth for:
Base.sol integration points and TODO/FIXME policyWhen the target protocol depends on simple external ERC20s that are not part of the in-scope deployment graph, prefer the scaffolded utils/MockERC20.sol helper unless the project already includes a more faithful token mock.
The key output of this step is a compiling scaffold with a realistic Base.sol::setup() function and the rest of the core scaffold adjusted to match it.
If {MODE} = "guided", after the edits to Base.sol are complete and before running forge build, print a Setup Review block summarising what was wired:
setup() (name + address variable + constructor args source)Then ask the user: "Setup looks right? Reply 'proceed' to build, or tell me what to adjust." If the user requests adjustments, apply them and re-print the review block; loop until they approve. In {MODE} = "automatic", skip the review block and continue directly.
Run cd {PROJECT_ROOT} && forge build before moving on.
Read handler-patterns.md.
First, run the handler generation script to produce pre-populated stubs with correct function signatures and type mappings:
node {SKILL_PATH}/scripts/generate_handlers.js {PROJECT_ROOT} --suite-dir {SUITE_DIR} --meta-dir {META_DIR}
Then read these in parallel:
{PROJECT_ROOT}/{SUITE_DIR}/handlers/Handlers.sol{PROJECT_ROOT}/{SUITE_DIR}/handlers/<Contract>Handler.solThen refine the generated {PROJECT_ROOT}/{SUITE_DIR}/handlers/<Contract>Handler.sol for the selected contracts. The stubs already contain correct signatures and clamping hints — focus on wiring the actual protocol calls, adding semantic clamping, and implementing boundary-value stress variants.
Use handler-patterns.md as the source of truth for:
Update Handlers.sol to import and inherit from all generated handlers.
Run cd {PROJECT_ROOT} && forge build and fix compile issues before moving on.
Before generating invariants, ensure the generated harness can drive enough protocol coverage under Medusa.
When via_ir = true is set in foundry.toml, the Yul IR optimizer aggressively merges and eliminates branches. This deflates Medusa's coverage numbers — you may be at 85% source coverage but Medusa reports 65%. This must be handled before the first Medusa run.
Step 8.0: Detect and configure fuzz profile.
{PROJECT_ROOT}/foundry.toml and check whether via_ir = true is set under [profile.default] or at the top level.via_ir is not enabled, skip this subsection — no fuzz profile is needed.via_ir is enabled, run:bash {SKILL_PATH}/scripts/setup_fuzz_profile.sh {PROJECT_ROOT}This script:
[profile.fuzz] section to foundry.toml with via_ir = falseFOUNDRY_PROFILE=fuzz forge buildFUZZ_PROFILE=no-irvia_ir = true and optimizer_runs = 0, exits 0, prints FUZZ_PROFILE=ir-no-optRead the script output to determine the fuzz profile mode:
FUZZ_PROFILE=no-ir → accurate coverage, use standard targetsFUZZ_PROFILE=ir-no-opt → reduced deflation but still some; lower coverage targets by ~10%Record the profile mode in {PROJECT_ROOT}/{META_DIR}/coverage-targets.md at the top:
no-ir: "Fuzz profile: via_ir disabled — coverage numbers are accurate"ir-no-opt: "Fuzz profile: via_ir required (stack too deep), optimizer_runs=0 — coverage deflated ~10%, targets adjusted"For all subsequent forge build commands in Steps 8–11, use:
cd {PROJECT_ROOT} && FOUNDRY_PROFILE=fuzz forge build (if fuzz profile was created)cd {PROJECT_ROOT} && forge build (if no fuzz profile needed)Store the build command in a variable {FUZZ_BUILD_CMD} for reuse in later steps.
Every Medusa run in this step must use run_medusa.js, including reruns after harness changes. Do not switch to raw medusa fuzz for later cycles in this step.
Before launching Medusa, rebuild with the fuzz profile if one was configured:
{FUZZ_BUILD_CMD}Run the Medusa script asynchronously using the agent's command-execution tool. This is critical — do NOT use shell backgrounding (&), do NOT use sleep + tail to poll, and do NOT wait synchronously in a single blocking command. Start the process in a way the agent runtime can track and notify on completion.
node {SKILL_PATH}/scripts/run_medusa.js {PROJECT_ROOT} --meta-dir {META_DIR} --coverage-modeThe wrapper starts a temporary local browser log viewer and prints its URL, but does not open the browser by default. Read the viewer URL from the wrapper output and provide it to the user. Add --logs to the command only if the user asks for the viewer to open in their browser automatically.
When the background command completes and you are notified, inspect the resulting coverage with focus on the core protocol contracts, not peripheral mocks or helper libraries.
Not all contracts require the same coverage. Assign per-contract targets based on the contract's role:
| Contract Role | Target (no-ir) | Target (ir-no-opt) | Target (ir fallback) |
|---|---|---|---|
| Core protocol logic (vault, pool, lending core, staking engine) | 80%+ | 70%+ | 65%+ |
| Access control / role management | 60%+ | 50%+ | 45%+ |
| Peripheral helpers (routers, views, adapters) | 50%+ | 40%+ | 35%+ |
| Libraries and math utilities | Coverage inherited from callers | Same | Same |
Use the column matching the fuzz profile mode determined in Step 8.0.
After the first Medusa run, review the per-contract coverage report and classify each contract. If a contract has legitimately unreachable paths in the harness context (e.g., fork-only branches, multi-block MEV paths, oracle-failure paths), note them and adjust that contract's target downward rather than wasting cycles on unreachable code.
Write the per-contract targets and any skip justifications to {PROJECT_ROOT}/{META_DIR}/coverage-targets.md so the user can review them.
--coverage-mode, the wrapper runs medusa fuzz --timeout 300, allows at least 60 seconds of fuzzing, then stops earlier if 5 consecutive progress lines show no increase in branches hit, and prints the coverage report path when finished (add --logs to also open it in the browser)FoundryTester.sol to quickly PoC it{FUZZ_BUILD_CMD} before each new Medusa runnode {SKILL_PATH}/scripts/run_medusa.js {PROJECT_ROOT} --meta-dir {META_DIR} --coverage-mode, inspect coverage, adjust the harness, then rebuildAfter each cycle, build and print a per-contract coverage summary table from the Medusa coverage report:
| Contract | Role | Target | Hit | Status |
|---|
Status is ✅ if Hit ≥ Target, else ❌. Append the same table to {PROJECT_ROOT}/{META_DIR}/coverage-targets.md under a timestamped ## Cycle N heading so the history is preserved.
Then branch on {MODE}:
{MODE} = "automatic": if all contracts meet target, give a brief summary and proceed. Otherwise loop; cap at 3 cycles total, then log remaining gaps in coverage-targets.md and proceed to the next step with the current harness.{MODE} = "guided": after every cycle (including cycle 1), ask the user "iterate / adjust targets / proceed". iterate = run another cycle with the current harness adjustments. adjust targets = let the user edit per-contract targets in coverage-targets.md before the next cycle. proceed = exit the loop regardless of gaps. Honour the user's choice; there is no hard cap in guided mode.After exiting the loop, provide the coverage report path for optional review: {PROJECT_ROOT}/{META_DIR}/corpus_medusa/coverage/coverage_report.html.
It is acceptable to skip coverage for specific functions or paths when:
require(msg.sender == bridgeContract))Document each skip in {PROJECT_ROOT}/{META_DIR}/coverage-targets.md with the reason.
Do not move to invariant generation while Medusa coverage is still clearly too low for the selected flows, unless the user explicitly tells you to proceed anyway.
Skip this step only when the user passed --no-invariants.
This is the make-or-break step. It uses 5 specialized discovery agents in parallel, each applying a different invariant discovery approach drawn from 50+ real DeFi bugs caught by fuzzers.
Read property-generation.md, then build INVARIANT_CONTEXT by reading:
{PROJECT_ROOT}/x-ray/x-ray.md when available, otherwise {PROJECT_ROOT}/{META_DIR}/protocol-understanding.md{PROJECT_ROOT}/{META_DIR}/additional-context.md if it exists (guided-mode supplementary docs/links from Step 3)Base.sol, Snapshots.sol, Properties.solExtract from the codebase:
total*, sum*, accumulated*, or any variable that multiple functions write toconvertTo*, preview*, toAssets, toShares, or any function mapping between two unit systemsonlyOwner, onlyAdmin, onlyRole, require(msg.sender, custom role modifiersCRITICAL: All 5 agents MUST be spawned in a SINGLE message (one tool call per agent, all in the same response).
| Agent | File | Discovery Approach |
|---|---|---|
| 1. Conservation Auditor | {SKILL_PATH}/agents/invariant-discovery/conservation-auditor.md | Sum-of-parts = tracked-whole for every aggregate variable |
| 2. Round-Trip & Rounding Analyst | {SKILL_PATH}/agents/invariant-discovery/roundtrip-rounding-analyst.md | Forward+inverse operations, directional rounding |
| 3. State Transition Mapper | {SKILL_PATH}/agents/invariant-discovery/state-transition-mapper.md | Postconditions, monotonicity, entity counts, state machine |
| 4. Adversarial Profit Maximizer | {SKILL_PATH}/agents/invariant-discovery/adversarial-profit-maximizer.md | Attacker thinking — DoS, value extraction, edge states |
| 5. Protocol-Type Specialist | {SKILL_PATH}/agents/invariant-discovery/protocol-type-specialist.md | Auto-detect type, apply domain templates (vault/lending/AMM/etc.) |
Read each agent file, replace {INVARIANT_CONTEXT} and {FILE_PATHS} with actual values, spawn as general-purpose agent with model: "{AGENT_MODEL}".
After all 5 agents return, read the Synthesizer agent file:
| Agent | File |
|---|---|
| 6. Synthesizer | {SKILL_PATH}/agents/invariant-discovery/synthesizer.md |
Replace {AGENT_OUTPUTS} with the outputs from agents 1-5, {META_DIR} with the actual {META_DIR} path, {PROJECT_ROOT} with the actual {PROJECT_ROOT} path, and {SUITE_DIR} with the actual {SUITE_DIR} path, then spawn as general-purpose agent with model: "{AGENT_MODEL}".
The Synthesizer merges, deduplicates, prioritizes, and writes BOTH:
{PROJECT_ROOT}/{META_DIR}/property-plan.md — implementation tables with stable Spec IDs (GL-NN, SP-NN){PROJECT_ROOT}/PROPERTIES.md — English-language spec with [ ] checkboxes, one entry per property, identified by the same Spec IDs. This is the artifact that the /fizz-convert command and the implementers in Step 9d operate on.Each property carries a Guarantee tag set at generation time — SHOULD-HOLD (explicitly guaranteed by docs/spec/standard or an exact identity, with evidence cited) or EXPLORATORY (inferred). This tag is what lets Step 10 triage a violation without post-campaign severity guessing: a violated SHOULD-HOLD property is a confirmed bug, a violated EXPLORATORY property is a lead for human review.
Print a summary: "Generated X properties (N HIGH, N MEDIUM, N LOW; P SHOULD-HOLD, Q EXPLORATORY)" with a brief list of the top properties by priority.
If {MODE} = "guided", pause here: tell the user the file path ({PROJECT_ROOT}/PROPERTIES.md), summarise what the Synthesizer produced, and ask "Review PROPERTIES.md and edit freely — rename, add, remove, or reword properties. Keep the Spec IDs (GL-NN / SP-NN) on entries you want implemented, and leave [ ] checkboxes unchanged. Reply 'proceed' when done, or 'regenerate' to re-run the Synthesizer with additional guidance." If they reply regenerate, ask what to change, then re-spawn the Synthesizer (Step 9c) with the extra guidance appended to its input. If they reply proceed, continue to Step 9d. In {MODE} = "automatic", skip the pause and proceed directly.
Read each agent file:
| Agent | File | Scope |
|---|---|---|
| 7A. Global Property Implementer | {SKILL_PATH}/agents/implementers/global-property-implementer.md | Ghosts in Base.sol, State in Snapshots.sol, global properties in Properties.sol, harness contracts if needed |
| 7B. Specific Property Implementer | {SKILL_PATH}/agents/implementers/specific-property-implementer.md | Specific properties in Properties.sol, handler wiring (ghost updates + snapshot calls + property assertions) |
Replace {META_DIR} with the actual {META_DIR} path, {SKILL_PATH} with the actual {SKILL_PATH} path, {PROJECT_ROOT} with the actual {PROJECT_ROOT} path, and {SUITE_DIR} with the actual {SUITE_DIR} path, then spawn both as general-purpose agents with model: "{AGENT_MODEL}" in parallel.
Both implementers MUST flip [ ] → [x] in {PROJECT_ROOT}/PROPERTIES.md for each property they actually implement (matching by Spec ID GL-NN / SP-NN). Properties left as TODO stubs stay [ ] so /fizz-convert can pick them up later.
Run {FUZZ_BUILD_CMD} after the edits. Fix any compilation errors.
If a property is low-confidence after implementation, prefer a commented TODO over a brittle assertion.
After invariant generation and validation, run a full fuzzing campaign to find property violations.
{MODE} = "automatic": default to Medusa — faster with multi-worker parallelism and better suited for initial runs.{MODE} = "guided": ask the user "Which fuzzer for this campaign — Medusa (default, parallel workers) or Echidna?" and use their answer. If they express no preference, default to Medusa.Do not run both fuzzers simultaneously — this consumes excessive resources and can cause system instability or crashes. Campaign Iteration (below) is where a complementary run on the other fuzzer is offered.
Run the wrapper for the chosen fuzzer, either Medusa or Echidna (ONLY ONE), asynchronously using the agent's command-execution tool, then wait for that tracked background job to finish — the wrapper exits when the fuzzer's own stop condition triggers or the --timeout is reached. Both wrappers start a temporary local browser log viewer and print its URL, but do not open the browser by default; read the viewer URL from the wrapper output and provide it to the user. Add --logs to the command only if the user asks for the viewer to open in their browser automatically.
For Medusa, run this command:
node {SKILL_PATH}/scripts/run_medusa.js {PROJECT_ROOT} --meta-dir {META_DIR} --timeout 600For Echidna, run this command:
node {SKILL_PATH}/scripts/run_echidna.js {PROJECT_ROOT} --meta-dir {META_DIR} --timeout 600If Echidna fails with unlinked libraries detected in bytecode, link the libraries in echidna.yaml:
deployContracts: [["0xf1", "Lib1"], ["0xf2", "Lib2"]]
cryticArgs: ["--compile-libraries=(Lib1,0xf1), (Lib2,0xf2)"]Replace Lib1, Lib2 with the actual library names and 0xf1, 0xf2 with the desired deployment addresses.
After the campaign completes:
PROPERTIES.md): a violated SHOULD-HOLD property is a confirmed bug — the protocol broke a documented/mathematical guarantee, so report it as such; a violated EXPLORATORY property is flagged for human review — it may be a real bug or an over-strong inferred assumption, so present the call sequence and let a human judge rather than asserting a bug. Always rule out a harness/property bug first regardless of tag.If the user wants to explore further after the first campaign:
Run:
{FUZZ_BUILD_CMD}FoundryTester.sol exists, cd {PROJECT_ROOT} && FOUNDRY_PROFILE=fuzz forge test --match-contract FoundryTester (or without FOUNDRY_PROFILE if no fuzz profile was created)If validation fails:
If the campaign in Step 10 found property violations, generate a Foundry reproduction test for each distinct violation in FoundryTester.sol. This turns fuzzer output into deterministic, one-command proof that the violation is real.
When to run: only when {PROJECT_ROOT}/{META_DIR}/corpus_medusa/test_results/ contains violation JSON files (Medusa) or the Echidna log contains failed! lines with call sequences. If the campaign found no violations, skip this sub-step entirely.
How to generate repros:
test_results/; Echidna: parse call sequences from the log after each failed! line).test_repro_<propertyName>() function in FoundryTester.sol.methodSignature and inputValues fields directly.vm.roll() and vm.warp() to advance block number and timestamp by the blockNumberDelay and blockTimestampDelay from each call entry.from addresses map to actors by index: 0x10000 → actors[0], 0x20000 → actors[1], 0x30000 → actors[2]. The clamped handlers accept an actor seed parameter that runs through toActor(), so pass the raw fuzzer input values — the seed modulus selects the right actor.property_* functions that return bool): replay the full call sequence, then assert the property returns false: assertFalse(property_xxx(), "property should be violated");. The test passes when the property is violated._prop_* / _check* assertions inside handlers that revert via assert() / t()): the violating handler call will revert with panic(0x01) before any post-call assertion can run. Wrap the violating call in try this._repro_helperN() { revert("assertion should have fired"); } catch {}, where _repro_helperN() is an external helper function that calls the handler. The try/catch proves the assertion fired. The test passes when the catch triggers.{FUZZ_BUILD_CMD} and then cd {PROJECT_ROOT} && FOUNDRY_PROFILE=fuzz forge test --match-contract FoundryTester -vvv to confirm all repro tests pass.// TODO: manual repro needed — fuzzer sequence did not reproduce under Foundry note and move on.Repro naming convention: test_repro_<propertyName> (e.g., test_repro_property_solvency, test_repro_prop_depositIncreasesShares). If the same property was violated by multiple distinct root causes (different call sequences hitting different code paths), suffix with _1, _2.
After validation succeeds, spawn the Report Writer subagent to synthesize the final report. Read {SKILL_PATH}/agents/report-writer.md, replace {SKILL_PATH} with the actual {SKILL_PATH}, {PROJECT_ROOT} with the actual {PROJECT_ROOT}, {META_DIR} with the actual {META_DIR}, and {SUITE_DIR} with the actual {SUITE_DIR}, then spawn as a general-purpose agent with model: "{AGENT_MODEL}".
The agent reads the campaign outputs (coverage, corpus, medusa-run.log, PROPERTIES.md, Properties.sol, handler files, open TODOs) and writes {PROJECT_ROOT}/{META_DIR}/report.md.
The report-writer agent will also print the report content to the conversation (so the user sees it inline) and remind the user of the commands to run campaigns manually:
medusa fuzz (from project root)echidna . --contract FuzzTester --config echidna.yamlAfter the final report is written, capture a sync snapshot so the /fizz-sync skill can detect drift on subsequent source changes without re-running the full pipeline:
node {SKILL_PATH}/scripts/fizz_sync.js {PROJECT_ROOT} --init --meta-dir {META_DIR} --suite-dir {SUITE_DIR}If the snapshot already exists (because a prior run already initialised it), re-run with --refresh-snapshot instead so the new baseline reflects the current state:
node {SKILL_PATH}/scripts/fizz_sync.js {PROJECT_ROOT} --refresh-snapshot --meta-dir {META_DIR} --suite-dir {SUITE_DIR}This writes {PROJECT_ROOT}/{META_DIR}/last-run.json — a hash+signature snapshot of the in-scope contracts, handlers, and PROPERTIES.md entries. Later, when the user modifies sources and invokes /fizz-sync, that skill diffs against this file to detect added/removed/changed functions, quarantine stale properties, and regenerate only the drifted handler stubs.
© pashov, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 55 other files (scripts, references) in fizz of pashov/skills.
Open the folder on GitHubat commit 92b0ea6
We found 2 copies of this SKILL.md (exact, near-identical or edited) in other folders, from 2 other GitHub owners. This page covers the copy in pashov/skills, which our catalogue first saw on October 7, 2026.
Fizz next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Fizz this skillpashov/skills | 1.2k | 2 repos | ~11k | Automated safety check: Pass | MIT | |
| Stateful Invariant Testingaviggiano/security | 144 | — | ~2.8k | Automated safety check: Pass | MIT | |
| Property Based Testingtrailofbits/skills | 7.4k | — | ~1.1k | Automated safety check: Pass | CC-BY-SA-4.0 | |
| Reentrancy Auditoralt-research2/SolidityGuard | 104 | — | ~1.8k | Automated safety check: Pass | Custom licence | |
| Smart Contract Auditforefy/.context | 152 | 1 repos | ~5.1k | Automated safety check: Pass | MIT | |
| Smart Contract Auditelophanto/EloPhanto | 106 | — | ~2.7k | Automated safety check: Pass | Custom licence |
aviggiano/security
Build metric-driven Chimera/create-chimera-app stateful invariant testing campaigns for Solidity projects.
trailofbits/skills
Writes, reviews, and debugs property-based tests — Hypothesis, fast-check, proptest, jqwik, rapid, and Echidna or Medusa for Solidity invariants.
alt-research2/SolidityGuard
Deep reentrancy vulnerability analysis for Solidity contracts.
forefy/.context
Comprehensive smart contract security audit framework with multi-expert analysis.
elophanto/EloPhanto
A skill your agent uses when reviewing a Solidity, Vyper, or Rust (Solana/Anchor) smart contract for paid audit work or pre-launch sanity check.
ccashwell/evm-cortex
A skill your agent uses when writing fuzz tests for Solidity contracts.
pashov/skills
Generates an x-ray.md pre-audit report covering overview, enhanced threat model (protocol-type profiling, git-weighted attack surfaces, temporal risk analysis, composability dependency mapping)…
pashov/skills
Convert English-language properties in PROPERTIES.md (produced by the Fizz skill) into Solidity assertions inside the existing fuzz harness, then flip their checkboxes.
pashov/skills
Reconcile an existing Fizz harness with a changed source tree.
pashov/skills
Security audit of Solidity code while you develop. An agent skill from pashov/skills.
Works with
Categories
Generate Echidna/Medusa-compatible Solidity fuzz suites from Foundry or Hardhat projects. Fizz is an agent skill from pashov/skills. Generate Echidna/Medusa-compatible Solidity fuzz suites from Foundry or Hardhat projects.
Fizz fits situations like: generate fuzz suite; build fuzz harness; stateful fuzzing; fuzzing harness.
Run `npx skills add pashov/skills --skill fizz -a claude-code`. Or copy the skill folder (fizz in pashov/skills) into .claude/skills/fizz in your project. Claude Code loads it when a task matches its description.
Run `npx skills add pashov/skills --skill fizz -a codex`. Or copy the skill folder (fizz in pashov/skills) into .agents/skills/fizz in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add pashov/skills --skill fizz -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/fizz, .gemini/skills/fizz, .github/skills/fizz and .opencode/skills/fizz in your project.
Going by SKILL.md and its folder, Fizz needs the command-line tools its instructions call (node, bash and git).
SKILL.md names 2 domains. In commands or code: secure-contracts.com and getfoundry.sh; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Fizz is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 11k tokens (SKILL.md is roughly 44k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 7.3k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Fizz: Stateful Invariant Testing (aviggiano/security, 144 stars), Property Based Testing (trailofbits/skills, 7.4k stars), Reentrancy Auditor (alt-research2/SolidityGuard, 104 stars) and Smart Contract Audit (forefy/.context, 152 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
pashov (a GitHub user) maintains it in pashov/skills, which has 1,230 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on October 5, 2026.
Source: pashov/skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.