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 Vyper-specific bug classes — compiler-version reentrancy bugs, raw_call return-value handling, send/raw_call gas defaults, immutables vs constants, default-bytes scope, msg.sender-based auth corner cases. Activate on any `.vy` file or `pragma version` directive.
$ npx -y skills add omermaksutii/RugProof --skill vyper-specific --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/vyper-specificContext preview
The summary Claude sees to decide when to auto-load this skill.
Detect Vyper-specific bug classes — compiler-version reentrancy bugs, raw_call return-value handling, send/raw_call gas defaults, immutables vs constants, default-bytes scope, msg.sender-based auth corner cases. Activate on any `.vy` file or `pragma version` directive.
name: vyper-specific description: Detect Vyper-specific bug classes — compiler-version reentrancy bugs, raw_call return-value handling, send/raw_call gas defaults, immutables vs constants, default-bytes scope, msg.sender-based auth corner cases. Activate on any `.vy` file or `pragma version` directive.
Vyper has different defaults than Solidity. Some patterns that are safe in Solidity are unsafe in Vyper, and vice versa. Vyper has also had compiler-level reentrancy bugs (most famously the 2023 Curve incident). Audit Vyper with Vyper-aware eyes.
Vyper 0.2.15 / 0.2.16 / 0.3.0 had a broken `@nonreentrant` lock — the lock was per-key, but the same key was reused across functions, causing locks to fail to engage in some compositions. Caused the Curve / Vyper exploit (July 2023, ~$73M).
Action: any contract pinned to `# @version 0.2.15`, `0.2.16`, or `0.3.0` — treat reentrancy guards as **broken**. Recommend upgrade to ≥ 0.3.7 (or 0.3.10+ for ongoing support).
# @version 0.2.15 # ← FLAG: known-broken nonreentrant
raw_call(target, b"...") # ← return data ignored entirely
Default `revert_on_failure=True` will revert the calling tx on a revert, but the *return data* is silently dropped. If you need to read the return:
ok: bool = raw_call(target, b"...", max_outsize=32, revert_on_failure=False)
Same problem as Solidity `transfer`. Smart-wallet recipients can't receive. On L2s with different base costs (Berachain, etc.), the 2300 gas bound is unreliable.
Use `raw_call(addr, b"", value=amount)` with explicit gas bounds.
`HashMap[address, uint256]` defaults to 0 for unset keys. Vyper devs sometimes assume "exists vs not-exists" semantics that don't apply. Use a paired bool flag if existence matters.
Same EIP-6780 caveats apply ([[selfdestruct-eip6780]]). Vyper code that relies on selfdestruct deleting code is broken post-Cancun on mainnet (March 2024).
EIP-1167 minimal-proxy clones via `create_forwarder_to`. Same gotcha as Solidity: per-clone init guard required, or anyone can initialize a fresh clone.
Vyper's `decimal` type is fixed-point with limited range. Mixing `uint256` and `decimal` for price math invites overflow or precision loss. Most modern Vyper code avoids `decimal`; if present, audit carefully.
Vyper enforces explicit visibility (`@external`, `@internal`, `@view`, `@pure`). Functions without visibility are syntax errors — but `@external` + missing access checks are still a foot-gun (same as Solidity).
Vyper allows `msg.sender == self` checks — used to gate "callable only from internal call". Easy to typo `msg.sender == self.owner` vs `== self`. Check the intent.
`assert` and `raise` both revert. `assert <cond>` reverts if false; `raise "reason"` always reverts. Mix-up on devs coming from Solidity. Both are safe; just consistency.
Vyper isn't typically used for upgradeable contracts (no equivalent of OZ Upgradeable plugin). If you find a Vyper proxy implementation, audit storage layout very carefully — reserved slots aren't a Vyper convention.
| Pattern | Severity | |---|---| | Vyper 0.2.15/0.2.16/0.3.0 with `@nonreentrant` | **Critical** *(known-broken)* | | `raw_call` to user-supplied target | **High** | | `send` (2300 gas) to dynamic recipient on L2 | **High** | | `selfdestruct` for state cleanup post-Cancun | **High** | | `create_forwarder_to` without per-clone init guard | **High** | | Decimal scaling errors in price math | **High** | | Storage layout in Vyper proxy without explicit reservation | **High** | | `default_value` semantics misuse | **Medium** | | `assert` vs `raise` inconsistency | **Low** |
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…