> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://www.getfoundry.sh/api/mcp` to find what you need.
>
> **Have feedback?** Use `submit_feedback` on the same MCP server.

## Linting

Forge includes a built-in linter to catch common issues and enforce best practices.

### Run the linter

```bash
$ forge lint
```

The linter checks for:

* Incorrect shift operations
* Unchecked external calls
* Divide-before-multiply bugs
* Incorrect ERC721 interface signatures
* Incorrect ERC20 interface definitions
* Strict equality on externally-influenced values
* Unsafe typecasts
* Naming convention violations
* Use of tx.origin for authorization
* Return bomb risks from gas-limited calls
* Unused imports
* Gas optimizations

### Configuration

Configure linter rules in `foundry.toml`:

```toml [foundry.toml]
[lint]
severity = ["high", "med", "low"]
exclude_lints = ["mixed-case-function", "custom-errors"]
```

#### Severity levels

Control which lints run by severity:

```toml [foundry.toml]
[lint]
severity = ["high", "med"]  # Only high and medium severity
```

Valid severity levels: `high`, `med`, `low`, `info`, `gas`, `code-size`

Gas and code-size lints skip test and script files.

#### Exclude specific lints

Disable specific lint rules globally:

```toml [foundry.toml]
[lint]
exclude_lints = ["mixed-case-variable", "asm-keccak256"]
```

### Ignoring files

Exclude files from linting:

```toml [foundry.toml]
[lint]
ignore = ["src/legacy/**", "test/**"]
```

### Inline suppression

Disable lints for specific lines or blocks using comment directives.

#### Disable on current line

```solidity
uint256 Mixed_Case = 1; // forge-lint: disable-line(mixed-case-variable)
```

#### Disable on next line

```solidity
// forge-lint: disable-next-line(custom-errors)
revert("Use custom errors instead");
```

#### Disable for next item

Disable lints for an entire function, struct, or contract:

```solidity
// forge-lint: disable-next-item(mixed-case-function)
function non_standard_name() public {
    // entire function is excluded from the lint
}
```

#### Disable a block

```solidity
// forge-lint: disable-start(asm-keccak256)
bytes32 hash1 = keccak256(abi.encodePacked(a, b));
bytes32 hash2 = keccak256(abi.encodePacked(c, d));
// forge-lint: disable-end(asm-keccak256)
```

#### Disable multiple lints

```solidity
// forge-lint: disable-next-line(custom-errors, mixed-case-variable)
```

#### Disable all lints

```solidity
// forge-lint: disable-next-line
```

Or explicitly:

```solidity
// forge-lint: disable-next-line(all)
```

### Disable linting on build

By default, `forge build` runs the linter. To disable for a single invocation, pass
`--no-lint` (alias `--skip-lint`):

```bash
forge build --no-lint
```

To disable persistently:

```toml [foundry.toml]
[lint]
lint_on_build = false
```

### CI integration

Add linting to your CI pipeline with `--deny warnings`, which exits with a non-zero
status when warning-level lint diagnostics are emitted:

```yaml
- name: Run linter
  run: forge lint --deny warnings
```

See the [linter reference](/config/reference/linter) for all configuration options.

### Lint reference

Every lint emitted by `forge lint` has its own page describing what it flags, why it
matters, and how to fix it. Use the index below to jump to a specific lint, or use the
navigation on the right to browse by severity.

#### High severity

