paint-brush
Ethereum Virtual Machines: Your Guide to create, create2 and create3by@adamboudj
1,908 reads
1,908 reads

Ethereum Virtual Machines: Your Guide to create, create2 and create3

by Adam BoudjemaaNovember 2nd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Ethereum offers distinct deployment tools: "create" for straightforward contract deployment, "create2" for predictable contract addresses, and "create3" (a library) for multi-chain consistency. These tools enable developers to deploy contracts efficiently on EVM-compatible chains. Dive deeper to pick the right tool for your needs.
featured image - Ethereum Virtual Machines: Your Guide to create, create2 and create3
Adam Boudjemaa HackerNoon profile picture

Creating contracts on blockchains that work with the Ethereum Virtual Machine (EVM) involves the use of special instructions called opcodes. Among these instructions:

  • “CREATE”
  • “CREATE2”
  • “CREATE3”

While the first two are actual opcodes, “create3” is a useful library that acts similarly. This guide will give you a quick overview of what each one does and why they matter.

Leveraging the “create” Opcode

The create opcode is the most commonly used contract creation opcode. When contracts are deployed from scripts or other development environments, the create opcode is the low-level instruction executed by the EVM to deploy and generate a contract address.

Use-case:

  1. Creating new contracts within a contract dynamically.
  2. More cost-effective for deploying multiple contracts.
  3. When the address predictability is not a requirement.

Leveraging the “create2” Opcode

  1. A fixed prefix, which is always 0xFF.

  2. The sender’s address ensures the contract is tied to a specific creator.

  3. A chosen salt value adds uniqueness to the contract address.

  4. The bytecode contains the code of the new contract to be deployed.


By combining these parameters, CREATE2 computes a deterministic address for the new contract, which remains the same even as the blockchain evolves.

create2 code

Use-case:

  1. Ensuring a deterministic and predictable address for a new contract.
  2. Useful in state channels, contract wallets, and off-chain interactions.
  3. Offers an added layer of security by checking for existing contracts before deployment.

Leveraging the “create3” Opcode

CREATE3 is similar to CREATE2 but without including the contract initCode on the address derivation formula. It can be used to generate deterministic contract addresses that aren’t tied to a specific contract code.


CREATE3 is a way to use CREATE and CREATE2 in combination such that bytecode no longer affects the deployment address.   CREATE3 is more expensive than CREATE or CREATE2 (Fixed extra cost of ~55k gas).

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

