From 781914319c25510b765bfc8dc9dc200eda6bc944 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 8 Jul 2025 20:08:01 -0400 Subject: [PATCH 1/6] feat: add `unwrapped_modifier_logic` gas lint --- crates/lint/src/sol/gas/mod.rs | 5 +- .../src/sol/gas/unwrapped_modifier_logic.rs | 77 +++++++ crates/lint/testdata/ModifierLogic.sol | 200 ++++++++++++++++++ crates/lint/testdata/ModifierLogic.stderr | 152 +++++++++++++ 4 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 crates/lint/src/sol/gas/unwrapped_modifier_logic.rs create mode 100644 crates/lint/testdata/ModifierLogic.sol create mode 100644 crates/lint/testdata/ModifierLogic.stderr diff --git a/crates/lint/src/sol/gas/mod.rs b/crates/lint/src/sol/gas/mod.rs index 69bc9422f57f3..bb98db6e9e2f2 100644 --- a/crates/lint/src/sol/gas/mod.rs +++ b/crates/lint/src/sol/gas/mod.rs @@ -3,4 +3,7 @@ use crate::sol::{EarlyLintPass, SolLint}; mod keccak; use keccak::ASM_KECCAK256; -register_lints!((AsmKeccak256, (ASM_KECCAK256))); +mod unwrapped_modifier_logic; +use unwrapped_modifier_logic::UNWRAPPED_MODIFIER_LOGIC; + +register_lints!((AsmKeccak256, (ASM_KECCAK256)), (ModifierLogic, (UNWRAPPED_MODIFIER_LOGIC))); diff --git a/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs new file mode 100644 index 0000000000000..ee7737b259ba8 --- /dev/null +++ b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs @@ -0,0 +1,77 @@ +use super::ModifierLogic; +use crate::{ + linter::{EarlyLintPass, LintContext}, + sol::{Severity, SolLint}, +}; +use solar_ast::{ExprKind, FunctionKind, ItemFunction, Stmt, StmtKind}; + +declare_forge_lint!( + UNWRAPPED_MODIFIER_LOGIC, + Severity::Gas, + "unwrapped-modifier-logic", + "modifier logic should be wrapped to avoid code duplication and reduce codesize" +); + +impl<'ast> EarlyLintPass<'ast> for ModifierLogic { + fn check_item_function(&mut self, ctx: &LintContext<'_>, func: &'ast ItemFunction<'ast>) { + // Only check modifiers + if func.kind != FunctionKind::Modifier { + return; + } + + // Skip if modifier has no body or the body is empty + let body = match &func.body { + Some(body) if !body.is_empty() => body, + _ => return, + }; + + // Emit lint if the modifier contains unwrapped logic + if contains_unwrapped_logic(body) + && let Some(name) = func.header.name + { + ctx.emit(&UNWRAPPED_MODIFIER_LOGIC, name.span); + } + } +} + +/// Returns true if the modifier body contains any logic other than: +/// - The placeholder `_;` +/// - Calls to internal/private/public functions via direct identifier +fn contains_unwrapped_logic(stmts: &[Stmt<'_>]) -> bool { + stmts.iter().any(|stmt| !is_permitted_statement(stmt)) +} + +/// Returns true if the statement is allowed in a modifier without triggering the lint +fn is_permitted_statement(stmt: &Stmt<'_>) -> bool { + match &stmt.kind { + StmtKind::Placeholder => true, + StmtKind::Expr(expr) => is_permitted_expression(expr), + _ => false, + } +} + +/// Returns true if the expression is a call to a function via direct identifier +/// (i.e., not a built-in like require/assert/revert, and not a member/external call) +fn is_permitted_expression(expr: &solar_ast::Expr<'_>) -> bool { + match &expr.kind { + // Only allow function calls. + ExprKind::Call(func_expr, _) => match &func_expr.kind { + // Allow direct calls to user-defined functions (by identifier) + ExprKind::Ident(ident) => { + // Disallow calls to built-in control flow functions like require/assert/revert + !matches!(ident.name.as_str(), "require" | "assert" | "revert") + } + + // Disallow member calls (e.g., object.method()), which could be external or library + // calls + // TODO: enable library calls + ExprKind::Member(_, _) => false, + + // Disallow all other forms of function expressions (e.g., function pointers, etc.) + _ => false, + }, + + // Disallow all other expression types (not a function call) + _ => false, + } +} diff --git a/crates/lint/testdata/ModifierLogic.sol b/crates/lint/testdata/ModifierLogic.sol new file mode 100644 index 0000000000000..2826b939bd101 --- /dev/null +++ b/crates/lint/testdata/ModifierLogic.sol @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/** + * @title ModifierLogicTest + * @notice Test cases for the unwrapped-modifier-logic lint + * @dev This lint helps optimize gas by preventing modifier code duplication. + * Solidity inlines modifier code at each usage point instead of using jumps, + * so any logic in modifiers gets duplicated, increasing deployment costs. + */ +contract ModifierLogicTest { + mapping(address => bool) public isOwner; + + // Good patterns: Only call internal/private/public methods + + modifier empty() { + _; + } + + modifier onlyOwnerPublic() { + checkOwnerPublic(msg.sender); + _; + } + + modifier onlyOwnerPrivate() { + checkOwnerPrivate(msg.sender); + _; + } + + modifier onlyOwnerInternal() { + checkOwnerInternal(msg.sender); + _; + } + + modifier ownerOwnerPublicPrivateInternal(address owner0, address owner1, address owner2) { + checkOwnerPublic(owner0); + checkOwnerPrivate(owner1); + checkOwnerInternal(owner2); + _; + } + + modifier singleInternalWithParam(address sender) { + checkOwnerInternal(sender); + _; + } + + modifier multipleInternalWithParam(address owner0, address owner1, address owner2) { + checkOwnerPublic(owner0); + checkOwnerPrivate(owner1); + checkOwnerInternal(owner2); + _; + } + + function checkOwnerPublic(address sender) public view { + require(isOwner[sender], "Not owner"); + } + + function checkOwnerPrivate(address sender) private view { + require(isOwner[sender], "Not owner"); + } + + function checkOwnerInternal(address sender) internal view { + require(isOwner[sender], "Not owner"); + } + + // Bad patterns: Any logic that is not just a call to an internal/private/public method + + // 1. require + modifier onlyOwnerRequire() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + require(isOwner[msg.sender], "Not owner"); + _; + } + + // 2. require with param + modifier onlyOwnerRequireWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + require(isOwner[sender], "Not owner"); + _; + } + + // 3. assert + modifier onlyOwnerAssert() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + assert(isOwner[msg.sender]); + _; + } + + // 4. assert with param + modifier onlyOwnerAssertWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + assert(isOwner[sender]); + _; + } + + // 5. conditional revert + modifier onlyOwnerConditionalRevert() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + if (!isOwner[msg.sender]) { + revert("Not owner"); + } + _; + } + + // 6. conditional revert with param + modifier onlyOwnerConditionalRevertWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + if (!isOwner[sender]) { + revert("Not owner"); + } + _; + } + + // 7. assignment + modifier setOwner(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + isOwner[sender] = true; + _; + } + + // 8. assignment with param + modifier setOwnerWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + isOwner[sender] = true; + _; + } + + // 9. combination: require + internal call + modifier requireAndInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + require(isOwner[sender], "Not owner"); + checkOwnerInternal(sender); + _; + } + + // 10. combination: assignment + internal call + modifier assignAndInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + isOwner[sender] = true; + checkOwnerInternal(sender); + _; + } + + // 11. combination: require + assignment + modifier requireAndAssign(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + require(isOwner[sender], "Not owner"); + isOwner[sender] = false; + _; + } + + // 12. combination: require + public call + modifier requireAndPublic(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + require(isOwner[sender], "Not owner"); + checkOwnerPublic(sender); + _; + } + + // 13. combination: assignment + public call + modifier assignAndPublic(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + isOwner[sender] = true; + checkOwnerPublic(sender); + _; + } + + // 14. combination: require + assignment + internal call + modifier requireAssignInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + require(isOwner[sender], "Not owner"); + isOwner[sender] = false; + checkOwnerInternal(sender); + _; + } + + // 15. combination: require + assignment + public call + modifier requireAssignPublic(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + require(isOwner[sender], "Not owner"); + isOwner[sender] = false; + checkOwnerPublic(sender); + _; + } + + // 16. inline assembly + modifier withAssembly(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + assembly { + let x := sender + } + _; + } + + // 17. event emission + event DidSomething(address who); + + modifier emitEvent(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + emit DidSomething(sender); + _; + } + + // 18. inline revert string + modifier inlineRevert(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + if (sender == address(0)) revert("Zero address"); + _; + } + + // 19. combination: event + require + internal call + modifier eventRequireInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + emit DidSomething(sender); + require(isOwner[sender], "Not owner"); + checkOwnerInternal(sender); + _; + } +} \ No newline at end of file diff --git a/crates/lint/testdata/ModifierLogic.stderr b/crates/lint/testdata/ModifierLogic.stderr new file mode 100644 index 0000000000000..988c45ca81c79 --- /dev/null +++ b/crates/lint/testdata/ModifierLogic.stderr @@ -0,0 +1,152 @@ +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +69 | modifier onlyOwnerRequire() { + | ---------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +75 | modifier onlyOwnerRequireWithParam(address sender) { + | ------------------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +81 | modifier onlyOwnerAssert() { + | --------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +87 | modifier onlyOwnerAssertWithParam(address sender) { + | ------------------------ + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +93 | modifier onlyOwnerConditionalRevert() { + | -------------------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +101 | modifier onlyOwnerConditionalRevertWithParam(address sender) { + | ----------------------------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +109 | modifier setOwner(address sender) { + | -------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +115 | modifier setOwnerWithParam(address sender) { + | ----------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +121 | modifier requireAndInternal(address sender) { + | ------------------ + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +128 | modifier assignAndInternal(address sender) { + | ----------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +135 | modifier requireAndAssign(address sender) { + | ---------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +142 | modifier requireAndPublic(address sender) { + | ---------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +149 | modifier assignAndPublic(address sender) { + | --------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +156 | modifier requireAssignInternal(address sender) { + | --------------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +164 | modifier requireAssignPublic(address sender) { + | ------------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +172 | modifier withAssembly(address sender) { + | ------------ + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +182 | modifier emitEvent(address sender) { + | --------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +188 | modifier inlineRevert(address sender) { + | ------------ + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + +note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize + --> ROOT/testdata/ModifierLogic.sol:LL:CC + | +194 | modifier eventRequireInternal(address sender) { + | -------------------- + | + = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic + From 3328c32270dfefede370e14aeb1c0217e8a17cda Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Wed, 9 Jul 2025 00:22:44 -0400 Subject: [PATCH 2/6] cleanup --- crates/lint/src/sol/gas/mod.rs | 2 +- .../src/sol/gas/unwrapped_modifier_logic.rs | 58 ++++++++----------- ...erLogic.sol => UnwrappedModifierLogic.sol} | 4 +- ...c.stderr => UnwrappedModifierLogic.stderr} | 38 ++++++------ 4 files changed, 46 insertions(+), 56 deletions(-) rename crates/lint/testdata/{ModifierLogic.sol => UnwrappedModifierLogic.sol} (98%) rename crates/lint/testdata/{ModifierLogic.stderr => UnwrappedModifierLogic.stderr} (85%) diff --git a/crates/lint/src/sol/gas/mod.rs b/crates/lint/src/sol/gas/mod.rs index bb98db6e9e2f2..0949f27154862 100644 --- a/crates/lint/src/sol/gas/mod.rs +++ b/crates/lint/src/sol/gas/mod.rs @@ -6,4 +6,4 @@ use keccak::ASM_KECCAK256; mod unwrapped_modifier_logic; use unwrapped_modifier_logic::UNWRAPPED_MODIFIER_LOGIC; -register_lints!((AsmKeccak256, (ASM_KECCAK256)), (ModifierLogic, (UNWRAPPED_MODIFIER_LOGIC))); +register_lints!((AsmKeccak256, (ASM_KECCAK256)), (UnwrappedModifierLogic, (UNWRAPPED_MODIFIER_LOGIC))); diff --git a/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs index ee7737b259ba8..117b5e5839cd7 100644 --- a/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs +++ b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs @@ -1,9 +1,9 @@ -use super::ModifierLogic; +use super::UnwrappedModifierLogic; use crate::{ linter::{EarlyLintPass, LintContext}, sol::{Severity, SolLint}, }; -use solar_ast::{ExprKind, FunctionKind, ItemFunction, Stmt, StmtKind}; +use solar_ast::{ExprKind, ItemFunction, Stmt, StmtKind}; declare_forge_lint!( UNWRAPPED_MODIFIER_LOGIC, @@ -12,21 +12,21 @@ declare_forge_lint!( "modifier logic should be wrapped to avoid code duplication and reduce codesize" ); -impl<'ast> EarlyLintPass<'ast> for ModifierLogic { +impl<'ast> EarlyLintPass<'ast> for UnwrappedModifierLogic { fn check_item_function(&mut self, ctx: &LintContext<'_>, func: &'ast ItemFunction<'ast>) { - // Only check modifiers - if func.kind != FunctionKind::Modifier { + // If not a modifier, skip. + if !func.kind.is_modifier() { return; } - // Skip if modifier has no body or the body is empty + // If modifier has no contents, skip. let body = match &func.body { - Some(body) if !body.is_empty() => body, + Some(body) => body, _ => return, }; - // Emit lint if the modifier contains unwrapped logic - if contains_unwrapped_logic(body) + // If body contains unwrapped logic, emit. + if body.iter().any(|stmt| !is_valid_stmt(stmt)) && let Some(name) = func.header.name { ctx.emit(&UNWRAPPED_MODIFIER_LOGIC, name.span); @@ -34,44 +34,34 @@ impl<'ast> EarlyLintPass<'ast> for ModifierLogic { } } -/// Returns true if the modifier body contains any logic other than: -/// - The placeholder `_;` -/// - Calls to internal/private/public functions via direct identifier -fn contains_unwrapped_logic(stmts: &[Stmt<'_>]) -> bool { - stmts.iter().any(|stmt| !is_permitted_statement(stmt)) -} - -/// Returns true if the statement is allowed in a modifier without triggering the lint -fn is_permitted_statement(stmt: &Stmt<'_>) -> bool { +fn is_valid_stmt(stmt: &Stmt<'_>) -> bool { match &stmt.kind { + // If the statement is an expression, emit if not valid. + StmtKind::Expr(expr) => is_valid_expr(expr), + + // If the statement is a placeholder, skip. StmtKind::Placeholder => true, - StmtKind::Expr(expr) => is_permitted_expression(expr), + + // Disallow all other statements. _ => false, } } -/// Returns true if the expression is a call to a function via direct identifier -/// (i.e., not a built-in like require/assert/revert, and not a member/external call) -fn is_permitted_expression(expr: &solar_ast::Expr<'_>) -> bool { +fn is_valid_expr(expr: &solar_ast::Expr<'_>) -> bool { match &expr.kind { - // Only allow function calls. + // If the expression is a function call... ExprKind::Call(func_expr, _) => match &func_expr.kind { - // Allow direct calls to user-defined functions (by identifier) - ExprKind::Ident(ident) => { - // Disallow calls to built-in control flow functions like require/assert/revert - !matches!(ident.name.as_str(), "require" | "assert" | "revert") - } + // If the expression is a built-in control flow function, emit. + ExprKind::Ident(ident) => !matches!(ident.name.as_str(), "require" | "assert"), - // Disallow member calls (e.g., object.method()), which could be external or library - // calls - // TODO: enable library calls - ExprKind::Member(_, _) => false, + // If the expression is a member call, emit. + ExprKind::Member(_, _) => false, // TODO: enable library calls - // Disallow all other forms of function expressions (e.g., function pointers, etc.) + // Disallow all other expressions. _ => false, }, - // Disallow all other expression types (not a function call) + // Disallow all other expressions. _ => false, } } diff --git a/crates/lint/testdata/ModifierLogic.sol b/crates/lint/testdata/UnwrappedModifierLogic.sol similarity index 98% rename from crates/lint/testdata/ModifierLogic.sol rename to crates/lint/testdata/UnwrappedModifierLogic.sol index 2826b939bd101..5462d744cf172 100644 --- a/crates/lint/testdata/ModifierLogic.sol +++ b/crates/lint/testdata/UnwrappedModifierLogic.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; /** - * @title ModifierLogicTest + * @title UnwrappedModifierLogicTest * @notice Test cases for the unwrapped-modifier-logic lint * @dev This lint helps optimize gas by preventing modifier code duplication. * Solidity inlines modifier code at each usage point instead of using jumps, * so any logic in modifiers gets duplicated, increasing deployment costs. */ -contract ModifierLogicTest { +contract UnwrappedModifierLogicTest { mapping(address => bool) public isOwner; // Good patterns: Only call internal/private/public methods diff --git a/crates/lint/testdata/ModifierLogic.stderr b/crates/lint/testdata/UnwrappedModifierLogic.stderr similarity index 85% rename from crates/lint/testdata/ModifierLogic.stderr rename to crates/lint/testdata/UnwrappedModifierLogic.stderr index 988c45ca81c79..bb05275d4683b 100644 --- a/crates/lint/testdata/ModifierLogic.stderr +++ b/crates/lint/testdata/UnwrappedModifierLogic.stderr @@ -1,5 +1,5 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 69 | modifier onlyOwnerRequire() { | ---------------- @@ -7,7 +7,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 75 | modifier onlyOwnerRequireWithParam(address sender) { | ------------------------- @@ -15,7 +15,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 81 | modifier onlyOwnerAssert() { | --------------- @@ -23,7 +23,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 87 | modifier onlyOwnerAssertWithParam(address sender) { | ------------------------ @@ -31,7 +31,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 93 | modifier onlyOwnerConditionalRevert() { | -------------------------- @@ -39,7 +39,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 101 | modifier onlyOwnerConditionalRevertWithParam(address sender) { | ----------------------------------- @@ -47,7 +47,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 109 | modifier setOwner(address sender) { | -------- @@ -55,7 +55,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 115 | modifier setOwnerWithParam(address sender) { | ----------------- @@ -63,7 +63,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 121 | modifier requireAndInternal(address sender) { | ------------------ @@ -71,7 +71,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 128 | modifier assignAndInternal(address sender) { | ----------------- @@ -79,7 +79,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 135 | modifier requireAndAssign(address sender) { | ---------------- @@ -87,7 +87,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 142 | modifier requireAndPublic(address sender) { | ---------------- @@ -95,7 +95,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 149 | modifier assignAndPublic(address sender) { | --------------- @@ -103,7 +103,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 156 | modifier requireAssignInternal(address sender) { | --------------------- @@ -111,7 +111,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 164 | modifier requireAssignPublic(address sender) { | ------------------- @@ -119,7 +119,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 172 | modifier withAssembly(address sender) { | ------------ @@ -127,7 +127,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 182 | modifier emitEvent(address sender) { | --------- @@ -135,7 +135,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 188 | modifier inlineRevert(address sender) { | ------------ @@ -143,7 +143,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/ModifierLogic.sol:LL:CC + --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 194 | modifier eventRequireInternal(address sender) { | -------------------- From e24bd1510cdf4d6ba07508171dbefc849bf22d77 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Wed, 9 Jul 2025 00:54:03 -0400 Subject: [PATCH 3/6] cleanup test --- .../lint/testdata/UnwrappedModifierLogic.sol | 190 ++++++------------ .../testdata/UnwrappedModifierLogic.stderr | 122 ++--------- 2 files changed, 69 insertions(+), 243 deletions(-) diff --git a/crates/lint/testdata/UnwrappedModifierLogic.sol b/crates/lint/testdata/UnwrappedModifierLogic.sol index 5462d744cf172..8a6bc344c0c2f 100644 --- a/crates/lint/testdata/UnwrappedModifierLogic.sol +++ b/crates/lint/testdata/UnwrappedModifierLogic.sol @@ -11,190 +11,112 @@ pragma solidity ^0.8.0; contract UnwrappedModifierLogicTest { mapping(address => bool) public isOwner; - // Good patterns: Only call internal/private/public methods + // Helpers - modifier empty() { - _; - } - - modifier onlyOwnerPublic() { - checkOwnerPublic(msg.sender); - _; - } - - modifier onlyOwnerPrivate() { - checkOwnerPrivate(msg.sender); - _; - } - - modifier onlyOwnerInternal() { - checkOwnerInternal(msg.sender); - _; - } - - modifier ownerOwnerPublicPrivateInternal(address owner0, address owner1, address owner2) { - checkOwnerPublic(owner0); - checkOwnerPrivate(owner1); - checkOwnerInternal(owner2); - _; - } - - modifier singleInternalWithParam(address sender) { - checkOwnerInternal(sender); - _; - } - - modifier multipleInternalWithParam(address owner0, address owner1, address owner2) { - checkOwnerPublic(owner0); - checkOwnerPrivate(owner1); - checkOwnerInternal(owner2); - _; - } - - function checkOwnerPublic(address sender) public view { + function checkPublic(address sender) public { require(isOwner[sender], "Not owner"); } - function checkOwnerPrivate(address sender) private view { + function checkPrivate(address sender) private { require(isOwner[sender], "Not owner"); } - function checkOwnerInternal(address sender) internal view { + function checkInternal(address sender) internal { require(isOwner[sender], "Not owner"); } - // Bad patterns: Any logic that is not just a call to an internal/private/public method - - // 1. require - modifier onlyOwnerRequire() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - require(isOwner[msg.sender], "Not owner"); - _; - } - - // 2. require with param - modifier onlyOwnerRequireWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - require(isOwner[sender], "Not owner"); - _; - } - - // 3. assert - modifier onlyOwnerAssert() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - assert(isOwner[msg.sender]); - _; - } - - // 4. assert with param - modifier onlyOwnerAssertWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - assert(isOwner[sender]); - _; - } - - // 5. conditional revert - modifier onlyOwnerConditionalRevert() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - if (!isOwner[msg.sender]) { - revert("Not owner"); - } - _; - } + // Good patterns - // 6. conditional revert with param - modifier onlyOwnerConditionalRevertWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - if (!isOwner[sender]) { - revert("Not owner"); - } + modifier empty() { _; } - // 7. assignment - modifier setOwner(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - isOwner[sender] = true; + modifier publicFn() { + checkPublic(msg.sender); _; } - // 8. assignment with param - modifier setOwnerWithParam(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - isOwner[sender] = true; + modifier privateFn() { + checkPrivate(msg.sender); _; } - // 9. combination: require + internal call - modifier requireAndInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - require(isOwner[sender], "Not owner"); - checkOwnerInternal(sender); + modifier internalFn() { + checkInternal(msg.sender); _; } - // 10. combination: assignment + internal call - modifier assignAndInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - isOwner[sender] = true; - checkOwnerInternal(sender); + modifier publicPrivateInternal(address owner0, address owner1, address owner2) { + checkPublic(owner0); + checkPrivate(owner1); + checkInternal(owner2); _; } - // 11. combination: require + assignment - modifier requireAndAssign(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - require(isOwner[sender], "Not owner"); - isOwner[sender] = false; - _; - } + // Bad patterns - // 12. combination: require + public call - modifier requireAndPublic(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - require(isOwner[sender], "Not owner"); - checkOwnerPublic(sender); + modifier requireBuiltIn() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + checkPublic(msg.sender); + require(isOwner[msg.sender], "Not owner"); + checkPrivate(msg.sender); _; + checkInternal(msg.sender); } - // 13. combination: assignment + public call - modifier assignAndPublic(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - isOwner[sender] = true; - checkOwnerPublic(sender); + modifier assertBuiltIn() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + checkPublic(msg.sender); + assert(isOwner[msg.sender]); + checkPrivate(msg.sender); _; + checkInternal(msg.sender); } - // 14. combination: require + assignment + internal call - modifier requireAssignInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - require(isOwner[sender], "Not owner"); - isOwner[sender] = false; - checkOwnerInternal(sender); + modifier conditionalRevert() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + checkPublic(msg.sender); + if (!isOwner[msg.sender]) { + revert("Not owner"); + } + checkPrivate(msg.sender); _; + checkInternal(msg.sender); } - // 15. combination: require + assignment + public call - modifier requireAssignPublic(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - require(isOwner[sender], "Not owner"); - isOwner[sender] = false; - checkOwnerPublic(sender); + modifier assign(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + checkPublic(sender); + bool _isOwner = true; + checkPrivate(sender); + isOwner[sender] = _isOwner; _; + checkInternal(sender); } - // 16. inline assembly - modifier withAssembly(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier assemblyBlock(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + checkPublic(sender); assembly { let x := sender } + checkPrivate(sender); _; + checkInternal(sender); } - // 17. event emission - event DidSomething(address who); - - modifier emitEvent(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - emit DidSomething(sender); + modifier uncheckedBlock(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + checkPublic(sender); + unchecked { + sender; + } + checkPrivate(sender); _; + checkInternal(sender); } - // 18. inline revert string - modifier inlineRevert(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize - if (sender == address(0)) revert("Zero address"); - _; - } + event DidSomething(address who); - // 19. combination: event + require + internal call - modifier eventRequireInternal(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier emitEvent(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + checkPublic(sender); emit DidSomething(sender); - require(isOwner[sender], "Not owner"); - checkOwnerInternal(sender); + checkPrivate(sender); _; + checkInternal(sender); } } \ No newline at end of file diff --git a/crates/lint/testdata/UnwrappedModifierLogic.stderr b/crates/lint/testdata/UnwrappedModifierLogic.stderr index bb05275d4683b..62764aebf8a0b 100644 --- a/crates/lint/testdata/UnwrappedModifierLogic.stderr +++ b/crates/lint/testdata/UnwrappedModifierLogic.stderr @@ -1,152 +1,56 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | -69 | modifier onlyOwnerRequire() { - | ---------------- +58 | modifier requireBuiltIn() { + | -------------- | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | -75 | modifier onlyOwnerRequireWithParam(address sender) { - | ------------------------- +66 | modifier assertBuiltIn() { + | ------------- | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | -81 | modifier onlyOwnerAssert() { - | --------------- +74 | modifier conditionalRevert() { + | ----------------- | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | -87 | modifier onlyOwnerAssertWithParam(address sender) { - | ------------------------ +84 | modifier assign(address sender) { + | ------ | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | -93 | modifier onlyOwnerConditionalRevert() { - | -------------------------- +93 | modifier assemblyBlock(address sender) { + | ------------- | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | -101 | modifier onlyOwnerConditionalRevertWithParam(address sender) { - | ----------------------------------- +103 | modifier uncheckedBlock(address sender) { + | -------------- | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | -109 | modifier setOwner(address sender) { - | -------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -115 | modifier setOwnerWithParam(address sender) { - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -121 | modifier requireAndInternal(address sender) { - | ------------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -128 | modifier assignAndInternal(address sender) { - | ----------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -135 | modifier requireAndAssign(address sender) { - | ---------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -142 | modifier requireAndPublic(address sender) { - | ---------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -149 | modifier assignAndPublic(address sender) { - | --------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -156 | modifier requireAssignInternal(address sender) { - | --------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -164 | modifier requireAssignPublic(address sender) { - | ------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -172 | modifier withAssembly(address sender) { - | ------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -182 | modifier emitEvent(address sender) { +115 | modifier emitEvent(address sender) { | --------- | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -188 | modifier inlineRevert(address sender) { - | ------------ - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize - --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC - | -194 | modifier eventRequireInternal(address sender) { - | -------------------- - | - = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic - From 4d6369b236d67b2efbb2436d966d4c626576a68e Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Wed, 9 Jul 2025 00:59:30 -0400 Subject: [PATCH 4/6] fmt --- crates/lint/src/sol/gas/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/lint/src/sol/gas/mod.rs b/crates/lint/src/sol/gas/mod.rs index 0949f27154862..7fac2bf1c465e 100644 --- a/crates/lint/src/sol/gas/mod.rs +++ b/crates/lint/src/sol/gas/mod.rs @@ -6,4 +6,7 @@ use keccak::ASM_KECCAK256; mod unwrapped_modifier_logic; use unwrapped_modifier_logic::UNWRAPPED_MODIFIER_LOGIC; -register_lints!((AsmKeccak256, (ASM_KECCAK256)), (UnwrappedModifierLogic, (UNWRAPPED_MODIFIER_LOGIC))); +register_lints!( + (AsmKeccak256, (ASM_KECCAK256)), + (UnwrappedModifierLogic, (UNWRAPPED_MODIFIER_LOGIC)) +); From e75f687132d5569b354fbd5216a5ae1edf5ea3bc Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Wed, 9 Jul 2025 07:35:31 -0400 Subject: [PATCH 5/6] nits --- .../src/sol/gas/unwrapped_modifier_logic.rs | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs index 117b5e5839cd7..9897a5a69bf6b 100644 --- a/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs +++ b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs @@ -20,10 +20,7 @@ impl<'ast> EarlyLintPass<'ast> for UnwrappedModifierLogic { } // If modifier has no contents, skip. - let body = match &func.body { - Some(body) => body, - _ => return, - }; + let Some(body) = &func.body else { return }; // If body contains unwrapped logic, emit. if body.iter().any(|stmt| !is_valid_stmt(stmt)) @@ -47,21 +44,16 @@ fn is_valid_stmt(stmt: &Stmt<'_>) -> bool { } } +// TODO: Support library member calls like `Lib.foo` (throws false positives). fn is_valid_expr(expr: &solar_ast::Expr<'_>) -> bool { - match &expr.kind { - // If the expression is a function call... - ExprKind::Call(func_expr, _) => match &func_expr.kind { - // If the expression is a built-in control flow function, emit. - ExprKind::Ident(ident) => !matches!(ident.name.as_str(), "require" | "assert"), - - // If the expression is a member call, emit. - ExprKind::Member(_, _) => false, // TODO: enable library calls - - // Disallow all other expressions. - _ => false, - }, - - // Disallow all other expressions. - _ => false, + // If the expression is a call, continue. + if let ExprKind::Call(func_expr, _) = &expr.kind + && let ExprKind::Ident(ident) = &func_expr.kind + { + // If the call is a built-in control flow function, emit. + return !matches!(ident.name.as_str(), "require" | "assert"); } + + // Disallow all other expressions. + false } From 41fc4f8fa6ed0918cabaf0d2cddafe8209193056 Mon Sep 17 00:00:00 2001 From: "clandestine.eth" <96172957+0xClandestine@users.noreply.github.com> Date: Tue, 15 Jul 2025 14:42:40 -0400 Subject: [PATCH 6/6] nit: shorten warning --- .../lint/src/sol/gas/unwrapped_modifier_logic.rs | 2 +- crates/lint/testdata/UnwrappedModifierLogic.sol | 14 +++++++------- crates/lint/testdata/UnwrappedModifierLogic.stderr | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs index 9897a5a69bf6b..ec96f45ea2399 100644 --- a/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs +++ b/crates/lint/src/sol/gas/unwrapped_modifier_logic.rs @@ -9,7 +9,7 @@ declare_forge_lint!( UNWRAPPED_MODIFIER_LOGIC, Severity::Gas, "unwrapped-modifier-logic", - "modifier logic should be wrapped to avoid code duplication and reduce codesize" + "wrap modifier logic to reduce code size" ); impl<'ast> EarlyLintPass<'ast> for UnwrappedModifierLogic { diff --git a/crates/lint/testdata/UnwrappedModifierLogic.sol b/crates/lint/testdata/UnwrappedModifierLogic.sol index 8a6bc344c0c2f..8f7dfc689295a 100644 --- a/crates/lint/testdata/UnwrappedModifierLogic.sol +++ b/crates/lint/testdata/UnwrappedModifierLogic.sol @@ -55,7 +55,7 @@ contract UnwrappedModifierLogicTest { // Bad patterns - modifier requireBuiltIn() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier requireBuiltIn() { //~NOTE: wrap modifier logic to reduce code size checkPublic(msg.sender); require(isOwner[msg.sender], "Not owner"); checkPrivate(msg.sender); @@ -63,7 +63,7 @@ contract UnwrappedModifierLogicTest { checkInternal(msg.sender); } - modifier assertBuiltIn() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier assertBuiltIn() { //~NOTE: wrap modifier logic to reduce code size checkPublic(msg.sender); assert(isOwner[msg.sender]); checkPrivate(msg.sender); @@ -71,7 +71,7 @@ contract UnwrappedModifierLogicTest { checkInternal(msg.sender); } - modifier conditionalRevert() { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier conditionalRevert() { //~NOTE: wrap modifier logic to reduce code size checkPublic(msg.sender); if (!isOwner[msg.sender]) { revert("Not owner"); @@ -81,7 +81,7 @@ contract UnwrappedModifierLogicTest { checkInternal(msg.sender); } - modifier assign(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier assign(address sender) { //~NOTE: wrap modifier logic to reduce code size checkPublic(sender); bool _isOwner = true; checkPrivate(sender); @@ -90,7 +90,7 @@ contract UnwrappedModifierLogicTest { checkInternal(sender); } - modifier assemblyBlock(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier assemblyBlock(address sender) { //~NOTE: wrap modifier logic to reduce code size checkPublic(sender); assembly { let x := sender @@ -100,7 +100,7 @@ contract UnwrappedModifierLogicTest { checkInternal(sender); } - modifier uncheckedBlock(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier uncheckedBlock(address sender) { //~NOTE: wrap modifier logic to reduce code size checkPublic(sender); unchecked { sender; @@ -112,7 +112,7 @@ contract UnwrappedModifierLogicTest { event DidSomething(address who); - modifier emitEvent(address sender) { //~NOTE: modifier logic should be wrapped to avoid code duplication and reduce codesize + modifier emitEvent(address sender) { //~NOTE: wrap modifier logic to reduce code size checkPublic(sender); emit DidSomething(sender); checkPrivate(sender); diff --git a/crates/lint/testdata/UnwrappedModifierLogic.stderr b/crates/lint/testdata/UnwrappedModifierLogic.stderr index 62764aebf8a0b..3ec35fa9304dc 100644 --- a/crates/lint/testdata/UnwrappedModifierLogic.stderr +++ b/crates/lint/testdata/UnwrappedModifierLogic.stderr @@ -1,4 +1,4 @@ -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 58 | modifier requireBuiltIn() { @@ -6,7 +6,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 66 | modifier assertBuiltIn() { @@ -14,7 +14,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 74 | modifier conditionalRevert() { @@ -22,7 +22,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 84 | modifier assign(address sender) { @@ -30,7 +30,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 93 | modifier assemblyBlock(address sender) { @@ -38,7 +38,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 103 | modifier uncheckedBlock(address sender) { @@ -46,7 +46,7 @@ note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code d | = help: https://book.getfoundry.sh/reference/forge/forge-lint#unwrapped-modifier-logic -note[unwrapped-modifier-logic]: modifier logic should be wrapped to avoid code duplication and reduce codesize +note[unwrapped-modifier-logic]: wrap modifier logic to reduce code size --> ROOT/testdata/UnwrappedModifierLogic.sol:LL:CC | 115 | modifier emitEvent(address sender) {