* [`arbitrary-send-erc20`](/forge/linting/arbitrary-send-erc20) — Flags ERC20 `transferFrom` and `safeTransferFrom` calls whose `from` argument is not constrained to `msg.sender` or `address(this)`, including SafeERC20 library calls.
* [`arbitrary-send-erc20-permit`](/forge/linting/arbitrary-send-erc20-permit) — Flags `transferFrom` and `safeTransferFrom` calls preceded by a `permit` for the same token and owner in the same function, when `from` is not constrained to `msg.sender` or `address(this)`. This includes common SafeERC20 and SafeTransferLib wrappers.
* [`arbitrary-send-eth`](/forge/linting/arbitrary-send-eth) — Flags ETH transfers to caller-controlled destinations in functions without a recognized caller restriction. This includes `transfer`, `send`, calls with `{value: ...}`, `selfdestruct`, and common OpenZeppelin and Solady ETH-transfer helpers.
* [`controlled-delegatecall`](/forge/linting/controlled-delegatecall) — Flags `delegatecall` targets other than a trusted literal, constant, zero address, or `address(this)`.
* [`encode-packed-collision`](/forge/linting/encode-packed-collision) — Encode Packed Collision
* [`enumerable-loop-removal`](/forge/linting/enumerable-loop-removal) — Flags `EnumerableSet.remove` inside a loop that also reads the same set with `at` using an increasing index.
* [`erc20-unchecked-transfer`](/forge/linting/erc20-unchecked-transfer) — Warns when a function with the same signature as `transfer(address,uint256)` or `transferFrom(address,address,uint256)` and a `bool` return type is invoked but the result is not checked.
* [`function-selector-collision`](/forge/linting/function-selector-collision) — Reports different proxy and implementation function signatures with the same four-byte selector. Identical signatures are not reported.
* [`incorrect-exp`](/forge/linting/incorrect-exp) — Reports `a ^ b` when both operands are decimal integer literals and `a` is `2` or `10`. In Solidity, `^` is bitwise XOR, so `10 ^ 18` evaluates to `24`, not `10 ** 18`. Hexadecimal and scientific-notation operands are excluded.
* [`incorrect-shift`](/forge/linting/incorrect-shift) — Warns when the first argument to a Yul `shl` or `shr` call is dynamic and the second argument is a literal. Yul shift calls take the shift amount first and the value second, so `shr(value, 8)` shifts the literal `8` by `value`; the usual intended expression is `shr(8, value)`.
* [`protected-vars`](/forge/linting/protected-vars) — Protected variables
* [`reentrancy-balance`](/forge/linting/reentrancy-balance) — Reports public or external functions that save `address(this).balance`, make an external call that permits reentry, and then check the current balance against the saved value.
* [`reentrancy-eth`](/forge/linting/reentrancy-eth) — Reports low-level `.call{value: ...}(...)` operations without a concrete gas cap, including `gas: gasleft()`, when a state variable read before the call is written after it.
* [`rtlo`](/forge/linting/rtlo) — Detects the right-to-left override codepoint (`U+202E`) and other bidirectional control characters embedded in identifiers, strings, and comments.
* [`unchecked-call`](/forge/linting/unchecked-call) — Warns when the boolean returned by a low-level call is discarded — either because the return value is not assigned or because only the `bytes memory` payload is used.
* [`unprotected-initializer`](/forge/linting/unprotected-initializer) — Unprotected initializer

#### Medium severity

