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 fee-on-transfer / deflationary / rebasing token accounting bugs — crediting the *passed amount* instead of the measured balance delta. Activate whenever code calls transfer/transferFrom and then credits, mints shares for, or records the literal amount argument, in
$ npx -y skills add omermaksutii/RugProof --skill fee-on-transfer --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/fee-on-transferContext preview
The summary Claude sees to decide when to auto-load this skill.
Detect fee-on-transfer / deflationary / rebasing token accounting bugs — crediting the *passed amount* instead of the measured balance delta. Activate whenever code calls transfer/transferFrom and then credits, mints shares for, or records the literal amount argument, in
name: fee-on-transfer description: Detect fee-on-transfer / deflationary / rebasing token accounting bugs — crediting the *passed amount* instead of the measured balance delta. Activate whenever code calls transfer/transferFrom and then credits, mints shares for, or records the literal amount argument, in deposits, AMM swaps, lending collateral, vaults, staking, or bridges — without measuring balanceAfter - balanceBefore.
Trigger on any of:
function deposit(uint256 amount) external {
token.transferFrom(msg.sender, address(this), amount);
balanceOf[msg.sender] += amount; // ← amount, but FoT token delivered less
totalDeposited += amount;
}**Signal:** the contract believes it holds more than it does. On a fee-on-transfer token (USDT with fee switch enabled, PAXG, many meme tokens), `totalDeposited` exceeds the real balance — last withdrawers are insolvent, or an attacker mints excess shares/LP.
Router records input `amountIn` into reserves but the pair received `amountIn - fee`. `k` invariant is computed on phantom reserves, mispricing the swap and letting an attacker drain the shortfall over time (the Uniswap-V2-style "supports FoT" `*SupportingFeeOnTransferTokens` functions exist precisely because of this).
uint256 shares = amount * totalShares / totalAssets; // totalAssets from stored var
**Signal:** stored `totalAssets` vs live `token.balanceOf(this)` diverge as a positive-rebasing token (stETH, aTokens, OHM) grows. Crediting against the stale stored figure under- or over-mints shares; an attacker times deposits around rebase.
Lock `amount` on chain A, emit message, mint `amount` on chain B — but only `amount - fee` was locked. Repeated deposits accumulate an unbacked mint surplus that can be withdrawn on A by honest users, draining the lockbox.
| Pattern | Severity | Notes | |---|---|---| | Bridge mints unbacked surplus | **Critical** | Cross-chain insolvency | | Vault/LP shares minted on phantom amount | **High** | Direct over-mint, fund loss | | AMM reserve recorded as passed amount | **High** | Mispricing, drainable shortfall | | Lending collateral over-credited | **High** | Under-collateralized borrow | | Rebasing stored-vs-live `totalAssets` drift | **Medium** | Timing-dependent | | FoT token on a hard whitelist of non-FoT tokens | **Info** | Surface closed by policy |
1. **Measure the delta:** `uint256 before = token.balanceOf(address(this)); token.transferFrom(...); uint256 received = token.balanceOf(address(this)) - before;` and credit `received`. 2. **Reject FoT on entry** — if `received != amount`, revert (OpenZeppelin `SafeERC20` does not do this for you). 3. **Use live balance, not stored totals**, for rebasing tokens, or wrap them (wstETH) so balances are non-rebasing. 4. **Token allowlist** for assets known not to take a transfer fee; document the assumption. 5. **On bridges**, lock the *measured* received amount and mint exactly that.
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…