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.
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 / result | Example A | Example B |
|---|---|---|
| Second initializer address | 0x5050505050505050505050505050505050505050 | 0x5151515151515151515151515151515151515151 |
| Full init-code hash | 0x82c940f3425d5b6d300b9d83fce6e4c179e5bbb6e5ba86621abdeb37c6a2cdbe | 0x34566302fe22f91ca08557b9368dbc4feac958f6cae978794a30cc30386e77de |
| Calculated address | 0xc4d56ea9fd41fb2741b8697848436dd26eaa66b6 | 0x02f2329a1c562d24384b03dc96ee2f53e1380c28 |
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.