Best Way to Deploy Contract on Multi Chains

How does you contract address comes from? What is the best way to deploy same address on different EVM chains?

Here is the answer:

Keyless + Factory

TL, DR

  • 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

  • Create is related to wallet address and nocne. Create2 is influenced by address of factory, salt, and bytecode. Create3 is affected by salt and factory address, not influenced by bytecode

  • Keyless does not need to maintain the wallet address of the deployment factory

Contract Creation

We have three ways to deploy contract, create and create2 are EVM codes and create3 is not.

Create

address = keccak256(rlp([sender_address,sender_nonce]))[12:]

address is determined by the sender and sender nonce.

And here is the opcode:

create(vaule,offset,size);

If want to keep addresses are same on different EVM chains, addresses should not be associated with nonce.

Create2

By create2, contract can be calculated:

address = keccak256(0xff + sender_address + salt + keccak256(initialisation_code))[12:]

sender_address is usually contract factory address.

So the contract is influenced by: address of factory, salt, and contract bytecode. It's possible to know the contract's address on blockchain before it's deployed.

opcode:

create2(vaule,offset,size,salt) => address //return address 0 if failed 

In order to keep the addresses consistent, the fewer influencing factors, the better. So we want to get rid of the influence of bytecode.

Create3

CREATE3 is a way to use CREATE and CREATE2 in combination such that bytecode no longer affects the deployment address.  Is not an EVM opcode.

Contract calculation depends on address of factory and salt.

How to achieve it?

address proxy;
/// @solidity memory-safe-assembly
assembly {
    // Deploy a new contract with our pre-made bytecode via CREATE2.
    // We start 32 bytes into the code to avoid copying the byte length.
    proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), salt)
}
require(proxy != address(0), "DEPLOYMENT_FAILED");

deployed = getDeployed(salt);
(bool success, ) = proxy.call{value: value}(creationCode);
  1. Deploy a new contract with our pre-made bytecode via CREATE2, so we can ensure the factory contract

  2. Deploy actually contract by the factory contract deployed via CREATE2, and the nonce is always 1.

    (Notice: Factory nonce is 1 rather than 0 of EOA)

At this point, we can deploy the same contract address on different EVM chains, and the address is only affected by salt

Notice: The creation code is contract bytecode that you want to deploy. If you want to deploy an upgradable contract, the bytecode is ERC1967 Contract bytecode with its constructor:

abi.encodePacked(ERC1967bytecode + abi.encode(<impl address, initData>))

Here initData is ERC1967 data. And if the contract is UUPS type, you have to call initialize() when deploying the contract. So the initData will be encoded by initialize()

Now here is another question: we still have to preserve the private key that controls the address safely. the EOA that deploys the factory. Is there a way that don't need to regardless of this?

Keyless

What is keyless address?

Keyless means no one owns the private key of the address.

Understanding transaction in Ethereum

We need to understand ethereum tx again. A simple ethereum tx format:

{
  nonce: "0x00",
  chainID:"1",
  gasPrice: "0x09184e72a000",
  gasLimit: "0x27100",
  to: "0x0000000000000000000000000000000000000000",
  value: "0x01",
  data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057",
  sig:{r,s,v}
}

Network using ecrecover function to get signer address and get gasFee

addressRecovered = ecrecover(Transaction, v, r, s);

Here is the point that it doesn't matter who forwards the transaction to network because the address executing the transaction is the one recovered from the transaction

Usage of Keyless

Even with the alteration of v, r, and s values to randomly generated values, the transaction will not be considered as “invalid”. It will still be executed successfully from the recovered address. The entire process of deploying a contract is:

  1. Generate the tx by field with random value for r,s and data (bytecode of the factory cotract)

  2. Recover the address by "ecrecover" and fund that address

  3. Serialize and broadcast the transaction to the network. And can also get the address of contract that will be created

  4. Deploy business contract by factory that deployed by keyless

By using this method, there is no need to safeguard a private key or need to be concerned about nonce. The nonce of keyless is always zero

Seaport contract (opensea contract) is deployed in this way.

https://github.com/ProjectOpenSea/seaport/blob/main/docs/Deployment.md

End. we can deploy the same contract address on different EVM chains, and the address is only affected by salt, and we don’t need to manage the private key of EOA that deploy the factory contract.

Just need to keep factory bytecode and salt is enough.

And here is the code example of factory deployment:

https://x.com/bloom_cry/status/1861602210964218131

Send me message to get the full code example of factory + keyless deployment!