Opcode Summary

PropertyValue
Opcode0x39
MnemonicCODECOPY
Gas3 + 3 × ceil(len / 32) + mem_expansion
Stack InputdstOst, ost, len
Stack Output(none)
BehaviorCopies len bytes of the executing contract’s bytecode starting at offset ost into memory at position dstOst. Source bytes beyond the code boundary are zero-padded.

Threat Surface

CODECOPY sits at the intersection of code introspection and memory manipulation, making it architecturally unique among EVM opcodes. It serves three core roles that each carry distinct security implications:

  1. Constructor return mechanism: CODECOPY is the fundamental opcode the Solidity compiler emits during contract deployment to copy the runtime bytecode from the init code into memory, then RETURN it to the EVM for storage. This means the integrity of every deployed contract depends on CODECOPY executing correctly during construction. If an attacker can influence what CODECOPY copies — by injecting malicious bytes into the init code’s data region — the deployed runtime code differs from what the source code suggests.

  2. Dynamic gas via memory expansion: Unlike fixed-cost opcodes, CODECOPY’s cost has two variable components: the per-word copy cost (3 gas per 32-byte word) and the memory expansion cost, which grows quadratically with the highest memory address touched. An attacker who controls the dstOst or len parameters can force massive memory allocation, consuming all available gas in a single operation.

  3. Code introspection for anti-analysis: CODECOPY allows a contract to read its own bytecode at runtime. This enables legitimate patterns (self-referential hash checks, blueprint contracts) but also enables anti-analysis techniques: contracts can detect decompilers, verify their own integrity to resist patching, or build self-modifying deployment patterns that obscure malicious intent.

The combination of deployment-time trust, dynamic gas, and self-introspection makes CODECOPY a multi-vector threat surface that spans constructor attacks, gas griefing, and evasion.


Smart Contract Threats

T1: Code Injection During Constructor (Critical)

During deployment, the Solidity compiler generates init code that uses CODECOPY to copy the runtime bytecode from a known offset into memory, then RETURNs it. The init code layout is:

[constructor logic] [CODECOPY runtime to memory] [RETURN] [runtime bytecode]

If an attacker can manipulate the data region of the init code — for example, by controlling constructor arguments that embed directly into bytecode (Solidity immutable variables) — they can inject valid EVM opcodes that become part of the deployed runtime code. The source code compiles cleanly, passes Etherscan verification, and auditors see only the Solidity source, not the bytecode-level payload.

Attack vector: Solidity’s immutable keyword causes the compiler to patch values directly into the deployed bytecode (not storage) after construction. If a constructor argument destined for an immutable slot contains bytes that form valid opcodes (starting with JUMPDEST 0x5b), and the contract contains arithmetic that computes a jump target landing on those bytes, the hidden code executes. This is the “Immutable Executable Injection” technique.

Why CODECOPY matters: CODECOPY is the mechanism that copies the patched bytecode — including injected immutable values — from init code into memory for deployment. Without CODECOPY, there is no way to materialize the init code’s data region as runtime bytecode.

T2: Memory Expansion Gas Griefing (High)

CODECOPY writes to memory, and EVM memory expansion cost is quadratic:

memory_cost = (words^2 / 512) + (3 * words)

An attacker who can influence the dstOst (destination offset) or len parameters of a CODECOPY call can force the EVM to expand memory to an arbitrarily high address. Even if the copied data is never used, the memory expansion cost is incurred immediately. A single CODECOPY with dstOst = 0xFFFFFF (16 MB offset) would require ~billions of gas in memory expansion alone, exceeding any block gas limit.

Vulnerable pattern: Contracts that accept user-supplied offsets or lengths and pass them to inline assembly CODECOPY without bounds checking:

function extractCode(uint256 offset, uint256 length) external view returns (bytes memory) {
    bytes memory code = new bytes(length);
    assembly {
        // If 'length' is attacker-controlled and very large,
        // memory expansion cost can consume all gas
        codecopy(add(code, 0x20), offset, length)
    }
    return code;
}

