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
최근 소스 활동
2026년 4월 10일 16:31
감지된 SKILL.md 언어
영어
스타
131
포크
18

설치 방법

기본적으로 소스를 먼저 확인하는 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에서 보기