Opcode Summary
| Property | Value |
|---|---|
| Opcode | 0x3B |
| Mnemonic | EXTCODESIZE |
| Gas | 100 (warm) / 2600 (cold) |
| Stack Input | addr (20-byte address, zero-padded to 32 bytes) |
| Stack Output | len(addr.code) (byte length of the code at the target address) |
| Behavior | Pushes the size (in bytes) of the code stored at the given external address onto the stack. Returns 0 for EOAs, contracts mid-construction, destroyed contracts, and precompiled contracts. Subject to EIP-2929 warm/cold access pricing: first access to an address in a transaction costs 2600 gas; subsequent accesses cost 100 gas. |
Threat Surface
EXTCODESIZE is the external-address variant of CODESIZE — it answers “how many bytes of code exist at address X?” This seemingly simple query is the foundation of the most widely deployed (and most widely exploited) contract detection pattern in Ethereum: isContract().
The threat surface is dominated by three properties:
-
isContract()is fundamentally unreliable. EXTCODESIZE returns 0 in at least four contexts where a contract does exist or will exist: during constructor execution (code not yet deployed), afterSELFDESTRUCT(code cleared), for precompiled contracts (native implementations at addresses 0x01-0x0a with no EVM bytecode), and for futureCREATE2addresses (no code yet but deployment is deterministic). Conversely, with EIP-7702 (Pectra, 2025), EOAs can now have non-zero code size via delegation. The result: EXTCODESIZE can neither confirm nor deny that an address is a contract. OpenZeppelin removedAddress.isContract()entirely in v5.0. -
Cold access gas is a DoS vector. Before EIP-2929 (Berlin, 2021), EXTCODESIZE cost a flat 700 gas regardless of whether the account had been accessed before. Attackers exploited this in the 2016 Shanghai DoS attack by calling EXTCODESIZE ~50,000 times per block against cold addresses, forcing nodes to perform expensive disk I/O at minimal gas cost. EIP-2929 raised cold access to 2600 gas, but the opcode remains one of the cheaper ways to force state reads against arbitrary addresses. Contracts that loop over user-supplied address lists and call EXTCODESIZE on each are vulnerable to gas griefing.
-
Code existence ≠ code immutability. Even when EXTCODESIZE correctly returns a non-zero value, it says nothing about what code is at that address. Metamorphic contracts (CREATE2 + SELFDESTRUCT) can swap code at a fixed address between transactions. EXTCODESIZE returns a fresh non-zero value after redeployment, giving false assurance that the address is the “same” contract.
Smart Contract Threats
T1: isContract() Bypass via Constructor Execution (Critical)
The most exploited pattern involving EXTCODESIZE. Contracts use extcodesize(addr) > 0 to detect whether the caller is a contract, then restrict access to EOAs only. During constructor execution, the calling contract has no deployed bytecode, so EXTCODESIZE returns 0 and the guard passes.
// Vulnerable pattern -- trivially bypassed
function isContract(address addr) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}
modifier onlyEOA() {
require(!isContract(msg.sender), "No contracts");
_;
}Attack: An attacker deploys a contract whose constructor calls the protected function. During construction, extcodesize(address(this)) == 0, so isContract() returns false. The attacker’s constructor executes the restricted logic, then deployment completes normally. A factory contract can repeat this thousands of times in a single transaction.
Why it matters: This was the basis of OpenZeppelin’s Address.isContract(), used by thousands of contracts across the ecosystem. OpenZeppelin removed the function in v5.0 (October 2023), stating: “Attempting to prevent calls from contracts is highly discouraged because it breaks composability, breaks support for smart wallets like Gnosis Safe, and doesn’t provide security since it can be circumvented by calling from a contract constructor.”
T2: Unreliable EOA Detection — Smart Wallets Excluded (High)
Contracts that use extcodesize(msg.sender) == 0 as a proxy for “caller is an EOA” systematically exclude legitimate smart contract wallets:
-
Gnosis Safe / ERC-4337 accounts: These are contracts with non-zero code size. Any
require(!isContract(msg.sender))check blocks them from participating. -
EIP-7702 delegation (Pectra, 2025): EOAs can now delegate to contract code, writing a 23-byte delegation designator to their account. EXTCODESIZE on a delegated EOA returns the size of the delegation designator (non-zero), causing
isContract()to returntruefor what is still an EOA. The EOA/contract binary distinction is fully broken. -
False negative cascade: Protocol features gated by
isContract() == falseblock a growing share of the user base (smart wallets, account-abstracted wallets, EIP-7702 delegated EOAs) while providing zero protection against constructor-based bypasses.
Why it matters: Account abstraction (ERC-4337) adoption is accelerating. Protocols that use EXTCODESIZE-based contract detection are building technical debt that will exclude an increasing fraction of users.
T3: Precompiled Contracts Return 0 (Medium)
Ethereum precompiles (addresses 0x01 through 0x0a) implement cryptographic and utility functions natively. They have no EVM bytecode — EXTCODESIZE returns 0 for all of them. This creates edge cases:
- A contract that validates “address has code before calling” will reject calls to precompiles, even though they are valid call targets that execute successfully.
- A contract that interprets
extcodesize == 0as “address is an EOA” will misclassify precompiles as EOAs. - L2s and sidechains may add custom precompiles at different addresses. Code that hardcodes precompile address ranges will break on those chains.
Why it matters: While precompile misclassification is rarely exploited directly, it causes subtle bugs in cross-chain code and multi-chain deployments where precompile sets differ.
T4: Gas Griefing via Cold Access (High)
EXTCODESIZE costs 2600 gas for cold access (first touch of an address in a transaction) versus 100 gas for warm access. An attacker who can influence the list of addresses checked via EXTCODESIZE can force expensive cold reads:
// Vulnerable: iterates over user-supplied addresses
function batchValidate(address[] calldata targets) external view {
for (uint256 i = 0; i < targets.length; i++) {
require(isContract(targets[i]), "not a contract");
}
}With 100 unique cold addresses, the EXTCODESIZE calls alone cost 260,000 gas. With 1,000 addresses, 2.6 million gas. In a meta-transaction or relayer context, the relayer pays gas while the attacker chooses addresses, creating a direct financial griefing vector.
Historical context: The 2016 Shanghai DoS attacks exploited EXTCODESIZE (then priced at ~700 gas) to force ~50,000 cold state reads per block, causing nodes to take 20-80 seconds per block instead of milliseconds. EIP-150 and later EIP-2929 repriced the opcode, but loops over cold addresses remain a griefing risk.
T5: Metamorphic Contracts — Code Size as False Identity (Critical)
EXTCODESIZE returns a non-zero value after a metamorphic contract is redeployed to the same address, even though the underlying code has completely changed. Any system that uses “code exists at this address” as a trust signal is vulnerable:
- Contract A deploys at address X via CREATE2 (EXTCODESIZE = len(A))
- Contract A passes audit, gets whitelisted in governance or token approvals
SELFDESTRUCTclears A’s code (EXTCODESIZE = 0)- Contract B deploys to address X via CREATE2 with same salt (EXTCODESIZE = len(B))
- Address X is still whitelisted but now runs completely different code
Post-Dencun (EIP-6780): SELFDESTRUCT no longer clears code except in the same transaction as deployment, significantly limiting this pattern on L1 Ethereum. However, it remains viable on L2s and alternative EVMs that haven’t adopted EIP-6780.
Why it matters: The Tornado Cash governance attack (May 2023) used this exact pattern to replace an approved proposal contract with malicious code, stealing ~$2.17M in TORN tokens.
Protocol-Level Threats
P1: State Access DoS (Medium)
EXTCODESIZE forces a state trie lookup for the target account’s code. Cold access requires disk I/O to load the account from the state trie, which is the most expensive operation nodes perform. The 2016 Shanghai attacks demonstrated that EXTCODESIZE (then at 700 gas) was severely underpriced relative to its I/O cost, enabling DoS attacks that slowed block processing to 20-80 seconds.
EIP-2929 (Berlin, 2021) raised cold access to 2600 gas, reducing the worst case to ~7-27 seconds per block. EIP-7907 proposes further per-word metering for code loading beyond 24KB. The risk is reduced but not eliminated — EXTCODESIZE remains cheaper than EXTCODECOPY for triggering a cold state read since it only retrieves the size, not the full bytecode.
P2: Consensus Safety (Low)
EXTCODESIZE is deterministic — it reads the code length from the account state, which is committed in the state trie. All conformant client implementations agree on the result. No consensus bugs have been attributed to EXTCODESIZE itself. The one subtle area is the interaction with SELFDESTRUCT within the same transaction (code is not cleared until end-of-transaction), which was a source of Foundry/Anvil divergence in testing environments but not on mainnet.
P3: EIP-7702 Delegation Designator Semantics (Medium)
EIP-7702 (Pectra, 2025) writes a 23-byte delegation designator (0xef0100 || address) to an EOA’s code field. EXTCODESIZE on such an account returns 23 (the size of the designator), not the size of the delegated contract’s code. This creates a new class of semantic ambiguity:
extcodesize(addr) > 0now returnstruefor delegated EOAs- The returned size (23) doesn’t reflect the actual executable code size
- Contracts that cache or compare code sizes will see unexpected values
This was formalized in a January 2025 PR to EIP-7702, distinguishing between EXTCODESIZE (returns designator size) and CODESIZE/CODECOPY within the delegated context (reflects target contract’s code).
P4: EXTCODESIZE Across Hard Forks (Low)
Key gas repricing history:
- Pre-Tangerine Whistle: ~20 gas (severely underpriced)
- EIP-150 (Tangerine Whistle, 2016): Raised to 700 gas
- EIP-2929 (Berlin, 2021): Warm/cold split — 100/2600 gas
- EIP-6780 (Cancun/Dencun, 2024): SELFDESTRUCT neutered, reducing metamorphic risk
- EIP-7702 (Pectra, 2025): Delegation designator changes EXTCODESIZE semantics for EOAs
Semantics have not changed (still returns code length), but gas costs and the meaning of a “0” or “non-zero” result have shifted significantly across forks.
Edge Cases
| Edge Case | Behavior | Security Implication |
|---|---|---|
| Target is an EOA (no code) | Returns 0 | Correct behavior; but indistinguishable from constructor or destroyed contract |
| Target is mid-construction | Returns 0 | Root cause of isContract() bypass; attacker calls from constructor |
Target has been SELFDESTRUCTed (same tx, pre-Cancun) | Returns non-zero (code not yet cleared) | Within-transaction code still appears present; cleared at end of tx |
Target has been SELFDESTRUCTed (next tx, pre-Cancun) | Returns 0 | Address looks like an EOA; cached trust assumptions are broken |
| Target is a precompile (0x01-0x0a) | Returns 0 | Precompiles have no EVM bytecode; misclassified as EOA |
| Target is EIP-7702 delegated EOA | Returns 23 (designator size) | EOA now appears to be a “contract”; breaks isContract() in the opposite direction |
| Target does not exist (never received ETH or code) | Returns 0 | Indistinguishable from EOA; same as EXTCODEHASH returning keccak256("") |
| Target is at max address (2^160 - 1) | Returns 0 (no code at that address) | No special behavior; extreme address values don’t cause overflow |
| Cold vs. warm access | 2600 gas vs. 100 gas (EIP-2929) | Gas cost varies 26x based on prior access; critical for gas estimation in loops |
| Target has code size 0 after EIP-6780 SELFDESTRUCT | SELFDESTRUCT only sends ETH, code persists | Post-Dencun: code is NOT cleared unless SELFDESTRUCT runs in the same tx as deployment |
Real-World Exploits
Exploit 1: Fomo3D Airdrop Pot Drain — EXTCODESIZE Bypass (July 2018)
Root cause: isHuman() modifier using extcodesize(msg.sender) == 0 bypassed by calling from a contract constructor.
Details: Fomo3D was a viral lottery/game contract that distributed ETH prizes from an “airdrop pot.” The contract used an isHuman() modifier implemented as extcodesize(msg.sender) == 0 to restrict participation to EOAs. Attackers deployed contracts whose constructors called Fomo3D’s airdrop function — during construction, EXTCODESIZE returned 0 for the attacker’s address, and the isHuman() guard passed.
The attack was further amplified by iterative pre-calculated contract creation: Fomo3D’s “randomness” for airdrop winners was derived from on-chain data (block number, timestamp, sender address). Since contract addresses from CREATE are deterministic (based on deployer address + nonce), attackers pre-calculated which nonce would produce an address that would win the airdrop. They deployed throwaway contracts to increment their nonce until reaching the winning deployment, then deployed the attack contract at the right nonce. Failed attempts were simply reverted, costing minimal gas.
EXTCODESIZE’s role: The entire security model of the isHuman() modifier rested on EXTCODESIZE returning non-zero for contracts. The constructor-time exception made this check worthless.
Impact: Multiple attackers drained significant ETH from the airdrop pot over several weeks. The exploit was automated and repeatable, with PeckShield documenting the iterative pre-calculation technique in detail.
References:
Exploit 2: 2016 Shanghai DoS Attacks — EXTCODESIZE as Network Weapon (September 2016)
Root cause: EXTCODESIZE severely underpriced relative to its disk I/O cost, enabling computational DoS at the protocol level.
Details: During Ethereum’s Devcon2 in Shanghai, attackers launched sustained DoS attacks against the network by crafting transactions that called EXTCODESIZE approximately 50,000 times per block against cold (never-before-accessed) addresses. Each call forced nodes to perform a state trie lookup and disk read to retrieve the code size of the target address. At ~20 gas per call (pre-Tangerine Whistle), the attacker could trigger 50,000 disk reads for less than 1M gas — well within the block gas limit.
The result was devastating: blocks that should have processed in milliseconds took 20-80 seconds. Miners struggled to validate blocks in time, causing chain stalls and uncle rate spikes. The attacker also combined EXTCODESIZE with SELFDESTRUCT to cheaply create ~19 million empty accounts, bloating the state trie and further degrading node performance.
EXTCODESIZE’s role: EXTCODESIZE was the primary opcode exploited because it forced a full state trie lookup (disk I/O) at minimal gas cost. The attacker specifically targeted addresses that would not be in the node’s memory cache, maximizing the I/O cost per call.
Impact: Network degradation lasted weeks. Led to emergency hard forks: EIP-150 (Tangerine Whistle, October 2016) raised EXTCODESIZE cost to 700 gas, and EIP-161 (Spurious Dragon, November 2016) implemented empty account pruning. EIP-2929 (Berlin, 2021) later introduced the warm/cold model at 100/2600 gas.
References:
- Ethereum Foundation: Network DoS Announcement
- Ethos.dev: Shanghai Attacks Analysis
- Vitalik Buterin: Gas Cost Increases for State Access Opcodes
Exploit 3: Tornado Cash Governance Takeover — Metamorphic EXTCODESIZE Deception ($2.17M, May 2023)
Root cause: Governance system trusted an address based on a one-time code verification. EXTCODESIZE returned non-zero after metamorphic redeployment, masking the code swap.
Details: An attacker submitted a governance proposal to Tornado Cash that appeared identical to a previously approved, legitimate proposal. Voters approved it after cursory review. After the vote passed, the attacker triggered a hidden SELFDESTRUCT in the proposal contract, clearing its code (EXTCODESIZE → 0). The attacker then redeployed a malicious contract to the same address via CREATE2. EXTCODESIZE returned a non-zero value again, and the governance system still recognized the address as an approved proposal.
The malicious code granted the attacker 1.2 million fake TORN governance votes (far exceeding ~700,000 legitimate votes). With full governance control, the attacker drained 483,000 TORN tokens ($2.17M) from governance vaults.
EXTCODESIZE’s role: EXTCODESIZE could not detect the code swap. Before destruction it returned non-zero (benign code), briefly returned 0, then returned non-zero again (malicious code). Any check based on “does code exist at this address?” would pass at both the submission and execution phases. Only an EXTCODEHASH comparison could have detected the change.
Impact: $2.17M stolen. Full governance takeover of one of Ethereum’s highest-profile protocols.
References:
- Halborn: Tornado Cash Hack Explained
- pcaversaccio: Tornado Cash Exploit PoC
- CoinDesk: Attacker Takes Over Tornado Cash DAO
Exploit 4: OpenZeppelin Address.isContract() Removal — Ecosystem-Wide Pattern Death (October 2023)
Root cause: Systemic ecosystem reliance on EXTCODESIZE for contract detection, despite fundamental unreliability.
Details: OpenZeppelin’s Address.isContract() was the most widely deployed contract detection utility in the Solidity ecosystem. It compiled to account.code.length > 0 (EXTCODESIZE). Over years of use and multiple exploits, the function was found to be unreliable in every direction:
- Returns
falsefor contracts in their constructor (bypass via constructor calls) - Returns
falsefor CREATE2 pre-computed addresses (no code yet) - Returns
falsefor destroyed contracts (SELFDESTRUCT) - Returns
falsefor precompiles (no EVM bytecode) - Returns
truefor EIP-7702 delegated EOAs (have delegation designator)
OpenZeppelin filed Issue #3417 and removed the function in PR #3682, shipped with Contracts v5.0. The removal forced hundreds of downstream projects to refactor their contract detection logic.
EXTCODESIZE’s role: isContract() was a thin wrapper around EXTCODESIZE. Its removal was the ecosystem’s formal acknowledgment that code size is not a reliable indicator of account type.
References:
- OpenZeppelin Issue #3417: Remove isContract
- OpenZeppelin PR #3682: Remove Address.isContract
- ConsenSys: EXTCODESIZE Checks Best Practices
Attack Scenarios
Scenario A: Constructor-Based isContract() Bypass (Airdrop Drain)
// Vulnerable: airdrop restricted to "humans only"
contract VulnerableAirdrop {
mapping(address => bool) public claimed;
function claim() external {
require(!isContract(msg.sender), "No contracts");
require(!claimed[msg.sender], "Already claimed");
claimed[msg.sender] = true;
_mint(msg.sender, AIRDROP_AMOUNT);
}
function isContract(address addr) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}
}
// Attack: bypass from constructor, then forward tokens
contract AirdropSniper {
constructor(address airdrop, address attacker) {
// extcodesize(address(this)) == 0 during construction
VulnerableAirdrop(airdrop).claim();
// Forward minted tokens to attacker's EOA
IERC20(airdrop).transfer(attacker, IERC20(airdrop).balanceOf(address(this)));
}
}
// Factory deploys hundreds of snipers in one transaction
contract SniperFactory {
function snipe(address airdrop, uint256 count) external {
for (uint256 i = 0; i < count; i++) {
new AirdropSniper(airdrop, msg.sender);
}
}
}Scenario B: NFT Mint Bot Bypassing Anti-Contract Guard
contract VulnerableNFTMint {
uint256 public constant MAX_PER_WALLET = 3;
uint256 public constant PRICE = 0.08 ether;
function mint(uint256 quantity) external payable {
require(!isContract(msg.sender), "No bots allowed");
require(quantity <= MAX_PER_WALLET, "Exceeds max");
require(msg.value == quantity * PRICE, "Wrong ETH");
_safeMint(msg.sender, quantity);
}
function isContract(address addr) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}
}
// Bot mints from constructor, bypassing the isContract check
contract MintBot {
constructor(address nft, uint256 qty) payable {
VulnerableNFTMint(nft).mint{value: msg.value}(qty);
}
}
// Deployer creates many MintBots, each minting MAX_PER_WALLET
contract BotFactory {
function massMint(address nft, uint256 bots) external payable {
uint256 costPerBot = 3 * 0.08 ether;
for (uint256 i = 0; i < bots; i++) {
new MintBot{value: costPerBot}(nft, 3);
}
}
}Scenario C: Cold Access Gas Griefing via Relayer
contract RelayedValidator {
// Relayer pays gas for meta-transactions
function validateTargets(address[] calldata targets) external view returns (bool) {
for (uint256 i = 0; i < targets.length; i++) {
uint256 size;
assembly { size := extcodesize(targets[i]) }
if (size == 0) return false;
}
return true;
}
}
// Attacker submits a meta-transaction with 1,000 unique cold addresses.
// EXTCODESIZE cold access = 2,600 gas each → 2,600,000 gas consumed.
// The relayer pays for it all. The attacker pays nothing.
// Even if the function reverts, the relayer's gas is spent.Scenario D: Precompile Misclassification
contract TokenWithContractCheck {
function transfer(address to, uint256 amount) external {
// Intended: prevent transfers to "non-contract" addresses
// Bug: blocks transfers to precompile addresses
// and to EOAs with legitimate use cases
require(_isContract(to), "Must send to a contract");
_transfer(msg.sender, to, amount);
}
function _isContract(address addr) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}
}
// Calling transfer(address(0x01), 100) reverts
// even though 0x01 is ecrecover -- a valid, functional address.
// Also blocks all EOA recipients, smart wallets during construction, etc.Mitigations
| Threat | Mitigation | Implementation |
|---|---|---|
| T1: isContract() bypass via constructor | Do not use EXTCODESIZE to distinguish EOAs from contracts | Remove isContract() checks entirely; OpenZeppelin removed theirs in v5.0 |
| T1: If EOA-only is truly required | Use msg.sender == tx.origin (with caveats) | Blocks contract callers including constructors, but also blocks smart wallets and ERC-4337 accounts; not future-proof |
| T2: Smart wallet exclusion | Design for contract callers as first-class citizens | Use reentrancy guards and per-address rate limits instead of contract exclusion; support ERC-4337 and EIP-7702 |
| T3: Precompile misclassification | Don’t rely on code size for call target validation | Use try/catch for external calls; maintain explicit precompile address lists if needed |
| T4: Cold access gas griefing | Bound loops over user-supplied address lists | Cap array length (require(targets.length <= MAX_BATCH)); pre-warm addresses via access lists (EIP-2930) |
| T4: Relayer gas griefing | Cap gas for relayed meta-transactions | Set explicit gas limits on relayed calls; require gas deposits from meta-transaction signers |
| T5: Metamorphic contract detection | Verify code hash at interaction time, not just on first encounter | require(addr.codehash == expectedHash) before approvals or governance execution |
| T5: Governance proposal integrity | Re-verify proposal code at execution time | Check EXTCODEHASH in execute(), not just in propose(); reject if hash changed |
| General | Use EXTCODEHASH instead of EXTCODESIZE for identity | EXTCODEHASH is strictly more informative — same gas cost, returns full code hash instead of just length |
| General | Use off-chain allowlists for access control | Merkle tree of approved addresses verified via MerkleProof.verify() instead of on-chain contract detection |
Compiler/EIP-Based Protections
- EIP-2929 (Berlin, 2021): Introduced warm/cold access pricing. EXTCODESIZE cold access costs 2600 gas (up from 700), reducing DoS amplification by ~3.7x.
- EIP-2930 (Berlin, 2021): Access lists allow transactions to pre-declare addresses they will touch, converting cold accesses to warm at a fixed per-address cost. Mitigates gas estimation issues in loops.
- EIP-6780 (Dencun, 2024): SELFDESTRUCT no longer clears code except in the same transaction as deployment. Effectively kills the metamorphic contract pattern on L1, making EXTCODESIZE results more stable across transactions.
- EIP-7702 (Pectra, 2025): EOAs can delegate to contract code. EXTCODESIZE returns the delegation designator size (23 bytes), not the delegated code size. Permanently breaks the
extcodesize == 0 → EOAassumption. - OpenZeppelin v5.0 (October 2023): Removed
Address.isContract(). Ecosystem-wide signal that EXTCODESIZE-based contract detection is deprecated.
Severity Summary
| Threat ID | Category | Severity | Likelihood | Real-World Precedent |
|---|---|---|---|---|
| T1 | Smart Contract | Critical | High | Fomo3D airdrop drain (2018), countless isContract() bypasses |
| T2 | Smart Contract | High | High | Smart wallet exclusion across DeFi; worsened by EIP-7702 |
| T3 | Smart Contract | Medium | Low | Precompile misclassification in multi-chain deployments |
| T4 | Smart Contract | High | Medium | 2016 Shanghai DoS attacks; relayer gas griefing |
| T5 | Smart Contract | Critical | Medium (Low post-Dencun on L1) | Tornado Cash governance takeover ($2.17M, 2023) |
| P1 | Protocol | Medium | Low | 2016 Shanghai DoS (mitigated by EIP-2929) |
| P2 | Protocol | Low | Low | — |
| P3 | Protocol | Medium | Medium | EIP-7702 breaking isContract() semantics |
| P4 | Protocol | Low | Low | — |
Related Opcodes
| Opcode | Relationship |
|---|---|
| CODESIZE (0x38) | Returns code size of the currently executing contract (self-referential). EXTCODESIZE is the external-address variant. Both return 0 during constructor execution, but CODESIZE queries the executing context while EXTCODESIZE queries an arbitrary address. |
| EXTCODECOPY (0x3C) | Copies code bytes from an external address into memory. Subject to the same cold/warm gas pricing and constructor-returns-empty edge cases. More expensive than EXTCODESIZE (copies full bytes vs. just length) but provides actual code content. |
| EXTCODEHASH (0x3F) | Returns keccak256 of an address’s code. Strictly more informative than EXTCODESIZE for identity verification — can detect metamorphic code swaps. Returns keccak256("") for accounts with no code, 0 for non-existent accounts. Same cold/warm gas pricing. |
| BALANCE (0x31) | Returns ETH balance at an address. Same EIP-2929 cold/warm pricing model. Combined with EXTCODESIZE in some patterns to determine account type (e.g., “has balance but no code” → likely EOA). |