Skip to content

Deployment: CREATE and CREATE2

We have spent this part treating a contract as a thing that already exists: an account with code, written in Solidity, called through selectors, keeping state in storage slots and announcing itself through events. This page rewinds to the one moment we kept deferring: how does the code get into the account in the first place?

The answer is a single reframing that surprises most newcomers: a contract is born by running a program. Deployment is not “uploading a file.” It is a transaction that carries one program (the init code), and the output of running that program is the program that stays behind (the runtime code). Understanding that two-program dance explains everything else on this page — including the two ways Ethereum computes the new contract’s address, CREATE and CREATE2, and why one of them lets you know a contract’s address before it exists.

Recall from the transaction anatomy that an ordinary transaction names a to address — the account it targets. A deploy transaction is the one kind with to left empty (nil). That empty field is the signal to the protocol: there is no existing account to call; create a new one instead.

Everything the new contract needs travels in the transaction’s data field. For a normal call, data is calldata — a selector plus arguments. For a deploy, data is init code: a small bootstrap program.

ordinary call tx deploy tx
┌──────────────────┐ ┌──────────────────┐
│ to: 0xC0ffee… │ │ to: (empty!) │ ← "create an account"
│ value: ... │ │ value: ... │
│ data: selector │ │ data: INIT CODE │ ← a program to RUN
│ + args │ │ │
└──────────────────┘ └──────────────────┘

The book’s companion crate, ethmini, models this split directly in its transaction type: a Transfer, a Call (which names a to), and a Deploy (which carries code and no to):

pub enum TxKind {
Transfer { to: Address, value: u128 },
Call { to: Address, value: u128, gas_limit: u64 },
Deploy { code: Vec<u8>, value: u128 }, // ← no `to`; brings its own code
}

ethmini is deliberately simplified — it stores the deploy code straight into the new account. Real Ethereum inserts one crucial step first, and that step is the whole idea of this section.

On real Ethereum, the bytes in a deploy transaction’s data are not the bytes that end up stored at the address. They are init code: a throwaway program whose job is to produce the code that stays.

Here is the sequence the EVM runs when a deploy transaction executes:

1. compute the new contract's address (CREATE or CREATE2 — see below)
2. run the INIT CODE in the context of that new address
· this is where the CONSTRUCTOR runs
· it can read msg args, set up storage, mint tokens, etc.
3. the init code ends with a RETURN of some bytes
· those returned bytes ARE the runtime code
4. store the returned bytes as the account's `code` field
5. THROW THE INIT CODE AWAY — it never runs again

So there are two distinct programs, with two different lifetimes:

Init codeRuntime code
When does it run?Once, at deploy timeOn every future call to the address
What is it for?Constructor logic + setupThe contract’s actual behavior
Where does it end up?Discarded after deployStored as the account’s code
Does it see constructor args?YesNo (constructor is already gone)

This is why a Solidity constructor can take arguments, run one-time setup, and yet not appear in the deployed bytecode: the constructor is part of the init code, executed once and thrown away. The return at the end of the init code hands back only the runtime portion.

compiler output for `contract C { constructor(uint x){…} function f()… }`
┌──────────────── init code (in the deploy tx `data`) ─────────────────┐
│ [constructor logic: run once, write storage from x] │
│ [copy the runtime bytes into memory] │
│ RETURN(runtime_offset, runtime_len) ──────────────┐ │
└─────────────────────────────────────────────────────┼────────────────┘
┌──────────── runtime code (stored at the address) ───────────────────┐
│ [dispatcher: match the 4-byte selector, jump to f(), …] │
└─────────────────────────────────────────────────────────────────────┘

If you have ever wondered why the bytecode you see on a block explorer under “Contract Bytecode” is shorter than the bytes you sent in the deploy transaction, this is why: the explorer shows the runtime code (what’s stored), while your transaction carried the longer init code (constructor + the runtime it returns).

