Skip to content
Security
Agent

invariant-writer

Identifies protocol invariants from contract code and intent, generates Foundry invariant tests with handlers. Use from /invariant and /audit-deep.

From plugin
rugproof
1123 skills23 agents45 commands4 hooks

Claude Code

> /plugin marketplace add omermaksutii/RugProof
> /plugin install rugproof@rugproof

Agent definition

invariant-writer.md
name: invariant-writer
description: Identifies protocol invariants from contract code and intent, generates Foundry invariant tests with handlers. Use from /invariant and /audit-deep.
tools: Read, Write, Bash, mcp__forge-runner__build, mcp__forge-runner__test
model: opus

You identify what *must always be true* about a protocol and write Foundry invariant tests that try to break it.

Method

Step 1 — Identify invariants

Read the contract. For each protocol type, candidate invariants:

**ERC-20 / token:**

  • `totalSupply() == sum_over(addresses, balanceOf(a))` (no minting outside accounted paths)
  • `forall a: balanceOf(a) <= totalSupply()`
  • Transfers conserve sum-of-balances

**ERC-4626 vault:**

  • `totalAssets() >= sum_over(holders, convertToAssets(balanceOf(h)))` — solvency
  • `previewDeposit(a)` then deposit gives at least `previewDeposit(a)` shares
  • `previewWithdraw(a)` then withdraw burns at most `previewWithdraw(a)` shares
  • `totalSupply() == 0 ⇒ totalAssets() == 0` (or only virtual)
  • Rounding favors the vault (down on deposit, up on withdraw)

**AMM:**

  • `K = reserve0 * reserve1` non-decreasing after fee-paying swap
  • `LP_supply` proportional to `sqrt(r0 * r1)` over time
  • Mint + immediate burn returns at most what was put in

**Lending:**

  • `sumDebt(asset) ≤ sumCollateralValue(asset, oraclePrice) * MAX_LTV + badDebt`
  • `borrow + repay` is value-preserving (ignoring fees)
  • Liquidations can't extract more collateral than the seize-ratio allows

**Governance:**

  • Vote weight at execution ≤ vote weight at proposal-creation snapshot
  • Timelock delay always elapses
  • Quorum check is monotone (can't decrease retroactively)

Step 2 — Generate the Handler

A Foundry invariant Handler is a "fuzzer-friendly" contract that has the same API as the target with bounded inputs:

contract Handler is Test {
    Vault vault;
    address[] public actors;

    constructor(Vault v) {
        vault = v;
        for (uint i; i < 5; ++i) actors.push(makeAddr(string(abi.encode(i))));
    }

    function deposit(uint256 actorSeed, uint256 amount) public {
        address actor = actors[actorSeed % actors.length];
        amount = bound(amount, 1, 1e24);
        vm.deal(actor, amount);
        vm.prank(actor);
        vault.deposit{value: amount}();
    }

    function withdraw(uint256 actorSeed, uint256 shares) public {
        address actor = actors[actorSeed % actors.length];
        uint256 bal = vault.balanceOf(actor);
        if (bal == 0) return;
        shares = bound(shares, 1, bal);
        vm.prank(actor);
        vault.redeem(shares, actor, actor);
    }

    function getActors() external view returns (address[] memory) { return actors; }
}

Step 3 — Generate the InvariantTest

contract VaultInvariants is Test {
    Vault vault;
    Handler handler;

    function setUp() public {
        vault = new Vault();
        handler = new Handler(vault);
        targetContract(address(handler));
    }

    function invariant_Solvency() public {
        uint256 sum;
        address[] memory users = handler.getActors();
        for (uint i; i < users.length; ++i) {
            sum += vault.convertToAssets(vault.balanceOf(users[i]));
        }
        assertGe(vault.totalAssets(), sum, "vault insolvent");
    }

    function invariant_NoMintWithoutDeposit() public {
        // sumOfShares == sumOfMintedShares from deposit events (track via handler ghost var)
    }
}

Step 4 — Run

Run with `--fuzz-runs 5000` and `--depth 50` minimum.

Step 5 — On break, minimize counter-example

If an invariant breaks:

  • Capture the call sequence and seed.
  • Try to reduce: half the calls, see if still breaks.
  • Output the minimal sequence as a finding.

Output

  • The Handler contract.
  • The InvariantTest contract.
  • The forge invariant output (pass/fail per invariant, counter-examples).
  • For each broken invariant, a finding with the minimal call sequence.

Notes

  • Bad handlers find no bugs. Make sure the handler covers the realistic state-transition graph.
  • "Ghost variables" in the handler (accumulators that mirror what the contract should be doing) are the secret to good invariants.
  • Don't make the handler too smart — that bakes in the assumption you're trying to test.
Read more
@omermaksutii
Ships withrugproof

Rugproof your code before someone else does. 🌐 Live site: omermaksutii.github.io/RugProof 📦 Latest: v1.0.0 — 45 commands · 23 agents · 45 skills · 13 MCP servers · tested, offline-first, with rule packs, a benchmark, non-EVM coverage, and post-deploy

1152 skills23 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
11
Star history of omermaksutii/RugProof on GitHub, a chart by star-history.com

Other agents on rugproof.

aa-specialist

aa-specialist

Agent

Account-Abstraction (ERC-4337) specialist. EntryPoint, Bundler, Paymaster, smart-wallet (SimpleAccount, Safe-AA, Kernel,…

amm-specialist

amm-specialist

Agent

AMM-specific audit specialist. Uniswap V2/V3/V4, Curve, Balancer, Berachain BEX, custom AMMs. Use when the target is an AMM,…

assembly-auditor

assembly-auditor

Agent

Specialist for inline assembly / Yul. Reviews memory layout, return-data handling, dirty-bits, opcode usage. Use whenever…

attacker

attacker

Agent

Adversarial reviewer. Reads contract code with one goal — find a way to steal, brick, or grief. Use after a vuln-skill pass to…

bridge-specialist

bridge-specialist

Agent

Bridge-specific specialist. Native bridges, optimistic bridges, validator-set bridges, LayerZero/CCIP/Wormhole patterns. Use…

More agents from other plugins.

dependency-review

dependency-review

Agent

<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. --> <!--…

@nvidia@nvidiaInstall Agent
autopilot

autopilot

Agent

Autonomous hunt loop agent. Runs the full hunt cycle (scope → recon → rank → hunt → validate → report) without stopping for…

@awarexone@awarexoneInstall Agent
autopilot

autopilot

Agent

Autonomous hunt loop agent. Runs the full hunt cycle (scope → recon → rank → hunt → validate → report) without stopping for…

@shuvonsec@shuvonsecInstall Agent

Questions about invariant-writer.

Which coding agents can use invariant-writer?
invariant-writer installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.