* [`assert-state-change`](/forge/linting/assert-state-change) — Warns when an `assert()` argument contains a state-mutating operation: a pre- or post-increment/decrement (`++`/`--`) on a state variable, an assignment (`=`, `+=`,etc.) to a state variable, a `delete` of a state variable, or a call to a function that writes state variables.
* [`block-number-across-roll`](/forge/linting/block-number-across-roll) — Warns in tests and scripts when a value derived from `block.number` can be used after `vm.roll`, or when `block.number` is read both before and after a roll in the same call.
* [`block-timestamp-across-warp`](/forge/linting/block-timestamp-across-warp) — Warns in tests and scripts when a value derived from `block.timestamp` can be used after `vm.warp`, or when `block.timestamp` is read both before and after a warp in the same call.
* [`boolean-cst`](/forge/linting/boolean-cst) — Reports literal boolean conditions in `if`, `for`, and `do while`, `while (false)`, and boolean operators (`&&`, `||`) where one side is a literal `true`/`false`. The idiomatic infinite loop `while (true)` is exempt.
* [`dangerous-unary-operator`](/forge/linting/dangerous-unary-operator) — Reports `x =- y` and `x =~ y`, where `=` is written directly beside a unary operator. These are assignments, not compound operations: `x =- 1` means `x = -1`, not `x -= 1`. The intentional spaced forms (`x = -1`, `x = ~y`) and compound operators (`x -= 1`) are not flagged.
* [`divide-before-multiply`](/forge/linting/divide-before-multiply) — Warns on expressions of the form `(a / b) * c` (or equivalent shapes), where the integer division truncates before the result is multiplied.
* [`ecrecover`](/forge/linting/ecrecover) — Reports direct `ecrecover` calls without a check that `s` is at most `0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0` before the recovered address is used.
* [`incorrect-erc20-interface`](/forge/linting/incorrect-erc20-interface) — For each function whose name and parameter types match a canonical ERC20 method (`totalSupply`, `balanceOf`, `transfer`, `transferFrom`, `approve`, `allowance`), the lint checks that the return type matches the spec. A mismatch is reported.
* [`incorrect-erc721-interface`](/forge/linting/incorrect-erc721-interface) — For each function whose name and parameter types match a canonical ERC721/ERC165 method (`balanceOf`, `ownerOf`, `safeTransferFrom`, `transferFrom`, `approve`, `setApprovalForAll`, `getApproved`, `isApprovedForAll`, `supportsInterface`), the lint checks that the return type matches the spec. A mismatch is reported.
* [`incorrect-strict-equality`](/forge/linting/incorrect-strict-equality) — Incorrect Strict Equality
* [`locked-ether`](/forge/linting/locked-ether) — Locked Ether
* [`mapping-deletion`](/forge/linting/mapping-deletion) — Reports `delete x` when `x` is a struct or array whose type holds a `mapping`, directly or through a nested struct or array. Deleting a whole mapping is not valid Solidity, but deleting a container of one compiles and silently leaves the mapping's entries in place.
* [`non-reentrant-not-first`](/forge/linting/non-reentrant-not-first) — Reports a function, fallback, or receive function when `nonReentrant` appears after another modifier, for example `onlyOwner nonReentrant`.
* [`reentrancy-no-eth`](/forge/linting/reentrancy-no-eth) — Reports public or external functions that read a state variable, make an external call without sending ETH, and then write the same state variable.
* [`tautological-compare`](/forge/linting/tautological-compare) — Reports `a <op> a` where `a` is a side-effect-free expression (an identifier, member access, or indexing) and `<op>` is `<`, `<=`, `>`, `>=`, `==`, or `!=`. Such a comparison has a constant result. Comparisons whose sides could legitimately differ (for example involving a function call) are left untouched, as are comparisons on user-defined value types, whose operators are user-defined (`using {f as ==} for T`) and need not be constant.
* [`tx-origin`](/forge/linting/tx-origin) — Reports `tx.origin` reads when they are used as part of a guard condition. Plain reads outside of guard predicates are not reported.
* [`type-based-tautology`](/forge/linting/type-based-tautology) — Flags comparisons that are always true or false because of an integer type's range, such as `uint256 x >= 0`. Also reports conditions that cover the entire range, such as `x > 0 || x == 0` for unsigned `x`.
* [`uninitialized-local`](/forge/linting/uninitialized-local) — Reports local variables that can be read before being assigned. Parameters and state variables are excluded.
* [`uninitialized-state`](/forge/linting/uninitialized-state) — Reports state variables that are read but never assigned in the contract or its base contracts. An assignment at the declaration or in a constructor satisfies the lint.
* [`unsafe-oz-erc721-mint`](/forge/linting/unsafe-oz-erc721-mint) — Reports calls to OpenZeppelin's ERC721 `_mint`, including overrides that delegate to it, without a recognized recipient check.
* [`unsafe-typecast`](/forge/linting/unsafe-typecast) — Reports casts where the source value's type can exceed the target type (for example, `uint256 → uint128` or `int256 → uint128`). An unsigned value masked to the target width, such as `uint8(value & 0xff)`, is not flagged. A preceding manual range check may still produce a warning; review that check before suppressing the lint.
* [`unused-return`](/forge/linting/unused-return) — Detects high-level external calls (member calls on contract-typed variables or interface-cast addresses) that return one or more values when the entire result is discarded or any slot of a tuple return is omitted. ERC20 `transfer` and `transferFrom` are excluded as they are handled by the separate `erc20-unchecked-transfer` lint.
* [`weak-prng`](/forge/linting/weak-prng) — Reports direct use of `block.timestamp`, `block.number`, `block.coinbase`, `blockhash(...)`, `block.prevrandao`, or `block.difficulty` in modulo expressions or `keccak256(...)`. `abi.encode*` calls are treated as entropy only when they feed one of those expressions.

