crates/lint/README.md
lint)Solidity linter for identifying potential errors, vulnerabilities, gas optimizations, and style guide violations. It helps enforce best practices and improve code quality within Foundry projects.
forge-lint includes rules across several categories:
incorrect-exp: Flags ^ (bitwise xor) used where ** (exponentiation) was likely intended.incorrect-shift: Warns against shift operations where operands might be in the wrong order.unchecked-call: Low-level calls should check the success return value.erc20-unchecked-transfer: ERC20 transfer and transferFrom calls should check the return value.arbitrary-send-erc20: Flags transferFrom/safeTransferFrom calls whose from argument is not provably msg.sender or address(this).arbitrary-send-erc20-permit: Flags arbitrary transferFrom calls preceded by a covering permit; on non-permit tokens with a fallback (e.g. WETH) the permit silently succeeds and previously-approved tokens can be drained.controlled-delegatecall: Flags delegatecall calls whose target is not provably trusted.encode-packed-collision: Flags abi.encodePacked() calls with multiple dynamic-type arguments (string, bytes, dynamic arrays) that can produce hash collisions.enumerable-loop-removal: Flags remove on an EnumerableSet inside a loop that also iterates a set with at; swap-and-pop removal corrupts the iteration.function-selector-collision: Flags colliding selectors between a proxy and the statically typed implementation API targeted by its fallback.rtlo: Flags Unicode bidirectional override characters ("Trojan Source", CVE-2021-42574) that can hide malicious code.reentrancy-balance: Flags reentrant calls between saving address(this).balance and checking the current balance against that stale value.reentrancy-eth: Flags uncapped ETH-transferring low-level calls followed by writes to state that was read before the call.protected-vars: Flags externally callable entry points that write a state variable without its required @custom:security write-protection function or modifier.unprotected-initializer: Upgradeable initializers should not be callable on the implementation contract.assert-state-change: Flags state-modifying expressions inside assert() arguments.boolean-cst: Flags misuse of boolean constants.dangerous-unary-operator: Flags an assignment whose = is fused to a unary operator (=-, =~), e.g. x =- 1, which parses as x = -1 instead of the intended compound x -= 1.divide-before-multiply: Warns against performing division before multiplication in the same expression, which can cause precision loss.ecrecover: Flags direct ecrecover calls whose signature s value is not proven to be canonical and non-malleable.incorrect-erc20-interface: Flags ERC20 interfaces and implementations with non-compliant function signatures.incorrect-erc721-interface: Flags ERC721 interfaces and implementations with non-compliant function signatures.incorrect-strict-equality: Dangerous strict equality check on an externally-influenced value (ETH balance, ERC-20 balance).mapping-deletion: delete on a value containing a mapping does not clear the mapping.reentrancy-no-eth: Flags non-ETH external calls followed by writes to state that was read before the call.tautological-compare: Comparing an expression with itself is always true or false.tx-origin: Flags use of tx.origin in authorization-like predicates.uninitialized-local: Local variable is read before being explicitly initialized.uninitialized-state: State variable is read in functions but never written, so it always returns its zero-value default.unsafe-oz-erc721-mint: Flags calls resolving to ERC721._mint, which does not check that the recipient can receive the token; use _safeMint.unsafe-typecast: Typecasts that can truncate values should be checked.unused-return: Return value of an external call is not used.locked-ether: Contracts that can receive ETH but have no mechanism to send it out.non-reentrant-not-first: nonReentrant should be the first modifier on guarded entry points.weak-prng: Flags randomness-like expressions derived from predictable on-chain values.block-timestamp: Warns when block.timestamp is used in a comparison, as it may be manipulated by validators.calls-loop: External calls inside loops can cause denial-of-service if a call reverts or exhausts gas.delegatecall-loop: Payable functions should not use delegatecall inside a loop.deprecated-oz-function: OpenZeppelin deprecated SafeERC20.safeApprove (use safeIncreaseAllowance / safeDecreaseAllowance) and AccessControl._setupRole (use _grantRole).empty-block: Flags regular functions with an empty body; constructors, receive/fallback, virtual functions, functions with modifiers and value-less payable functions are exempt.incorrect-modifier: Modifiers should not be able to finish without executing _ or reverting.inconsistent-type-names: Flags shorthand uint/int declarations when the same contract also uses uint256/int256.missing-events-access-control: Access control changes should emit events.missing-zero-check: Address parameter is used in a state write or value transfer without a zero-address check.reentrancy-events: Events emitted after external calls can be reordered or fabricated by a reentrant callee and mislead off-chain consumers.return-bomb: External calls with a gas limit should not consume unbounded return data.solmate-safe-transfer-lib: solmate's released SafeTransferLib does not check that the token has code, so token operations against a token-less address succeed silently.boolean-equal: Boolean comparisons to constants should be simplified.too-many-digits: Numeric literals with 5+ consecutive zeros are error-prone.pascal-case-struct: Flags for struct names not adhering to PascalCase.mixed-case-function: Flags for function names not adhering to mixedCase.mixed-case-variable: Flags for mutable variable names not adhering to mixedCase.screaming-snake-case-const: Flags for constant variable names not adhering to SCREAMING_SNAKE_CASE.screaming-snake-case-immutable: Flags for immutable variable names not adhering to SCREAMING_SNAKE_CASE.unused-import: Unused imports should be removed.unaliased-plain-import: Use named imports {A, B} or alias import ".." as X.named-struct-fields: Prefer initializing structs with named fields.unsafe-cheatcode: Usage of unsafe cheatcodes that can perform dangerous operations.multi-contract-file: Prefer having only one contract, interface, or library per file.interface-file-naming: Interface file names should be prefixed with I.interface-naming: Interface names should be prefixed with I.pragma-inconsistent: Flags projects whose source files declare different Solidity pragma version requirements.redundant-base-constructor-call: Flags explicit empty base-constructor arguments (e.g. is A()) when the base requires no arguments.missing-inheritance: Flags contracts that implement every external function of an interface without explicitly inheriting from it.low-level-calls: Direct use of low-level calls should be avoided.event-fields: address event parameters should be indexed for efficient log filtering.todo-comment: Detects unresolved TODO and FIXME markers in comments.unused-error: Custom error declarations that are never referenced should be removed.literal-instead-of-constant: A literal value repeated inside a contract should be a named constant.function-init-state: State variable initializers run before the constructor; depending on a non-pure function or another state variable there observes partial state.internal-function-used-once: Internal functions referenced exactly once can usually be inlined into their caller.cyclomatic-complexity: functions with a cyclomatic complexity above 11 should be split into smaller functions.incorrect-using-for: using ... for directives naming a library with no function applicable to the type attach nothing and should be fixed or removed.modifier-used-only-once: Modifiers invoked by exactly one function can usually be inlined as checks in that function.asm-keccak256: Recommends using inline assembly for keccak256 for potential gas savings.cache-array-length: Recommends caching storage dynamic array lengths used in for loop conditions.costly-loop: Flags storage variable writes inside loops; accumulate into a local variable and write once after the loop instead.could-be-immutable: Recommends declaring constructor-only state variables as immutable.could-be-constant: Recommends declaring never-written state variables with a compile-time-constant initializer as constant.custom-errors: Recommends using custom errors instead of strings and plain reverts for potential gas savings.external-function: public functions never called internally should be declared external to avoid copying reference-type arguments into memory.unused-state-variables: State variables that are never used should be removed.var-read-using-this: Reads of state variables (or other view/pure functions) via this cause an unnecessary STATICCALL; access them directly.write-after-write: Flags storage variables written consecutively without the first value ever being read; only the final write is needed.unwrapped-modifier-logic: Recommends wrapping modifier logic to reduce contract code size.The behavior of the SolidityLinter can be customized with the following options:
| Option | Default | Description |
|---|---|---|
with_severity | None | Filters active lints by their severity (High, Med, Low, Info, Gas, CodeSize). None means all severities. |
with_lints | None | Specifies a list of SolLint instances to include. Overrides severity filter if a lint matches. |
without_lints | None | Specifies a list of SolLint instances to exclude, even if they match other criteria. |
with_description | true | Whether to include the lint's description in the diagnostic output. |
with_json_emitter | false | If true, diagnostics are output in rustc-compatible JSON format; otherwise, human-readable text. |
Check out the foundry contribution guide.
Guidelines for contributing to forge lint:
Forge lint does not work
Forge lint breaks
Forge lint unexpected behavior
Forge lint does not flag incorrect shift operations
T-Bug for bugs or T-feature for features), add C-forge and Cmd-forge-fmt labels.lint/testdata that specifically demonstrates the bug and is fixed by your changes. Ensure all tests pass.Check the dev docs for a full implementation guide.