Generate Foundry proof-of-concept tests that validate HIGH and CRITICAL security findings from entrypoint analysis. Produces runnable Solidity test files with attack contracts, setup scaffolding, and pass/fail assertions. Use after running entrypoint-analysis or call-analysis when findings need validation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/solidity-language-server:poc-generator <file-path> [--finding N] [--all-high]<file-path> [--finding N] [--all-high]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate Foundry proof-of-concept tests that validate HIGH and CRITICAL security findings. Each POC is a self-contained Foundry test that demonstrates whether a finding is exploitable or safely mitigated.
Generate Foundry proof-of-concept tests that validate HIGH and CRITICAL security findings. Each POC is a self-contained Foundry test that demonstrates whether a finding is exploitable or safely mitigated.
$ARGUMENTS — Parse the arguments:
If no findings are provided in the arguments, check the conversation history for the most recent entrypoint-analysis or call-analysis output and extract findings from there.
From the conversation context or the target file, identify all HIGH and CRITICAL findings. For each finding, extract:
For each finding, determine the POC approach:
| Attack Vector | POC Type | Requires |
|---|---|---|
| Reentrancy via ERC-20 | Malicious token contract with reentrant transferFrom/transfer | Custom ERC-20 with callback hook |
| Reentrancy via ETH receive | Malicious contract with reentrant receive() | Attack contract deployed as swapper/recipient |
| Oracle manipulation | Adversarial oracle returning controlled prices | Mock oracle implementing the protocol's oracle interface |
| Access control bypass | Direct call from unauthorized address | vm.prank() with attacker address |
| Input validation bypass | Crafted parameters that skip checks | Specific parameter values that reach sinks |
| State corruption | Cross-entrypoint state manipulation | Multi-step scenario: write state in tx1, exploit in tx2 |
| Arithmetic overflow/underflow | Edge-case inputs near bounds | Large values, zero values, max uint values |
| Front-running / sandwich | Mempool-observable tx ordering | Multi-tx scenario with vm.prank for attacker/victim |
Search the test directory for:
Patterns to find:
- Test base contracts (SetupHook, BaseTest, etc.)
- Helper functions (_makeOrder, _swap, _netInput, etc.)
- Existing mock contracts (MockERC20, MockOracle, etc.)
- Deployment patterns (deployCodeTo, vm.etch, etc.)
- Import conventions
Use Glob test/**/*.sol and Grep to find:
Critical: Reuse existing test infrastructure. Do NOT duplicate setup logic that already exists in a base contract. Inherit from the base test contract and use its helpers (_swap, _makeOrder, _netInput, etc.).
For each finding, identify which interfaces the POC needs to interact with:
For each finding, generate the necessary attack contracts. Follow these patterns:
contract MaliciousToken {
// Minimal ERC-20 with reentrant hook in transferFrom
// Must: track balances, implement approve/transfer/transferFrom
// Attack: in transferFrom, call back into the target contract
// Control: arm() function to set attack parameters
// Safety: disarm after one re-entry to prevent infinite loops
// Tracking: reentrancyCalls counter for assertion verification
}
contract MaliciousReceiver {
// Contract with receive() that re-enters on ETH receipt
// Must: store attack parameters, disarm after one attempt
// Attack: in receive(), call fill() or other entrypoint
// Tracking: reentrancyCalls counter for assertion verification
}
contract AdversarialOracle {
// Implements the protocol's oracle interface exactly
// Exposes setter functions: setPrice(), setQuote(), setQuoteFromTick()
// Can be configured to return: normal prices, extreme prices, stale timestamps, or revert
// Keep state in simple mappings — no complex logic
}
Rules for attack contracts:
arm() function to configure attack parameters for reentrancy contractsarmed = false after first re-entry) to prevent infinite loopspragma) as the projectFor each finding, generate a test function that follows this structure:
/// @notice [Finding #N] <title>
/// @dev Validates: <what the POC proves>
/// Expected: <revert | succeed with profit | state corruption>
function test_poc_finding_N_<descriptive_name>() public {
// 1. SETUP — deploy attack contracts, configure state
// Reuse base contract helpers where possible
// 2. PRECONDITIONS — create the vulnerable state
// (e.g., create an order that will be attacked)
// 3. SNAPSHOT — record balances/state before attack
// 4. ATTACK — execute the exploit
// Use vm.prank() for attacker context
// Use vm.expectRevert() if the attack should fail
// 5. ASSERTIONS — verify the outcome
// Did the attacker profit? Did state corrupt?
// Compare pre/post balances
// Check invariants that should hold
}
Test naming convention: test_poc_finding_<N>_<snake_case_description>()
This is critical and easy to get wrong. Before writing any oracle manipulation or price-related POC, work out the directionality on paper first:
For a swap protocol with zeroForOne direction:
zeroForOne=true means: user sells token0, buys token1sqrtPriceX96 = sqrt(token1/token0)) determines the "fair" exchange ratezeroForOne=true: a higher oracle price (higher tick) means token0 is more valuable → user overpaidzeroForOne=false: a lower oracle price (lower tick) means token1 is more valuable → user overpaidBefore writing the test: write a 1-line comment: "Oracle at tick X means token0 is worth Y relative to token1, so the user [overpaid/underpaid]." If you can't write this comment confidently, read the _computeInputForOutputAtSqrtPrice function to understand the math.
For USD-denominated oracle manipulation:
inputPriceX18 > outputPriceX18 (adjusted for decimals) means user's input is worth more → user overpaidassertLt(fillerReceived, inputAvailable * 80 / 100, "filler lost >20%");
assertGt(userRebate, inputAvailable * 10 / 100, "user rebate >10%");
assertEq(fillerReceived + userRebate + protocolSurplus, inputAvailable, "all input accounted for");
-vvv) are the fastest way to diagnose:
emit log_named_uint("Input available", inputAvailable);
emit log_named_uint("Filler received", fillerReceived);
emit log_named_uint("User rebate", userRebate);
For every adversarial POC, write a matching control test that uses honest/fair parameters. This proves:
function test_poc_control_fair_<description>() public {
// Same setup as adversarial test, but with honest oracle/parameters
// Assert: filler gets ~100%, no surplus drained
}
For oracle manipulation findings, also test boundary values:
TickMath.MAX_TICK, TickMath.MIN_TICK)Important: edge cases are directional too. Work out what each extreme value means for the specific swap direction before asserting.
Some protocols enforce surplus capture only in one direction (e.g., user-side but not filler-side). When writing POCs:
preview.active is true for both directions or only oneCombine all POCs into a single test file. Structure:
// SPDX-License-Identifier: UNLICENSED
pragma solidity <same as project>;
// Imports — match existing test conventions
import {Test} from "forge-std/Test.sol";
// ... existing base contracts, deployers, types
// ============================================================
// Attack Contracts
// ============================================================
/// @notice Attack contract for Finding #N: <title>
contract <AttackContractName> {
// ...
}
// ============================================================
// POC Tests
// ============================================================
/// @title POC Validation Tests
/// @notice Generated from entrypoint-analysis findings
contract PocTest is <BaseTestContract> {
// ... setup if needed beyond base
// ================================================================
// Finding #N: <title>
// ================================================================
/// @notice Finding #1: <title>
function test_poc_finding_1_<name>() public {
// ...
}
// ================================================================
// Control tests
// ================================================================
/// @notice Control: fair parameters, no exploit
function test_poc_control_<name>() public {
// ...
}
}
Execute the generated test file with Forge. Always use --no-cache to ensure recompilation picks up new/changed test files:
forge test --match-path test/<TestFile>.t.sol --no-cache -vvv
If Forge reports "No tests found" or "No files changed, compilation skipped", the file was not picked up. Use --no-cache or --force to force recompilation.
For each test:
-vvv trace output, check setup, fix the POCWhen a test fails, follow this procedure:
Read the revert reason from the -vvv output. Common issues:
"SLIPPAGE" — set minAmountOut = 0 in setup swaps"UNKNOWN_POOL" — pool not initialized or wrong poolId"ORDER_ALREADY_FILLED" — order already consumed, check orderId derivation"FILL_AMOUNT_TOO_SMALL" — fillAmount must be >= 50% of remainingapprove() — filler must approve the hook for ERC-20 transfersvm.prank() — wrong msg.sender contextCheck directionality — if an assertion like "user should appear disadvantaged" fails, the oracle price direction is probably wrong. Re-read the directionality rules above and swap the oracle values.
Check the preview — if preview.active is false when you expect true, log the preview fields:
emit log_named_uint("claimShare", preview.claimShare);
emit log_named_uint("fairShare", preview.fairShare);
emit log_named_uint("deviationBps", preview.deviationBps);
Common causes: oracle is stale (maxAge exceeded), deviation is within maxDeviationBps tolerance, or the filler is disadvantaged (detection-only, active=false).
Fix and re-run with --no-cache. Repeat up to 3 times before marking as "needs manual review".
For each POC, produce a verdict:
## POC Results
| Finding | Test | Result | Verdict |
|---|---|---|---|
| #1 HIGH — Reentrancy via ERC-20 | test_poc_finding_1_reentrancy_erc20 | PASS (re-entry reverted) | MITIGATED by CEI — downgrade to LOW |
| #2 HIGH — Oracle manipulation | test_poc_finding_2_oracle_manipulation | PASS (surplus skewed) | CONFIRMED — filler lost 67% of input share |
Verdicts:
Present the full output in this order:
npx claudepluginhub asyncswap/skillsCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.