The Complete Production SOP: How to Deploy and Run Foundry Forge Invariant Tests & Fuzzing in Autonomous AI Agents
Step-by-step SOP to install and run Foundry Forge Invariant Tests & Fuzzing in Codex, Claude Code, and Cursor. Master handler architecture and fuzzing.
Technical Overview & Architectural Context
Autonomous coding agents frequently stumble when writing smart contract fuzz tests and invariant test suites in Foundry. Without deterministic procedural boundaries, LLMs default to standard unit tests or write stateless fuzz tests that assume arbitrary raw calldata can be sent directly to contract functions. In complex DeFi protocols involving stateful interactions (such as automated market makers, lending pools, or ERC-4626 vaults), stateless random fuzzing hits contract reverts in over 99% of generated executions due to uninitialized balances, unauthorized callers, or out-of-order sequence dependencies. Furthermore, unguided agents routinely fail to implement the Handler-based testing pattern, creating unbounded state spaces that overwhelm Foundry's fuzz engine and lead to false positives or test timeouts. They frequently omit proper `targetContract()`, `targetSelector()`, and `targetArtifactSelector()` configurations in `setUp()`, or fail to clamp input parameters using `bound()` and `vm.assume()`, triggering high discard-run ratios. The `foundry-forge-invariant-testing` skill fixes these systemic failures by injecting a rigorous, deterministic SOP into agent runtimes like Codex, Claude Code, Cursor, and Windsurf. It establishes strict rules: enforcing dedicated handler contracts that ghost-track protocol accounting, clamping fuzz domains correctly, implementing assertions using the required `invariant_*` naming conventions, and utilizing trace verbosity flags (`-vvvv`) to self-diagnose counterexamples.
Prerequisites & Environment Requirements
Verified Search Intents & Long-Tail Queries Solved
This operational tutorial is engineered to provide deterministic solutions for these developer search queries:
Step-by-Step Execution Playbook
Local Manifest Installation & Skill Mounting
Mount the SKILL.md file into the workspace agent directory to enable deterministic activation across runtimes.
Agent runtimes such as Claude Code, Cursor, and Windsurf index skill manifests from designated directory paths (e.g., `.agents/skills/` or `.cursor/rules/`). By placing the verified `SKILL.md` into your repository, the runtime parser registers frontmatter triggers and mounts execution rules into the LLM system context upon query matching.
mkdir -p .agents/skills/foundry-forge-invariant-testing && curl -fsSL https://askills.dev/api/skills/foundry-forge-invariant-testing/raw -o .agents/skills/foundry-forge-invariant-testing/SKILL.md---
name: "foundry-forge-invariant-testing"
description: "Write blazing-fast property-based fuzz tests and invariant checks in native Solidity."
metadata:
author: "Foundry"
version: "1.0.0"
runtime_compatibility:
- "codex"
- "claude-code"
- "cursor"
- "windsurf"
triggers:
- "write foundry invariant test"
- "run forge test fuzzing"
- "create handler based fuzzing foundry"
permissions:
fs_read: true
fs_write: "workspace"
shell_exec: false
net_access: false
---To save LLM tokens, keep helper contracts modular: instruct the agent to inspect interfaces rather than feeding 1,000+ line protocol contracts into prompt context.
Ensure the agent's file system write permission is restricted to the workspace root. Do not grant global shell write access outside repo boundaries.
Configuring foundry.toml for Fuzz & Invariant Depth
Tune the invariant engine parameters in foundry.toml to ensure sufficient depth without causing agent timeouts.
Foundry's default fuzz and invariant settings can cause autonomous test runs to stall or fail prematurely if run counts and call sequence depths are misconfigured. Configure invariant depth, run counts, and fail-on-revert behavior explicitly in `foundry.toml` so the agent can execute deterministic self-verification checks.
cat << 'EOF' >> foundry.toml
[invariant]
runs = 256
depth = 32
fail_on_revert = false
shrink_run_limit = 5000
EOF[profile.default]
src = "src"
out = "out"
libs = ["lib"]
[invariant]
runs = 256
depth = 32
fail_on_revert = false
call_override = false
dictionary_weight = 80Set fail_on_revert = false during initial agent scaffolding to let the handler gracefully filter out invalid actions before turning on strict invariant assertions.
Setting runs > 1000 or depth > 128 during interactive agent sessions can cause tool execution timeouts in Claude Code and Cursor background runners.
Handler-Based Architecture Implementation
Instruct the agent to scaffold a dedicated Handler contract that clamps inputs and tracks ghost state.
A robust invariant test requires an intermediate Handler contract. The Handler acts as the caller proxy, clamping random inputs into meaningful domains (e.g., constraining deposit amounts to realistic user balances and selecting actors from a pre-seeded address array) while maintaining ghost variables to compare protocol state against ground truth.
forge build// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {ERC20Vault} from "../../src/ERC20Vault.sol";
contract Handler is Test {
ERC20Vault public immutable vault;
uint256 public ghost_totalDeposited;
address[] public actors;
address internal currentActor;
constructor(ERC20Vault _vault) {
vault = _vault;
actors.push(address(0xAAAA));
actors.push(address(0xBBBB));
}
modifier useActor(uint256 actorIndexSeed) {
currentActor = actors[bound(actorIndexSeed, 0, actors.length - 1)];
vm.startPrank(currentActor);
_;
vm.stopPrank();
}
function deposit(uint256 actorSeed, uint256 amount) external useActor(actorSeed) {
amount = bound(amount, 1, 100_000 ether);
deal(address(vault.asset()), currentActor, amount);
vault.asset().approve(address(vault), amount);
vault.deposit(amount, currentActor);
ghost_totalDeposited += amount;
}
}Use ghost variables (prefixed with ghost_) to record intended balance mutations; this enables instant parity checks against protocol state in the invariant contract.
Avoid using vm.assume() with overly restrictive conditions in the handler as it exhausts Foundry's max-assumes limit. Use bound() instead.
Executing Invariant Verification & Self-Healing Loop
Run invariant tests with high verbosity and trigger agent self-healing on failure traces.
The skill mandates executing `forge test --match-test invariant_ -vvvv` upon test file generation. If an invariant is violated, Foundry outputs the sequence of calls and counter-example inputs that broke the property. The agent reads this counterexample trace, identifies the logical loophole, and either patches the contract or adjusts the test invariant boundary.
forge test --match-test invariant_ -vvvv// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {ERC20Vault} from "../../src/ERC20Vault.sol";
import {Handler} from "../handlers/Handler.sol";
contract VaultInvariants is Test {
ERC20Vault public vault;
Handler public handler;
function setUp() public {
vault = new ERC20Vault();
handler = new Handler(vault);
targetContract(address(handler));
bytes4[] memory selectors = new bytes4[](1);
selectors[0] = Handler.deposit.selector;
targetSelector(FuzzSelector({addr: address(handler), selectors: selectors}));
}
function invariant_vaultAssetsMatchGhostDeposits() public view {
assertEq(
vault.totalAssets(),
handler.ghost_totalDeposited(),
"Solvency invariant broken: vault assets diverge from ghost deposits"
);
}
}Always inspect the git diff of modified contracts before finalizing agent tasks to confirm the agent didn't weaken the invariant check rather than fixing the underlying vulnerability.
Forgetting targetContract(address(handler)) will cause Foundry to fuzz both the Handler and test contracts directly, leading to broken assertions via corrupted test harness state.
Real-World Scenario: Developing a Handler-Based Invariant Suite for an ERC-4626 Yield Vault
“Create a handler-based invariant test suite for our ERC20Vault contract using Foundry. Ensure that vault assets never fall below total ghost-tracked deposits across arbitrary user deposit and withdraw sequences.”
Runtime Troubleshooting & Failure Modes
Root Cause: A sequence of calls broke the state invariant. Foundry provided a counterexample trace showing rounding errors down to 0 in withdrawal share calculation.
Inspect the trace from 'forge test --match-test invariant_vaultSolvency -vvvv'. Update the vault's share calculation logic to round against the user in src/ERC20Vault.sol, or clamp minimum initial liquidity deposits in the handler: bound(amount, 1000, 100_000 ether).Root Cause: The agent used vm.assume(condition) with an excessively narrow filter condition inside a fuzzed handler function, exhausting Foundry's maximum rejection threshold.
Replace 'vm.assume(amount > 0 && amount < max)' with the Foundry bounding helper: 'amount = bound(amount, 1, max);' in test/handlers/Handler.sol.Frequently Asked Questions (Foundry Forge Invariant Tests & Fuzzing SOP)
Q:Can I use Foundry Forge Invariant Tests & Fuzzing across Codex, Claude Code, and Cursor simultaneously?
Yes. This skill conforms to the universal SKILL.md specification. It mounts identically in Claude Code (.agents/skills/), Cursor (.cursor/rules/), and Codex workspaces without any modifications.
Q:How does Foundry Forge Invariant Tests & Fuzzing prevent context window overflow during long agent sessions?
The skill instructs the agent to analyze target interfaces instead of loading full implementations, isolate handlers in dedicated files, and only request the final counter-example trace (-vvvv) upon test failure rather than re-reading the entire test logs.
Q:Does this skill require escalated root or container permissions?
No. The skill is audited with a 98/100 security score. It operates strictly within workspace filesystem boundaries (fs_write: workspace) and relies exclusively on standard Foundry CLI commands.
Ready to equip your autonomous agent with Foundry Forge Invariant Tests & Fuzzing?
Copy the complete audited SKILL.md specification or download the 1-click zip bundle directly from our verified registry.