> ## Documentation Index
> Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# EXTCODEHASH (0x3f)

> Get keccak256 hash of external account's code

<Warning>
  **This page is a placeholder.** All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.
</Warning>

## Overview

**Opcode:** `0x3f`
**Introduced:** Constantinople (EIP-1052)

EXTCODEHASH returns the keccak256 hash of an account's bytecode, or 0 for empty accounts.

## Specification

**Stack Input:**

```
address (uint160 as uint256)
```

**Stack Output:**

```
hash (bytes32 as uint256)
```

**Gas Cost:** Variable (hardfork-dependent)

* Constantinople: 400 gas
* Istanbul (EIP-1884): 700 gas
* Berlin (EIP-2929): 2600 gas (cold) / 100 gas (warm)

## Behavior

Returns keccak256(account.code) for contracts, or 0 for:

* EOAs (externally owned accounts)
* Non-existent accounts
* Empty code (code.length == 0)

More efficient than EXTCODESIZE + EXTCODECOPY + KECCAK256 for code verification.

## Examples

### Basic Usage

```typescript theme={null}
import { extcodehash } from '@tevm/voltaire/evm/context';

const contractAddr = Address('0x...');
const frame = createFrame({ stack: [BigInt(contractAddr)] });

const err = extcodehash(frame, host);

console.log(frame.stack[0]); // keccak256(code) as u256
```

### Implementation Verification

```solidity theme={null}
contract ProxyWithVerification {
    address public implementation;
    bytes32 public implementationHash;

    function setImplementation(address impl) public {
        bytes32 hash;
        assembly {
            hash := extcodehash(impl)
        }
        require(hash != 0, "Must be contract");

        implementation = impl;
        implementationHash = hash;
    }

    function verifyImplementation() public view returns (bool) {
        bytes32 currentHash;
        assembly {
            currentHash := extcodehash(implementation)
        }
        return currentHash == implementationHash;
    }
}
```

### Factory Pattern

```solidity theme={null}
contract Factory {
    mapping(bytes32 => address[]) public deployments;

    function deploy(bytes memory code) public returns (address instance) {
        assembly {
            instance := create(0, add(code, 0x20), mload(code))
        }

        bytes32 codeHash;
        assembly {
            codeHash := extcodehash(instance)
        }

        deployments[codeHash].push(instance);
    }

    function findDeployments(address target) public view returns (address[] memory) {
        bytes32 hash;
        assembly {
            hash := extcodehash(target)
        }
        return deployments[hash];
    }
}
```

## Gas Cost

**Historical evolution:**

| Hardfork       | Cold | Warm |
| -------------- | ---- | ---- |
| Constantinople | 400  | -    |
| Istanbul       | 700  | -    |
| Berlin         | 2600 | 100  |

**Comparison to alternatives:**

```solidity theme={null}
// EXTCODEHASH: 700 gas (Istanbul)
bytes32 hash;
assembly { hash := extcodehash(account) }

// EXTCODESIZE + EXTCODECOPY + KECCAK256: ~1400+ gas
uint256 size;
assembly { size := extcodesize(account) }
bytes memory code = new bytes(size);
assembly { extcodecopy(account, add(code, 0x20), 0, size) }
bytes32 hash = keccak256(code);
```

## Common Usage

### Contract Identity Check

```solidity theme={null}
function isSameCode(address a, address b) public view returns (bool) {
    bytes32 hashA;
    bytes32 hashB;
    assembly {
        hashA := extcodehash(a)
        hashB := extcodehash(b)
    }
    return hashA == hashB && hashA != 0;
}
```

### Minimal Proxy Detection

```solidity theme={null}
function isMinimalProxy(address account) public view returns (bool) {
    // Minimal proxy (EIP-1167) has specific bytecode pattern
    bytes32 expectedHash = keccak256(minimalProxyBytecode);

    bytes32 actualHash;
    assembly {
        actualHash := extcodehash(account)
    }

    return actualHash == expectedHash;
}
```

### Upgrade Validation

```solidity theme={null}
contract UpgradeableProxy {
    bytes32[] public validImplementations;

    function upgrade(address newImpl) public {
        bytes32 hash;
        assembly {
            hash := extcodehash(newImpl)
        }

        require(isValidImplementation(hash), "Invalid implementation");

        implementation = newImpl;
    }

    function isValidImplementation(bytes32 hash) internal view returns (bool) {
        for (uint i = 0; i < validImplementations.length; i++) {
            if (validImplementations[i] == hash) return true;
        }
        return false;
    }
}
```

