Post

Deterministic EVM Deployments and the Immutable Deployment Factory

The Ethereum blockchain has pioneered decentralized applications (dapps) with the introduction of smart contracts, forever changing how we participate in digital agreements and transactions. With the evolving landscape of blockchain technology, the methods for deploying these smart contracts have also advanced, leading to more efficient, secure, and deterministic deployment processes.

We’re here today to teach you about EVM smart contract deployments and tell you about a factory contract that we’ve created (the ImmutableDeploymentFactory) that exploits some technical tricks to allow for deterministic contract deployments across any Ethereum virtual machine (EVM) compatible chain. This enables users/organizations to deploy contracts to the same address cross-chain, establishing a unified identity, while optionally choosing the contract’s address to meet some vanity criteria.

This blog post explores the create, create2, and create3 deployment techniques, their importance, and how they can be used to produce immutable smart contracts across any EVM-compatible chain.

If you want to skip to the good part, the code for the ImmutableDeploymentFactory can be found at this link

The Basics

Before beginning, we assume you’re familiar with the following concepts:

  • Contract bytecode [1] [2]
  • EOA vs Smart contract account [1] [2]
  • Address nonce [1] [2]
  • Role of a cryptographic Salt [1]

Why Deterministic Deployments?

Gas Savings and Vanity Addresses

Deterministic deployment methods like create2 and create3 can be used to generate vanity addresses, or even optimize gas costs.

This can be a useful tool for individuals & organizations to provide an extra layer of trust to their online presence by establishing a unified identity. For instance, an organization can choose to deploy contracts to the same address across any EVM chain. This allows users to have a consistent address to interact with, something that can be very useful to establish trust and brand-identity. This means users do not have to juggle as many addresses when trying to interact with some cross-chain dapp.

Somewhat surprisingly, users save gas when interacting with contracts that are deployed to addresses that contain more 0-bytes. This is due to how the EVM encodes transactions. The gas savings per-transaction are not high (about 64 gas per 0-byte), but add up when you consider a smart contract that has many users such as Uniswap or OpenSea’s Seaport. For example, If one of these protocols were to deploy to an address with say 4 leading 0-bytes (0x00000000...), that would save 256 gas per-transaction which when you have thousands of users quickly grows to be huge gas savings.

Additionally, deterministic deployments enable the creation of vanity addresses, which are custom contract addresses with desirable patterns, adding a layer of personalized branding to smart contracts. Since EVM addresses are represented as hexadecimal, they are able to contain the characters 0-9 and a-f. Even though there are a reduced number of characters available, you can still construct some interesting addresses such as: 0xdeadbeef... 0x0000000... 0x0f0f0f0f....

While calculating deterministic deployment is a solved problem, searching for desirable deterministic deployments is a hard problem in the sense that they can be quite rare to mine. 0age has created an excellent breakdown on the complexity & likelihood of finding various vanity addresses.

Immutable and Overlapping Deployments

The most significant advantage of deterministic deployments is the ability to know a contract’s deployment address before it is actually deployed.

One interesting side effect of this, is the ability to deploy more than one contract to the same address. Contracts such as this are called metamorphic contracts. This is possible with the create2 and create3 opcodes since contract creation is related to a salt instead of deployer’s nonce.

One way to combat this, is to set up a factory contract to track which addresses it has previously deployed. By doing so it can prevent deploying additional contracts to the same address, effectively creating “Immutable” smart contracts (such as with a standard create deployment). We believe immutable deployments to be the best approach, as they help protect the end-user from a malicious dev who could choose to overwrite an existing contract for some illicit behavior.

We have created such a factory contract, as described in the later in this post.

Enter Deterministic Deployments

We will cover 3 methods of deploying smart contracts on EVM-based chains: create create2 and create3.

The create Method

The create operation is the original method for deploying smart contracts on the Ethereum blockchain. When a contract is deployed using create, its address is determined by the following formula:

1
2
// create address derivation
keccak256(address ++ nonce)[12:]

With the above shown create formula, a contract’s deployment address can be precomputed and is determined by:

  • the creator’s address (the sender of the contract-creation transaction)
  • the creator’s nonce

This makes predicting a contract’s deployment address easy, but not that useful. If a user is careful and makes sure to use the same account (starting from the same nonce) on every EVM chain, they would end up deploying a contract to the same address regardless of the contract’s bytecode.

The create method is the most straight-forward to understand, but it comes with its drawbacks. For instance, if you already are using the deployer’s address (multi-chain or not) it is possible, but more difficult to deploy contracts with the same address to more than one chain.

Traditionally the create method of contract deployment is only performed by EOA accounts, but can also be accessed by smart contracts (important later).

The Evolution to create2

The next logical step for contract deployments, is to have the ability to deterministically influence a contract’s deployment address. This was introduced with the create2 opcode in EIP-1014 by Vitalik Buterin.

1
2
// create2 address derivation
keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]

With the above shown create2 formula, a contract’s deployment address can be precomputed and is determined by:

  • the factory contract’s address
  • a salt (a 32-byte value)
  • the contract’s initialization code

This predictability is critical for many decentralized applications, as it allows a contract address to be known before deployment, with that address no longer relying on the deployer’s nonce.

This makes predicting a contract’s deployment address easy, and also much more useful. Now a user can more easily deploy a contract to the same address, regardless of chain, and does not need to worry about their nonce.

