Skip to main content
Sigvex

Incorrect Constructor Remediation

How to ensure initialization code runs once at deployment and cannot be re-invoked, using the constructor keyword or a guarded initializer.

Incorrect Constructor Remediation

Overview

Before Solidity 0.4.22 a constructor was a function whose name matched the contract. Renaming the contract, or a typo in that function name, demoted the constructor to an ordinary public function that anyone could call after deployment to re-run initialization and seize ownership. The fix is to use the constructor keyword so the compiler guarantees the code runs exactly once at deployment; for upgradeable contracts, which cannot use a constructor for proxy state, use an initializer protected against re-entry.

Related Detector: Incorrect Constructor

Before (Vulnerable)

// solidity 0.4.21 — contract renamed from "CrowdSale", init left behind
contract TokenSale {
    address public owner;

    function CrowdSale() public {   // now a public, callable function
        owner = msg.sender;
    }
}

After (Fixed)

// solidity ^0.8.0
contract TokenSale {
    address public owner;

    constructor() {
        owner = msg.sender;         // runs once, at deployment only
    }
}

Alternative Mitigations

Upgradeable contracts — proxy-based contracts run their logic via delegatecall, so a constructor does not initialize proxy storage. Use a one-time initializer guarded so it cannot be called twice:

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract TokenSaleUpgradeable is Initializable {
    address public owner;

    function initialize() external initializer {  // reverts on second call
        owner = msg.sender;
    }
}

Disable the initializer on the implementation — call _disableInitializers() in the implementation’s constructor so the logic contract itself can never be initialized directly.

Common Mistakes

An initialize() with no guard — without an initializer modifier (or an explicit require(!initialized) flag set on first call), the function is just a public re-initializer, which is the same vulnerability in modern form.

Leaving the implementation uninitialized — an unguarded implementation behind a proxy can be initialized by an attacker directly; pair the initializer with _disableInitializers() on the implementation.

References