Bird
Raised Fist0
Blockchain / Solidityprogramming~10 mins

Minimal proxy (clone) pattern in Blockchain / Solidity

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Introduction

The minimal proxy pattern helps create many copies of a smart contract cheaply by sharing code. It saves gas and storage on the blockchain.

When you want to deploy many similar smart contracts without repeating all code.
When you want to save blockchain storage and reduce deployment costs.
When you want to upgrade logic in one place and have all clones use it.
When you want to create lightweight contract instances quickly.
When you want to keep your contract system modular and efficient.
Syntax
Blockchain / Solidity
contract Clone {
    address public implementation;

    constructor(address _implementation) {
        implementation = _implementation;
    }

    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

The proxy contract stores the address of the original contract (implementation).

Calls to the proxy are forwarded to the implementation using delegatecall, preserving context.

Examples
This example shows a minimal proxy forwarding all calls to the implementation contract.
Blockchain / Solidity
contract MinimalProxy {
    address public implementation;

    constructor(address _impl) {
        implementation = _impl;
    }

    fallback() external payable {
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), sload(0), 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}
This example uses a library to create minimal proxy clones easily.
Blockchain / Solidity
// Using OpenZeppelin's Clones library
import "@openzeppelin/contracts/proxy/Clones.sol";

contract Factory {
    address public implementation;

    constructor(address _impl) {
        implementation = _impl;
    }

    function createClone() external returns (address) {
        return Clones.clone(implementation);
    }
}
Sample Program

This program shows a logic contract and a minimal proxy that forwards calls to it. The proxy stores data separately but uses logic from the original contract.

Blockchain / Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Logic {
    uint public x;

    function setX(uint _x) public {
        x = _x;
    }
}

contract MinimalProxy {
    address public implementation;

    constructor(address _impl) {
        implementation = _impl;
    }

    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

// Deployment steps (not in code):
// 1. Deploy Logic contract.
// 2. Deploy MinimalProxy with Logic's address.
// 3. Call setX on MinimalProxy to set x in proxy's storage.

// After calling setX(42) on MinimalProxy, reading x from MinimalProxy returns 42.
OutputSuccess
Important Notes

The proxy uses delegatecall to run code in the context of the proxy's storage.

Minimal proxies are very cheap to deploy compared to full contracts.

Be careful with storage layout to avoid conflicts between proxy and implementation.

Summary

Minimal proxy pattern creates cheap clones by forwarding calls to a shared implementation.

It saves gas and storage on the blockchain.

Useful for deploying many similar contracts efficiently.

Practice

(1/5)
1. What is the main purpose of the Minimal proxy (clone) pattern in blockchain development?
easy
A. To replace the original contract with a new one
B. To increase the size of deployed contracts
C. To create cheap copies of contracts by forwarding calls
D. To store large amounts of data on-chain

Solution

  1. Step 1: Understand the pattern's goal

    The minimal proxy pattern is designed to save gas and storage by creating lightweight copies of a contract.
  2. Step 2: Identify the correct purpose

    It achieves this by forwarding calls to the original contract instead of duplicating all code.
  3. Final Answer:

    To create cheap copies of contracts by forwarding calls -> Option C
  4. Quick Check:

    Minimal proxy pattern = cheap contract copies [OK]
Hint: Minimal proxy means cheap clones forwarding calls [OK]
Common Mistakes:
  • Thinking it increases contract size
  • Confusing it with data storage methods
  • Assuming it replaces original contracts
2. Which of the following Solidity code snippets correctly declares a minimal proxy clone using the create opcode?
easy
A. address clone = create(0, bytecode, bytecode.length);
B. address clone = new Contract();
C. address clone = create2(0, bytecode, bytecode.length);
D. address clone = delegatecall(bytecode);

Solution

  1. Step 1: Identify the correct opcode for minimal proxy creation

    The create opcode is used to deploy a new contract with given bytecode.
  2. Step 2: Match the syntax

    The syntax create(0, bytecode, bytecode.length) correctly uses create with zero value and bytecode parameters.
  3. Final Answer:

    address clone = create(0, bytecode, bytecode.length); -> Option A
  4. Quick Check:

    Minimal proxy uses create opcode like address clone = create(0, bytecode, bytecode.length); [OK]
Hint: Minimal proxy uses create, not new or delegatecall [OK]
Common Mistakes:
  • Using new keyword which deploys full contract
  • Confusing create2 with create
  • Using delegatecall which does not deploy
3. Given the following Solidity code snippet for deploying a minimal proxy clone, what will be the output of clone.owner() if the original contract's owner is set to address 0x1234...?
address clone = Clones.clone(original);
// original.owner() returns 0x1234...
// clone forwards calls to original
medium
A. 0x1234... (same owner as original)
B. 0x0000... (zero address)
C. Revert error due to missing owner variable
D. Address of the clone contract

Solution

  1. Step 1: Understand call forwarding in minimal proxy

    The clone forwards calls to the original contract, but storage is separate, so state variables like owner are not shared.
  2. Step 2: Determine owner value returned

    Since owner() reads from the clone's storage which is uninitialized, it returns 0x0000... (zero address).
  3. Final Answer:

    0x0000... (zero address) -> Option B
  4. Quick Check:

    Clone forwards calls but has separate storage, owner = zero address [OK]
Hint: Clone has separate storage, owner defaults to zero [OK]
Common Mistakes:
  • Assuming clone shares storage with original
  • Expecting original owner to be returned
  • Thinking clone address is returned
4. Identify the error in this minimal proxy deployment code snippet:
function clone(address implementation) external returns (address instance) {
    bytes20 targetBytes = bytes20(implementation);
    assembly {
        let clone_code := mload(0x40)
        mstore(clone_code, 0x3d602d80600a3d3981f3)
        mstore(add(clone_code, 0x14), targetBytes)
        instance := create(0, clone_code, 0x37)
    }
    require(instance != address(0), "Create failed");
}
medium
A. No error, code is correct
B. Missing delegatecall opcode in assembly
C. Using bytes20 instead of bytes32 for target address
D. Incorrect length passed to create (0x37 instead of 0x2d)

Solution

  1. Step 1: Check the length parameter for create

    The minimal proxy bytecode length is typically 0x2d (45 bytes), but 0x37 (55 bytes) is passed incorrectly.
  2. Step 2: Understand impact of wrong length

    Passing wrong length causes deployment of invalid bytecode, leading to failure or unexpected behavior.
  3. Final Answer:

    Incorrect length passed to create (0x37 instead of 0x2d) -> Option D
  4. Quick Check:

    Create length must match bytecode size [OK]
Hint: Check create length matches bytecode size [OK]
Common Mistakes:
  • Ignoring bytecode length mismatch
  • Confusing bytes20 and bytes32 usage
  • Assuming delegatecall needed in deployment
5. You want to deploy 1000 instances of a contract cheaply using the minimal proxy pattern. Which approach best reduces gas and storage costs while allowing each clone to have its own owner?
hard
A. Use minimal proxies forwarding to one implementation and store owner in each clone's storage
B. Deploy 1000 full contracts separately with unique owners
C. Use minimal proxies forwarding to one implementation and store owner in the implementation contract
D. Deploy one contract and share the same owner for all clones

Solution

  1. Step 1: Understand minimal proxy benefits

    Minimal proxies save gas by sharing code but have separate storage for each clone.
  2. Step 2: Assign unique owners per clone

    Storing owner in each clone's storage allows unique ownership while sharing logic.
  3. Step 3: Evaluate options

    Use minimal proxies forwarding to one implementation and store owner in each clone's storage uses minimal proxies with per-clone storage, reducing gas and allowing unique owners.
  4. Final Answer:

    Use minimal proxies forwarding to one implementation and store owner in each clone's storage -> Option A
  5. Quick Check:

    Minimal proxy + per-clone storage = cheap unique owners [OK]
Hint: Store owner in clone storage, share code via proxy [OK]
Common Mistakes:
  • Storing owner only in implementation (shared state)
  • Deploying full contracts wastes gas
  • Sharing one owner for all clones