#### Low severity

* [`block-timestamp`](/forge/linting/block-timestamp) — Reports comparison expressions (`<`, `<=`, `>`, `>=`, `==`, `!=`) involving `block.timestamp`.
* [`calls-loop`](/forge/linting/calls-loop) — Reports high-level contract calls, low-level `call`/`delegatecall`/`staticcall`, Ether `send`/`transfer`, external self-calls through `this`, and contract creation inside a loop. Internal and private library calls and `super` dispatch are not treated as external calls.
* [`delegatecall-loop`](/forge/linting/delegatecall-loop) — Reports `delegatecall` expressions that appear in the body of a `for`, `while`, or `do while` loop when the enclosing function is `public payable` or `external payable`.
* [`deprecated-oz-function`](/forge/linting/deprecated-oz-function) — Reports uses of OpenZeppelin's `SafeERC20.safeApprove` and `AccessControl._setupRole`, including their upgradeable variants.
* [`empty-block`](/forge/linting/empty-block) — Reports a function whose body is `{}` (a comment does not make a body non-empty).
* [`inconsistent-type-names`](/forge/linting/inconsistent-type-names) — Reports shorthand `uint` or `int` declarations when the same contract also uses `uint256` or `int256`, respectively. This includes types within arrays and mappings.
* [`incorrect-modifier`](/forge/linting/incorrect-modifier) — Flags modifiers that can finish successfully without reaching the `_` placeholder. A path that reverts before `_` is allowed, but calling a function that might revert does not by itself prevent the modifier from skipping the body.
* [`missing-events-access-control`](/forge/linting/missing-events-access-control) — Flags protected public or external functions that change ownership, roles, or other state used in authorization checks without a related event containing the changed value or key.
* [`missing-events-arithmetic`](/forge/linting/missing-events-arithmetic) — Flags protected public or external functions that update integer parameters used in arithmetic by an unprotected entry point without emitting an event. Updates include assignments from function input and arithmetic changes.
* [`missing-zero-check`](/forge/linting/missing-zero-check) — Reports `address` parameters used in a state write or value transfer by an externally callable state-mutating function or constructor without a check against `address(0)`.
* [`msg-value-loop`](/forge/linting/msg-value-loop) — Reports `msg.value` expressions that execute inside a `for`, `while`, or `do while` loop reachable from a `public payable` or `external payable` entry point.
* [`reentrancy-events`](/forge/linting/reentrancy-events) — Reports events emitted after an external interaction, such as a state-changing contract call, low-level `call` or `delegatecall`, ETH `send` or `transfer`, or contract creation. Static calls and `view` or `pure` calls are excluded.
* [`require-revert-in-loop`](/forge/linting/require-revert-in-loop) — Reports `require` calls and Solidity or Yul `revert` operations inside loops.
* [`return-bomb`](/forge/linting/return-bomb) — Detects low-level `call`, `delegatecall`, and `staticcall` expressions that specify `{gas: ...}`. Solidity copies the full returndata for these calls even when the second tuple element is ignored. It also detects high-level external calls with `{gas: ...}` that consume dynamically encoded return values such as `bytes`, `string`, dynamic arrays, or structs containing dynamic fields.
* [`solmate-safe-transfer-lib`](/forge/linting/solmate-safe-transfer-lib) — Reports uses of `safeTransfer`, `safeTransferFrom`, and `safeApprove` from solmate's `SafeTransferLib`. ETH transfers and similarly named libraries from other packages are excluded.

#### Informational

