ERC2535 Diamonds vs. LSP17 Contract Extension
Both standards let a deployed contract grow new functions after launch. ERC-2535 Diamonds route calls via delegatecall to facet contracts sharing one storage layout, controlled by a permanent diamondCut admin authority. LSP17 Contract Extension routes unknown function selectors through a fallback to per-selector extension contracts using CALL β never delegatecall β registered under ERC725Y and gated by LSP6, with the base contract's own bytecode immutable either way.
Comparisonβ
| Feature | ERC-2535 Diamonds | LSP17 Contract Extension |
|---|---|---|
| Extension mechanism | delegatecall to a facet, dispatched by selector | CALL to an extension, dispatched by selector β delegatecall is explicitly not used |
| Storage | shared with facets β strict layout discipline required | extension keeps its own separate storage; the base contract's storage can't be corrupted by an extension |
| Registration | diamondCut, called by the diamond owner | setData on ERC725Y, permissioned via LSP6 |
| Upgrade authority over base bytecode | diamond owner can replace any facet, permanently | none β the base contract's own bytecode is immutable either way |
| Which extension handles a selector | diamond owner can repoint any facet, permanently | whoever holds ADDEXTENSIONS/CHANGEEXTENSIONS can add or change an extension mapping β scoped by LSP6, not a single admin lever over the whole contract |
| Permission granularity | one diamondCut function gates everything | per-selector setData grants β independently scoped per extension |
CALL, not DELEGATECALL β a narrower blast radius by constructionβ
Diamonds make the base contract itself a router: every call goes through delegatecall into a facet sharing the diamond's storage, so a malicious or buggy facet can corrupt the diamond's own storage or selfdestruct it outright. Both adding and upgrading behavior go through the same diamondCut function β powerful, but a standing risk: whoever holds diamondCut authority can rewrite what the contract does, permanently, at any time.
LSP17 is stricter by construction. Extensions are invoked with CALL, never delegatecall β an extension runs in its own storage context and can't overwrite the base contract's storage or selfdestruct it, whatever it does. Known functions run as immutable base bytecode that no extension can touch; only unknown selectors hit the fallback. Which extension handles a given selector can still be changed by whoever holds ADDEXTENSIONS/CHANGEEXTENSIONS β that's a real, scoped LSP6 permission, not a claim that extensions can never change β but the base contract's own bytecode has no diamondCut-style lever at all.
The right fit for an account, not just a tokenβ
For a protocol contract that genuinely needs to patch bugs after launch, Diamonds' upgrade lever is the point β and users accept that authority as the cost. For an account contract like a Universal Profile, that tradeoff inverts: users should never have to trust that a single admin key can rewrite an already-defined function on their account. LSP0 uses LSP17 for exactly this reason β a Universal Profile can register a new signature verifier or a new asset receiver as an extension for a selector that didn't exist before, but no key, however permissioned, can touch the base contract's own bytecode or the behavior of a function it already defines.
Extending an account, a wallet, or any contract where the base bytecode needs to stay immutable no matter who holds permissions later β LSP17 can only add handling for previously-undefined selectors, never rewrite one the contract already implements. Reach for Diamonds only when shared storage across facets is a genuine requirement and the permanent diamondCut authority β including the ability to replace already-defined behavior β is an accepted cost.
Related reading: The contract-extension problem Β· ERC721 vs LSP8