0

Well, I’m not talking about the mainnet but an ᴇᴠᴍ compatible ʟ2 still using an Ethereum smart‑contract for storing its state : Optimistic Ethereum.

A recent contract creation on Optimism seems to prove it is possible to create a contract at an arbitrary address, or a system address since system contracts start by 0x42 on Optimism. But the problem is that by looking at block explorer data, the transaction looks like a plain normal CREATE transaction without any contract interaction nor code change in their fork of Geth. Yet, 0x4200000000000000000000000000000000000014 definitely looks like not to be a randomly generated address.

So, how can I deploy a contract to 0x4200000000000000000000000000000000000015 for example ? Or if it was a privileged operation, how it was implemented as 0x53A6eecC2dD4795Fcc68940ddc6B4d53Bd88Bd9E is a normal mainnet address with its Optimism funds having been bridged from Ethereum ?

3 Answers 3

2
+50

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:

  1. Both the deployer accounts and the smart contract addresses above were hardcoded in l2geth/rollup/rcfg/system_address.go.
  2. 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.
3
  • But seriously… Getting a specific address like 0x4200000000000000000000000000000000000014 is has much as possible as bruteforcing arbitrary Ethereum public key… You also won’t get so many 0 with bruteforcing. Commented Nov 11, 2022 at 19:41
  • I’m thinking what happened is Optimism specific and the question is about finding what… Commented Nov 11, 2022 at 19:43
  • @user2284570 I found out the correct answer, and I've updated it above. I hope you find my explanation sufficient :) Commented Nov 17, 2022 at 19:12
0

use create2 opcode. From deploying-with-create2

The whole idea behind this opcode is to make the resulting address independent of future events. Regardless of what may happen on the blockchain, it will always be possible to deploy the contract at the precomputed address.

New addresses are a function of:

  • 0xFF, a constant that prevents collisions with CREATE

  • The sender’s own address

  • A salt (an arbitrary value provided by the sender)

  • The to-be-deployed contract’s bytecode

new_address = hash(0xFF, sender, salt, bytecode)

CREATE2 guarantees that if sender ever deploys bytecode using CREATE2 and the provided salt, it will be stored in new_address.

Because bytecode is included in this computation other agents can rely on the fact that, if a contract is ever deployed to new_address, it will be one they know about. This is the key concept behind counterfactual deployments.

this is an example of how Uniswap uses in UniswapV2Factory.sol

function createPair(address tokenA, address tokenB)
        external
        returns (address pair)
    {
        require(tokenA != tokenB, "UniswapV2: IDENTICAL_ADDRESSES");
        (address token0, address token1) = tokenA < tokenB
            ? (tokenA, tokenB)
            : (tokenB, tokenA);
        require(token0 != address(0), "UniswapV2: ZERO_ADDRESS");
        require(
            getPair[token0][token1] == address(0),
            "UniswapV2: PAIR_EXISTS"
        );         
        bytes memory bytecode = type(UniswapV2Pair).creationCode;
         
        // CREATE2 opcode gives us the ability predict the address where a contract will be deployed, without ever having to do so. This opens up lots of possibilities to improve  
      
        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        assembly {
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        IUniswapV2Pair(pair).initialize(token0, token1);
        getPair[token0][token1] = pair;
        getPair[token1][token0] = pair; // populate mapping in the reverse direction
        allPairs.push(pair);
        emit PairCreated(token0, token1, pair, allPairs.length);
    }
1
  • Wrong answer ! the question isn’t to have a fixed address but arbitrary addresses like 0x4200000000000000000000000000000000000014… Remember it’s Otpimism, not the true Ethereum and hence the question is how a regular transaction managed to have a contract deployed on 0x4200000000000000000000000000000000000014 ? Commented Nov 13, 2022 at 21:46
-1

0x4200000000000000000000000000000000000014 is a system address reserved for future pre-deploys.

See: https://twitter.com/scottfits/status/1531798505198481408/photo/1

And contracts that start by 0x42 are considered pre-deploys that are marked as created by Genesis, meaning they are available from the blockchain's beginning. Hence, I don't think you can deploy a contract to the address 0x4200000000000000000000000000000000000015. The address 0x53A6eecC2dD4795Fcc68940ddc6B4d53Bd88Bd9E is a wallet address of a L1 account.

See: https://dashboard.tenderly.co/wallet/mainnet/0x53a6eecc2dd4795fcc68940ddc6b4d53bd88bd9e

2
  • Yet, 0x4200000000000000000000000000000000000014 was created by a regular transaction and not from Genesis state. I was asking how this was implemented for a transaction to deploy a contract at that address, even if this was allowed before. Commented Nov 8, 2022 at 11:35
  • 0x53A6eecC2dD4795Fcc68940ddc6B4d53Bd88Bd9E is the address L2 used for deploying funded by it s L1 counterpart and is not only an L1 address. Commented Nov 8, 2022 at 11:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.