* [`boolean-equal`](/forge/linting/boolean-equal) — Reports any equality comparison between a boolean expression and a literal `true` or `false`.
* [`cyclomatic-complexity`](/forge/linting/cyclomatic-complexity) — Reports functions with a complexity score above 11. The score starts at one and increases for each decision point: `if`, a loop with a condition, a ternary, a `catch` clause, or an additional assembly `switch` case. Boolean `&&` and `||` operators do not add to the score.
* [`event-fields`](/forge/linting/event-fields) — Reports unindexed `address` and `address payable` event parameters when the event has no indexed parameters. Contract, interface, and user-defined value types are excluded.
* [`function-init-state`](/forge/linting/function-init-state) — Reports inline state-variable initializers that reference a non-constant state variable or a non-pure function. Constants, pure functions, and assignments in the constructor body are excluded.
* [`incorrect-using-for`](/forge/linting/incorrect-using-for) — Reports `using L for T` when library `L` has no non-private function whose first parameter accepts `T`, including through an implicit conversion.
* [`inline-assembly`](/forge/linting/inline-assembly) — Reports every inline assembly statement, including blocks marked `memory-safe`.
* [`interface-file-naming`](/forge/linting/interface-file-naming) — Reports interface-only files whose path basename does not start with `I` (e.g. `IERC20.sol`).
* [`interface-naming`](/forge/linting/interface-naming) — Reports `interface Foo` where `Foo` does not start with `I` (e.g. `IFoo`).
* [`internal-function-used-once`](/forge/linting/internal-function-used-once) — Reports internal and free functions referenced exactly once across the compiled sources.
* [`literal-instead-of-constant`](/forge/linting/literal-instead-of-constant) — Reports repeated number, address, or hex-string values within a contract's executable code. Equivalent spellings, such as `100` and `0x64`, count as the same value.
* [`low-level-calls`](/forge/linting/low-level-calls) — Warns whenever a contract uses a low-level call expression, even if the success return value is captured and checked.
* [`missing-inheritance`](/forge/linting/missing-inheritance) — Reports contracts that implement an interface's external functions without inheriting it. An already-inherited base that provides the interface's functions satisfies the lint. Abstract contracts containing only interface declarations are also considered.
* [`mixed-case-function`](/forge/linting/mixed-case-function) — Reports functions whose names contain embedded underscores, start with an uppercase letter, or otherwise deviate from `mixedCase`. Leading and trailing underscores are preserved, and single-character names are not checked. Test functions starting with `test`, `invariant_`, or `statefulFuzz`, configured uppercase patterns (for example, `ERC20`), and external constant-style getters are exempted.
* [`mixed-case-variable`](/forge/linting/mixed-case-variable) — Reports mutable variable identifiers that contain embedded underscores, start with an uppercase letter, or otherwise deviate from `mixedCase`. Leading and trailing underscores are preserved, and single-character names are not checked.
* [`modifier-used-only-once`](/forge/linting/modifier-used-only-once) — Reports modifiers used by exactly one function or constructor across the compiled sources. Virtual modifiers, overrides, and unused modifiers are excluded.
* [`multi-contract-file`](/forge/linting/multi-contract-file) — Reports each top-level `contract`, `interface`, or `library` definition (after the first) in a file that contains more than one such declaration.
* [`named-struct-fields`](/forge/linting/named-struct-fields) — Reports `Struct(a, b, c)` style struct construction; suggests `Struct({ field1: a, field2: b, field3: c })` instead.
* [`pascal-case-struct`](/forge/linting/pascal-case-struct) — Reports `struct` identifiers longer than one character that do not match the `PascalCase` convention. Single-character names are not checked.
* [`pragma-inconsistent`](/forge/linting/pragma-inconsistent) — Reports inconsistent `pragma solidity ...;` requirements across source files, such as different exact versions or mixed caret, tilde, and range constraints.
* [`redundant-base-constructor-call`](/forge/linting/redundant-base-constructor-call) — For every base contract listed in a contract's inheritance specifier or invoked from a derived constructor's header, the lint reports the empty `()` when the base does not require any arguments.
* [`screaming-snake-case-const`](/forge/linting/screaming-snake-case-const) — Reports state variables declared `constant` whose identifier is longer than one character and deviates from `SCREAMING_SNAKE_CASE`. Leading and trailing underscores are preserved.
* [`screaming-snake-case-immutable`](/forge/linting/screaming-snake-case-immutable) — Reports state variables declared `immutable` whose identifier deviates from `SCREAMING_SNAKE_CASE`. Single-character names are not checked, and leading and trailing underscores are preserved.
* [`todo-comment`](/forge/linting/todo-comment) — Reports `TODO` and `FIXME` markers in line, block, and NatSpec comments, regardless of case. This includes common forms such as `TODO:`, `FIXME(...)`, and a bare marker at the start of a comment line. Ordinary filenames such as `todo.md` are not markers.
* [`too-many-digits`](/forge/linting/too-many-digits) — Reports Solidity and Yul numeric literals that contain a run of 5 or more `0` characters. Decimal literals with scientific notation, literals with a sub-denomination, and 40-digit hexadecimal address literals are skipped. Other hexadecimal literals remain in scope because long padded masks and bit patterns are also difficult to review.
* [`unaliased-plain-import`](/forge/linting/unaliased-plain-import) — Reports plain imports of the form `import "path";`. Suggests using either named imports (`import { A, B } from "path"`) or an aliased import (`import "path" as X`).
* [`unsafe-cheatcode`](/forge/linting/unsafe-cheatcode) — Reports calls to `ffi`, `readFile`, `readLine`, `writeFile`, `writeLine`, `removeFile`, `closeFile`, `setEnv`, or `deriveKey`. Unrelated methods with these names may also be flagged.
* [`unused-error`](/forge/linting/unused-error) — Reports custom error declarations that are never used by a revert, `require`, or selector reference anywhere in the compiled sources.
* [`unused-import`](/forge/linting/unused-import) — Reports `import "..."`, `import "..." as X`, and `import { A, B } from "..."` statements where one or more imported names are never used. This includes unused namespace imports (`import * as X`).

