Skip to main content
Sigvex

Immutable Violation

Detects modifications to storage variables that should be immutable, including constructor-set values modified by public functions and view-named functions that write to storage.

Immutable Violation

Overview

Remediation Guide: How to Fix Immutable Violation

The immutable violation detector identifies storage variables that are written during contract construction and then modified by other functions, violating the expectation that certain values remain constant after deployment. At the bytecode level, Solidity’s immutable keyword is not explicitly encoded — the detector uses heuristics to identify constructor-initialized slots that are subsequently overwritten.

The detector also flags functions whose names suggest read-only behavior (matching get, view, balance, total patterns) but contain SSTORE instructions, indicating a mislabeled or incorrectly implemented function.

Why This Is an Issue

Immutability guarantees are a cornerstone of smart contract security. When a variable set during construction (such as a trusted oracle address, bridge endpoint, or token contract) can be modified later, an attacker who gains write access to that slot can redirect the contract’s behavior entirely.

The Nomad Bridge exploit ($190M, August 2022) involved an initialization flaw where a critical variable was set to a value that effectively disabled message verification. Stable-swap pool factory vulnerabilities have similarly involved immutable address changes.

How to Resolve

// Before: Vulnerable — critical address stored in regular storage
contract VulnerableBridge {
    address public validator;  // Mutable storage variable

    constructor(address _validator) {
        validator = _validator;
    }

    // BUG: validator can be changed after deployment
    function setValidator(address _new) external {
        validator = _new;
    }
}

// After: Fixed — use immutable keyword
contract SafeBridge {
    address public immutable validator;  // Cannot be changed after construction

    constructor(address _validator) {
        validator = _validator;
    }
    // No setter function — validator is permanently fixed
}

Examples

Vulnerable Code

contract VulnerableOracle {
    address public priceFeed;
    address public owner;

    constructor(address _feed) {
        priceFeed = _feed;
        owner = msg.sender;
    }

    // Anyone could call this if onlyOwner is missing or compromised
    function updateFeed(address _newFeed) external {
        priceFeed = _newFeed;  // Immutability violation — feed should not change
    }

    // Misleading name — this function writes to storage
    function getBalance() external returns (uint256) {
        lastQueried = block.timestamp;  // SSTORE in a "get" function
        return address(this).balance;
    }
}

Fixed Code

contract SafeOracle {
    address public immutable priceFeed;
    address public immutable owner;

    constructor(address _feed) {
        priceFeed = _feed;
        owner = msg.sender;
    }

    // View function — correctly marked, no state changes
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

Sample Sigvex Output

{
  "detector_id": "immutable-violation",
  "severity": "critical",
  "confidence": 0.80,
  "description": "Storage slot 0x1 is written in the constructor and modified by function updateFeed(address). This variable appears to be intended as immutable.",
  "location": { "function": "updateFeed(address)", "offset": 128 }
}

Detection Methodology

  1. Constructor analysis: Identifies storage slots written during initialization (the creation-time code path).
  2. Cross-function write tracking: For each constructor-written slot, checks whether any runtime function also writes to the same slot.
  3. View-function heuristic: Flags functions whose names match read-only patterns (get*, view*, balance*, total*) but contain SSTORE instructions. A list of known-mutating standard library functions (e.g., renounceOwnership, transferOwnership) is excluded.
  4. Write-once detection: Identifies storage slots that are written exactly once in the constructor and never again — if a runtime function writes to such a slot, it is flagged with high confidence.

Limitations

False positives:

  • Contracts that intentionally allow one-time migration of immutable-like values (e.g., a migrate() function with a guard) may be flagged.
  • Proxy patterns where the constructor sets initial values and a separate initialize() function is intended to be called once may trigger findings.

False negatives:

  • Variables that are set through complex initialization patterns (factory deployment, CREATE2) may not be recognized as constructor-initialized.
  • Storage slots accessed through computed offsets (dynamic mappings) are not tracked for immutability.

References