Even in the absence of direct user control, proxy patterns or factory contracts that compute CODECOPY parameters from untrusted input are at risk.

T3: Bytecode Analysis Evasion (High)

Contracts can use CODECOPY to read their own bytecode at runtime and implement anti-analysis logic:

  • Integrity checks: A contract CODECOPYs its own bytecode, hashes it, and compares against a hardcoded hash. If the hash doesn’t match (e.g., the contract was redeployed at a different address with modified code, or analysis tools patched the bytecode), execution reverts or takes a different path. This makes it harder for security researchers to instrument or modify contracts for analysis.

  • Environment detection: A contract reads its own code to verify it hasn’t been decompiled and redeployed with instrumentation. Malicious contracts (rug pulls, honeypots) use this to behave benignly when analyzed but maliciously when executed normally.

  • Opcode-level self-reference: CODECOPY can extract specific byte ranges from the contract’s own code and use them as data inputs (jump tables, encoded parameters), making static analysis tools unable to resolve the values without actually executing the code.

Why it matters: Honeypot tokens and rug-pull contracts use CODECOPY-based introspection to defeat automated scanners. The contract appears safe in decompiled form but contains hidden logic activated through self-referential bytecode reads.

T4: Metamorphic Contract Patterns (Critical)

CODECOPY is a key building block in metamorphic contracts — contracts that can change their runtime code at a fixed address. The pattern works as follows:

  1. A factory deploys a metamorphic proxy using CREATE2 at a deterministic address. The proxy’s init code uses CODECOPY or EXTCODECOPY to fetch the actual runtime code from an external source (often the factory’s storage or another contract).
  2. The proxy is destroyed via SELFDESTRUCT (pre-Dencun) or its logic is swapped.
  3. The factory redeploys to the same CREATE2 address with new runtime code fetched via CODECOPY.

The result: a contract at a fixed, trusted address whose code silently changes between transactions. This was the mechanism behind the Tornado Cash governance attack and is the foundation of 0age’s metamorphic contract framework.

CODECOPY’s role: In many metamorphic patterns, the init code uses CODECOPY to load the runtime bytecode from a location within itself (where the factory wrote it), then RETURNs it. The same init code hash (required for CREATE2 address determinism) can produce different runtime code depending on external state read during construction.


Protocol-Level Threats

P1: Quadratic Memory Cost as DoS Vector (Medium)

At the protocol level, CODECOPY’s memory expansion cost is the primary concern. While the quadratic cost formula is designed to prevent abuse, it creates an asymmetry: the caller pays gas proportional to O(n^2) for memory but only copies O(n) bytes of data. A transaction that triggers CODECOPY with a large destination offset can consume a disproportionate share of the block gas limit without performing useful computation, reducing block throughput.

This is mitigated by the block gas limit itself, but in contexts where gas is subsidized (meta-transactions, relayers, account abstraction) the party paying for gas may not be the party choosing the parameters.

P2: Client Implementation Divergence — Zero Padding (Low)

When ost + len exceeds the contract’s code size, the EVM must zero-pad the out-of-bounds region. Different client implementations must agree on the exact semantics: which bytes are copied from code, which are zero-filled, and how the boundary is calculated. While no known consensus bug has arisen from CODECOPY zero-padding, it is a potential divergence point, particularly for L2 and zkEVM implementations where CODECOPY semantics must be replicated in circuits.

P3: zkEVM Circuit Complexity (Medium)

CODECOPY is one of the more expensive opcodes to prove in zero-knowledge circuits because:

  • The source data (contract bytecode) must be committed and proved consistent with the state trie
  • The variable-length copy requires dynamic circuit sizing
  • Zero-padding semantics add conditional logic to the circuit

Scroll’s zkEVM audit revealed completeness issues in gas calculation for memory-copy opcodes in out-of-gas scenarios, demonstrating that CODECOPY’s complexity creates real implementation risks in ZK contexts.


Edge Cases