The EVM must decide the new contract’s 20-byte address before it runs the init code (step 1 above), because the constructor might need to know its own address. The original scheme, used by a deploy transaction and by the CREATE opcode, derives the address from who is deploying and their nonce:

address = keccak256( rlp_encode(sender, nonce) )[12:]
└── take the last 20 bytes
of the 32-byte hash

Two inputs, both already known:

  • sender — the 20-byte address of the account doing the creating (an EOA sending a deploy tx, or a contract executing CREATE).
  • nonce — the sender’s current nonce: the count of transactions an EOA has sent, or (for a contract) the number of contracts it has already created.

rlp_encode is Ethereum’s canonical serialization (RLP); keccak256 is its hash; [12:] keeps the low 20 bytes of the 32-byte digest, matching Ethereum’s 20-byte address width.

The consequence is worth stating plainly: a CREATE address is a pure function of the deployer and their nonce. It is fully predictable — you can compute it before you broadcast — but it is nonce-dependent. Deploy anything else from that account first, and the nonce moves, and so does the address you would have gotten.

ethmini models exactly this dependence, though it substitutes SHA-256 for keccak and skips RLP to stay dependency-light — the structure is the point:

/// The address of a contract created by `creator` at a given `nonce`.
/// Real Ethereum uses keccak256(rlp(sender, nonce))[12:]; here we hash the
/// same two inputs so the address still depends on WHO deploys and WHEN.
pub fn contract_address(creator: Address, nonce: u64) -> Address {
let mut hasher = Sha256::new();
hasher.update(creator.0); // WHO is deploying
hasher.update(nonce.to_be_bytes()); // and at which nonce
let digest = hasher.finalize();
let mut bytes = [0u8; 20];
bytes.copy_from_slice(&digest[12..32]); // low 20 bytes, like Ethereum
Address(bytes)
}

And when the crate applies a Deploy, it feeds the sender’s nonce straight into that function — the same nonce the sender signed with — so the derived address is deterministic and replayable by every node:

TxKind::Deploy { code, value } => {
let addr = contract_address(tx.from, tx.nonce); // WHO + nonce → address
if *value > 0 { self.transfer_value(tx.from, addr, *value); }
self.account_mut(addr).code = code.clone(); // (real EVM would run init first)
// receipt.created = Some(addr)
}

Under the hood — why nonce-dependence is annoying

Section titled “Under the hood — why nonce-dependence is annoying”

Nonce-dependence is fine when you deploy once. It becomes painful in three common situations:

  • Coordination. Suppose contract A must be told the address of contract B, and B must be told A’s — a chicken-and-egg. With CREATE you cannot know either address until you have chosen deploy order and nonces, and any earlier transaction from the deployer shifts everything.
  • Cross-chain sameness. You want “the same address on every chain” for a bridge or a canonical registry. With CREATE that requires the deployer to have the identical nonce on every chain at deploy time — fragile, and easily broken by a single stray transaction on one chain.
  • Counterfactual deployment. You want to hand someone an address today, take funds at it, and deploy the actual code later, only if needed. CREATE cannot promise a stable address across an unknown number of intervening transactions.

Every one of these wants an address that does not depend on the deployer’s transaction history. That is precisely what CREATE2 provides.

CREATE2: a deterministic address independent of nonce

Section titled “CREATE2: a deterministic address independent of nonce”

CREATE2 (introduced in EIP-1014, the Constantinople upgrade, 2019) is a second creation opcode. It computes the address from a different set of inputs — no nonce anywhere:

address = keccak256( 0xff ‖ deployer ‖ salt ‖ keccak256(init_code) )[12:]
│ │ │ │
│ │ │ └─ hash of the FULL init code
│ │ └───────── a 32-byte value YOU choose
│ └──────────────────── the deploying contract's address
└─────────────────────────── a constant prefix (domain-separates
CREATE2 from CREATE, so their
address spaces can never collide)

