In fact, the CREATE
opcode hasn't gone anywhere — it's used every time a contract is created using the new
keyword:
Smart Contracts Aren’t Deployed Yet, but Addresses Already Exist: Why CREATE2 (EIP-1014) Matters

It used to be better — or at least more reliable. That's how you could describe the CREATE
opcode, the predecessor of CREATE2
. It was simple and didn’t cause issues (potential vulnerabilities).
Opcode CREATE
contract Bar {
// @notice Creating a contract via create without sending ETH to the new address
function createFoo() external returns (address) {
Foo foo = new Foo();
return address(foo);
}
// @notice Creating a contract via create with sending ETH to the new address
function createBaz() external payable returns (address) {
Baz baz = new Baz{value: msg.value}();
return address(baz);
}
}
The full contract code is provided here.
The CREATE
opcode takes three arguments and returns one value:
Input data (Stack input):
value
— the amount of native currency in wei to be sent to the new address.offset
— the byte offset where the contract’s initialization code starts.size
— the size of the initialization code.
Output data (Stack output):
address
— the address of the deployed contract, or0
if an error occurred.
Deploying a contract via assembly looks more illustrative:
contract Deployer {
// @notice Creating a contract via create without sending wei to the new address
function deployFoo() public returns (address) {
address foo;
bytes memory initCode = type(Foo).creationCode;
assembly {
// Load the initialization code into memory
let codeSize := mload(initCode) // Size of the initialization code
let codeOffset := add(initCode, 0x20) // Skip 32 bytes that contain the length of the initCode array
// Call CREATE without sending msg.value
foo := create(0, codeOffset, codeSize)
// Check that the contract was successfully created
if iszero(foo) { revert(0, 0) }
}
return foo;
}
}
The full contract code with creation via assembly is here.
Address Calculation with the CREATE Opcode (0xf0)
For the CREATE
opcode to return the address of the deployed contract, it needs the caller’s address (msg.sender
) and its nonce
:
In simplified form, it looks like this:
address = hash(sender, nonce)
But in reality, the process is more complex:
address = keccak256(rlp([sender_address, sender_nonce]))[12:]
Where:
sender_address
— the address of the sender creating the contract.sender_nonce
— the sender’s nonce (number of transactions sent from this address).rlp
— RLP encoding function. RLP (Recursive Length Prefix) is used for serializing data in Ethereum, ensuring unambiguous and predictable encoding.keccak256
— the Keccak-256 hash function.[12:]
— the first 12 bytes are discarded sincekeccak256
returns 32 bytes, and an Ethereum address takes the last 20 bytes of the hash (32 - 20 = 12).
Thus, in theory, it’s possible to calculate the future contract address in advance. However, there's a problem: this address depends on the nonce
. If another transaction is sent before the contract is deployed, the nonce
will increase, and the calculated address will become invalid.
Due to the use of RLP for address calculation, the following bulky function is needed in Solidity before deployment:
function computeAddressWithCreate(uint256 _nonce) public view returns (address) {
address _origin = address(this);
bytes memory data;
if (_nonce == 0x00) {
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80));
} else if (_nonce <= 0x7f) {
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce));
} else if (_nonce <= 0xff) {
data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce));
} else if (_nonce <= 0xffff) {
data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce));
} else if (_nonce <= 0xffffff) {
data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce));
} else {
data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce));
}
return address(uint160(uint256(keccak256(data))));
}
The total encoding length depends on how many bytes are needed to encode the nonce
, since the address has a fixed length of 20 bytes — hence all the if
statements.
For example, if the nonce is 0, the parameters mean the following:
0xd6
— the total length of the structure is 22 bytes (in the case wherenonce
is 0).bytes1(0x94)
— indicates that the following field is 20 bytes long._origin
— the address field.bytes1(0x80)
— indicates that thenonce
is 0, according to RLP.
The rest is similar — only the nonce
is added as one byte and so on. In RLP encoding, it’s important to explicitly specify the length of the data before the data itself.
I added this function to the Deployer contract — you can test it in Remix.
The Premises for CREATE2 Creation
In 2018, Vitalik Buterin proposed EIP-1014: Skinny CREATE2 with the following motivation:
Allows interactions (actual or counterfactual in channels) with addresses that do not yet exist on-chain but can be relied on to eventually contain code created by a given initialization code. This is important for state channel use cases involving counterfactual interactions with contracts.
Sounds complicated, but I’ll try to explain. It’s about state channels. Before rollups appeared, they were considered a way to scale Ethereum.
In short, state channels had inefficiencies that could be eliminated with counterfactual instantiation. The idea is that a smart contract could exist counterfactually — meaning it didn’t need to be deployed, but its address was known in advance.
This contract could be deployed on-chain if needed — for example, if one of the channel participants tried to cheat the other during an off-chain transaction.
Example from the mechanism description:
Imagine a payment channel between Alice and Bob. Alice sends Bob 4 ETH through the channel by signing the corresponding transaction. This transaction can be deployed on-chain at any time, but it isn’t. So, you can say: 'Counterfactually, Alice has sent Bob 4 ETH'. This allows them to act as if the transaction has already happened — it is final within the given threat models.
How the CREATE2 Opcode Works (0xf5)
The CREATE2
opcode was introduced in the Constantinople hard fork as an alternative to CREATE
. The main difference is the way the address of the created contract is calculated. Instead of the deployer's nonce
, it uses the initialization code (creationCode
) and a salt (salt
).
New address calculation formula:
address = keccak256(0xff + sender_address + salt + keccak256(initialisation_code))[12:]
0xff
— a prefix that prevents collisions with addresses created viaCREATE
. In RLP encoding,0xff
can only be used for petabyte-sized data, which is unrealistic in the EVM. Additionally,keccak256
provides protection against collisions.sender_address
— the address of the sender creating the contract.salt
— a 32-byte value, usually thekeccak256
hash of some dataset that ensures the uniqueness of this salt.initialisation_code
— the initialization code of the contract.
Important! If CREATE
or CREATE2
is called in a contract creation transaction and the target address already has a non-zero nonce
or non-empty code
, the creation immediately reverts — similar to the case when the first byte of the initialisation_code
is an invalid opcode.
This means that if a deployment results in an address collision with an already existing contract (for example, one deployed via CREATE
), a revert
will occur because the address’s nonce
is already non-zero. This behavior cannot be changed even with SELFDESTRUCT
, since it does not reset the nonce
within the same transaction.
Compared to CREATE
, CREATE2
differs only by the addition of one input parameter — salt
.
Input data (Stack input):
value
— the amount of native currency (wei) to send to the new address.offset
— the byte offset where the initialization code starts.size
— the size of the initialization code.salt
— a 32-byte value used during contract creation.
Output data (Stack output):
address
— the address of the deployed contract, or0
if an error occurred.
Using CREATE2 in Solidity
In Solidity, CREATE2
can be used just like CREATE
, simply by adding a salt
:
contract DeployerCreate2 {
/// @notice Creating a contract via create2 without sending wei
function create2Foo(bytes32 _salt) external returns (address) {
Foo foo = new Foo{salt: _salt}();
return address(foo);
}
/// @notice Creating a contract via create2 with sending wei
function create2Bar(bytes32 _salt) external payable returns (address) {
Bar bar = new Bar{value: msg.value, salt: _salt}();
return address(bar);
}
}
The full contract code is here.
Important! The
CREATE
andCREATE2
opcodes are used only for creating smart contracts from other smart contracts. During the initial deployment of a contract, things work very differently — theto
field in the transaction is set tonil
(equivalent tonull
), and the actual creation is done by theRETURN
opcode inside thecreationCode
, not byCREATE
.
CREATE2 Using Assembly
Example code in Assembly (taken from Cyfrin):
function deploy(bytes memory bytecode, uint256 _salt) public payable {
address addr;
/*
NOTE: How to call create2
create2(v, p, n, s)
creates a new contract with code in memory from p to p + n
and sends v wei
and returns the new address
where the new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n)]))
s = a big-endian 256-bit value
*/
assembly {
addr :=
create2(
callvalue(), // wei sent with the call
add(bytecode, 0x20), // Code starts after the first 32 bytes (array length)
mload(bytecode), // Code size (first 32 bytes)
_salt // Salt
)
if iszero(extcodesize(addr)) { revert(0, 0) }
}
emit Deployed(addr, _salt);
}
The full contract code is here.
Gas Costs
Previously, when computing the address via CREATE
, only address
and nonce
were used, taking no more than 64 bytes. That’s why no additional gas was charged for computation (see evm.codes).
In CREATE2
, a hash of the initialization code (hash_cost
) is added to the computation, since its size can vary greatly. This changed the gas calculation formula:
minimum_word_size = (size + 31) / 32
init_code_cost = 2 * minimum_word_size
hash_cost = 6 * minimum_word_size
code_deposit_cost = 200 * deployed_code_size
static_gas = 32000
dynamic_gas = init_code_cost + hash_cost + memory_expansion_cost + deployment_code_execution_cost + code_deposit_cost
Thus, using CREATE2
is more expensive than CREATE
, but it allows more flexibility when working with a smart contract’s address before it’s created — which opens up new possibilities.
Advantages of CREATE2
What did the introduction of the new opcode bring?
Counterfactual Initialization
CREATE2
allows reserving contract addresses before they are actually deployed. This is especially useful in state channels, as we discussed earlier.Simplified User Onboarding In the context of account abstraction, counterfactual initialization allows creating accounts off-chain and deploying them only with the first transaction — which can even be paid for via a relayer. This makes creating an abstract account easier than creating an EOA.
When
CREATE2
first appeared, it was just an idea — but three years later, the concept was implemented in ERC-4337. It uses a static call toentryPoint.getSenderAddress(bytes initCode)
, which allows retrieving the counterfactual wallet address before it's deployed.Vanity Addresses You can generate a "pretty" address by brute-forcing the
salt
, for example, if you want it to start or end with certain characters:0xC0FFEE...
,0xDEADBEEF...
, and so on.Efficient Addresses In the EVM, the gas cost differs for zero and non-zero bytes. Each non-zero byte in
calldata
costsG_txdatanonzero
(16 gas), while a zero byte costsG_txdatazero
(4 gas). This means that if your address starts with zeros, using it will be cheaper.This aspect is explained in detail here: On Efficient Ethereum Addresses (though the gas calculations are outdated due to changes in
calldata
pricing).Metamorphic Contracts A method of upgrading contracts via
CREATE2
, where a contract is destroyed (SELFDESTRUCT
) and then recreated at the same address with new code. Fortunately, the community didn’t adopt this approach — for example, in this article it’s called the "ugly stepbrother of the Transparent Proxy."You can check out code examples here.
Address Calculation Instead of Storage In many cases, it’s easier to compute the address of a contract deployed via
CREATE2
than to store it. A clear example of this is Uniswap v2.How does it work?
To create pairs via UniswapV2Factory,
CREATE2
is used, and the salt is derived from the addresses of the two tokens in the pair. Note that the pair contract uses aninitialize
function to store the token addresses — this is an important detail.
function createPair(address tokenA, address tokenB) external returns (address pair) {
/// ...
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
/// ...
}
- Now the
UniswapV2Library
can compute the pair address using the knowninit code hash
in the pairFor function. The initialization code can be hardcoded, because there’s no need to pass constructor arguments — that’s exactly whyinitialize
is used:
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}
init code hash
, as it depends on the factory (factory
), whose address is set in the constructor of the pair contract.
And now, with the
pairFor
function, you can easily compute this address whenever needed. Just look at how often this function is used in UniswapV2Router01. For example, here’s how the add liquidity function looks:
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
///...
}
As you can see, CREATE2
has unlocked a lot of new possibilities, though it also comes with some drawbacks.
Ethereum development domain
Help startups develop projects on the Ethereum blockchain
CREATE2 Vulnerability
You can find the following warning in the Solidity documentation:
Salt creation has some caveats. A contract can be re-created at the same address after it has been destroyed. The newly created contract can have different deployed bytecode, even if the creation bytecode was the same (which is required, otherwise the address would change). This happens because the constructor may query external state, which might have changed between the two deployments, and include it in the deployed bytecode before it’s stored.
This refers to a well-known vulnerability: a combination of CREATE
and CREATE2
together with SELFDESTRUCT
allows deploying different contracts at the same address. This exact method was used in the Tornado Cash hack, where $1M was stolen.
This also applies to metamorphic contracts.
Attack Demonstration
There’s a repository that reproduces a similar attack. I slightly modified that code so it can be tested in Remix — we’ll go through it in more detail below:
Factory contract:
contract Factory {
function createFirst() public returns (address) {
return address(new First());
}
function createSecond(uint256 _number) public returns (address) {
return address(new Second(_number));
}
function kill() public {
selfdestruct(payable(address(0)));
}
}
This factory creates contracts with identical addresses but different code. 😈
Step 1. Deploy MetamorphicContract
and call the firstDeploy
function:
function firstDeploy() external {
factory = new Factory{salt: keccak256(abi.encode("evil"))}();
first = First(factory.createFirst());
emit FirstDeploy(address(factory), address(first));
first.kill();
factory.kill();
}
This call:
- Deploys the factory and the first version of the contract.
- Immediately destroys them after deployment.
- Logs their addresses.
- Destroys both contracts.

