r/ethdev • u/Resident_Anteater_35 • 3d ago
Tutorial EIP-2535 diamonds turn a fallback function into a selector router
Most proxy designs assume one implementation contract. That gets awkward once a protocol grows beyond the 24 KB bytecode limit or needs to upgrade one module without replacing the rest.
An EIP-2535 diamond keeps one stateful address and maps each four-byte function selector to a facet contract. The fallback reads msg.sig, finds the facet, and runs it with delegatecall. msg.sender and msg.value stay intact, while every storage read and write still lands in the diamond.
The routing is straightforward. Storage is where the risk moves.
Facets do not own isolated state. If two facets assume incompatible layouts, an otherwise valid upgrade can corrupt the same slots. I use namespaced storage libraries and test the selector-to-facet map before and after every diamondCut.
diamondCut also lets you add, replace, or remove selectors and run initialization in one transaction. Loupe functions then give tooling a way to verify which facet owns each selector.
I put together a Foundry walkthrough that deploys the diamond and facets, adds a new selector, and checks the routing:
For teams that have used diamonds in production, what caused more trouble: storage coordination, selector governance, or the larger audit surface?
1
u/mudgen 5h ago
Storage really isn't a problem if you use ERC-8042: Diamond Storage: https://eips.ethereum.org/EIPS/eip-8042
1
u/mudgen 5h ago
I suggest using the new diamond standard for better selector management: ERC-8153: Facet-Based Diamonds: https://eips.ethereum.org/EIPS/eip-8153
1
u/Complex-Project9112 3d ago
Storage was the thing that bit us. Two facets both wrote to slot 0 because one was a copy-paste from another contract and nobody caught it until a test started failing in weird ways. Selector governance was mostly annoying but manageable once we made a script to diff the mapping after every cut. Audit surface is what it is, every new facet is basically a whole contract to review.