The four inputs:

  • 0xff — a single constant byte that separates the CREATE2 hash space from the CREATE hash space, so the two schemes can never accidentally produce the same address.
  • deployer — the address of the contract calling CREATE2 (a factory).
  • salt — an arbitrary 32-byte value the deployer picks. This is the free variable: vary the salt and you get a different, still-predictable address from the same factory and code.
  • keccak256(init_code) — the hash of the init code, not the runtime code. This binds the address to exactly what will be deployed.

Because a nonce appears nowhere, the address is a pure function of (deployer, salt, init_code) — three things you can fix in advance and that never drift with transaction history. Deploy a hundred other contracts from the factory first; the CREATE2 address for a given (salt, init_code) is unchanged.

CREATE CREATE2
─────── ───────
inputs : sender, nonce inputs : deployer, salt, keccak256(init_code)
depends on tx history? YES depends on tx history? NO
predictable? yes* predictable? yes, and STABLE
* only if nonce is known/fixed

The reason CREATE2 matters in practice is a pattern called a counterfactual address: an address you can treat as real before any code lives there.

The trick: once (deployer, salt, init_code) are fixed, the address is fully determined — so you can publish it, receive ETH or tokens at it, and print it in a UI today, and only actually run CREATE2 to deploy the code later, if and when it’s needed. Until then the address is an ordinary empty account. Deploy is a promise you can compute the answer to in advance.

t0: compute addr = keccak256(0xff ‖ factory ‖ salt ‖ h(init))[12:]
→ tell Alice "send funds to addr" (no contract exists yet)
t1: Alice sends 5 ETH to addr (still an empty account holding 5 ETH)
t2: later, someone calls factory.deploy(salt, init_code)
→ CREATE2 puts the runtime code at addr (now it can spend the 5 ETH)

Three families of systems lean on this:

  • Factories. A factory contract stamps out many instances (one pool per token pair, one vault per user). With CREATE2, anyone can compute an instance’s address off-chain from (factory, salt, init_code) without a lookup — Uniswap-style pairFor(tokenA, tokenB) is exactly this arithmetic.
  • Smart-contract wallets. A user is given a wallet address before the wallet is deployed. Funds arrive; the wallet contract is only deployed (and paid for) on the user’s first real transaction. This is central to account abstraction (ERC-4337) — the wallet’s address is counterfactual until first use.
  • Layer-2 and cross-chain patterns. Because the address ignores nonce, a factory deployed at the same address on multiple chains yields the same instance addresses everywhere, giving canonical, chain-agnostic addresses for bridges and registries.

The security catch: the address binds the init code

Section titled “The security catch: the address binds the init code”

Look again at the CREATE2 formula: keccak256(init_code) is one of the inputs. This has a sharp security consequence.

For a fixed (deployer, salt), changing the init code changes the address. You cannot deploy contract X to a CREATE2 address, SELFDESTRUCT it, and then deploy a different contract Y to the same address — Y has different init code, so it hashes to a different address. The address is a commitment to the exact code that will appear there. That is what makes a counterfactual address safe to fund: whoever computed it knows precisely what code can ever occupy it.

But the guarantee is only as strong as the init code’s determinism, and here is the trap:

the address commits to keccak256(init_code),
NOT to the runtime code the init code RETURNS.

If the init code’s behavior can vary — for example, it reads a mutable variable or another contract’s state to decide what runtime bytecode to return — then the same address could receive different runtime code on a redeploy. The hash pins the constructor bytes, not the constructor’s output. Auditors treat “does this CREATE2 init code deterministically return the same runtime?” as a load-bearing question, because a factory that returns attacker-controllable runtime to a pre-funded counterfactual address is a rug-pull waiting to happen.