The create2 method now enables deterministic contract deployments, but it also comes with its drawbacks. Specifically, the contract’s bytecode affects the deployment address. This is not necessarily a bad thing, but if a contract’s code (or even comments) change, then given the same salt a different deployment address would be found. This typically only affects developers during the development cycle, but is still a limitation.

The create2 method of contract deployment (as of now) can only be performed by smart contracts. This ultimately means that the address field of the create2 address formula is the smart contract which will be making the create2 call (also known as a factory contract). We have created a factory contract which allows for create2 contract deployments, discussed later.

Due to how the create2 opcode determines a contract’s deployment address, an interesting behavior arises: overlapping deployments explained later.

The Introduction of create3

The create3 method is not an EVM opcode but rather a clever deployment approach that pushes the boundary further, offering even more flexibility by combining the previous create and create2 methods.

In doing so, create3 allows for a deterministic contract deployment which only depends on the deployer’s address (factory contract) and a chosen salt. Now, a user can easily deploy a contract to a consistent address, regardless of chain, and does not need to worry about their nonce OR the deployed contract’s bytecode.

In an attempt to explain this clearly, consider the following pseudo-code which shows how a contract address is calculated for each deployment method we’ve covered:

1
2
3
create1_addr = create(deployer, nonce)
create2_addr = create2(deployer, salt, init_code)
create3_addr = create3(deployer, salt)

Next, consider the following (simplified) contract whose sole purpose is to deploy another contract:

1
2
3
4
contract create1 (deployBytecode) {
	// contract's nonce starts at 1
	return create(address(self), 1, deployBytecode)
}

Now, we can finally cover how a create3 deployment is performed. First, the factory contract performs a create2 deployment of the above create1 contract with the provided salt. Next, the create1 contract is called, and provided with the bytecode the user wants deployed:

1
2
3
4
5
6
7
contract create3 (salt, deployBytecode) {
	create1_contract = create2(address(self), salt, create1.initCode)
	
	create3_addr = create1_contract(deploy_bytecode)
	
	return create3_addr
}

So again: the create3 approach deploys a create factory contract using a chosen salt, and uses the new factory to deploy the user’s bytecode. This removes create2’s dependency on the user’s bytecode, by focusing on deploying the create1 factory contract. And since a new create1 factory contract was created, we know its nonce starts at 1 for the create call that deploys the user’s bytecode.

This may seem somewhat confusing at first, but you should come to realize that by using this approach we can perform deterministic deployments which no longer rely on the contract’s bytecode, or the deployer’s nonce. This makes predicting a contract’s deployment address easy and (in my opinion) the most useful both for the developer and the organization they are developing for.

Similar to create2, a create3 contract deployment can only be performed by smart contracts. We have created a factory contract which allows for create3 contract deployments, discussed in the next section.

The ImmutableDeploymentFactory: A Game-Changer

We have created the ImmutableDeploymentFactory which enables deterministic, omnichain smart contract deployments.

The code for the ImmutableDeploymentFactory can be found at this link and is deployed to the Ethereum mainnet at 0x0000086e1910D5977302116fC27934DC0254266C

This factory contract allows developers to deploy immutable smart contracts on any EVM-compatible chain to the same address using create2 and create3. This capability is invaluable for projects requiring consistent contract addresses across different networks, while also optionally preventing front-running of deployments.

As we have described, with deterministic deployments all that matters is the factory contract’s address, and a chosen salt. This being the case, a malicious party could choose to use a salt consumed on one network to deploy a completely different contract to the same address on a different network. In the case of an organization like Uniswap, this could be disastrous for users moving to a new-chain.

Our factory contract allows for deployment salts to be used that prevent font-running of contract deployments. The ImmutableDeploymentFactory accepts salts in two formats:

  • 0x1231231231231231231231231231231231231231XXXXXXXXXXXXXXXXXXXXXXXX - limits deployment to caller 0x1231231231231231231231231231231231231231
  • 0x0000000000000000000000000000000000000000XXXXXXXXXXXXXXXXXXXXXXXX- can be deployed by any address

    The X’s here represent a ‘wildcard’ and can be any hexadecimal character

As shown in the example above, to prevent front-running of a deterministic deployment you must include the address that will be calling the factory contract to perform a deployment. Or, if front-running is not important, you just include 20 0-bytes in place of the caller’s address.

this front-running behavior is only guaranteed when using the ImmutableDeploymentFactory

Factory Deployment

In the situation that you want to use the factory on some EVM chain where the factory does not yet exist (0x0000086e1910D5977302116fC27934DC0254266C), we have set up a process where anyone can deploy the ImmutableDeploymentFactory. We have a full guide on how to do so in our repository. In short, you will need to fund a deployer address determined by a “keyless” contract deployment (Nick’s method), and then submit a pre-created deployment transaction. We’ll create a post on this topic separately, at some point.

Conclusion

The evolution from create to create2 to  create3 marks a significant leap in smart contract deployments, offering developers unprecedented control, security, and even gas-efficiency. The ImmutableDeploymentFactory exemplifies this progress, providing a robust tool for deterministic, immutable smart contract deployments across any EVM-compatible blockchain.

I hope you’ve gained a better understanding of one small aspect of the EVM and how smart contract deployments work. As we move into the ever-changing future of blockchain development, the importance of these innovations cannot be overstated, paving the way for more sophisticated, reliable, and user-friendly decentralized applications.

Honourable Mentions

As always, all great works are built on the shoulders’ of previous giants. Our ImmutableDeploymentFactory would not be possible without the following contributions:

This post is licensed under CC BY 4.0 by the author.