skills/solidity-audit/SKILL.md
Security audit and code review checklist. Covers 30+ vulnerability types with real-world exploit cases (2021-2026) and EVMbench Code4rena patterns. Use when conducting security audits, code reviews, or pre-deployment security assessments.
npx skillsauth add 0xlayerghost/solidity-agent-kit solidity-auditInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Usage: This skill is for security audits and code reviews. It is NOT auto-invoked — call
/solidity-auditwhen reviewing contracts for vulnerabilities.
| Variant | Description | Check |
|---------|-------------|-------|
| Same-function | Attacker re-enters the same function via fallback/receive | All external calls after state updates (CEI pattern)? |
| Cross-function | Attacker re-enters a different function sharing state | All functions touching shared state protected by nonReentrant? |
| Cross-contract | Attacker re-enters through a different contract that reads stale state | External contracts cannot read intermediate state? |
| Read-only | View function returns stale data during mid-execution state | No critical view functions used as oracle during state transitions? |
Case: GMX v1 (Jul 2025, $42M) — reentrancy in GLP pool on Arbitrum, attacker looped withdrawals to drain liquidity.
| Check | Detail | |-------|--------| | Missing modifier | Every state-changing function has explicit access control? | | Modifier logic | Modifier actually reverts on failure (not just empty check)? | | State flag | Access-once patterns properly update storage after each user? | | Admin privilege scope | Owner powers are minimal and time-limited? |
Case: Bybit (Feb 2025, $1.4B) — Safe{Wallet} UI injected with malicious JS, hijacked signing process. Not a contract flaw, but access control at the infrastructure layer.
| Check | Detail |
|-------|--------|
| Zero address | All address params reject address(0)? |
| Zero amount | Fund transfers reject zero amounts? |
| Array bounds | Paired arrays validated for matching length? |
| Arbitrary call | No unvalidated address.call(data) where attacker controls data? |
| Numeric bounds | Inputs bounded to prevent dust attacks or gas griefing? |
| Variant | Mechanism | Defense | |---------|-----------|---------| | Price manipulation | Flash-borrow → swap to move price → exploit price-dependent logic → repay | TWAP oracle with min-liquidity check | | Governance | Flash-borrow governance tokens → vote → repay in same block | Snapshot voting + minimum holding period + timelock ≥ 48h | | Liquidation | Flash-borrow → manipulate collateral value → trigger liquidation | Multi-oracle price verification + circuit breaker | | Combo (rounding) | Flash-borrow → manipulate pool → micro-withdrawals exploit rounding → repay | Minimum withdrawal amount + virtual shares |
Cases:
| Check | Detail |
|-------|--------|
| Single oracle dependency | Using multiple independent price sources? |
| Stale price | Checking updatedAt timestamp and rejecting old data? |
| Spot price usage | Never using raw AMM reserves for pricing? |
| Minimum liquidity | Oracle reverts if pool reserves below threshold? |
| Price deviation | Circuit breaker if price moves beyond threshold vs last known? |
| Chainlink round completeness | Checking answeredInRound >= roundId? |
Case: Cream Finance (Oct 2021, $130M) — attacker manipulated yUSD vault price by reducing supply, then used inflated collateral to drain all lending pools.
| Type | Description | Defense |
|------|-------------|---------|
| Primitive overflow | uint256 a = uint8(b) + 1 — reverts if b=255 on Solidity ≥0.8 | Use consistent types, avoid implicit narrowing |
| Truncation | int8(int256Value) — silently overflows even on ≥0.8 | Use SafeCast library for all type narrowing |
| Rounding / precision loss | usdcAmount / 1e12 always rounds to 0 for small amounts | Multiply before divide; check for zero result |
| Division before multiplication | (a / b) * c loses precision | Always (a * c) / b |
Case: Bunni (Sep 2025, $8.4M) — rounding errors in micro-withdrawals exploited via flash loan.
| Type | Description | Defense |
|------|-------------|---------|
| ecrecover returns address(0) | Invalid sig returns address(0), not revert | Always check recovered != address(0) |
| Replay attack | Same signature reused across txs/chains | Include chainId + nonce + deadline in signed data |
| Signature malleability | ECDSA has two valid (s, v) pairs per signature | Use OpenZeppelin ECDSA.recover (enforces low-s) |
| Empty loop bypass | Signature verification in for-loop, attacker sends empty array | Check signatures.length >= requiredCount before loop |
| Missing msg.sender binding | Proof/signature not bound to caller | Always include msg.sender in signed/proven data |
| Issue | Description | Defense |
|-------|-------------|---------|
| Fee-on-transfer | transfer(100) may deliver <100 tokens | Check balance before/after, use actual received amount |
| Rebase tokens | Token balances change without transfers | Never cache external balances; always read live |
| No bool return | Some tokens (USDT) don't return bool on transfer | Use SafeERC20.safeTransfer |
| ERC777 hooks | Transfer hooks can trigger reentrancy | Use ReentrancyGuard on all token-receiving functions |
| Zero-amount transfer | transferFrom(A, B, 0) — address poisoning | Reject zero-amount transfers |
| Approval race | Changing allowance from N to M allows spending N+M | Use safeIncreaseAllowance / safeDecreaseAllowance |
| Type | Description | Defense |
|------|-------------|---------|
| Sandwich attack | Attacker front-runs buy + back-runs sell around victim | Slippage protection + deadline parameter |
| ERC4626 inflation | First depositor donates to inflate share price, rounding out later depositors | Minimum first deposit or virtual shares (ERC4626 with offset) |
| Approval front-run | Attacker spends old allowance before new allowance tx confirms | Use increaseAllowance not approve |
| Unrestricted withdrawal | Attacker monitors mempool for withdraw tx, front-runs with own | Require commit-reveal or auth binding |
| Issue | Description |
|-------|-------------|
| Storage pointer | Foo storage foo = arr[0]; foo = arr[1]; — does NOT update arr[0] |
| Nested delete | delete structWithMapping — inner mapping data persists |
| Private variables | All contract storage is publicly readable via eth_getStorageAt |
| Unsafe delegatecall | Delegatecall to untrusted contract can selfdestruct the caller |
| Proxy storage collision | Upgrade changes parent order → variables overwrite each other (use storage gaps) |
| msg.value in loop | msg.value doesn't decrease in loop — enables double-spend |
| Method | How it works |
|--------|-------------|
| Constructor call | Attack from constructor — extcodesize == 0 during deployment |
| CREATE2 pre-compute | Pre-calculate contract address, use as EOA before deploying |
Source: EVMbench Paper §4.2, Appendix H / Code4rena 2024-07-basin H-01
| Check | Detail |
|-------|--------|
| _authorizeUpgrade access control | UUPS _authorizeUpgrade must have onlyOwner modifier? |
| Permissionless factory/registry | Can attacker use permissionless factory (e.g. Aquifer boreWell) to satisfy upgrade checks? |
| upgradeTo modifier | Overridden upgradeTo/upgradeToAndCall retains onlyProxy modifier? |
| Initializer protection | initializer modifier prevents re-initialization? Implementation calls _disableInitializers()? |
| Storage layout compatibility | Upgrade-safe storage layout (storage gaps or ERC-7201 namespace)? |
Case: Code4rena 2024-07-basin H-01 (via EVMbench Paper Fig.12, p.19) — _authorizeUpgrade only checked delegatecall and Aquifer registration but lacked onlyOwner, allowing anyone to upgrade a Well proxy to a malicious implementation and drain funds. Oracle patch: add a single onlyOwner modifier.
Source: EVMbench Paper §4.2.1, Fig.6 / Code4rena 2024-04-noya H-08, 2024-07-benddao
| Check | Detail |
|-------|--------|
| Cross-vault trust isolation | Registry/Router relay calls verify vault-level authorization? |
| Trusted sender abuse | Functions like sendTokensToTrustedAddress verify source vault, not just router identity? |
| Flash loan + routing combo | Can attacker use flash loan callback to make router impersonate arbitrary vault? |
| Collateral ownership verification | Liquidation/staking operations verify actual NFT/collateral owner? |
| Cross-contract state dependency | Multi-contract interactions free from intermediate state dependencies? |
Cases:
sendTokensToTrustedAddressisolateLiquidate did not verify NFT ownership, allowing attacker to pass others' tokenIds for liquidationSource: EVMbench Paper Appendix H.1, Fig.19-21 / Code4rena 2024-08-phi H-06
| Check | Detail |
|-------|--------|
| Counter/ID increment order | credIdCounter++ or similar ID increments happen before external calls? |
| Auto-buy in create | create() functions with auto buy() calls execute only after ID/state fully initialized? |
| Refund timing | ETH refund (excess) happens after all state updates complete? |
| Bonding curve metadata overwrite | Can attacker reenter to modify bonding curve/pricing params — buy cheap, switch to expensive curve, sell high? |
Case: Code4rena 2024-08-phi H-06 (via EVMbench Paper Appendix H.1, p.25-28) — _createCredInternal called buyShareCred before incrementing credIdCounter; _handleTrade refunded excess ETH before updating lastTradeTimestamp. Attacker reentered to accumulate shares on cheap curve, overwrote metadata to expensive curve, sold to drain all contract ETH. Fix: add nonReentrant to buyShareCred/sellShareCred.
Any restriction based on
mapping(address => ...)can be circumvented by multi-address splitting or intermediate transfer.
| Restriction Type | Bypass Method | Defense |
|---|---|---|
| Cooldown (_lastTxTime[addr]) | A buys → transfers to B → B sells immediately | Inherit sender's cooldown on transfer: _lastTxTime[to] = _lastTxTime[from] |
| Max tx amount (maxTxAmount) | Split across N addresses, each within limit | Also limit by tx.origin per block, or accept as known tradeoff |
| Max wallet balance (maxWalletBalance) | Distribute tokens to multiple wallets controlled by same entity | Inherently hard to enforce on-chain; monitor off-chain |
| Same-block protection (_lastTxBlock[addr]) | Use different addresses in same block | Inherit sender's block: _lastTxBlock[to] = _lastTxBlock[from] |
| Trade count limit | Rotate addresses, each uses one trade | Same as cooldown — propagate state on transfer |
Audit Methodology (3 Steps):
mapping(address => — for each, ask: "Can this be bypassed by switching addresses?"Defense Principle: Token flow carries restriction state — wherever tokens go, the relevant per-address state must follow.
Two Common Pitfalls:
block.timestamp: Using _lastTxTime[to] = block.timestamp allows anyone to grief a target by sending dust tokens, resetting cooldown. Use sender's state instead._lastTxTime[to] = _lastTxTime[from] allows a near-expired sender to shorten receiver's existing cooldown. Use max: if (_lastTxTime[from] > _lastTxTime[to]) _lastTxTime[to] = _lastTxTime[from] — only extends, never shortens.Attackers inject malicious code into the dApp frontend or signing interface.
Defense: Verify transaction calldata matches expected function selector and parameters before signing. Use hardware wallet with on-device transaction preview. Audit all frontend dependencies regularly.
Case: Bybit (Feb 2025, $1.4B) — malicious JavaScript injected into Safe{Wallet} UI, tampered with transaction data during signing.
Compromised keys remain the #1 loss source in 2025-2026.
Defense: Store keys in HSM or hardware wallet. Use multisig (≥ 3/5) for all treasury and admin operations. Never share seed phrases with any "support" contact. Conduct regular social engineering awareness training.
Case: Step Finance (Jan 2026, $30M) — treasury wallet private keys compromised via device breach.
| Check | Detail | |-------|--------| | Inherited code | Audit all bridge logic inherited from third-party frameworks | | Message verification | Cross-chain messages validated with proper signatures and replay protection? | | Liquidity isolation | Bridge funds separated from protocol treasury? |
Case: SagaEVM (Jan 2026, $7M) — inherited vulnerable EVM precompile bridge logic from Ethermint.
Old contracts with known bugs remain callable on-chain forever.
Defense: Permanently pause or migrate funds from deprecated contracts. Monitor old contract addresses for unexpected activity. Remove mint/admin functions before deprecation.
Case: Truebit (Jan 2026, $26.4M) — Solidity 0.6.10 contract lacked overflow protection, attacker minted tokens at near-zero cost.
When slither MCP is configured, run automated analysis BEFORE the manual checklist below:
Step 1: slither MCP automated scan
→ get_detector_results(path, impact="High")
→ get_detector_results(path, impact="Medium")
Step 2: Review Slither findings — triage true positives vs false positives
Step 3: Manual checklist below — catch what Slither misses (business logic, economic attacks)
Step 4: Cross-reference — Slither + manual findings combined into final report
| Tool | Usage | Complements |
|---|---|---|
| get_contract_metadata | Extract functions, inheritance, flags | Manual access control review |
| get_function_source | Get exact source code with line numbers | Faster than grep for locating code |
| find_implementations | Find all implementations of a function signature | Cross-contract reentrancy analysis |
| get_detector_results | Run 90+ security detectors, filter by impact/confidence | Automated version of manual checklist |
| get_detector_metadata | List available detectors with descriptions | Understanding what's being checked |
| Slither Catches Well | Manual Review Still Needed | |---|---| | Reentrancy patterns | Business logic flaws | | Unprotected functions | Economic attack vectors (flash loan combos) | | Unused state variables | Cross-protocol composability risks | | Shadowing issues | Oracle manipulation scenarios | | Incorrect ERC20 interface | Trust boundary architecture issues | | Dead code | MEV/front-running specific to business logic |
Key Principle: Slither provides ground truth via static analysis — reduces false negatives on known vulnerability patterns. But it cannot reason about protocol-level economic attacks — that's where the manual checklist below is essential.
Graceful degradation: If slither MCP is not configured, skip this section and proceed directly to the manual checklist. All checklist items remain valid and self-contained.
When conducting a security audit, check each item:
Reentrancy:
nonReentrantAccess Control:
Input & Logic:
call / delegatecalltx.origin for authenticationToken Handling:
SafeERC20Price & Oracle:
updatedAt / answeredInRound)Signature & Crypto:
ecrecover result checked against address(0)chainId, nonce, msg.sender, deadlineECDSA (low-s enforced)msg.senderFlash Loan Defense:
Proxy & Upgrade (EVMbench):
_authorizeUpgrade has onlyOwner — [EVMbench/basin H-01]upgradeTo/upgradeToAndCall retains onlyProxy — [EVMbench/basin H-01]_disableInitializers() — [EVMbench/basin H-01]Trust Boundary & Composability (EVMbench):
State Ordering (EVMbench):
Per-Address State Bypass:
mapping(address => ...) restrictions audited for multi-address bypassmax(_lastTxTime[to], _lastTxTime[from]) — not block.timestamp (griefing) or direct assignment (overwrite shortening)Infrastructure:
Source: EVMbench (OpenAI/Paradigm, Feb 2026) — evaluated AI agents on 120 high-severity vulnerabilities from 40 Code4rena audit repositories across Detect/Patch/Exploit modes.
Source: EVMbench Table 4 (p.17) — 40 audit repositories
Source: EVMbench Paper §4.1 (p.7), Fig.7 (p.10), Fig.10 (p.18), Fig.11 (p.19)
| Date | Project | Loss | Attack Type | Root Cause | Source | |------|---------|------|-------------|------------|--------| | Oct 2021 | Cream Finance | $130M | Flash loan + oracle | yUSD vault price manipulation via supply reduction | rekt.news | | Feb 2025 | Bybit | $1.4B | UI injection / supply chain | Safe{Wallet} JS tampered via compromised dev machine | NCC Group | | Mar 2025 | Abracadabra | $13M | Logic flaw | State tracking error in cauldron liquidation | Halborn | | Jul 2025 | GMX v1 | $42M | Reentrancy | GLP pool cross-contract reentrancy on Arbitrum | Halborn | | Sep 2025 | Bunni | $8.4M | Flash loan + rounding | Rounding direction error in withdraw, 44 micro-withdrawals | The Block | | Oct 2025 | Abracadabra #2 | $1.8M | Logic flaw | cook() validation flag reset, uncollateralized MIM borrow | Halborn | | Jan 2026 | Step Finance | $30M | Key compromise | Treasury wallet private keys stolen via device breach | Halborn | | Jan 2026 | Truebit | $26.4M | Legacy contract | Solidity 0.6.10 integer overflow in mint pricing | CoinDesk | | Jan 2026 | SagaEVM | $7M | Supply chain / bridge | Inherited Ethermint precompile bridge vulnerability | The Block |
testing
[AUTO-INVOKE] MUST be invoked when designing or reviewing ERC20 token contracts that need flash loan protection. Covers token-level defense design patterns: cost tracking, same-block cooldown, progressive sell tax, minimum balance retention, EIP-7702 aware address checks, front-run protection, and referral binding. Trigger: any ERC20 token with anti-flash-loan, anti-bot, or tokenomics security design requirements.
development
[AUTO-INVOKE] MUST be invoked BEFORE writing or modifying any Solidity contract (.sol files). Covers pragma version, naming conventions, project layout, OpenZeppelin library selection standards, oracle integration, and anti-patterns. Trigger: any task involving creating, editing, or reviewing .sol source files.
testing
[AUTO-INVOKE] MUST be invoked BEFORE any on-chain operation (cast send, forge script --broadcast). Systematic 6-layer verification checklist: permissions, dependencies, parameters, security, testing, and knowledge capture. Trigger: any task involving sending transactions, deploying contracts, or interacting with on-chain state.
development
[AUTO-INVOKE] MUST be invoked BEFORE writing or modifying any test files (*.t.sol). Covers test structure, naming conventions, coverage requirements, fuzz testing, and Foundry cheatcodes. Trigger: any task involving creating, editing, or running Solidity tests.