Skip to main content

minimal-proxy

Use when deploying many identical contracts cheaply with EIP-1167 minimal proxy (clones). Covers Clones library, initialization patterns, gas efficiency, factory patterns, and limitations.

معلومات المصدر

المستودع
ccashwell/evm-cortex
آخر نشاط في المصدر
١٠ أبريل ٢٠٢٦ في ١٦:٣١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٣١
التفرعات
١٨

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
minimal-proxy
description
Use when deploying many identical contracts cheaply with EIP-1167 minimal proxy (clones). Covers Clones library, initialization patterns, gas efficiency, factory patterns, and limitations.
# EIP-1167 Minimal Proxy (Clones) ## Overview EIP-1167 defines a minimal bytecode proxy that delegates all calls to a known implementation. The clone is only 45 bytes of bytecode and costs ~36,000 gas to deploy (vs ~200,000+ for a full contract). Not upgradeable — the implementation address is baked into the bytecode. ## Clone Bytecode ``` 363d3d373d3d3d363d73<implementation>5af43d82803e903d91602b57fd5bf3 ``` This is a pure `delegatecall` forwarder. All storage lives in the clone, logic executes from the implementation. ## Factory Pattern with OpenZeppelin Clones ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; contract VaultFactory { using Clones for address; address public immutable implementation; address[] public allVaults; event VaultCreated(address indexed vault, address indexed owner, uint256 index); constructor(address implementation_) { implementation = implementation_; } function createVault( address owner, address asset, string calldata name ) external returns (address vault) { vault = implementation.clone(); IVault(vault).initialize(owner, asset, name); allVaults.push(vault); emit VaultCreated(vault, owner, allVaults.length - 1); } function createVaultDeterministic( address owner, address asset, string calldata name, bytes32 salt ) external returns (address vault) { vault = implementation.cloneDeterministic(salt); IVault(vault).initialize(owner, asset, name); allVaults.push(vault); emit VaultCreated(vault, owner, allVaults.length - 1); } function predictAddress(bytes32 salt) external view returns (address) { return implementation.predictDeterministicAddress(salt, address(this)); } function vaultCount() external view returns (uint256) { return allVaults.length; } } ``` ## Implementation Contract (Clone Target) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; interface IVault { function initialize(address owner, address asset, string calldata name) external; } contract Vault is Initializable, IVault { using SafeERC20 for IERC20; address public owner; IERC20 public asset; string public name; modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(address owner_, address asset_, string calldata name_) external override initializer { owner = owner_; asset = IERC20(asset_); name = name_; } function deposit(uint256 amount) external { asset.safeTransferFrom(msg.sender, address(this), amount); } function withdraw(uint256 amount) external onlyOwner { asset.safeTransfer(owner, amount); } } ``` ## Gas Comparison | Operation | Full Deploy | Clone Deploy | Savings | |-----------|------------|-------------|---------| | Deploy cost | ~500,000 gas | ~46,000 gas | ~91% | | Runtime overhead | 0 | ~700 gas/call | Negligible | | Bytecode size | Full contract | 45 bytes | ~99% | For protocols deploying many instances (vaults, escrows, pools), clones can save millions of gas. ## Deterministic Clones (CREATE2) Predict clone addresses before deployment: ```solidity bytes32 salt = keccak256(abi.encode(msg.sender, tokenAddress, block.timestamp)); address predicted = Clones.predictDeterministicAddress(implementation, salt, address(factory)); // Deploy later: address deployed = Clones.cloneDeterministic(implementation, salt); assert(deployed == predicted); ``` ## Clones with Immutable Args (CWIA) For clones that need constructor-like args baked into bytecode (not storage), use the CWIA pattern. Args are appended to the clone bytecode and read via `calldataload`: ```solidity import {ClonesWithImmutableArgs} from "clones-with-immutable-args/ClonesWithImmutableArgs.sol"; contract Factory { using ClonesWithImmutableArgs for address; function createClone(address impl, address owner, uint256 threshold) external returns (address) { bytes memory data = abi.encodePacked(owner, threshold); return impl.clone(data); } } contract CloneImpl { function _getArgAddress(uint256 argOffset) internal pure returns (address) { return address(uint160(_getArgUint256(argOffset))); } function owner() public pure returns (address) { return _getArgAddress(0); } function threshold() public pure returns (uint256) { return _getArgUint256(20); } } ``` ## Limitations - **Not upgradeable**: Implementation address is hardcoded in bytecode - **Same bytecode**: All clones share the same logic, differ only in storage - **No constructor**: Must use `initialize` pattern - **Double-init risk**: Always use `Initializable` to prevent re-initialization - **`address(this)` in implementation**: Returns the clone's address (correct behavior) - **Cannot receive ETH without `receive()`**: Implementation must have `receive()` if clones need to accept ETH ## Testing ```solidity function test_cloneFactory() public { Vault impl = new Vault(); VaultFactory factory = new VaultFactory(address(impl)); address vault1 = factory.createVault(alice, address(usdc), "Vault 1"); address vault2 = factory.createVault(bob, address(weth), "Vault 2"); // Different addresses, same logic assertTrue(vault1 != vault2); assertEq(Vault(vault1).owner(), alice); assertEq(Vault(vault2).owner(), bob); // Cannot re-initialize vm.expectRevert(); Vault(vault1).initialize(bob, address(weth), "Hijack"); } function test_deterministicClone() public { bytes32 salt = keccak256("test"); address predicted = factory.predictAddress(salt); address deployed = factory.createVaultDeterministic(alice, address(usdc), "V1", salt); assertEq(predicted, deployed); } ``` ## When to Use Clones - **Use when**: Deploying many instances of the same contract (pools, vaults, escrows, accounts) - **Don't use when**: Contracts need upgradeability, or each instance has different logic - **Consider CWIA when**: Clones need immutable configuration that doesn't change after deploy
عرض على GitHub