Skip to content

feat(forge-lint): [claude] check for unwrapped modifiers #10967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

0xClandestine
Copy link

@0xClandestine 0xClandestine commented Jul 9, 2025

Motivation

Coauthored by Claude 4 Opus Max

Modifiers in Solidity that contain logic directly in their body can lead to code duplication when used across multiple functions. Since modifiers are inlined at compile time, any logic within them is duplicated for each function that uses the modifier increasing contract size and deployment cost. A common best practice is to extract modifier logic into internal functions that can be called from within the modifier, reducing code duplication and contract size.

Solution

This PR adds a new gas optimization lint unwrapped-modifier-logic that detects when modifiers contain logic directly in their body instead of using internal/private/public functions.

The lint checks for modifiers that contain any statements other than:

  • Direct calls to internal/private/public functions.
  • The placeholder _;

Example of code that triggers the lint:

modifier onlyOwner() {
    require(msg.sender == owner, "Not owner"); // Unwrapped logic
    _;
}

Recommended pattern:

modifier onlyOwner() {
    _checkOwner(); // Wrapped in internal function
    _;
}

function _checkOwner() internal view {
    require(msg.sender == owner, "Not owner");
}

PR Checklist

  • Added Tests - Created testdata/ModifierLogic.sol and testdata/ModifierLogic.stderr with comprehensive test cases
  • Added Documentation - Need to add lint documentation to the Foundry book under the gas optimization section
  • Breaking changes - No breaking changes, this is a new lint rule

@0xClandestine 0xClandestine changed the title feat(forge-lint): check for unwrapped modifiers feat(forge-lint): [claude] check for unwrapped modifiers Jul 9, 2025
@0xClandestine 0xClandestine marked this pull request as ready for review July 9, 2025 04:55
@grandizzy grandizzy requested a review from 0xrusowsky July 9, 2025 05:01
Copy link
Collaborator

@grandizzy grandizzy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, makes sense. left some nits, pending @0xrusowsky @DaniPopes review

}
}

fn is_valid_expr(expr: &solar_ast::Expr<'_>) -> bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be simplified to

fn is_valid_expr(expr: &solar_ast::Expr<'_>) -> bool {
    if let ExprKind::Call(func_expr, _) = &expr.kind
        && let ExprKind::Ident(ident) = &func_expr.kind
    {
        return !matches!(ident.name.as_str(), "require" | "assert");
    }
    false
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

// If modifier has no contents, skip.
let body = match &func.body {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be simplified to

let Some(body) = &func.body else { return };

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xrusowsky
Copy link
Contributor

overall the PR looks good, however i'd like to hold it back until we merge:

and then make it do a fix suggestion

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

3 participants