Contract Creation
With CREATE, the new address = keccak256(rlp([deployer, deployerNonce]))[12:] — it depends on the nonce, so it is predictable only if you know the nonce.
24.1 new (CREATE)
With CREATE, the new address = keccak256(rlp([deployer, deployerNonce]))[12:] — it depends on the nonce, so it is predictable only if you know the nonce.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Wallet {
address public owner;
constructor(address _owner) payable { owner = _owner; }
}
contract WalletFactory {
Wallet[] public wallets;
event Created(address wallet, address owner);
function create() external payable returns (Wallet w) {
w = new Wallet{value: msg.value}(msg.sender); // send Ether to constructor
wallets.push(w);
emit Created(address(w), msg.sender);
}
}24.2 CREATE2: deterministic addresses
address = keccak256(0xff ++ deployer ++ salt ++ keccak256(initCode))[12:]
Uses: counterfactual wallets (ERC-4337 accounts have addresses before deployment), same address on multiple chains, state channels, Uniswap pair/pool addresses computed without storage lookups. Deploying twice with the same salt and init code fails (the address already has code).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Child {
address public owner;
constructor(address _owner) { owner = _owner; }
}
contract Create2Factory {
event Deployed(address addr, bytes32 salt);
function deploy(bytes32 salt, address owner) external returns (address addr) {
Child c = new Child{salt: salt}(owner); // CREATE2 via `new` with salt
addr = address(c);
emit Deployed(addr, salt);
}
function predict(bytes32 salt, address owner) external view returns (address) {
bytes memory initCode = abi.encodePacked(type(Child).creationCode, abi.encode(owner));
bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(initCode)));
return address(uint160(uint256(hash)));
}
}24.3 Minimal proxies (clones, ERC-1167)
Deploying a full contract per user is expensive. A clone is a ~45-byte proxy that delegatecalls to a fixed implementation:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
contract VaultImpl {
address public owner;
bool private initialized;
function initialize(address _owner) external {
require(!initialized, "init");
initialized = true;
owner = _owner;
}
}
contract CloneFactory {
address public immutable implementation = address(new VaultImpl());
function createVault(bytes32 salt) external returns (address clone) {
clone = Clones.cloneDeterministic(implementation, salt); // CREATE2 clone
VaultImpl(clone).initialize(msg.sender); // constructor doesn't run for clones
}
}24.4 Size limits
Runtime code is limited to 24,576 bytes (EIP-170); init code to 49,152 bytes (EIP-3860). Exceeding it makes deployment fail.
Remedies: optimizer (runs lower for size), via-IR, moving code to libraries with external functions, splitting contracts, custom errors instead of strings, Diamond pattern (Chapter 29).
🧪 Part II Exercises
Write a Bank with deposit, withdraw, and transfer that uses custom errors with arguments and emits events for each action.
Create Ownable2Step-style ownership: transferOwnership sets a pending owner, who must call acceptOwnership.
Build a Pausable + onlyOwner contract using modifiers, then refactor the modifiers to call internal functions and compare bytecode size.
Implement the A/B/C/D diamond-inheritance example and verify the order of emitted events in a test.
Write an abstract Auction contract with a virtual _settle hook and two concrete implementations (English and Dutch).
Create a library StringUtils with toUpper, startsWith, and equals, attached with using StringUtils for string;.
Define type TokenAmount is uint256; and type Price is uint256; with operators, and show that mixing them fails to compile.
Write a contract that receives Ether through receive, logs unknown calls in fallback, and lets the owner withdraw with call.
Use try/catch to call an untrusted contract that may revert with a string, a panic, or a custom error, and handle each case.
Build a CREATE2 factory, predict the address off-chain with cast compute-address --create2 (or viem getContractAddress), and confirm it matches.