Skip to content

feat(lint): feature parity with slither/aderyn #14381

@0xrusowsky

Description

@0xrusowsky

Missing Lint Detectors in forge lint

this checklist compares Slither and Aderyn detectors against forge lint and merges equivalent detectors into single items.

NOTE: some items are blocked until Solar outputs a CFG (control-flow graph), as they need data-flow analysis, taint tracking, or path-sensitive reasoning.

Implementable Today (only require AST or HIR)

High

  • encode-packed-collisionabi.encodePacked with multiple dynamic types causes hash collisions
  • msg-value-loopmsg.value reused inside a loop (double-spend)
  • delegatecall-loop — Payable delegatecall inside a loop
  • rtlo — Right-to-left-override Unicode character hiding malicious code
  • incorrect-exp^ (xor) used instead of ** (exponentiation)
  • yul-returnreturn in assembly/Yul halts entire EVM execution, not just the function
  • suicidal — Unprotected selfdestruct (no access-control modifier)
  • weak-prngblock.timestamp/blockhash used as randomness source
  • shadowing-state — Child contract re-declares parent's state variable
  • controlled-array-length — Direct assignment to storage array .length
  • array-by-reference — Storage array modified via memory copy has no effect
  • function-selector-collision — Colliding 4-byte function selectors
  • name-reused — Contract name reused across inheritance tree
  • enumerable-loop-removal — Removing items from EnumerableSet inside a loop using at + remove corrupts order

Medium

  • tx-origintx.origin used for authorization
  • mapping-deletiondelete on struct containing a mapping doesn't clear it
  • tautological-comparex >= x always true
  • tautologyuint >= 0 always true, uint8 < 512 always true
  • erc20-interface — ERC20 functions with wrong return types
  • erc721-interface — ERC721 functions with wrong return types
  • boolean-cstif(false) or b || true — misused boolean constant
  • domain-separator-collision — Function selector collides with EIP-2612 DOMAIN_SEPARATOR()
  • reused-constructor — Base constructor called with args from two inheritance paths
  • ecrecover — Bare ecrecover without signature malleability check
  • non-reentrant-not-firstnonReentrant modifier not listed first
  • unsafe-oz-erc721-mint_mint instead of _safeMint for ERC721
  • out-of-order-retryable — Multiple L1→L2 retryable tickets depend on execution order
  • block-timestamp-deadlineblock.timestamp used as swap/tx deadline (proposer can hold)
  • dangerous-unary-operatorx=+1 instead of x+=1

Low

  • calls-loop — External calls inside unbounded loop (DoS risk)
  • require-revert-in-looprequire/revert inside a loop (single failure reverts all)
  • timestamp — Dangerous block.timestamp dependency
  • shadowing-local — Local variable shadows state variable
  • shadowing-builtin — Variable shadows Solidity builtin (now, assert, etc.)
  • incorrect-modifier — Modifier doesn't always execute _ or revert
  • void-cst — Calling empty parent constructor
  • centralization-risk — Single-owner critical functions
  • deprecated-oz-function — Deprecated OpenZeppelin function
  • solmate-safe-transfer-lib — Solmate SafeTransferLib doesn't check contract existence
  • empty-block — Empty function body
  • empty-require-revertrequire()/revert() with no error message
  • multiple-placeholders — Modifier with multiple _ placeholders

Info / Optimization

  • constable-states — State variable never modified; should be constant
  • immutable-states — State variable only set in constructor; should be immutable
  • cache-array-length — Storage .length re-read every loop iteration
  • var-read-using-thisthis.x adds unnecessary STATICCALL overhead
  • external-functionpublic function never called internally; use external
  • unused-state — Unused state variable
  • unused-error — Unused custom error definition
  • boolean-equalx == true instead of just x
  • redundant-statements — Statement with no side effect
  • assert-state-change — State modification inside assert()
  • too-many-digits10000000000000000000 instead of 10 ether
  • literal-instead-of-constant — Magic numbers should be named constants
  • deprecated-standardsthrow, msg.gas, sha3(), suicide()
  • erc20-indexed — Transfer/Approval events missing indexed keyword
  • unindexed-event-address — Event has address params but none indexed
  • function-init-state — State variable initialized via non-pure function call
  • missing-inheritance — Contract implements interface functions but doesn't inherit it
  • solc-version — Outdated or overly complex pragma
  • pragma — Different Solidity versions used across files
  • cyclomatic-complexity — Function complexity exceeds threshold
  • incorrect-using-forusing L for T where L has no functions taking T
  • unimplemented-functions — Derived contract missing interface implementations
  • todo — TODO comments left in production code
  • push-0-opcode — Use push0 opcode, available since solc 0.8.20
  • inconsistent-type-names — Inconsistent type naming across codebase
  • internal-function-used-once — Internal function used only once; inline it
  • modifier-used-only-once — Modifier used only once; inline it

Require CFG (blocked on Solar)

these detectors need control-flow graph, data-flow/taint analysis, or path-sensitive reasoning that cannot be done with AST pattern matching alone.

High

  • reentrancy-eth — Reentrancy that drains ETH (state change after external call)
  • reentrancy-no-eth — Reentrancy that manipulates state without ETH
  • reentrancy-balance — Reentrancy exploiting address(this).balance check
  • arbitrary-send-erc20 — Taint: attacker-controlled from in transferFrom
  • arbitrary-send-erc20-permit — Taint: transferFrom + permit front-running
  • arbitrary-send-eth — Taint: ETH sent to attacker-controlled destination
  • unprotected-upgrade — No access control on initialize() across all paths
  • controlled-delegatecall — Taint: user-controlled delegatecall target
  • uninitialized-state — State variable used before initialization on some path
  • uninitialized-storage — Storage pointer left uninitialized on some path
  • protected-vars — Protected variable accessed without required modifier

Medium

  • locked-ether — Contract accepts ETH (payable) but has no withdrawal path
  • incorrect-equality — Strict equality on address(this).balance (manipulable)
  • unused-return — External call return value ignored
  • write-after-write — Variable written twice without intermediate read
  • uninitialized-local — Local variable used before assignment on some path
  • costly-loopSSTORE inside loop body

Low

  • missing-zero-check — Parameter flows to state variable write without != address(0) check
  • events-access — State change on access-control parameter without emitting event
  • return-bomb — Callee can returnbomb caller via unlimited returndata copy
  • variable-scope — Variable used before declaration is dominated

Info / Optimization

  • dead-code — Unreachable internal functions (call graph reachability)
  • reentrancy-benign — Benign reentrancy (double-call, no exploit)
  • reentrancy-events — Reentrancy manipulates event emission order
  • reentrancy-unlimited-gas — Reentrancy via transfer/send with gas repricing risk

Additional context

ref: #11034 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No fields configured for Feature.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions