- name
- fizz
- description
- 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".
# Fizz
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.
Vendored from the Pashov Audit Group's open-source approach (`github.com/pashov/skills`), skill `fizz`, **VERSION 1** (see the `VERSION` file alongside this one). The `scripts/`, `templates/`, `references/`, and `agents/` directories are upstream code carried over intact; the only local adaptations are the x-ray acquisition path in Step 3 (EVM Cortex ships the methodology as the `xray-pre-audit` skill) and the companion-skill locations noted below. Check for a newer upstream revision before relying on this:
```bash
curl -sf https://raw.githubusercontent.com/pashov/skills/main/fizz/VERSION
```
### Relationship to other EVM Cortex skills
| Skill | Relationship |
|-------|--------------|
| `xray-pre-audit` | Produces the `x-ray/x-ray.md` protocol understanding that Step 3 consumes. Run it first — the fallback Protocol Analyzer is strictly worse input for invariant discovery. |
| `fizz-sync` | Re-use entry point. After source changes, run it instead of regenerating the suite. |
| `fizz-convert` | Turns plain-English properties added to `PROPERTIES.md` into Solidity assertions. |
| `invariant-testing`, `fuzzing-patterns` | Reference material for hand-writing or reviewing properties and handlers outside this pipeline. |
| `pashov-audit-pipeline` | Complementary, not overlapping — that pipeline reasons about attack paths, this one drives a fuzzer at the state space. A violated `SHOULD-HOLD` property here is strong input to a finding there. |
## Workflow Rules
- Follow the steps in order. Do not skip forward if a required artifact for the current step does not exist yet.
- If a step fails, stop there and report the blocker.
- If tooling is missing, say exactly what was attempted and what is missing.
- Keep the generated Solidity suite isolated under `test/fizz/` and the metadata/runtime files under `fizz_data/` unless the user explicitly asks for different paths.
- Reuse existing project setup and test logic whenever possible; do not invent a deployment flow if the repo already has one.
## Parameters
- `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.
- Optional contract arguments narrow handler generation to specific contracts.
- `--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.
## Run Mode
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.
### Resolving `{MODE}`
- If the user invoked with `--guided` / "guided mode" / "walk me through" / "let me review" → `{MODE} = "guided"`.
- If the user invoked with `--automatic` / `--auto` / "unguided" / "run the whole thing" / "no prompts" → `{MODE} = "automatic"`.
- Otherwise, leave `{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.
## Subagent Model
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}`.
### Resolving `{AGENT_MODEL}`
Resolve once at the start of the run and reuse it for every spawn below:
- If the user invoked with `--max` / `--opus` / "max quality" / "run on opus" / similar → `{AGENT_MODEL} = "opus"`.
- If the user invoked with `--sonnet` / "use sonnet" / "default model" → `{AGENT_MODEL} = "sonnet"`.
- Otherwise, leave `{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.
## Step 0: Print Banner
At the start of every skill run, **first** print this ASCII banner once before any other output — including any selection prompt:
```text
██████╗ █████╗ ███████╗██╗ ██╗ ██████╗ ██╗ ██╗ ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗
██╔══██╗██╔══██╗██╔════╝██║ ██║██╔═══██╗██║ ██║ ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝
██████╔╝███████║███████╗███████║██║ ██║██║ ██║ ███████╗█████╔╝ ██║██║ ██║ ███████╗
██╔═══╝ ██╔══██║╚════██║██╔══██║██║ ██║╚██╗ ██╔╝ ╚════██║██╔═██╗ ██║██║ ██║ ╚════██║
██║ ██║ ██║███████║██║ ██║╚██████╔╝ ╚████╔╝ ███████║██║ ██╗██║███████╗███████╗███████║
╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝
```
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.
- **Question for `{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."`
- **Question for `{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).
## Step 1: Verify Tooling And Environment
Run sequentially:
1. Read [template-map.md](./references/template-map.md).
2. Run `forge --version`.
3. If `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/installation`
4. If `forge --version` fails, stop here. Foundry is required before proceeding with the rest of the workflow.
5. Run `bash {SKILL_PATH}/scripts/ensure_foundry.sh {PROJECT_ROOT}`.
6. If `foundry.toml` is missing, allow `ensure_foundry.sh` to create one. If it fails, stop and report the error.
7. Run `medusa --version`.
8. Run `echidna --version`.
9. If either command fails, tell the user which tool is missing and suggest installing it using the official documentation:
Medusa install guide: `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.html`
10. If `medusa --version` fails, stop here. Medusa is required before proceeding with the rest of the workflow.
11. If `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.
## Step 2: Compile And Extract
Run sequentially:
1. Read `{PROJECT_ROOT}/foundry.toml`.
2. Run `cd {PROJECT_ROOT} && forge build`.
3. Run `node {SKILL_PATH}/scripts/extract_abis.js {PROJECT_ROOT} --meta-dir {META_DIR}`.
## Step 3: Understand The Protocol
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.
### x-ray Acquisition Protocol
In EVM Cortex the x-ray methodology ships as the local `xray-pre-audit` skill — do NOT clone or install anything from upstream. Attempts 1 and 2 both target that local skill.
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 `xray-pre-audit` 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 — execute the local skill inline.** If Attempt 1 reported the skill as unavailable, locate it on disk (`{SKILL_PATH}/../xray-pre-audit/SKILL.md`, else search for `**/skills/xray-pre-audit/SKILL.md`), read it, and execute its instructions inline against `{PROJECT_ROOT}`. If that produces `{PROJECT_ROOT}/x-ray/x-ray.md` → SUCCESS, exit Protocol. Only if the file cannot be located AND inline execution fails 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:
- deployment order
- constructor parameter meaning
- required post-deploy initialization
- actor roles and permissioned actions
- approvals, liquidity, or other state needed before handlers will be useful
- which external functions are real fuzzing entry points versus protocol-internal plumbing
- candidate invariants to carry forward into Step 9
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.
## Step 4: Select Entry Points
Read [selection-policy.md](./references/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:
- If `{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`
- If `{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:
- selected contracts
- selected functions by contract
- notable excluded functions
### Dispatcher for Low-Frequency Functions
After reading the selection, classify the selected functions into two tiers:
- **Primary**: core user flows that should be called frequently by the fuzzer (deposit, withdraw, mint, redeem, borrow, repay, swap, stake, unstake, claim, liquidate, etc.)
- **Secondary**: less common functions that are still useful but should be called less often (admin setters, configuration changes, pause/unpause, role grants, parameter tuning, etc.)
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.
Ver en GitHub