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.