Edge CaseBehaviorSecurity Implication
ost + len exceeds code sizeOut-of-bounds bytes zero-paddedContract reads zeros instead of code; may break integrity checks that expect non-zero data
len = 0No-op; no memory expansionSafe, but may bypass expected side effects if code assumes CODECOPY always writes
CODECOPY in delegatecall contextCopies the implementation contract’s code, not the proxy’sProxy reads implementation bytecode; self-hash checks return implementation’s hash, not proxy’s
CODECOPY during constructorCopies the init code (constructor + runtime), not just runtimeInit code includes constructor args and metadata; larger than expected code size
dstOst causes memory expansion beyond block gas limitTransaction reverts with out-of-gasGriefing vector if parameters are user-influenced; funds spent on gas are not refunded
CODECOPY at exact code boundary (ost = codesize)All bytes are zero-paddedReturns all zeros; may confuse parsers expecting valid opcodes
CODECOPY in staticcall contextSucceeds (read-only operation)No state modification risk; safe in static contexts

Real-World Exploits

Exploit 1: Tornado Cash Governance Attack — $1M+ Stolen (May 2023)

Root cause: Metamorphic contract pattern using CREATE2 + SELFDESTRUCT to replace a governance-approved proposal contract with malicious code at the same address.

Details: The attacker submitted a governance proposal that appeared benign — the proposal contract’s source code was verified on Etherscan and audited by community members. After the proposal passed the governance vote, the attacker triggered a hidden selfdestruct in the proposal contract, clearing its code. Using CREATE2 with the same salt and a factory whose CREATE-deployed child had its nonce reset to 0, the attacker redeployed a malicious contract at the exact same address. Since governance had already whitelisted that address, the malicious code executed with full governance privileges, granting the attacker 1.2 million TORN votes and control over the DAO treasury.

CODECOPY’s role: The metamorphic deployment pattern relies on init code that uses CODECOPY to load runtime bytecode from within itself. The init code hash remains constant (preserving the CREATE2 address), but the runtime bytecode — fetched via CODECOPY from a mutable external source during construction — changes between deployments. This is the fundamental mechanism that allows code to morph at a fixed address.

Impact: Attacker gained 1.2M TORN governance votes (~$1M+), full control of Tornado Cash governance, ability to drain the governance vault. The attacker later submitted a “remediation” proposal to restore governance, but the precedent demonstrated the existential risk of metamorphic contracts to DAO governance.

References:


Exploit 2: Immutable Executable Injection — samczsun’s Ethereum CTF 0day (November 2021)

Root cause: Solidity’s immutable keyword patches constructor argument values directly into deployed bytecode via CODECOPY, enabling hidden executable code injection that passes source-level verification.

Details: samczsun disclosed that Solidity appends constructor metadata and immutable values into the bytecode region copied by CODECOPY during deployment. An attacker can craft constructor arguments that, when patched into the bytecode, form valid EVM opcodes. If the contract contains an arithmetic expression that computes a JUMP target landing within the immutable region, the hidden opcodes execute. The attack passes Etherscan source verification because the compiler produces the “correct” bytecode for the given source + constructor args — but the constructor args themselves are the payload.

CODECOPY’s role: CODECOPY is the opcode that materializes the init code (including patched immutable values) as the deployed runtime bytecode. The injected opcodes exist only because CODECOPY faithfully copies whatever bytes are present in the init code’s data region into the deployed contract.

Impact: Published as the first Ethereum CTF challenge exploiting a 0day. Demonstrated that source-verified contracts can contain hidden executable code invisible to auditors reviewing only Solidity source. Etherscan subsequently improved its metadata handling, and the technique raised awareness about bytecode-level auditing.

References:


Exploit 3: 0age Metamorphic Framework — Widespread Pattern (2018–present)

Root cause: CODECOPY-based init code that loads runtime bytecode from an external mutable source, enabling redeployable contracts at fixed addresses via CREATE2.

