Overview#
Minimal proxies (EIP-1167 clones) are the cheapest way to deploy many instances of the same contract. Each clone delegates every call to a shared implementation contract, so you only pay for ~45 bytes of creation code instead of re-deploying the full bytecode.
The limitation is that clones share the implementation’s storage layout, so each instance still needs its own initialiser call to set instance-specific state — and that initialiser writes to storage, which is expensive.
OpenZeppelin’s Clones library (v5.2+) solves this by appending arbitrary immutable data to the clone’s bytecode at deploy time. The data lives in code, not storage, so reading it costs only CODECOPY — far cheaper than SLOAD. No initialiser is needed for values that never change.
When to Use It#
- Factory patterns where each clone needs a handful of fixed parameters (token address, fee, owner, pool ID, etc.).
- Gas-sensitive deployments — you create many instances and want to avoid storage writes during init.
- Immutable configuration — the values genuinely never change for the lifetime of the clone.
Avoid it when the “immutable” values might need upgrading, or when the data payload is very large (bytecode size affects deployment cost).
How It Works#
- The factory calls
Clones.cloneWithImmutableArgs(implementation, data). - The library deploys an EIP-1167 proxy with
dataappended after the delegatecall footer. - Inside the clone,
Clones.fetchCloneArgs()reads the appended bytes from the clone’s bytecode.
Because the data is part of the deployed bytecode it is truly immutable — no one can change it after deployment.
Example: Token Reward Pool Factory#
The implementation contract reads its immutable args via Clones.fetchCloneArgs() and unpacks them with abi.decode:
|
|
The factory deploys clones with ABI-encoded data:
|
|
Deterministic Deploys#
Use cloneDeterministicWithImmutableArgs to deploy to a predictable CREATE2 address. This is useful when other contracts or off-chain systems need to know the address before deployment:
|
|
Predict the address off-chain or from another contract with predictDeterministicAddressWithImmutableArgs.
Gotchas#
**Decode mismatch loses funds.** If the `abi.encode` in the factory and `abi.decode` in the implementation disagree on types or order, the getters silently return garbage. Write a test that round-trips every arg.
**`msg.sender` context.** Clones use `delegatecall` to the implementation, but the proxy's own address is `address(this)`. If the implementation contract also exists standalone on-chain, make sure you are interacting with the clone address, not the implementation.
**No reinitialisation.** There is no initialiser to call — the data is fixed at deploy time. If you need mutable state as well, set it via a separate `initialize()` function guarded by an `initialized` flag, exactly as you would with a normal proxy.
**Etherscan verification.** Proxy clones don't automatically show source on Etherscan. Use the "Is this a proxy?" feature to link to the implementation, or verify via the factory's creation event.
Testing#
A minimal Foundry test that verifies the immutable args round-trip:
|
|
Alternatives#
| Library | Notes |
|---|---|
Solady LibClone |
Gas-optimised alternative with immutable-args support and appendable clones. |
| wDAI ClonesWithImmutableArgs | The original implementation that popularised the pattern. Uses manual byte-offset getters instead of abi.decode. |