Skip to main content

Overview

Create2Factory is a utility contract that enables deterministic deployment of contracts across multiple chains. Using the CREATE2 opcode, it ensures that contracts deployed with the same salt and bytecode will have identical addresses on different EVM chains, which is essential for CCTP’s cross-chain functionality. Contract Location: src/v2/Create2Factory.sol

Purpose

The Create2Factory serves several critical functions in CCTP:
  1. Deterministic Addresses: Deploy contracts to the same address across multiple chains
  2. Predictability: Compute contract addresses before deployment
  3. Cross-Chain Compatibility: Enable seamless interaction between contracts on different chains
  4. Simplified Configuration: Reduce configuration complexity by using consistent addresses

How It Works

CREATE2 deployment uses the following formula to compute addresses:
address = keccak256(0xff ++ deployerAddress ++ salt ++ keccak256(bytecode))[12:]
Since the deployer address (Create2Factory), salt, and bytecode are known in advance, the resulting contract address is deterministic and can be computed before deployment.

Constructor

The contract inherits from Ownable and uses the standard Ownable constructor.
constructor()
The deployer of the Create2Factory becomes the owner.

Core Functions

deploy

function deploy(
    uint256 amount,
    bytes32 salt,
    bytes calldata bytecode
) external payable returns (address addr)
Deploys a contract deterministically using CREATE2. Reference: src/v2/Create2Factory.sol:37

Parameters

  • amount (uint256): Amount of native token (e.g., ETH) to seed the deployment with
  • salt (bytes32): A unique identifier for the deployment
  • bytecode (bytes): The contract bytecode to deploy (including constructor arguments if any)

Returns

  • addr (address): The deployed contract address

Requirements

  • Caller must be the owner
  • Must send amount of native token with the transaction if amount > 0

Behavior

Deploys the contract using OpenZeppelin’s Create2.deploy() function. The same salt and bytecode will always produce the same address, regardless of which chain the deployment occurs on (as long as the Create2Factory is at the same address).

deployAndMultiCall

function deployAndMultiCall(
    uint256 amount,
    bytes32 salt,
    bytes calldata bytecode,
    bytes[] calldata data
) external payable returns (address addr)
Deploys a contract and makes multiple calls to it in a single transaction. Reference: src/v2/Create2Factory.sol:54

Parameters

  • amount (uint256): Amount of native token to seed the deployment with
  • salt (bytes32): A unique identifier for the deployment
  • bytecode (bytes): The contract bytecode to deploy
  • data (bytes[]): Array of encoded function call data to execute on the deployed contract

Returns

  • addr (address): The deployed contract address

Requirements

  • Caller must be the owner
  • Must send amount of native token with the transaction if amount > 0
  • All function calls in data must succeed

Behavior

  1. Deploys the contract using CREATE2
  2. Iterates through the data array and makes each function call to the deployed contract
  3. Reverts if any function call fails
This is useful for initialization functions that need to be called immediately after deployment.

computeAddress

function computeAddress(
    bytes32 salt,
    bytes32 bytecodeHash
) external view returns (address addr)
Computes the address where a contract would be deployed given a salt and bytecode hash. Reference: src/v2/Create2Factory.sol:75

Parameters

  • salt (bytes32): The unique identifier for the deployment
  • bytecodeHash (bytes32): The keccak256 hash of the deployment bytecode

Returns

  • addr (address): The deterministic address where the contract would be deployed

Behavior

This is a view function that computes the address without performing a deployment. It’s useful for:
  • Predicting contract addresses before deployment
  • Verifying that a deployment will produce the expected address
  • Configuring systems that need to know contract addresses in advance

Usage in CCTP Deployment

The Create2Factory is used in CCTP deployment scripts to ensure that contracts are deployed to the same addresses across multiple chains:

Deployment Process

  1. Deploy Create2Factory: Deploy the factory to the same address on each chain (using CREATE2 itself or a consistent deployer account)
  2. Compute Addresses: Use computeAddress() to predict where contracts will be deployed
  3. Configure Systems: Configure contracts and off-chain systems with the predicted addresses
  4. Deploy Contracts: Use deploy() or deployAndMultiCall() to deploy contracts to the predicted addresses
  5. Verify: Confirm that deployed addresses match predicted addresses

