Skip to content

Enhancement: Comprehensive Dead Code Detection for Errors, Events, and Modifiers #2782

@dguido

Description

@dguido

Summary

Extend dead code detection to comprehensively identify unused custom errors, events, and modifiers in Solidity contracts, providing developers with a complete picture of removable code to optimize contract size and deployment costs.

Motivation

Current dead code detection in many analyzers focuses primarily on unused functions and variables, but misses other important unused elements:

  1. Custom Errors (introduced in Solidity 0.8.4) - Can accumulate during refactoring
  2. Events - Often left behind after removing functionality
  3. Modifiers - May become obsolete as access control evolves

Removing unused code elements provides significant benefits:

  • Reduced deployment costs - Smaller bytecode means lower deployment gas
  • Improved maintainability - Less code to understand and maintain
  • Better security posture - Eliminates potential attack surface
  • Cleaner interfaces - Removes confusion about which elements are actually used

Detection Targets

1. Unused Custom Errors

contract Example {
    error UnusedError(address user);        // DETECT: Never used
    error UsedError(uint256 amount);        // OK: Used below
    
    function doSomething(uint256 val) external {
        if (val == 0) revert UsedError(val);
    }
}

2. Unused Events

contract Example {
    event UnusedEvent(address indexed user); // DETECT: Never emitted
    event UsedEvent(uint256 value);          // OK: Emitted below
    
    function action() external {
        emit UsedEvent(100);
    }
}

3. Unused Modifiers

contract Example {
    modifier unusedModifier() {              // DETECT: Never applied
        require(msg.sender == owner);
        _;
    }
    
    modifier usedModifier() {                // OK: Used below
        require(msg.value > 0);
        _;
    }
    
    function pay() external payable usedModifier {
        // ...
    }
}

Implementation Approach

The detector should:

  1. Track all custom error, event, and modifier definitions
  2. Find all references/uses throughout the codebase
  3. Check inheritance chains for usage in child contracts
  4. Verify interface compliance requirements
  5. Calculate bytecode impact of unused elements
  6. Report with actionable recommendations

Expected Output

Comprehensive Dead Code Analysis Results

Unused Custom Errors (3):
  - UnusedError1 at Contract.sol:10
    Impact: Adds 200 bytes to bytecode
    Safe to remove: Yes
    
  - UnusedError2 at Contract.sol:15  
    Impact: Adds 150 bytes to bytecode
    Safe to remove: Check inheritance
    
Unused Events (2):
  - OldTransfer at Contract.sol:25
    Impact: Adds 300 bytes to bytecode
    Note: May be required by external tools/indexers
    
Unused Modifiers (1):
  - legacyOnlyOwner at Contract.sol:40
    Impact: Adds 400 bytes to bytecode
    Replaced by: newAccessControl modifier

Total potential savings: ~1050 bytes
Estimated deployment gas saved: ~210,000 gas

Advanced Features

Cross-Contract Analysis

  • Check if items are used in child contracts
  • Verify interface compliance
  • Track library usage

Inheritance Chain Analysis

contract Base {
    error BaseError();                   // Check usage in entire tree
    modifier baseModifier() virtual { _; }
}

contract Child is Base {
    modifier baseModifier() override { _; } // OK: Override in use
}

Test Cases

// Test contract with various unused elements
contract TestUnused {
    // Unused elements (should be detected)
    error Never_Used(string message);
    event Never_Emitted(uint256 value);
    modifier never_Applied() { _; }
    
    // Used elements (should not be detected)
    error Actually_Used(uint256 code);
    event Actually_Emitted(address user);
    modifier actually_Applied() { _; }
    
    function testFunction() external actually_Applied {
        emit Actually_Emitted(msg.sender);
        revert Actually_Used(404);
    }
}

// Interface compliance test
interface ITest {
    event Required_Event(uint256 id);
}

contract TestImpl is ITest {
    event Required_Event(uint256 id);  // Should not be flagged
    event Extra_Event(address user);   // Should be flagged if unused
}

Benefits

  1. Significant gas savings - Each removed element reduces deployment cost
  2. Improved code clarity - Only active code remains
  3. Better maintainability - Less code to review and understand
  4. Reduced attack surface - Fewer code paths to audit
  5. Optimization insights - Identifies refactoring opportunities

Priority

Medium-High - While not a security vulnerability, unused code significantly impacts deployment costs and contract maintainability. With the 24KB contract size limit, removing dead code can be critical for complex contracts. The detector provides immediate value with actionable results and quantifiable benefits.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions