I found the reason how Optimism was able to reserve both the 0x4200000000000000000000000000000000000014
and 0x4200000000000000000000000000000000000042
contract addresses, even though they deployed to those smart contract addresses after genesis.
If we check the creation transactions for the above smart contracts, the deployer accounts would be 0x53a6eecc2dd4795fcc68940ddc6b4d53bd88bd9e
and 0xcde47c1a5e2d60b9ff262b0a3b6d486048575ad9
in order. I went ahead and tried to see if I could reverse-engineer the process and compute the CREATE1
address of the smart contracts from the deployer accounts and using an nonce of 0
for both accounts using the following Solidity script:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract ComputeCREATE1 {
function contractAddressFrom(address deployer, uint256 nonce) public pure returns (address) {
if(nonce == 0x00) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80))))));
if(nonce <= 0x7f) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(uint8(nonce)))))));
if(nonce <= 0xff) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployer, bytes1(0x81), uint8(nonce))))));
if(nonce <= 0xffff) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployer, bytes1(0x82), uint16(nonce))))));
if(nonce <= 0xffffff) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployer, bytes1(0x83), uint24(nonce))))));
return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), deployer, bytes1(0x84), uint32(nonce))))));
}
}
The computed CREATE1
contract addresses were 0xa923A72d8FCa361a8Db421c807B8980354B8698f
and 0x3c0BEaDFeAdCa4D7d3bc2567968e7b3b57486082
in order. That triggered my curiosity, because it meant that the first deployed smart contract addresses were being computed differently for said deployer accounts.
I went ahead and started searching Optimism's geth implementation to see what was going on, and here's what I found out:
- Both the deployer accounts and the smart contract addresses above were hardcoded in
l2geth/rollup/rcfg/system_address.go
.
- Optimism had it's own implementation for
CREATE1
in l2geth/core/vm/evm
, where it checked the caller's account and nonce to see if it was a system-reserved deployer account. If it was, then the CREATE1
contract address should be one of the above hard-coded addresses, and not computed via the default CREATE1
process as in the Solidity script above.