example-fork-detection
TEMPLATE — replace with the description of your rule. Should activate on the specific code patterns your fork has. Activate on…
Detect cross-contract state inconsistency — two or more contracts sharing a token, oracle, or price feed where one mutates and another reads stale, cached state that drifts from source of truth, non-atomic multi-contract updates, accounting that assumes synchronized state, and
Claude Code
$ npx -y skills add omermaksutii/RugProof --skill cross-contract-state --agent claude-code
Codex
$ npx skills add omermaksutii/RugProof --skill cross-contract-state -a codex -y
Cursor
$ npx skills add omermaksutii/RugProof --skill cross-contract-state -a cursor -y
Gemini CLI
$ npx skills add omermaksutii/RugProof --skill cross-contract-state -a gemini-cli -y
GitHub Copilot
$ npx skills add omermaksutii/RugProof --skill cross-contract-state -a github-copilot -y
OpenCode
$ npx skills add omermaksutii/RugProof --skill cross-contract-state -a opencode -y
Windsurf
$ npx skills add omermaksutii/RugProof --skill cross-contract-state -a windsurf -y
name: cross-contract-state description: Detect cross-contract state inconsistency — two or more contracts sharing a token, oracle, or price feed where one mutates and another reads stale, cached state that drifts from source of truth, non-atomic multi-contract updates, accounting that assumes synchronized state, and reads during callbacks. Activate whenever a system spans multiple contracts that must agree on a value but update at different times.
Trigger on any of:
// Vault.deposit() transfers to Strategy, then: uint256 tvl = strategy.totalAssets(); // ← strategy hasn't accounted the deposit yet shares = amount * totalSupply / tvl; // wrong denominator
**Signal:** B reads A while A's update is incomplete (or vice versa). Share/price math uses a denominator that's about to change, letting an attacker mint mispriced shares. This is the read-only-reentrancy family generalized to ordinary call ordering.
uint256 public cachedPrice; // updated by poke()
function valueOf(uint256 amt) external view returns (uint256) {
return amt * cachedPrice / 1e18; // ← may be hours stale
}**Signal:** a mirrored value with no freshness guarantee or no atomic refresh-before-use. Trades/loans price off a cache that diverged from the source contract.
registry.setActive(id, true); // external call here can observe registry=active but vault=uninitialized vault.initialize(id);
**Signal:** state spread across contracts is updated in sequence; a reentrant or interleaved call observes a half-applied transaction (one contract updated, the other not). Invariants that span both contracts are temporarily violated.
Two contracts both read the same oracle/token but at different blocks or via different cached copies, then reconcile assuming equality. **Signal:** `assert(A.totalSupply() == B.mirroredSupply())`-style assumptions with independent update paths.
A hook/plugin invoked mid-operation calls back and reads the core contract's not-yet-finalized accounting — the Curve/Balancer read-only reentrancy shape, but also plain composability (e.g. an ERC-4626 vault read by a money-market during the vault's own deposit).
| Pattern | Severity | Notes | |---|---|---| | Stale cross-contract read in share/price math | **High** | Mispriced mint/redeem | | Read-during-callback of unfinalized state | **High** | Read-only reentrancy class | | Non-atomic multi-contract invariant break | **High** | Half-applied state observable | | Cached value drift used for valuation | **High** | Stale pricing | | Synchronized-state assumption across modules | **Medium** | Reconciliation error |
1. **Single source of truth** — derive values from one authoritative contract at read time; avoid mirrored copies, or make the mirror push-updated atomically in the same tx. 2. **Update-then-read ordering** — ensure A finalizes accounting before B reads; apply CEI across the call boundary, not just within a function. 3. **Atomic multi-contract updates** — batch via a single entrypoint / multicall that completes all writes before any external observation; guard with a system-wide reentrancy lock. 4. **Freshness gates on caches** — `require(block.timestamp - lastUpdate <= maxAge)` or refresh-on-read. 5. **Reentrancy-guard the read path too** (Balancer `ensureNotInVaultContext` / Curve lock) when callbacks can observe state.
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
TEMPLATE — replace with the description of your rule. Should activate on the specific code patterns your fork has. Activate on…
Detect unsafe assumptions about Solady's gas-optimized ERC20/ERC2612 permit and DN404 metadata. Solady's ERC20 uses custom…
Detect front-runnable ownership initialization in Solady Ownable / OwnableRoles. Solady's `_initializeOwner` is a guarded…
Detect Solady SafeTransferLib calls that assume the token has code. SafeTransferLib.safeTransfer/safeTransferFrom/safeApprove…
Detect Uniswap V4 hooks that fail to settle currency deltas with the PoolManager. Every credit/debit a hook creates…
Detect Uniswap V4 hooks whose address-encoded permission flags don't match the callbacks the hook actually implements. In V4 the…
Use for authorized security assessment of REST, GraphQL, WebSocket, or SOAP APIs, including discovery, authentication,…
Extract and decrypt Windows DPAPI-protected secrets (Credential Manager, browser logins/cookies, Wi-Fi credentials, KeePass keys)…
Clean and finalize authorized natural-language text intended for readers by auditing suspicious invisible Unicode and rewriting…