ERC-1167: Minimal Proxy Contract (Clones)
ERC-1167 standardises a 45-byte runtime bytecode that does nothing but DELEGATECALL to a fixed implementation address. Combined with CREATE2 (ERC-1014) it gives you cheap, deterministic clones of an implementation — the substrate behind every "factory" pattern: TBA registries, multisig factories, NFT collection deployers, AMM pool contracts, etc. OpenZeppelin's Clones.sol is the de-facto helper.
Bytecode Layout
0x363d3d373d3d3d363d73<implementation_20_bytes>5af43d82803e903d91602b57fd5bf3The runtime forwards calldata, executes DELEGATECALL, and returns the result. Storage is the proxy's own; the implementation contract supplies only logic.
Required Use Pattern
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
contract Factory {
address public immutable implementation;
function clone(bytes32 salt) external returns (address proxy) {
proxy = Clones.cloneDeterministic(implementation, salt);
IInitializable(proxy).initialize(msg.sender);
}
function predict(bytes32 salt) external view returns (address) {
return Clones.predictDeterministicAddress(implementation, salt, address(this));
}
}Neo Equivalent: Parameterised Deploy via ContractManagement
NeoVM doesn't have DELEGATECALL. Two patterns reproduce the ERC-1167 use case:
A. Parameterised manifest deploy — the factory ships a small NEF + a manifest template. On Clone(salt), the factory inlines the implementation's contract hash into a constant slot and calls ContractManagement.Deploy(nef, manifest). The "proxy" is a real contract whose every method does Contract.Call(implementation, method, args) and re-returns. Storage stays in the proxy. The deterministic address comes from ContractManagement.GetContractById post-deploy.
B. Single shared dispatcher, scoped storage — one shared "proxy" contract holds N tenants' state in (salt) -> KV namespaces. Cheap (no per-clone deploy fee) but loses per-clone address distinctness. Most factory patterns favour pattern A.
| ERC-1167 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
Clones.clone(impl) | ContractManagement.Deploy(nef, manifest) with impl baked in | Each clone is a real Neo contract; not a runtime DELEGATECALL |
Clones.cloneDeterministic(impl, salt) | Sender-scoped nonce + salt → deploy → Contract.Hash | Determinism comes from sender + nonce, not CREATE2 address algebra |
Clones.predictDeterministicAddress(impl, salt, deployer) | Off-chain compute via Helper.GetContractHash(deployer, nef.checksum, manifest.name) | Same predict-before-deploy capability |
| 45-byte runtime cost | One full Neo contract per clone (kilobytes) | Higher per-clone cost; offset by lower per-call overhead (no DELEGATECALL frame) |
| Storage in the proxy, logic in implementation | Storage in the proxy, logic looked up via Contract.Call(impl, ...) | Same separation; explicit instead of implicit |
Why The Cost Profile Is Different
ERC-1167 is cheap to deploy (45 bytes) but every call pays a full DELEGATECALL frame. On Neo, deploying a parameterised proxy costs more GAS upfront but every method call is a normal Contract.Call — no extra frame. For factories minting many clones with high call rates the Neo profile usually wins; for one-shot identity-clones it loses. Pattern B (shared dispatcher) is the answer when many cheap clones matter.
