A diamond is a proxy contract that delegatecalls to multiple implementation contracts called facets.
Diamond contracts were originally standardized by ERC-2535. This ERC builds on that foundation by defining a facet-based architecture in which facets self-describe their function selectors through a standardized introspection interface.
By moving selector discovery on-chain, this approach eliminates the need for off-chain selector management. As a result, diamond deployment and upgrades become simpler, more deterministic, and more gas efficient.
This ERC introduces a facet introspection function, exportSelectors(), which every facet MUST implement. This function returns the list of function selectors implemented by the facet, allowing a diamond to discover and register selectors on-chain during deployment or upgrade.
This ERC also defines facet-based events for adding, replacing, and removing facets.
Additionally, the ERC also defines an optional upgradeDiamond function. This function uses exportSelectors() to automatically determine which selectors should be added, replaced, or removed when applying facet changes.
Through a single contract address, a diamond provides functionality from multiple implementation contracts (facets). Each facet is independent, yet facets can share internal functions and storage. This architecture allows large smart-contract systems to be composed from separate facets and presented as a single contract, simplifying deployment, testing, and integration with other contracts, off-chain software, and user interfaces.
By decomposing large smart contracts into facets, diamonds can reduce complexity and make systems easier to reason about. Distinct areas of functionality can be isolated, organized, tested, and managed independently.
Diamonds combine the single-address convenience of a monolithic contract with the modular flexibility of distinct, integrated contracts.
This architecture is well suited to immutable smart-contract systems, where all functionality is composed from multiple facets at deployment time and permanently fixed thereafter.
For upgradeable systems, diamonds enable incremental development: new functionality can be added, and existing functionality modified, without redeploying unaffected facets.
Additional motivation and background for diamond-based smart-contract systems can be found in ERC-1538 and ERC-2535.
In the past, deploying and upgrading diamonds suffered from:
This standard reduces gas costs and eliminates off-chain selector management:
delegatecall, so reads/writes affect the diamond’s storage. The term facet is derived from the diamond industry, referring to a flat surface of a diamond.This diagram shows the structure of a diamond.
It shows that a diamond has a mapping from function to facet and that facets can access the storage inside a diamond.
When an external function is called on a diamond, its fallback function is executed. The fallback function determines which facet to call based on the first four bytes of the calldata (known as the function selector) and executes the function from the facet using delegatecall.
A diamond’s fallback function and delegatecall enable a diamond to execute a facet’s function as if it was implemented by the diamond itself. The msg.sender and msg.value values do not change and only the diamond’s storage is read and written to.
Here is an example of how a diamond’s fallback function might be implemented:
If the fallback function cannot find a facet for a function selector, and there is no default function or other mechanism to handle the call, the fallback MUST revert with the error FunctionNotFound(bytes4 _selector).
An ERC-8153 diamond MUST implement the same introspection functions as defined in ERC-2535. Specifically, these functions MUST be implemented:
Typically, these functions are implemented in a facet and the facet is added to diamonds.
Each facet MUST implement the following pure introspection function:
exportSelectors() returns a bytes array containing one or more 4-byte function selectors. The returned bytes array length is a multiple of 4, and each 4-byte chunk is a selector. The function MUST not return the same selector more than once.
The bytes array contains selectors of functions implemented by the facet that are intended to be added to a diamond.
This enables a diamond to discover selectors directly from facets at deployment or upgrade time. A diamond calls exportSelectors() on each facet to determine which selectors to add, replace, or remove.
Selector gathering is therefore no longer an off-chain responsibility.
This also means diamonds implementing this ERC are facet-based rather than function-based. Deployment and upgrades operate on facets.
This ERC replaces ERC-2535’s function-based events with facet-based events.
When facets are added, replaced, or removed, the diamond MUST emit the following events:
Block explorers and other tooling can obtain the function selectors for any of the facets referenced by these events by calling exportSelectors() on the facet address.
delegatecallsThis event is OPTIONAL.
This event can be used to record delegatecalls made by a diamond.
This event MUST NOT be emitted for delegatecalls made by a diamond’s fallback function when routing calls to facets. It is only intended for delegatecalls made by functions in facets or a diamond’s constructor.
This event enables tracking of changes to a diamond’s contract storage caused by delegatecall execution.
This event is OPTIONAL.
This event can be used to record versioning or other information about diamonds.
It can be used to record information about diamond upgrades.
A facet-based diamond MUST implement the following:
fallback() function.delegatecall.FunctionNotFound(bytes4 _selector).FacetAdded — when a facet is added to a diamond.FacetReplaced — when a facet is replaced with a different facet.FacetRemoved — when a facet is removed from a diamond.facets()facetFunctionSelectors(address _facet)facetAddresses()facetAddress(bytes4 _functionSelector)exportSelectors() function, which returns a bytes array.receive() functionA diamond MAY have a receive() function.
upgradeDiamond FunctionImplementing upgradeDiamond is OPTIONAL.
This function is specified for interoperability with tooling (e.g., GUIs and command-line tools) so that upgrades can be executed with consistent and predictable behavior.
upgradeDiamond adds, replaces, and removes any number of facets in a single transaction. It can also optionally execute a delegatecall to perform initialization or state migration.
The upgradeDiamond function works as follows:
exportSelectors() on the facet to obtain its function selectors.exportSelectors() on the old facet to obtain its packed selectors.exportSelectors() on the new facet to obtain its packed selectors.exportSelectors() on the facet to obtain its packed selectors.The upgradeDiamond function MUST adhere to the following requirements:
The complete definitions of events and custom errors referenced below are given earlier in this standard.
Inputs
_addFacets array of facet addresses to add._replaceFacets array of (oldFacet, newFacet) pairs._removeFacets array of facet addresses to remove.Execution Order
Event Emission
FacetAddedFacetReplacedFacetRemovedError Conditions
CannotAddFunctionToDiamondThatAlreadyExists.CannotRemoveFacetThatDoesNotExist.CannotReplaceFacetWithSameFacet.FacetToReplaceDoesNotExist.CannotReplaceFunctionFromNonReplacementFacet.Facet Validation
NoBytecodeAtAddress.exportSelectors() is missing, reverts, or cannot be called successfully, revert with ExportSelectorsCallFailed.exportSelectors() returns zero selectors, revert with NoSelectorsForFacet.Delegate Validation
_delegate is non-zero but contains no bytecode, revert with NoBytecodeAtAddress.Delegatecall Execution
_delegate is non-zero, the diamond MUST delegatecall _delegate with _delegateCalldata.delegatecall fails and returns revert data, the diamond MUST revert with the same revert data.delegatecall fails and returns no revert data, revert with DelegateCallReverted.delegatecall is performed, the diamond MUST emit the DiamondDelegateCall event._delegateCalldata MAY be empty. If empty, the delegatecall executes with no calldata.Metadata Event
_tag is non-zero or _metadata.length > 0, the diamond MUST emit the DiamondMetadata event.After adding, replacing, or removing facets, the diamond MAY perform a delegatecall to initialize, migrate, or clean up state.
It is also valid to call upgradeDiamond solely to perform a delegatecall (i.e., without adding, replacing, or removing any facets).
To skip an operation, supply an empty array for its parameter (for example, new address[](0) for _addFacets).
To deploy a facet-based diamond implementing this ERC, the deployer provides an array of facet addresses to the diamond constructor. The constructor calls exportSelectors() on each facet and registers those selectors in the diamond.
Because facets self-describe their selectors, deployers no longer need to gather selectors off-chain or depend on specialized selector tooling.
In a non-facet-based diamond, selectors are typically passed to the constructor as one or more bytes4[] arrays. These arrays are paid for in calldata and then copied into memory, incurring additional gas.
In a facet-based diamond, only facet addresses are passed to constructors. The diamond calls exportSelectors() on each facet to obtain selectors on-chain, avoiding calldata costs for selector lists. While calling exportSelectors() introduces some overhead, non-facet-based diamonds typically perform code-existence checks (e.g., extcodesize) on facet addresses anyway, incurring the cold account access gas cost.
In a non-facet-based diamond, function selectors are stored directly for introspection, typically in a bytes4[] selectors array (or an equivalent structure). Because a storage slot is 32 bytes, each slot can hold up to eight bytes4 selectors. As more functions are added, additional storage slots are required, so storage usage grows linearly with the number of selectors.
In a facet-based diamond, introspection data can be stored per facet instead of per function. Each facet only needs a single representative selector. This means one 32-byte storage slot can represent up to eight facets, regardless of how many function selectors each facet implements. Storage usage therefore grows with the number of facets, not the number of functions.
Alternatively, a facet-based diamond can be implemented as a linked list of facets. With this design, introspection requires a single 32-byte storage slot, while supporting any number of facets and any number of selectors per facet.
exportSelectors() Function Return ValueexportSelectors() returns bytes rather than bytes4[] for two reasons:
bytes4[] Wastes MemoryEach element of a bytes4[] array occupies 32 bytes in memory, but only 4 bytes are meaningful. This wastes 87.5% of allocated memory, increasing gas costs. Packing selectors into bytes reduces memory overhead.
Facets can implement exportSelectors() concisely using Solidity's built-in function bytes.concat. Example:
The diamond can traverse the returned bytes and extract selectors efficiently.
This upgrade function specified by this standard is optional.
This means a couple things:
A Diamond does not have to have an upgrade function.
A diamond can be fully constructed within its constructor function without adding any upgrade function, making it immutable upon deployment.
A large immutable diamond can be built using well organized facets.
A diamond can initially be upgradeable, and later made immutable by removing its upgrade function.
You can design and create your own upgrade functions and remain compliant with this standard. All that is required is that you emit the appropriate add/replace/remove required events specified in the Facet-Based Events section, that the introspection functions defined in the Inspecting Diamonds section and the Inspecting Facets section continue to exists and accurately return function and facet information.
Routing calls via delegatecall introduces a small amount of gas overhead. In practice, this cost is mitigated by several architectural and tooling advantages enabled by diamonds:
Optional, gas-optimized functionality
By structuring functionality across multiple facets, diamonds make it straightforward to include specialized, gas-optimized features without increasing the complexity of core logic.
For example, an ERC-721 diamond may implement batch transfer functions in a dedicated facet, improving both gas efficiency and usability while keeping the base ERC-721 implementation simple and well-scoped.
Reduced external call overhead
Some contract architectures require multiple external calls within a single transaction. By consolidating related functionality behind a single diamond address, these interactions can execute internally with shared storage and shared authorization, reducing gas costs from external calls and repeated access-control checks.
Selective optimization per facet
Because facets are compiled and deployed independently, they may be built with different compiler optimizer settings. This allows gas-critical facets to use aggressive optimization configurations to reduce execution costs, without increasing bytecode size or compilation complexity for unrelated functionality.
Diamonds and facets need to use a storage layout organizational pattern because Solidity’s default storage layout doesn’t support proxy contracts or diamonds. The storage layout technique or pattern to use is not specified in this ERC. However, examples of storage layout patterns that work with diamonds are ERC-8042 Diamond Storage and ERC-7201 Namespaced Storage Layout.
Facets are separately deployed, independent units, but can share state and functionality in the following ways:
A deployed facet can be used by many diamonds.
It is possible to create and deploy a set of facets that are reused by different diamonds.
The ability to use the same deployed facets for many diamonds has the potential to reduce development time, increase reliability and security, and reduce deployment costs.
It is possible to implement facets in a way that makes them usable/composable/compatible with other facets.
Diamonds implementing this ERC have the same introspection functions as ERC-2535 diamonds, so they are compatible with ERC-2535 tooling that rely on these functions.
upgradeDiamondThe upgradeDiamond function allows arbitrary execution with access to the diamond’s storage (through delegatecall). Access to this function must be restricted carefully.
Only trusted and verified facets should be added to facet-based diamonds.
exportSelectors() MUST be pure and should not contain logic that varies the returned bytes. Facets should be immutable so returned selectors cannot change over time.
If a facet’s exportSelectors() output changes, upgrades that rely on it may add/remove/replace the wrong selectors and corrupt diamonds.
The specified upgradeDiamond behavior prevents a number of upgrade mistakes. Upgrades revert when:
exportSelectors() successfully.Selector collisions (two different signatures with the same 4-byte selector) are handled as “selector already exists” and are therefore prevented.
Use of selfdestruct in a facet is heavily discouraged. Misuse of it can delete a diamond or a facet.
A diamond emits an event every time a facet is added, replaced or removed. Source code can be verified. This enables people and software to monitor changes to a diamond.
Security and domain experts can review a diamond's upgrade history.
Copyright and related rights waived via CC0.