#### Gas optimization

* [`asm-keccak256`](/forge/linting/asm-keccak256) — Reports direct `keccak256(...)` calls in statements and initializers for gas review.
* [`cache-array-length`](/forge/linting/cache-array-length) — Reports comparison expressions in `for` loop conditions when either side reads `.length` from a state dynamic array, such as `i < values.length` or `values.length > i`, including comparisons nested inside `&&` / `||` conditions.
* [`costly-loop`](/forge/linting/costly-loop) — Reports assignments, compound assignments, increments/decrements, and `delete` expressions that directly write to a storage variable inside any `for`, `while`, or `do-while` loop body, including writes through storage array indices and mapping keys.
* [`could-be-constant`](/forge/linting/could-be-constant) — Reports non-`constant`, non-`immutable` state variables with a compile-time-constant initializer and no later assignments, when their type permits `constant`.
* [`could-be-immutable`](/forge/linting/could-be-immutable) — Reports each non-`constant`, non-`immutable` state variable whose only writes occur in the constructor (or in initialization at declaration time).
* [`custom-errors`](/forge/linting/custom-errors) — Reports `require` calls with no reason or whose second argument is a string literal, and `revert(...)` calls that are either bare or have a string-literal argument.
* [`external-function`](/forge/linting/external-function) — Flags implemented `public` functions with reference-type `memory` parameters that are never called internally and do not modify their parameters. Overrides are excluded.
* [`unused-state-variables`](/forge/linting/unused-state-variables) — Reports each state variable that has no read or write site across the project.
* [`var-read-using-this`](/forge/linting/var-read-using-this) — Reports calls through `this` to the contract's own public variable getters and `view` or `pure` functions, including inherited functions.
* [`write-after-write`](/forge/linting/write-after-write) — Reports assignments to state variables whose values are overwritten before being read. Compound assignments and writes to individual mapping entries, array elements, or struct fields are excluded.

#### Code size

* [`unwrapped-modifier-logic`](/forge/linting/unwrapped-modifier-logic) — Reports modifiers containing logic beyond a placeholder, simple `require` or `assert` checks, or a single library call. Assembly blocks are excluded from suggested extraction.
