The minimal proxy pattern helps create many copies of a smart contract cheaply by sharing code. It saves gas and storage on the blockchain.
Minimal proxy (clone) pattern in Blockchain / Solidity
Start learning this pattern below
Jump into concepts and practice - no test required
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.
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()) }
}
}
}// 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);
}
}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.
// 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.
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.
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
Minimal proxy (clone) pattern in blockchain development?Solution
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.Step 2: Identify the correct purpose
It achieves this by forwarding calls to the original contract instead of duplicating all code.Final Answer:
To create cheap copies of contracts by forwarding calls -> Option CQuick Check:
Minimal proxy pattern = cheap contract copies [OK]
- Thinking it increases contract size
- Confusing it with data storage methods
- Assuming it replaces original contracts
create opcode?Solution
Step 1: Identify the correct opcode for minimal proxy creation
Thecreateopcode is used to deploy a new contract with given bytecode.Step 2: Match the syntax
The syntaxcreate(0, bytecode, bytecode.length)correctly usescreatewith zero value and bytecode parameters.Final Answer:
address clone = create(0, bytecode, bytecode.length); -> Option AQuick Check:
Minimal proxy uses create opcode like address clone = create(0, bytecode, bytecode.length); [OK]
- Using new keyword which deploys full contract
- Confusing create2 with create
- Using delegatecall which does not deploy
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
Solution
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.Step 2: Determine owner value returned
Sinceowner()reads from the clone's storage which is uninitialized, it returns0x0000...(zero address).Final Answer:
0x0000... (zero address) -> Option BQuick Check:
Clone forwards calls but has separate storage, owner = zero address [OK]
- Assuming clone shares storage with original
- Expecting original owner to be returned
- Thinking clone address is returned
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");
}Solution
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.Step 2: Understand impact of wrong length
Passing wrong length causes deployment of invalid bytecode, leading to failure or unexpected behavior.Final Answer:
Incorrect length passed to create (0x37 instead of 0x2d) -> Option DQuick Check:
Create length must match bytecode size [OK]
- Ignoring bytecode length mismatch
- Confusing bytes20 and bytes32 usage
- Assuming delegatecall needed in deployment
Solution
Step 1: Understand minimal proxy benefits
Minimal proxies save gas by sharing code but have separate storage for each clone.Step 2: Assign unique owners per clone
Storing owner in each clone's storage allows unique ownership while sharing logic.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.Final Answer:
Use minimal proxies forwarding to one implementation and store owner in each clone's storage -> Option AQuick Check:
Minimal proxy + per-clone storage = cheap unique owners [OK]
- Storing owner only in implementation (shared state)
- Deploying full contracts wastes gas
- Sharing one owner for all clones
