Evidence note · 9 October 2026

Same salt, different address: check the initializer

A proxy’s initializer bytes can be part of its construction input. Changing an initializer argument can therefore change its CREATE2 address, even when the salt stays fixed.

Scope: an educational calculation with invented inputs. We did not deploy a contract, reproduce anyone’s transaction, or verify a migration.

Initializer argumentsConstruction inputCREATE2 address

One changed input, two different addresses

In this example, the factory, salt, implementation address and initial owner stay fixed. Only the second initializer address changes.

Input / resultExample AExample B
Second initializer address0x50505050505050505050505050505050505050500x5151515151515151515151515151515151515151
Full init-code hash0x82c940f3425d5b6d300b9d83fce6e4c179e5bbb6e5ba86621abdeb37c6a2cdbe0x34566302fe22f91ca08557b9368dbc4feac958f6cae978794a30cc30386e77de
Calculated address0xc4d56ea9fd41fb2741b8697848436dd26eaa66b60x02f2329a1c562d24384b03dc96ee2f53e1380c28

The bytecode is a synthetic stub, not OpenZeppelin proxy bytecode. It illustrates the input dependency; it is not a deployment recipe.

Control: the same inputs under a different descriptive chain label produce the same address. The label is not a CREATE2 input; this does not establish cross-chain deployment compatibility.

Exact synthetic inputs for independent reproduction
{
  "hash_function": "Keccak-256, not SHA3-256",
  "formula": "last_20_bytes(keccak256(0xff || factory_20_bytes || salt_32_bytes || keccak256(init_code)))",
  "stub_bytecode": "0x6001600c60003960016000f300",
  "constructor_abi": "(address,address,bytes)",
  "initializer_signature": "initialize(address,address,address,address)",
  "examples": [
    {
      "label": "A",
      "factory": "0x1010101010101010101010101010101010101010",
      "salt": "0x000000000000000000000000000000000000000000000000000000000000002a",
      "implementation": "0x2020202020202020202020202020202020202020",
      "initial_owner": "0x3030303030303030303030303030303030303030",
      "initializer_arguments": [
        "0x4040404040404040404040404040404040404040",
        "0x5050505050505050505050505050505050505050",
        "0x6060606060606060606060606060606060606060",
        "0x7070707070707070707070707070707070707070"
      ],
      "init_code": "0x6001600c60003960016000f3000000000000000000000000002020202020202020202020202020202020202020000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084f8c8765e000000000000000000000000404040404040404040404040404040404040404000000000000000000000000050505050505050505050505050505050505050500000000000000000000000006060606060606060606060606060606060606060000000000000000000000000707070707070707070707070707070707070707000000000000000000000000000000000000000000000000000000000",
      "predicted_address": "0xc4d56ea9fd41fb2741b8697848436dd26eaa66b6"
    },
    {
      "label": "B",
      "factory": "0x1010101010101010101010101010101010101010",
      "salt": "0x000000000000000000000000000000000000000000000000000000000000002a",
      "implementation": "0x2020202020202020202020202020202020202020",
      "initial_owner": "0x3030303030303030303030303030303030303030",
      "initializer_arguments": [
        "0x4040404040404040404040404040404040404040",
        "0x5151515151515151515151515151515151515151",
        "0x6060606060606060606060606060606060606060",
        "0x7070707070707070707070707070707070707070"
      ],
      "init_code": "0x6001600c60003960016000f3000000000000000000000000002020202020202020202020202020202020202020000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084f8c8765e000000000000000000000000404040404040404040404040404040404040404000000000000000000000000051515151515151515151515151515151515151510000000000000000000000006060606060606060606060606060606060606060000000000000000000000000707070707070707070707070707070707070707000000000000000000000000000000000000000000000000000000000",
      "predicted_address": "0x02f2329a1c562d24384b03dc96ee2f53e1380c28"
    }
  ]
}

Why this can happen with a proxy

The examined OpenZeppelin Hardhat Upgrades 3.9.1 source builds initializer data from the arguments and passes that data to the proxy constructor. The v5.4.0 transparent-proxy source explicitly receives those bytes. CREATE2 uses the complete initialization code, not just the contract’s eventual runtime code.

What to compare in a real case

Compare the actual factory address, 32-byte salt and full init-code hash. If the hash changed, inspect constructor arguments and encoded initializer data before attributing the result to a provider bug.

Calculation checks

Six published EIP-1014 address examples matched. The constructed comparison changed 20 bytes in a 288-byte constructor-argument payload. This verifies these calculations, not a production cryptography library or a real deployment.

Primary sources

  1. EIP-1014: the CREATE2 address definition
  2. OpenZeppelin plugin 3.9.1: pinned deployment source
  3. OpenZeppelin Contracts v5.4.0: transparent proxy constructor