example-fork-detection
TEMPLATE — replace with the description of your rule. Should activate on the specific code patterns your fork has. Activate on `<your trigger keywords or…
Detect ERC-4626 inflation/donation attacks — first depositor share-price manipulation, naive convertToShares math, missing virtual-shares defense. Activate on any ERC-4626 vault implementation, share/asset math, `convertToShares`, `convertToAssets`, `previewDeposit`,
$ npx -y skills add omermaksutii/RugProof --skill erc4626-inflation --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/erc4626-inflationContext preview
The summary Claude sees to decide when to auto-load this skill.
Detect ERC-4626 inflation/donation attacks — first depositor share-price manipulation, naive convertToShares math, missing virtual-shares defense. Activate on any ERC-4626 vault implementation, share/asset math, `convertToShares`, `convertToAssets`, `previewDeposit`,
name: erc4626-inflation description: Detect ERC-4626 inflation/donation attacks — first depositor share-price manipulation, naive convertToShares math, missing virtual-shares defense. Activate on any ERC-4626 vault implementation, share/asset math, `convertToShares`, `convertToAssets`, `previewDeposit`, `previewMint`, `totalAssets`, `_decimalsOffset`.
The classic "donation" or "inflation" attack on share-based vaults. First depositor mints 1 wei share for 1 wei of asset → share price = 1. Attacker then donates large amount directly to the vault (bypassing deposit) → totalAssets balloons → share price massively inflated → next depositor rounds shares to 0.
Has caused real, public losses across many forks of naive ERC-4626.
function convertToShares(uint256 assets) public view returns (uint256) {
if (totalSupply() == 0) return assets;
return assets * totalSupply() / totalAssets();
}First deposit: 1 share for 1 asset. Donation: totalAssets += 1e30. Next depositor's 1 asset → `1 * 1 / 1e30 = 0` shares. Funds lost.
Defense: OZ ERC4626 v4.9+ uses `_decimalsOffset()` to mint "virtual" shares:
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual override returns (uint256) {
return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
}This adds N virtual shares (typically 10**6 — 6 decimals offset) that nobody owns. Donations have far less leverage.
Alternative defense: mint a fixed amount (e.g. 1000 shares) to `address(0)` or `address(this)` on first deposit. Locks initial donation impact.
If first depositor deposits a large amount (say, 10K USDC), donation impact is bounded.
Some impls require admin to seed vault before allowing deposits. Acceptable defense; centralized but bounded.
ERC-4626 spec: rounding favors the vault.
Reversed direction = attacker can extract value via rounding.
Vaults holding rebasing tokens (stETH, etc.) — totalAssets() increases via rebase, not via internal accounting. Attack vectors similar but defenseless naive impl.
Naive implementation that always sums the actual balance is the vulnerable shape. Better: track internally and rebase carefully.
| Pattern | Severity | |---|---| | Naive share math + no virtual shares + no dead-shares + no min-deposit | **Critical** | | Naive math + minimum first deposit but very low | **High** | | Asymmetric round direction (favoring depositor) | **High** | | `totalAssets() = balanceOf(this)` with rebasing token, no defense | **High** | | Virtual shares of 0 decimals offset on 18-decimal asset (insufficient) | **High** | | Dead-shares but very small (e.g. 1 wei) | **High** | | OZ ERC4626 v4.9+ with default offset (6) | **Info** *(canonical defense)* |
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
Repo: omermaksutii/RugProof
TEMPLATE — replace with the description of your rule. Should activate on the specific code patterns your fork has. Activate on `<your trigger keywords or…
Detect unsafe assumptions about Solady's gas-optimized ERC20/ERC2612 permit and DN404 metadata. Solady's ERC20 uses custom storage slots, returns bools via…
Detect front-runnable ownership initialization in Solady Ownable / OwnableRoles. Solady's `_initializeOwner` is a guarded one-time setter (it reverts with…
Detect Solady SafeTransferLib calls that assume the token has code. SafeTransferLib.safeTransfer/safeTransferFrom/safeApprove deliberately skip the EXTCODESIZE…
Detect Uniswap V4 hooks that fail to settle currency deltas with the PoolManager. Every credit/debit a hook creates (BeforeSwapDelta, afterSwap hookDelta,…
Detect Uniswap V4 hooks whose address-encoded permission flags don't match the callbacks the hook actually implements. In V4 the hook's permissions live in the…