Details: 0age published a factory contract framework that deploys metamorphic contracts using CREATE2. The init code is minimal and fixed: it reads the desired runtime bytecode from the factory (via SLOAD or EXTCODECOPY), copies it into memory, and RETURNs it. Since the init code never changes, the CREATE2 address remains deterministic. But the runtime code — loaded dynamically during construction — can be anything.

CODECOPY’s role: The framework’s init code uses CODECOPY to position itself in memory, then overwrites with the dynamically loaded runtime code before RETURN. Some variants use CODECOPY to extract embedded data from within the init code itself, where the factory pre-wrote the desired runtime bytecode.

Impact: Not a single exploit but a class-enabling framework. Metamorphic contracts undermine the assumption that on-chain code is immutable, which is the security foundation for DEX approvals, governance proposals, and DeFi composability. Post-Dencun (EIP-6780), SELFDESTRUCT no longer clears code within the same transaction as deployment, significantly limiting this pattern on L1, but it remains viable on L2s and alternative EVMs that haven’t adopted EIP-6780.

References:


Attack Scenarios

Scenario A: Constructor Bytecode Injection via Immutables

contract TrojanToken {
    // Attacker deploys with craftedValue containing valid opcodes
    // starting with JUMPDEST (0x5b)
    uint256 public immutable config;
 
    constructor(uint256 _config) {
        config = _config;
    }
 
    function transfer(address to, uint256 amount) external {
        // Arithmetic computes a JUMP target that lands
        // inside the 'config' immutable's bytecode region
        uint256 target = amount ^ 0xDEAD;
        assembly {
            // Hidden: jump lands in immutable region,
            // executes injected opcodes (e.g., drain to attacker)
            jump(target)
        }
    }
}
// CODECOPY during deployment copies the patched immutable value
// into runtime bytecode -- the injected opcodes are now live

Scenario B: Memory Expansion Gas Griefing

contract CodeInspector {
    // Relayer pays gas for meta-transactions
    function inspectCode(uint256 offset, uint256 length) external view returns (bytes32 hash) {
        assembly {
            // Attacker passes length = 2^20 (1 MB)
            // Memory expansion to 1 MB costs ~500M gas (quadratic)
            // Transaction reverts OOG; relayer loses gas fees
            codecopy(0x00, offset, length)
            hash := keccak256(0x00, length)
        }
    }
}

Scenario C: Metamorphic Governance Takeover

```mermaid
sequenceDiagram
    participant Attacker
    participant Factory
    participant Proposal (Address X)
    participant Governance

    Attacker->>Factory: deploy benign proposal via CREATE2
    Factory->>Proposal (Address X): init code uses CODECOPY to load benign runtime
    Attacker->>Governance: submit proposal at Address X
    Governance->>Governance: community votes YES (code looks safe)

    Note over Attacker: After vote passes...

    Attacker->>Proposal (Address X): selfdestruct()
    Note right of Proposal (Address X): Code cleared, address empty

    Attacker->>Factory: update stored runtime bytecode to malicious code
    Attacker->>Factory: redeploy to Address X via CREATE2 (same salt)
    Factory->>Proposal (Address X): init code uses CODECOPY to load MALICIOUS runtime

    Governance->>Proposal (Address X): execute approved proposal
    Note right of Proposal (Address X): Malicious code runs with governance privileges

### Scenario D: Anti-Analysis Honeypot

```solidity
contract HoneypotToken {
    function _beforeTransfer(address from, address to) internal view {
        // CODECOPY reads own bytecode to detect if running
        // in a forked/modified environment
        bytes32 selfHash;
        assembly {
            let size := codesize()
            let ptr := mload(0x40)
            codecopy(ptr, 0, size)
            selfHash := keccak256(ptr, size)
        }
        // If bytecode was modified by analysis tools, hash differs
        // Contract behaves normally for buys, blocks all sells
        require(selfHash == EXPECTED_HASH || to == PAIR_ADDRESS);
    }
}

Mitigations

