← Protocols
ERC-2535 — Diamonds, Multi-Facet Proxy
Standard / EIP·EVM

ERC-2535 — Diamonds, Multi-Facet Proxy

01Description

Final ERC for modular, upgradeable contracts composed of multiple facets behind one address. Routes selectors to facet implementations via `diamondCut`, with optional `IDiamondLoupe` introspection — useful when contract size exceeds the 24 KB Spurious Dragon limit.

02Best for
  • 01very large contract systems
  • 02modular per-feature upgrades
  • 03shared facets across protocols
  • 04Aavegotchi / GMX-style architectures
03Install
  • git clone https://github.com/mudgen/diamond-3-hardhat
  • forge install mudgen/diamond-1-hardhat
  • pnpm add @solidstate/contracts
05Prompt snippet
Build the proxy against `IDiamondCut` (`diamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata)` with `Add | Replace | Remove`) and `IDiamondLoupe` (`facets()`, `facetFunctionSelectors()`, `facetAddresses()`, `facetAddress(bytes4)`). Use the Diamond Storage pattern: each facet keeps state in a struct stored at a unique deterministic slot via `keccak256('namespace.storage')` — never use sequential storage slots. Emit `DiamondCut(FacetCut[], address, bytes)` on every change. The canonical references are Nick Mudge's `diamond-1`/`diamond-3` repos; SolidState provides a typed alternative.
06Gotchas
  • Storage collisions: facets share the diamond's storage, so two facets that declare overlapping state variables in their own contract storage will silently corrupt each other — always use Diamond Storage / AppStorage with namespaced struct slots.
  • Selector clashes are common — adding two facets that both export `pause()` will revert the cut. Run a selector-collision check (e.g. via `diamond-1`'s `LibDiamond.diamondCut` or louper) before every upgrade.
  • Removing a facet that other facets `delegatecall` or rely on (e.g. ownership, access control) can brick the diamond — keep an emergency `OwnershipFacet` and `DiamondCutFacet` always present.
  • Initialization runs via `delegatecall` to the `_init` address with `_calldata` during `diamondCut` — failing inits revert the entire cut, but partially-initialized state from a prior cut can persist.
  • Block explorers and verification tooling are weaker for diamonds than for single-implementation proxies — verify each facet separately and consider Louper / Diamond Inspector for off-chain visibility.
  • The 24 KB EIP-170 limit applies per facet, not to the diamond as a whole — splitting facets too coarsely defeats the point.
07Alternatives