Result in Remix
Step 2. Now you can call the secondDeploy
function:
function secondDeploy() external {
/// Check that the contracts have been destroyed
emit CodeLength(address(factory).code.length, address(first).code.length);
/// Deploy the factory at the same address
factory = new Factory{salt: keccak256(abi.encode("evil"))}();
/// Deploy a new contract at the same address as the first one
second = Second(factory.createSecond(42));
/// Check that the addresses match
require(address(first) == address(second));
/// Execute the logic of the new contract
second.setNumber(21);
/// Log the addresses
emit SecondDeploy(address(factory), address(second));
}

Result in Remix
What just happened?
- Deployed the factory using
CREATE2
with a fixed salt. - The factory used
CREATE
to deploy an implementation contract. Its address depends on the factory’s address and itsnonce
. - Both contracts were destroyed using
SELFDESTRUCT
. This reset the factory’s nonce. - Deployed the same factory at the same address (since the salt didn’t change).
- Deployed a different implementation at the same address, because the factory’s
nonce
is again0
. - Now there's completely different code at the exact same address!
The full contract code is here.
Now you know how to deploy different contracts at the same address. 🚨
Links
- EIP-1014: Skinny CREATE2
- Docs: Salted contract creations / create2
- Blog: Precompute Contract Address with Create2
- Blog: Getting the most out of CREATE2
- Blog: A State Channels Adventure with Counterfactual Rick! (Part 1)
- Blog: Counterfactual: Generalized State Channels on Ethereum
- Blog: Understanding Solidity’s create & create2 with Tornado Cash $1M Hack
- Blog: On Efficient Ethereum Addresses
More articles by this author