ThreatMitigationImplementation
T1: Immutable injectionAudit bytecode, not just source codeUse cast disassemble or Dedaub decompiler to verify deployed bytecode matches expected compiler output
T1: Constructor arg payloadsValidate constructor arguments are within expected rangesRestrict immutable types to addresses/booleans; avoid arbitrary uint256 immutables from untrusted deployers
T2: Memory expansion griefingBound all CODECOPY length and offset parametersrequire(length <= MAX_COPY_SIZE && dstOst + length <= MAX_MEMORY) before assembly blocks
T2: Gas griefing via relayersCap gas forwarded to untrusted calleesUse gasleft() checks and explicit gas limits on internal calls
T3: Anti-analysis evasionFlag contracts using CODECOPY + KECCAK256 on own codeStatic analysis rules: alert on codecopy(_, 0, codesize()) followed by hash comparison
T3: Honeypot detectionTest buy AND sell paths in forked mainnet environmentUse Tenderly or Foundry fork simulation rather than static analysis alone
T4: Metamorphic contractsVerify code hash at interaction time, not just deployment timerequire(codehash(target) == expectedHash) before approvals or governance execution
T4: Post-Dencun mitigationRely on EIP-6780 semantics (SELFDESTRUCT no longer clears code in same tx)Metamorphic pattern is broken on L1 post-Dencun; verify L2s have adopted equivalent protections
GeneralAvoid CODECOPY in user-facing assemblyUse type(ContractName).creationCode or address(this).code in Solidity instead of raw assembly

Compiler/EIP-Based Protections

  • EIP-6780 (Dencun, 2024): SELFDESTRUCT only sends ETH; it no longer deletes code or resets nonce except in the same transaction as deployment. This effectively kills the metamorphic contract pattern on L1 Ethereum, since contracts can no longer be destroyed and redeployed at the same address across transactions.
  • EIP-3541: Rejects deployment of contracts whose bytecode starts with 0xEF, preventing confusion between EOF-formatted and legacy bytecode — relevant when CODECOPY is used to load code for deployment.
  • Solidity immutable improvements: Post-0.8.x compilers place immutable values in predictable bytecode locations, and metadata hashing improvements reduce the attack surface for injection.
  • ERC-5202 (Blueprint contracts): Standardizes a pattern for on-chain init code storage (prefixed with 0xFE to prevent execution), providing a safer alternative to ad-hoc CODECOPY-based code loading.

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractCriticalMediumImmutable Executable Injection (samczsun CTF 0day)
T2Smart ContractHighMediumGas griefing via memory expansion (class of attacks)
T3Smart ContractHighHighHoneypot tokens, anti-analysis rug pulls
T4Smart ContractCriticalMedium (Low post-Dencun on L1)Tornado Cash governance ($1M+), 0age framework
P1ProtocolMediumLowMemory expansion DoS
P2ProtocolLowLow
P3ProtocolMediumLowScroll zkEVM audit findings

OpcodeRelationship
CODESIZE (0x38)Returns the size of the executing contract’s code; used with CODECOPY to copy the entire bytecode (codecopy(0, 0, codesize()))
EXTCODECOPY (0x3C)Copies another contract’s bytecode; used in metamorphic patterns to load runtime code from external sources
CALLDATACOPY (0x37)Similar memory-copy semantics with zero-padding; shares the memory expansion griefing vector
CREATE (0xF0)Deploys contracts using init code that typically contains CODECOPY; nonce-dependent addressing enables metamorphic patterns
CREATE2 (0xF5)Deterministic deployment; combined with SELFDESTRUCT + CODECOPY-based init code enables metamorphic contracts
RETURN (0xF3)Used after CODECOPY in constructors to return the runtime bytecode to the EVM for storage
SELFDESTRUCT (0xFF)Pre-Dencun: enables metamorphic pattern by clearing code at an address for redeployment; post-Dencun (EIP-6780): neutered
EXTCODEHASH (0x3F)Can verify a contract’s code hasn’t changed; mitigation for metamorphic contracts