In the world of Ethereum development, the ability to deploy smart contracts deterministically offers powerful architectural advantages. However, this same flexibility introduces subtle but dangerous attack vectors—especially when combining CREATE and CREATE2 opcodes. This article explores how malicious actors can exploit these mechanisms to deploy different contracts to the same address over time, ultimately hijacking trusted systems through logic manipulation. We’ll break down the technical foundations, walk through a real-world attack scenario, and provide actionable defense strategies for developers and auditors.
Understanding Ethereum Contract Address Generation
To grasp the vulnerability, we first need to understand how contract addresses are generated in Ethereum. There are two primary methods: CREATE and CREATE2.
The CREATE Opcode
CREATE has been part of Ethereum since its inception. It dynamically deploys new contracts and calculates their addresses based on two factors:
- The deploying account’s address
- The account’s current nonce (transaction count)
The resulting contract address is derived as follows:
contract address = last 20 bytes of keccak256(RLP(sender, nonce))Because the nonce increments with each transaction or contract creation, the address is non-deterministic—you can’t know it in advance.
👉 Discover how secure blockchain platforms handle contract deployment safely.
The CREATE2 Opcode
Introduced during the Constantinople hard fork (February 2019) via EIP-1014, CREATE2 allows for deterministic address prediction before deployment. This enables advanced use cases like state channels and wallet recovery schemes.
The address is computed using four parameters:
contract address = last 20 bytes of keccak256(0xff || sender || salt || keccak256(init_code))This means anyone with knowledge of the sender, salt, and initialization code can predict the contract’s future address—even before deployment.
What Happens If an Address Already Exists?
Ethereum prevents accidental overwrites by rejecting contract creation at occupied addresses—with one critical exception.
Case 1: Target Is an Externally Owned Account (EOA)
If the target address belongs to a user wallet (EOA), deployment fails. The transaction consumes gas but does not overwrite any data.
Case 2: Target Is a Contract
Similarly, if the address already hosts a live contract, EVM rejects the deployment.
✅ Exception: If the existing contract has self-destructed via selfdestruct(), the address becomes available again—even for redeployment using CREATE2.
This exception is the linchpin of the attack we’re about to explore.
Exploiting CREATE and CREATE2: A Step-by-Step Attack
Consider the following DAO contract that uses delegatecall to execute proposals:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract DAO {
struct Proposal {
address target;
bool approved;
bool executed;
}
address public owner = msg.sender;
Proposal[] public proposals;
function approve(address target) external {
require(msg.sender == owner, "not authorized");
proposals.push(Proposal({target: target, approved: true, executed: false}));
}
function execute(uint256 proposalId) external payable {
Proposal storage proposal = proposals[proposalId];
require(proposal.approved, "not approved");
require(!proposal.executed, "executed");
proposal.executed = true;
(bool ok,) = proposal.target.delegatecall(
abi.encodeWithSignature("executeProposal()")
);
require(ok, "delegatecall failed");
}
}At first glance, only the owner can approve proposals, and each proposal executes only once. But there's a flaw: the target address isn’t validated at execution time.
Core Keywords
- Smart contract security
- CREATE2 vulnerability
- Selfdestruct exploit
- Delegatecall risk
- Deterministic deployment
- Contract redeployment
- Address collision
- EVM opcode security
These keywords reflect both technical depth and search intent from developers and auditors looking to understand real-world risks in DeFi and DAO systems.
How the Attack Works
Attackers exploit the combination of CREATE2, selfdestruct, and delegatecall in three stages.
Step 1: Gain Trust with a Benign Contract
The attacker deploys a harmless Proposal contract containing a safe executeProposal() function. The DAO owner reviews and approves it—trusting its behavior.
Step 2: Destroy and Reuse the Deployer
Using a factory pattern:
- The attacker uses
CREATE2with a fixed salt to deploy aDeployercontract. - That
Deployercreates the initialProposalcontract viaCREATE. - Then, the attacker calls
selfdestructon theDeployer, wiping its code and resetting its nonce to zero.
👉 See how leading platforms prevent unauthorized contract execution.
Step 3: Re-deploy Malicious Code to the Same Address
Now, the attacker reuses CREATE2 with identical parameters to redeploy a new Deployer at the same address. Since the nonce is reset, any subsequent contract created via CREATE will have the same address as the original Proposal—but now it's a malicious one.
When someone triggers execute(), the DAO delegates execution to what it thinks is the approved contract—but it's actually running attacker-controlled code in its own context.
Result? The attacker can:
- Change the DAO’s owner
- Drain funds
- Manipulate governance logic
This is possible because delegatecall executes code from an external address while maintaining the calling contract’s state.
Real Attack Flow Breakdown
- Alice deploys the vulnerable DAO.
- Attacker deploys
DeployerDeployer, which usesCREATE2to installDeployerat a predictable address. Deployercreates a benignProposalcontract (address P).- Alice approves P in the DAO.
- Attacker destroys
Deployerviaselfdestruct. - Attacker re-deploys
Deployerat the same address usingCREATE2. - New
Deployercreates anAttackcontract—the same nonce and sender mean it lands at address P again. - Now, P points to malicious code.
- Calling
execute()runs attacker logic inside the DAO’s context.
The DAO never verifies that the code at P hasn’t changed.
Defense Strategies for Developers
Preventing such attacks requires proactive design and runtime checks.
✅ Validate Code Hash on Execution
Instead of storing only the target address, record its bytecode hash during approval:
keccak256(type(Proposal).creationCode)Then verify it before delegatecall.
❌ Avoid Blind Delegatecalls
Never use delegatecall on untrusted or mutable addresses. If necessary, implement a allowlist or lock proposals after approval.
🔒 Monitor for Selfdestruct Patterns
During audits, check if any involved contracts contain selfdestruct. This is a red flag when combined with factory patterns or dynamic calls.
🧱 Use Immutable Factories
Avoid reusing factory addresses that can be destroyed and recreated. Prefer singleton factories or deploy via CREATE when predictability isn’t needed.
🔐 Enforce Address Locking
Once a proposal is approved, lock its state—don’t allow assumptions about code permanence without verification.
FAQ: Common Questions About Contract Redeployment Attacks
Q: Can CREATE be used to redeploy to an already used address?
A: No—only if the original contract was self-destructed and the nonce conditions align (e.g., via factory reset using CREATE2).
Q: Is CREATE2 inherently unsafe?
A: No—it’s a powerful tool when used correctly. The risk lies in assuming that a known address always points to the same code.
Q: How can I detect if a contract has selfdestructed?
A: Check if the address’s code size is zero (address.code.length == 0). A live contract should have non-zero bytecode.
Q: Does EIP-3607 prevent this attack?
A: EIP-3607 restricts EOAs from sending transactions if they have non-zero code—a protection against smart contract wallets masquerading as users. It doesn’t directly stop this attack.
Q: Are there tools to monitor contract redeployment?
A: Yes—blockchain explorers like Etherscan track contract creations and selfdestructs. Static analysis tools like Slither also flag risky patterns involving selfdestruct and delegatecall.
👉 Learn how secure ecosystems detect suspicious contract behavior early.
Final Thoughts
The intersection of CREATE, CREATE2, and selfdestruct opens up elegant solutions—but also dangerous loopholes when misused. As decentralized systems grow more complex, so must our security mindset.
Developers must stop treating contract addresses as immutable references. Just because an address passed review yesterday doesn’t mean it’s safe today.
By integrating runtime validation, minimizing trust in external code, and auditing for redeployment risks, we can build more resilient smart contracts that stand up to adversarial scrutiny.
Stay vigilant—and always assume that any address can change its code unless proven otherwise.