Skip to content
Last updated: February 06, 2026

SCWE-144: Bypassable Contract Existence Check via extcodesize

Stable Version v1.0

This content is in the version-(v1.0) and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).

Send Feedback

Relationships

Description

Using extcodesize(addr) > 0 to detect whether an address is a contract fails during construction. When code runs inside a constructor, extcodesize(address(this)) returns 0 because the contract's code is not yet deployed. An attacker can deploy a contract whose constructor calls the victim—the victim sees extcodesize(caller) == 0 and treats the caller as an EOA, bypassing contract-specific restrictions.

Remediation

  • Do not rely on extcodesize to distinguish EOAs from contracts.
  • If contract-only or EOA-only logic is required, use a different mechanism (e.g., trusted registry, explicit opt-in) or accept that constructor calls cannot be distinguished.

Examples

Vulnerable

pragma solidity ^0.8.0;

contract TokenGating {
    mapping(address => bool) public allowed;

    function claim() external {
        require(extcodesize(msg.sender) == 0, "Contracts not allowed");
        allowed[msg.sender] = true;
    }

    function extcodesize(address account) internal view returns (uint256 size) {
        assembly { size := extcodesize(account) }
    }
}
Why vulnerable: An attacker deploys Attacker whose constructor calls claim(). During the constructor, extcodesize(Attacker) == 0, so the check passes and the contract receives the claim.

Fixed

pragma solidity ^0.8.0;

contract TokenGating {
    mapping(address => bool) public allowed;
    mapping(address => bool) public claimed;

    function claim() external {
        require(allowed[msg.sender], "Not allowed");
        require(!claimed[msg.sender], "Already claimed");
        claimed[msg.sender] = true;
    }
}
Fix: Use an allowlist instead of extcodesize. Access control is based on explicit registration, not EOA vs contract detection.