## Security

### Empty Account Returns 0

```solidity theme={null}
function checkAccount(address account) public view returns (bool) {
    bytes32 hash;
    assembly {
        hash := extcodehash(account)
    }

    if (hash == 0) {
        // Could be EOA, non-existent, or empty code
        // Need additional checks to distinguish
    }
}
```

### Constructor Bypass

Like EXTCODESIZE, returns 0 during contract construction:

```solidity theme={null}
contract Detector {
    function check() public view returns (bytes32) {
        bytes32 hash;
        assembly {
            hash := extcodehash(caller())
        }
        return hash; // 0 if called from constructor
    }
}

contract Attacker {
    constructor(Detector d) {
        d.check(); // Returns 0!
    }
}
```

### Code Immutability Check

```solidity theme={null}
contract ImmutableChecker {
    mapping(address => bytes32) public initialHashes;

    function register() public {
        bytes32 hash;
        assembly {
            hash := extcodehash(caller())
        }
        initialHashes[msg.sender] = hash;
    }

    function verify(address account) public view returns (bool unchanged) {
        bytes32 currentHash;
        assembly {
            currentHash := extcodehash(account)
        }
        return currentHash == initialHashes[account];
    }
}
```

## Implementation

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { consumeGas } from "../Frame/consumeGas.js";
    import { popStack } from "../Frame/popStack.js";
    import { pushStack } from "../Frame/pushStack.js";
    import { fromNumber } from "../../primitives/Address/AddressType/fromNumber.js";
    import { Keccak256 } from "../../crypto/Keccak256/Keccak256.js";

    /**
     * EXTCODEHASH opcode (0x3f) - Get hash of account's code
     *
     * Stack: [address] => [hash]
     * Gas: 700 (Istanbul+) / 2600/100 (Berlin+ cold/warm)
     */
    export function extcodehash(
      frame: FrameType,
      host: BrandedHost
    ): EvmError | null {
      const addrResult = popStack(frame);
      if (addrResult.error) return addrResult.error;

      const addr = fromNumber(addrResult.value);

      const gasErr = consumeGas(frame, 700n); // Simplified
      if (gasErr) return gasErr;

      const code = host.getCode(addr);

      if (code.length === 0) {
        // Empty account returns 0
        const pushErr = pushStack(frame, 0n);
        if (pushErr) return pushErr;
      } else {
        // Compute keccak256 hash
        const hash = Keccak256.hash(code);

        // Convert hash to u256 (big-endian)
        let hashU256 = 0n;
        for (let i = 0; i < hash.length; i++) {
          hashU256 = (hashU256 << 8n) | BigInt(hash[i]);
        }

        const pushErr = pushStack(frame, hashU256);
        if (pushErr) return pushErr;
      }

      frame.pc += 1;
      return null;
    }
    ```
  </Tab>
</Tabs>

## Edge Cases

### EOA Hash

```typescript theme={null}
// EOA has no code
const eoaAddr = Address('0xUserEOA...');
const frame = createFrame({ stack: [BigInt(eoaAddr)] });

extcodehash(frame, host);
console.log(frame.stack[0]); // 0n
```

### Non-Existent Account

```typescript theme={null}
// Random address
const randomAddr = Address('0x9999999999999999999999999999999999999999');
const frame = createFrame({ stack: [BigInt(randomAddr)] });

extcodehash(frame, host);
console.log(frame.stack[0]); // 0n
```

### Empty Code Contract

Some contracts may have zero-length code (rare):

```typescript theme={null}
const emptyCodeAddr = Address('0x...');
extcodehash(frame, { getCode: () => new Uint8Array(0) });
console.log(frame.stack[0]); // 0n
```

## References

* [EIP-1052](https://eips.ethereum.org/EIPS/eip-1052) - EXTCODEHASH opcode
* [EVM Codes - EXTCODEHASH](https://www.evm.codes/#3f)
* [EIP-1884](https://eips.ethereum.org/EIPS/eip-1884) - Repricing (Istanbul)
* [EIP-2929](https://eips.ethereum.org/EIPS/eip-2929) - Access lists (Berlin)
* [Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf) - Appendix H