Aragon DAO v2: Plugins, Permissions, and the New OSx Architecture
Roman Yarlykov
Solidity developer

Articles
Algebra Finance: Modular DEX-as-a-Service with Plugins, Dynamic Fees, and Uniswap Compatibility
Roman Yarlykov
Solidity developer

Articles
Aerodrome Protocol: How a MetaDEX on Base Blends Uniswap, Curve, and Convex
Roman Yarlykov
Solidity developer

Overview of Blockchain Bridges: Interaction Between Different Networks
Roman Yarlykov
Solidity developer

Smart Contracts Aren’t Deployed Yet, but Addresses Already Exist: Why CREATE2 (EIP-1014) Matters
Roman Yarlykov
Solidity developer



How to Fork and Launch Uniswap V3 Smart Contracts: A Practical Guide
Alexei Kutsenko
Solidity developer


Bittensor: Overview of the Protocol for Decentralized Machine Learning
Alexei Kutsenko
Solidity developer

Aerodrome Protocol: How a MetaDEX on Base Blends Uniswap, Curve, and Convex
Roman Yarlykov
Solidity developer
Articles

Algebra Finance: Modular DEX-as-a-Service with Plugins, Dynamic Fees, and Uniswap Compatibility
Roman Yarlykov
Solidity developer
Articles

