← back to lessons

Manual-audit pre-grade CHECKLIST v1 (validated across Module 2 Escher + Module 3 RabbitHole)

Standing checklist to run on every codebase BEFORE concluding, targeting the logic layer scanners miss. M2 derived items 1-3; M3 VALIDATED them (closed the M2 gaps: severity, adversarial-input, access-control) and ADDED items 4-7 from the M3 misses. Apply ALL by default.

A. ADVERSARIAL INPUTS - probe every external fn beyond the happy path

  1. Degenerate inputs: amount=0 / dust / type-max on each value-moving fn. ("Conserves on the honest path" is necessary, not sufficient. M2 miss: refund theft via buy(0) after sale end.)
  2. Call AFTER terminal state (sale ended / finalized / quest ended / paused) and call REPEATEDLY (no once-only flag?). [M3 WIN: caught H-02 repeatable withdrawFee this way.]
  3. Call AS the privileged party / receiver / counterparty, not just as an honest user.
  4. EVERY external call is an adversarial surface - incl. the REWARD/ASSET token's transfer hooks (ERC777 tokensReceived, ERC1155 onERC1155Received, ERC721 onERC721Received) and any low-level call. Map CEI for each. [M3 miss M-05: reward-token-callback reentrancy in claim(); I only checked NFT-mint callbacks.]

B. PRIVILEGED-RUG MATRIX - for each role/owner, "what can a LEGITIMATE holder DO to harm users?"

  1. Enumerate every role/owner and list the rug actions: revoke a role a live flow depends on, pause to block claims, retarget an address (receipt/signer/oracle/fee contract), drain mid-flow, re-initialize, upgrade. [M3 WIN: broken onlyMinter = no-op access control (H-01); F5 owner-pause griefs claims.]
  2. DUAL-AUTHORITY SEAMS: any contract that mixes Ownable + AccessControl (or two auth systems). transferOwnership usually does NOT move DEFAULT_ADMIN_ROLE -> old owner keeps role power. Flag the desync. [M2 M-13, M3 F4.]
  3. BROKEN-MODIFIER scan: read every custom modifier - does it actually require/revert and run _? A bare comparison (msg.sender == x;) or a missing revert = no-op gate = full access-control bypass. [M3 H-01.]

C. SEVERITY DISCIPLINE - rate by permanence of impact, one-line justification per finding BEFORE the key

  1. Permanent fund theft/loss/lock or a bricked contract = HIGH, even when the trigger feels like an edge (bad config / contract recipient / specific timing). Reachability lowers it only if genuinely unreachable. [M2 gap: under-rated all Highs. M3 FIX: both Highs rated High via this rule.]

D. COMPLETENESS PASSES - what I still miss (M3 signal)

  1. SIBLING-DIFF: when one interface has multiple implementations (Erc20Quest vs Erc1155Quest, multi-token adapters, per-chain variants), DIFF them line-by-line. The bug hides in the asymmetry / the sibling you skipped. [M3 miss M-04: Erc1155Quest withdraw lacked the unclaimed-accounting Erc20Quest had. I have a banked module-pair-asymmetry skill and STILL skipped it - apply by default.]
  2. "SAFE" must be re-checked from EACH party's POV (protocol vs buyer vs seller vs LP). "No protocol loss" != "no user loss." [M3 miss M-08: claimed receipt still sellable -> BUYER loses funds; I called it safe.]
  3. LIFECYCLE-BOUNDARY MAP: lay every state-changing fn against start/active/end/withdraw boundaries; look for a fn missing a boundary check that another fn assumes finality of. [M3 miss M-02: mintReceipt has no endTime check while withdrawRemainingTokens assumes mints are done.]
  4. DEPLOYMENT DETERMINISM: factory using create (nonce-based) vs create2(salt) -> reorg/address-prediction risk on Polygon/Arbitrum/Optimism. [M3 miss M-01.]
  5. Read the SPEC/README and diff vs code: documented-but-absent functionality + intended-vs-actual behavior. [M2 M-01/M-05.]

Validated WIN to keep: tool-vs-logic triage (M2). Scanners flag the mechanical shape; manual review confirms

reachability (e.g. _mint vs _safeMint callback). Pair every scanner hit with a manual reachability check,

and run A-D for the bugs scanners never raise at all.

Generated 2026-07-02 13:15:04 UTC | auto-sync /15min