/**
  @title A library for deploying contracts EIP-3171 style.
  @author Agustin Aguilar <[email protected]>
*/
library Create3 {
  error ErrorCreatingProxy();
  error ErrorCreatingContract();
  error TargetAlreadyExists();

  /**
    @notice The bytecode for a contract that proxies the creation of another contract
    @dev If this code is deployed using CREATE2 it can be used to decouple `creationCode` from the child contract address

  0x67363d3d37363d34f03d5260086018f3:
      0x00  0x67  0x67XXXXXXXXXXXXXXXX  PUSH8 bytecode  0x363d3d37363d34f0
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 0x363d3d37363d34f0
      0x02  0x52  0x52                  MSTORE
      0x03  0x60  0x6008                PUSH1 08        8
      0x04  0x60  0x6018                PUSH1 18        24 8
      0x05  0xf3  0xf3                  RETURN

  0x363d3d37363d34f0:
      0x00  0x36  0x36                  CALLDATASIZE    cds
      0x01  0x3d  0x3d                  RETURNDATASIZE  0 cds
      0x02  0x3d  0x3d                  RETURNDATASIZE  0 0 cds
      0x03  0x37  0x37                  CALLDATACOPY
      0x04  0x36  0x36                  CALLDATASIZE    cds
      0x05  0x3d  0x3d                  RETURNDATASIZE  0 cds
      0x06  0x34  0x34                  CALLVALUE       val 0 cds
      0x07  0xf0  0xf0                  CREATE          addr
  */
  
  bytes internal constant PROXY_CHILD_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";

  //                        KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE);
  bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE = 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;

  /**
    @notice Returns the size of the code on a given address
    @param _addr Address that may or may not contain code
    @return size of the code on the given `_addr`
  */
  function codeSize(address _addr) internal view returns (uint256 size) {
    assembly { size := extcodesize(_addr) }
  }

  /**
    @notice Creates a new contract with given `_creationCode` and `_salt`
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
    @return addr of the deployed contract, reverts on error
  */
  function create3(bytes32 _salt, bytes memory _creationCode) internal returns (address addr) {
    return create3(_salt, _creationCode, 0);
  }

  /**
    @notice Creates a new contract with given `_creationCode` and `_salt`
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
    @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
    @param _value In WEI of ETH to be forwarded to child contract
    @return addr of the deployed contract, reverts on error
  */
  function create3(bytes32 _salt, bytes memory _creationCode, uint256 _value) internal returns (address addr) {
    // Creation code
    bytes memory creationCode = PROXY_CHILD_BYTECODE;

    // Get target final address
    addr = addressOf(_salt);
    if (codeSize(addr) != 0) revert TargetAlreadyExists();

    // Create CREATE2 proxy
    address proxy; assembly { proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)}
    if (proxy == address(0)) revert ErrorCreatingProxy();

    // Call proxy with final init code
    (bool success,) = proxy.call{ value: _value }(_creationCode);
    if (!success || codeSize(addr) == 0) revert ErrorCreatingContract();
  }

  /**
    @notice Computes the resulting address of a contract deployed using address(this) and the given `_salt`
    @param _salt Salt of the contract creation, resulting address will be derivated from this value only
    @return addr of the deployed contract, reverts on error

    @dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01]))
  */
  function addressOf(bytes32 _salt) internal view returns (address) {
    address proxy = address(
      uint160(
        uint256(
          keccak256(
            abi.encodePacked(
              hex'ff',
              address(this),
              _salt,
              KECCAK256_PROXY_CHILD_BYTECODE
            )
          )
        )
      )
    );

    return address(
      uint160(
        uint256(
          keccak256(
            abi.encodePacked(
              hex"d6_94",
              proxy,
              hex"01"
            )
          )
        )
      )
    );
  }
}

contract Child {
  function hola() external view returns (string memory) {
    return "mundo";
  }
}

contract Deployer {
  function deployChild() external {
    Create3.create3(keccak256(bytes("<my salt>")), type(Child).creationCode);
  }
}

contract Child2 {
  uint256 meaningOfLife;
  address owner;
  
  constructor(uint256 _meaning, address _owner) {
    meaningOfLife = _meaning;
    owner = _owner;
  }
}

contract Deployer2 {
  function deployChild() external {
    Create3.create3(
      keccak256(bytes("<my salt>")), 
      abi.encodePacked(
        type(Child).creationCode,
        abi.encode(
          42,
          msg.sender
        )
      )
    );
  }
}

Features:

  1. Deterministic contract address based on msg.sender + salt.
  2. The same contract addresses different EVM networks.
  3. Supports any EVM-compatible chain with support for CREATE2.
  4. Payable contract creation (forwarded to child contract) , Constructors supported.

Use case:

When the goal is to deploy your contracts to the same addresses on multiple blockchains, fewer factors affecting deployment addresses make it easier to achieve such a goal. So in this sense, CREATE3 is better to use than CREATE2.

Conclusion

Create,” “create2”, and “create3” are essential tools for anyone working with Ethereum contracts. Each one offers a different way to deploy contracts, with “create” being straightforward, “create2” providing predictable contract addresses, and “create3” offering multi-chain compatibility through a library approach.


For hands-on details, check out:


For more on Ethereum development and insights, visit smart-contracts-developer.com and solichain.com. Connect with me on Twitter, LinkedIn, and GitHub.


Also published here.