ERC-6909: Minimal Multi-Token Interface and Why It Matters for Ethereum Projects
Pavel Naydanov
Solidity developer

Uniswap v4 Explained: Hooks, Singleton Architecture, Dynamic Fees & ERC-6909
Pavel Naydanov
Solidity developer


AI Agents: How AI Agents Conquered the Crypto Market + Key Projects
MetaLamp editorial team

AI and Blockchain: Key Takeaways from 2024 and Industry Forecasts for 2025
MetaLamp editorial team

The main events in The Open Network (TON) ecosystem in 2024
MetaLamp editorial team

A Guide to EigenLayer: How the ETH Restaking Protocol Attracted $15 Billion TVL
MetaLamp editorial team

The Open Network 2025: figures, events, analytics, forecasts
MetaLamp editorial team

Overview of Blockchain Bridges: Interaction Between Different Networks
Roman Yarlykov
Solidity developer




5 Rules from the Founder: How an EdTech Project Can Attract Investments. The Case of the Online School “Logopotam”
Alexey Litvinov
CEO and founder of the online school Logopotam

Is it worth launching a project on Solana, despite the hype around memes?
MetaLamp editorial team

Mintless Jettons on TON: A New Feature Making TON Projects Even More Attractive
MetaLamp editorial team

3 reasons to choose a ready-made solution for mini-apps in Telegram instead of developing from scratch
Dmitriy Shipachev
CEO at Finch


