From de5bffd4b88db1e1fa487d5aa5abd9fcba0f4f28 Mon Sep 17 00:00:00 2001 From: ep0chzer0 Date: Mon, 23 Feb 2026 13:10:54 -0500 Subject: [PATCH 1/2] feat: add detectors for unused events and unused modifiers Add two new informational detectors to identify dead code: - `unused-events`: Detects events declared but never emitted anywhere in the compilation unit. Skips interface contracts and dependencies. - `unused-modifiers`: Detects modifiers declared but never applied to any function. Handles inheritance correctly by skipping virtual modifiers that are overridden in child contracts. Both detectors include comprehensive test contracts covering: - Basic unused/used elements - Inheritance chains (base events emitted by children) - Virtual modifier override patterns - Interface event declarations Closes #2782 (partial - errors covered by #2902) --- slither/detectors/all_detectors.py | 2 + slither/detectors/functions/unused_events.py | 75 ++++++++++++ .../detectors/functions/unused_modifiers.py | 108 ++++++++++++++++++ ...nusedEvents_0_8_0_unused_events_sol__0.txt | 6 + ...odifiers_0_8_0_unused_modifiers_sol__0.txt | 6 + .../unused-events/0.8.0/unused_events.sol | 64 +++++++++++ .../0.8.0/unused_events.sol-0.8.0.zip | Bin 0 -> 6256 bytes .../0.8.0/unused_modifiers.sol | 80 +++++++++++++ .../0.8.0/unused_modifiers.sol-0.8.0.zip | Bin 0 -> 5306 bytes tests/e2e/detectors/test_detectors.py | 10 ++ 10 files changed, 351 insertions(+) create mode 100644 slither/detectors/functions/unused_events.py create mode 100644 slither/detectors/functions/unused_modifiers.py create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_UnusedEvents_0_8_0_unused_events_sol__0.txt create mode 100644 tests/e2e/detectors/snapshots/detectors__detector_UnusedModifiers_0_8_0_unused_modifiers_sol__0.txt create mode 100644 tests/e2e/detectors/test_data/unused-events/0.8.0/unused_events.sol create mode 100644 tests/e2e/detectors/test_data/unused-events/0.8.0/unused_events.sol-0.8.0.zip create mode 100644 tests/e2e/detectors/test_data/unused-modifiers/0.8.0/unused_modifiers.sol create mode 100644 tests/e2e/detectors/test_data/unused-modifiers/0.8.0/unused_modifiers.sol-0.8.0.zip diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 6301979b50..6186205e81 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -81,6 +81,8 @@ from .statements.unary import IncorrectUnaryExpressionDetection from .operations.missing_zero_address_validation import MissingZeroAddressValidation from .functions.dead_code import DeadCode +from .functions.unused_events import UnusedEvents +from .functions.unused_modifiers import UnusedModifiers from .statements.write_after_write import WriteAfterWrite from .statements.msg_value_in_loop import MsgValueInLoop from .statements.msg_value_in_nonpayable import MsgValueInNonPayable diff --git a/slither/detectors/functions/unused_events.py b/slither/detectors/functions/unused_events.py new file mode 100644 index 0000000000..5e264557fa --- /dev/null +++ b/slither/detectors/functions/unused_events.py @@ -0,0 +1,75 @@ +""" +Module detecting unused events +""" + +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.slithir.operations.event_call import EventCall +from slither.utils.output import Output + + +class UnusedEvents(AbstractDetector): + """ + Detect events that are declared but never emitted + """ + + ARGUMENT = "unused-events" + HELP = "Events that are not emitted" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unused-events" + + WIKI_TITLE = "Unused events" + WIKI_DESCRIPTION = "Detect events that are declared but never emitted." + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +```solidity +contract Example { + event Unused(address indexed user); + event Used(uint256 value); + + function action() external { + emit Used(100); + } +} +``` +`Unused` is never emitted and should be removed.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "Remove unused events or emit them where appropriate." + + def _detect(self) -> list[Output]: + results = [] + + # Collect all emitted event names across the entire compilation unit + events_emitted: set[str] = set() + for contract in self.compilation_unit.contracts_derived: + for function in contract.functions_and_modifiers: + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, EventCall): + events_emitted.add(str(ir.name)) + + for contract in sorted( + self.compilation_unit.contracts, key=lambda x: x.name + ): + if contract.is_interface or contract.is_from_dependency(): + continue + + for event in sorted(contract.events_declared, key=lambda x: x.full_name): + if event.name not in events_emitted: + info: DETECTOR_INFO = [ + event, + " is never emitted in ", + contract, + "\n", + ] + res = self.generate_result(info) + results.append(res) + + return results diff --git a/slither/detectors/functions/unused_modifiers.py b/slither/detectors/functions/unused_modifiers.py new file mode 100644 index 0000000000..06f5b64adf --- /dev/null +++ b/slither/detectors/functions/unused_modifiers.py @@ -0,0 +1,108 @@ +""" +Module detecting unused modifiers +""" + +from slither.core.declarations import Modifier +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + DETECTOR_INFO, +) +from slither.utils.output import Output + + +class UnusedModifiers(AbstractDetector): + """ + Detect modifiers that are declared but never applied to any function + """ + + ARGUMENT = "unused-modifiers" + HELP = "Modifiers that are not applied" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unused-modifiers" + + WIKI_TITLE = "Unused modifiers" + WIKI_DESCRIPTION = "Detect modifiers that are declared but never applied to any function." + + # region wiki_exploit_scenario + WIKI_EXPLOIT_SCENARIO = """ +```solidity +contract Example { + modifier unused() { + require(msg.sender == address(0)); + _; + } + + modifier used() { + require(msg.value > 0); + _; + } + + function pay() external payable used { + // ... + } +} +``` +`unused` is never applied and should be removed.""" + # endregion wiki_exploit_scenario + + WIKI_RECOMMENDATION = "Remove unused modifiers or apply them where appropriate." + + def _detect(self) -> list[Output]: + results = [] + + # Collect all modifiers that are applied to any function + modifiers_used: set[str] = set() + for contract in self.compilation_unit.contracts_derived: + for function in contract.functions: + for modifier in function.modifiers: + if isinstance(modifier, Modifier): + modifiers_used.add(modifier.canonical_name) + + # Collect virtual modifiers that are overridden in child contracts + # (overridden_by may not be populated for modifiers, so check manually) + overridden_modifiers: set[str] = set() + for contract in self.compilation_unit.contracts: + for inherited_mod in contract.modifiers_inherited: + for declared_mod in contract.modifiers_declared: + if inherited_mod.name == declared_mod.name: + overridden_modifiers.add(inherited_mod.canonical_name) + + for modifier in sorted( + self.compilation_unit.modifiers, key=lambda x: x.canonical_name + ): + if not isinstance(modifier, Modifier): + continue + + if modifier.contract_declarer.is_from_dependency(): + continue + + if modifier.contract_declarer.is_interface: + continue + + # Skip if the modifier is used + if modifier.canonical_name in modifiers_used: + continue + + # Skip if not implemented (abstract) + if not modifier.is_implemented: + continue + + # Skip virtual modifiers that are overridden in child contracts + # (part of inheritance design pattern) + if modifier.is_virtual and ( + modifier.overridden_by + or modifier.canonical_name in overridden_modifiers + ): + continue + + info: DETECTOR_INFO = [ + modifier, + " is never used and should be removed\n", + ] + res = self.generate_result(info) + results.append(res) + + return results diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UnusedEvents_0_8_0_unused_events_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UnusedEvents_0_8_0_unused_events_sol__0.txt new file mode 100644 index 0000000000..86d7d4cf00 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_UnusedEvents_0_8_0_unused_events_sol__0.txt @@ -0,0 +1,6 @@ +UnusedEventContract.NeverEmitted(address) (unused_events.sol#7) is never emitted in UnusedEventContract (unused_events.sol#6-15) + +UnusedEventContract.AlsoNeverEmitted(uint256,string) (unused_events.sol#8) is never emitted in UnusedEventContract (unused_events.sol#6-15) + +BaseUnused.OrphanEvent(address) (unused_events.sol#46) is never emitted in BaseUnused (unused_events.sol#45-47) + diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UnusedModifiers_0_8_0_unused_modifiers_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UnusedModifiers_0_8_0_unused_modifiers_sol__0.txt new file mode 100644 index 0000000000..203c6c77ef --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_UnusedModifiers_0_8_0_unused_modifiers_sol__0.txt @@ -0,0 +1,6 @@ +UnusedModifierContract.alsoNeverUsed() (unused_modifiers.sol#14-17) is never used and should be removed + +UnusedVirtual.unusedVirtualMod() (unused_modifiers.sol#75-77) is never used and should be removed + +UnusedModifierContract.neverUsed() (unused_modifiers.sol#9-12) is never used and should be removed + diff --git a/tests/e2e/detectors/test_data/unused-events/0.8.0/unused_events.sol b/tests/e2e/detectors/test_data/unused-events/0.8.0/unused_events.sol new file mode 100644 index 0000000000..8cb005b963 --- /dev/null +++ b/tests/e2e/detectors/test_data/unused-events/0.8.0/unused_events.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// --- Should be flagged --- + +contract UnusedEventContract { + event NeverEmitted(address indexed user); + event AlsoNeverEmitted(uint256 value, string reason); + + event ActuallyEmitted(uint256 value); + + function action() external { + emit ActuallyEmitted(100); + } +} + +// --- Should NOT be flagged --- + +contract EmitsAll { + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); + + function transfer(address to, uint256 amount) external { + emit Transfer(msg.sender, to, amount); + } + + function approve(address spender, uint256 amount) external { + emit Approval(msg.sender, spender, amount); + } +} + +// --- Inheritance tests --- + +contract Base { + event BaseEvent(uint256 value); +} + +contract Child is Base { + function emitIt() external { + emit BaseEvent(42); + } +} + +// Unused event in base, never emitted anywhere +contract BaseUnused { + event OrphanEvent(address who); +} + +contract ChildNoEmit is BaseUnused { + function doNothing() external pure {} +} + +// --- Interface should not be flagged --- + +interface IExample { + event InterfaceEvent(uint256 id); +} + +contract Implementor is IExample { + // InterfaceEvent is inherited from interface, not declared here + function trigger() external { + emit InterfaceEvent(1); + } +} diff --git a/tests/e2e/detectors/test_data/unused-events/0.8.0/unused_events.sol-0.8.0.zip b/tests/e2e/detectors/test_data/unused-events/0.8.0/unused_events.sol-0.8.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..cd6f4efd0365059e054c9ef779f6ec14218990e3 GIT binary patch literal 6256 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 0); + _; + } + + modifier actuallyUsed() { + require(msg.sender != address(0)); + _; + } + + function doSomething() external actuallyUsed { + // uses actuallyUsed + } +} + +// --- Should NOT be flagged --- + +contract AllModifiersUsed { + address public owner; + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + modifier nonZero(uint256 val) { + require(val > 0); + _; + } + + function restricted() external onlyOwner { + // uses onlyOwner + } + + function validated(uint256 x) external nonZero(x) { + // uses nonZero + } +} + +// --- Inheritance: virtual modifier overridden --- + +contract BaseModifier { + modifier baseMod() virtual { + _; + } +} + +contract ChildModifier is BaseModifier { + modifier baseMod() override { + require(msg.sender != address(0)); + _; + } + + function action() external baseMod { + // uses overridden baseMod + } +} + +// --- Unused virtual modifier NOT overridden --- + +contract UnusedVirtual { + modifier unusedVirtualMod() virtual { + _; + } + + function nothing() external pure {} +} diff --git a/tests/e2e/detectors/test_data/unused-modifiers/0.8.0/unused_modifiers.sol-0.8.0.zip b/tests/e2e/detectors/test_data/unused-modifiers/0.8.0/unused_modifiers.sol-0.8.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..9d407d4aae482b567dd21a25d0426b4c7da5241f GIT binary patch literal 5306 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 7bd6e34600..a58a0c3905 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -1366,6 +1366,16 @@ def id_test(test_item: Test): "dead-code-library.sol", "0.8.0", ), + Test( + all_detectors.UnusedEvents, + "unused_events.sol", + "0.8.0", + ), + Test( + all_detectors.UnusedModifiers, + "unused_modifiers.sol", + "0.8.0", + ), Test( all_detectors.WriteAfterWrite, "write-after-write.sol", From b64b9c28b8c6ad4edd265559d205002ff9baea5c Mon Sep 17 00:00:00 2001 From: ep0chzer0 Date: Mon, 23 Feb 2026 13:13:29 -0500 Subject: [PATCH 2/2] style: apply ruff formatting --- slither/detectors/functions/unused_events.py | 4 +--- slither/detectors/functions/unused_modifiers.py | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/slither/detectors/functions/unused_events.py b/slither/detectors/functions/unused_events.py index 5e264557fa..51fc6cfaf6 100644 --- a/slither/detectors/functions/unused_events.py +++ b/slither/detectors/functions/unused_events.py @@ -55,9 +55,7 @@ def _detect(self) -> list[Output]: if isinstance(ir, EventCall): events_emitted.add(str(ir.name)) - for contract in sorted( - self.compilation_unit.contracts, key=lambda x: x.name - ): + for contract in sorted(self.compilation_unit.contracts, key=lambda x: x.name): if contract.is_interface or contract.is_from_dependency(): continue diff --git a/slither/detectors/functions/unused_modifiers.py b/slither/detectors/functions/unused_modifiers.py index 06f5b64adf..66c9309d3b 100644 --- a/slither/detectors/functions/unused_modifiers.py +++ b/slither/detectors/functions/unused_modifiers.py @@ -70,9 +70,7 @@ def _detect(self) -> list[Output]: if inherited_mod.name == declared_mod.name: overridden_modifiers.add(inherited_mod.canonical_name) - for modifier in sorted( - self.compilation_unit.modifiers, key=lambda x: x.canonical_name - ): + for modifier in sorted(self.compilation_unit.modifiers, key=lambda x: x.canonical_name): if not isinstance(modifier, Modifier): continue @@ -93,8 +91,7 @@ def _detect(self) -> list[Output]: # Skip virtual modifiers that are overridden in child contracts # (part of inheritance design pattern) if modifier.is_virtual and ( - modifier.overridden_by - or modifier.canonical_name in overridden_modifiers + modifier.overridden_by or modifier.canonical_name in overridden_modifiers ): continue