Example Deployment Flow

// 1. Compute the expected address
bytes32 salt = keccak256("MessageTransmitterV2");
bytes memory bytecode = abi.encodePacked(
    type(MessageTransmitterV2).creationCode,
    abi.encode(localDomain, version)
);
bytes32 bytecodeHash = keccak256(bytecode);
address expectedAddress = factory.computeAddress(salt, bytecodeHash);

// 2. Deploy to the predicted address
address deployedAddress = factory.deploy(
    0, // No ETH needed
    salt,
    bytecode
);

// 3. Verify
assert(expectedAddress == deployedAddress);

Initialization Example

// Deploy and initialize in one transaction
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSignature(
    "initialize(address,address,address,address,address[],uint256,uint256)",
    owner,
    pauser,
    rescuer,
    attesterManager,
    attesters,
    signatureThreshold,
    maxMessageBodySize
);

address deployed = factory.deployAndMultiCall(
    0,
    salt,
    bytecode,
    calls
);

Cross-Chain Deployment Example

To deploy CCTP contracts to the same address on multiple chains:

Step 1: Deploy Create2Factory

// Deploy Create2Factory to address 0x1234... on all chains
// This can be done using a consistent deployer address or
// by deploying the factory itself via CREATE2

Step 2: Deploy CCTP Contracts

// On Ethereum
bytes32 salt = keccak256("CCTP-MessageTransmitterV2");
address ethAddress = factory.deploy(0, salt, bytecode);
// Result: 0xabcd...

// On Avalanche (using same salt and bytecode)
address avaxAddress = factory.deploy(0, salt, bytecode);
// Result: 0xabcd... (same as Ethereum!)

// On Arbitrum (using same salt and bytecode)
address arbAddress = factory.deploy(0, salt, bytecode);
// Result: 0xabcd... (same as Ethereum and Avalanche!)

Benefits for CCTP

  1. Simplified Configuration: Contract addresses don’t need to be configured per-chain
  2. Reduced Errors: Eliminates mistakes from manual address configuration
  3. Easier Auditing: Consistent addresses make it easier to verify deployments
  4. Better UX: Users can interact with the same addresses across chains
  5. Improved Reliability: Reduces configuration complexity in cross-chain message validation

State Variables

Inherited from Ownable:
  • owner (address): The owner of the factory who can deploy contracts

Integration Example

Basic Deployment

// 1. Prepare deployment
bytes32 salt = keccak256(abi.encodePacked("MyContract", block.chainid));
bytes memory bytecode = type(MyContract).creationCode;

// 2. Compute expected address
address expected = factory.computeAddress(
    salt,
    keccak256(bytecode)
);

// 3. Deploy
address deployed = factory.deploy(0, salt, bytecode);

require(deployed == expected, "Address mismatch");

Deploy and Initialize

// Deploy a contract and call initialize() immediately
bytes[] memory initCalls = new bytes[](1);
initCalls[0] = abi.encodeWithSignature(
    "initialize(address,address)",
    admin,
    manager
);

address deployed = factory.deployAndMultiCall(
    0,
    salt,
    bytecode,
    initCalls
);

Important Considerations

  1. Unique Salts: Use unique salts to avoid deployment collisions
  2. Bytecode Matching: The exact bytecode (including constructor args) must match across chains
  3. Factory Address: The Create2Factory must be at the same address on all chains
  4. Chain-Specific Data: Constructor arguments that differ per chain will result in different addresses
  5. Gas Costs: Consider gas costs when deploying to multiple chains

Security Considerations

  1. Owner Access: Only the owner can deploy contracts - protect the owner key
  2. Salt Collisions: Using the same salt twice will revert the second deployment
  3. Bytecode Verification: Always verify that the bytecode matches expectations before deployment
  4. Initialization: Use deployAndMultiCall() to ensure contracts are properly initialized immediately after deployment
  5. Address Prediction: Always verify computed addresses match deployed addresses

Build docs developers (and LLMs) love