Incorrect Constructor
Detects initialization functions that were intended as constructors but became callable public functions due to naming errors in Solidity 0.4.x contracts.
Incorrect Constructor
Overview
Remediation Guide: How to Fix Incorrect Constructor
The incorrect constructor detector identifies public functions that exhibit constructor-like behavior — writing to multiple storage slots, storing msg.sender as an owner, and lacking re-initialization guards — but are callable by anyone after deployment. In Solidity versions before 0.4.22, constructors were functions matching the contract name. A typo in the function name, or renaming the contract without updating the constructor, turned the constructor into an ordinary public function that anyone could call to re-initialize the contract and claim ownership.
Sigvex detects this pattern at the bytecode level by identifying functions with high storage write density, CALLER opcode usage (for msg.sender), and no initialization guard (no SLOAD-then-compare check at the function entry).
Why This Is an Issue
When a constructor becomes a public function, any user can call it after deployment to overwrite critical state variables. The Rubixi contract (2016) had this exact vulnerability: the developer renamed the contract from DynamicPyramid to Rubixi but forgot to rename the constructor, leaving DynamicPyramid() as a public function that set the creator/owner.
While modern Solidity (0.4.22+) uses the constructor keyword, this vulnerability still appears in:
- Contracts compiled with older Solidity versions still deployed on mainnet
- Initialization functions in proxy patterns that lack proper guards
- Contracts imported from legacy codebases
How to Resolve
// Before: Vulnerable — Solidity 0.4.x constructor typo
contract MyContract {
address public owner;
// BUG: This was supposed to be "MyContract" — now anyone can call it
function myContract() public {
owner = msg.sender;
}
}
// After: Fixed — use constructor keyword (Solidity >= 0.4.22)
contract MyContract {
address public owner;
constructor() {
owner = msg.sender;
}
}
Examples
Vulnerable Code
// solidity 0.4.21
contract TokenSale {
address public owner;
uint256 public price;
// VULNERABLE: Not a constructor — contract was renamed from "CrowdSale"
function CrowdSale() public {
owner = msg.sender;
price = 1 ether;
}
function withdraw() public {
require(msg.sender == owner);
msg.sender.transfer(address(this).balance);
}
}
Fixed Code
// solidity ^0.8.0
contract TokenSale {
address public owner;
uint256 public price;
constructor() {
owner = msg.sender;
price = 1 ether;
}
function withdraw() external {
require(msg.sender == owner, "Not owner");
payable(owner).transfer(address(this).balance);
}
}
Sample Sigvex Output
{
"detector_id": "incorrect-constructor",
"severity": "critical",
"confidence": 0.78,
"description": "Public function CrowdSale() writes to 2 storage slots including msg.sender storage without an initialization guard. This appears to be a misnamed constructor.",
"location": { "function": "CrowdSale()", "offset": 0 }
}
Detection Methodology
- Initialization pattern matching: Identifies functions with multiple
SSTOREinstructions, particularly those that storeCALLER(msg.sender) — a hallmark of constructor logic. - Guard check detection: Looks for initialization guards (loading a flag from storage and comparing it) at the function entry. Functions with such guards are less likely to be vulnerable.
- Known mutating function exclusion: Skips well-known standard library functions like
renounceOwnershipandtransferOwnershipthat legitimately write to storage and reference the caller. - Name heuristic: Functions with names that are capitalized or match common constructor naming patterns receive higher confidence.
Limitations
False positives:
- Legitimate initialization functions in upgradeable contracts (e.g.,
initialize()) that intentionally perform constructor-like operations may be flagged if they lack visible guards. - Admin functions that set multiple state variables may match the initialization pattern.
False negatives:
- Constructors with minimal storage writes (only one slot) may not match the pattern threshold.
- Contracts where the constructor was correctly named but performs no
CALLERstorage may be missed.
Related Detectors
- Access Control — detects missing authorization on state-changing functions
- UUPS Initialization — detects uninitialized proxy implementations