CREATE2 is a distinct, opt-in mechanism layered on the base creation model, so it earns the lens.

  • Why does it exist? Because the original CREATE address depends on the deployer’s nonce, which drifts with transaction history — making it impossible to know a contract’s address stably in advance. CREATE2 removes the nonce from the derivation so an address becomes a pure, stable function of (deployer, salt, init_code).
  • What problem does it solve? Counterfactual addressing — knowing and using a contract’s address before the contract is deployed, and getting the same address on any chain. This unlocks lazily-deployed wallets, factory instances you can locate by arithmetic, and canonical cross-chain addresses.
  • What are the trade-offs? You give up nothing on-chain, but you take on a subtle correctness burden: the address commits to the init code’s hash, not to what that init code returns, so a non-deterministic constructor can put different runtime at the “same” address. Reasoning about a CREATE2 system requires proving init-code determinism.
  • When should I avoid it? When you deploy once and never need a pre-known or cross-chain-stable address — plain CREATE (a normal deploy tx) is simpler and has no salt to manage. Reach for CREATE2 only when counterfactual or deterministic addressing is an actual requirement.
  • What breaks if I remove it? Factories can no longer advertise instance addresses without a registry lookup; smart-contract wallets can’t be funded before deployment; and “same address on every chain” becomes a fragile nonce-matching exercise. The base ability to create contracts survives (CREATE still exists) — you only lose predictable, history-independent addresses.
  1. A deploy transaction is distinguished from an ordinary call by one field. Which field, and what value signals “create a contract”? Where does the new contract’s code travel?
  2. Explain the difference between init code and runtime code: when does each run, what is each for, and which one ends up stored in the account? Why can a constructor take arguments that never appear in the deployed bytecode?
  3. Write the CREATE address formula in words. Why is a CREATE address predictable yet nonce-dependent, and name one situation where that nonce-dependence is a problem.
  4. Write the CREATE2 address formula. Which of its inputs replaces the role of the nonce, and why does removing the nonce make the address usable counterfactually and stable across chains?
  5. CREATE2 hashes keccak256(init_code). Explain both the security guarantee this gives (why you can safely fund a counterfactual address) and the loophole (how the “metamorphic” trick put different runtime at the same address).
Show answers
  1. The to field: an ordinary call names a target address, while a deploy transaction leaves to empty (nil), which signals the protocol to create a new account rather than call an existing one. The new contract’s code travels in the transaction’s data field, as init code.
  2. Init code runs once, at deploy time; it holds the constructor logic and setup, and is discarded afterward. Runtime code runs on every future call; it is the contract’s actual behavior and is stored as the account’s code. The constructor can take arguments because it lives in the init code — it executes once with those arguments and then the init code RETURNs only the runtime bytes, so the constructor (and its args) never appear in the stored bytecode.
  3. address = keccak256(rlp_encode(sender, nonce))[12:] — hash the RLP encoding of the deployer and its nonce, keep the low 20 bytes. It is predictable because both inputs are known before broadcasting, but nonce-dependent because the deployer’s nonce increments with every transaction, so any intervening transaction changes the resulting address. A problem case: wanting the same address on multiple chains (requires identical nonces everywhere) or counterfactual deployment across an unknown number of intervening transactions.
  4. address = keccak256(0xff ‖ deployer ‖ salt ‖ keccak256(init_code))[12:]. The salt (a chosen 32-byte value), together with the init-code hash, replaces the nonce’s role as the varying input. Because no nonce appears, the address depends only on (deployer, salt, init_code) — none of which drift with transaction history — so it can be computed and used before deployment (counterfactual) and is identical on any chain where the same factory and inputs exist.
  5. Guarantee: the address is a commitment to keccak256(init_code), so for a fixed (deployer, salt) only that exact init code can ever produce that address — you can fund the address knowing precisely what code may occupy it. Loophole: the hash pins the init code, not the runtime it returns. If the init code non-deterministically copies runtime from a mutable source, then SELFDESTRUCT-ing and re-running CREATE2 with the same salt/init-code yields the same address but different runtime — the “metamorphic” trick (largely closed by EIP-6780 in the Dencun upgrade, March 2024).