Think of it like a hamster for traffic: how to attract an audience with a Telegram clicker game
Nico Bordunenko
Business Analyst at MetaLamp



Which Rollup to Choose for Your Project: Arbitrum, Optimism, Base, ZK EVM, and Others
MetaLamp editorial team


How We Adapted a Mobile RPG for Blockchain and Enabled NFT Sales
MetaLamp editorial team

How TON Payments Enable Fee-Free Micro-Transactions and Their Uses
MetaLamp editorial team

What You Need to Know Before Starting a Project on TON
MetaLamp editorial team

What is the Meaning and Benefits of MVP for Startups in 2024?
MetaLamp editorial team




RWA explained: Opportunities of Real-World Assets in 2024
MetaLamp editorial team


Creating a Crypto Transaction Widget for Google Sheets: The CPayToday Journey
MetaLamp editorial team


How Early-Stage Startups Can Stay on Track with Development
MetaLamp editorial team

How to Attract Investments: Insights from Successful 2023 Startups
Mykola Pryndiuk
Social Media Specialist

When and How to Find a Technical Partner for Your Startup
MetaLamp editorial team




Understanding the Necessity of Account Abstraction in the Crypto World
Pavel Naydanov
Solidity developer

Ways to Speed Up Development: Outstaffing Pros and Cons
MetaLamp editorial team



Freelancer, Agency, or Contract Employees: Who to Hire for Startup MVP Development
Yana Geydrovich
Partnership manager at MetaLamp

From Corporate Blog to Brand Media: The Birth of Metalamp Magazine
Mykola Pryndiuk
Social Media Specialist

La Migliore Offerta: The Impact of Cryptocurrency on Business and Economy in 2023
Roman Shtih
CEO Metalamp






How We Use Our Training Program to Recruit Plutus Engineers
Svetlana Dulceva
The Education Program Supervisor

Discover Why IT Companies Appreciate Our Junior Developers
Svetlana Dulceva
The Education Program Supervisor







How We Designed a No-Cost Education Program for Web Development
Sergey Cherepanov
CTO MetaLamp
Articles