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.
Motivation
Motivation for Diamond Contracts
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.
Motivation for this Standard
In the past, deploying and upgrading diamonds suffered from:
High gas costs
Function selector management complexity
Deploying or upgrading a diamond requires assembling function selectors off-chain. Since common tooling (e.g., Hardhat, Foundry) does not natively manage diamond selectors, developers rely on custom scripts or third-party libraries to handle diamond “plumbing”.
This standard reduces gas costs and eliminates off-chain selector management:
Diamonds become less expensive to deploy.
Function selectors no longer need to be gathered off-chain.
Standard deployment tools can be used without special diamond support.
ERC-2535 introspection functions have simple implementations.
Specification
Terms
A diamond is a smart contract that routes external function calls to one or more implementation contracts, referred to as facets. A diamond is stateful: all persistent data is stored in the diamond’s contract storage. A diamond implements the requirements in the Implementation Requirements section.
A facet is a smart contract that defines one or more external functions. A facet is deployed independently, and one or more of its functions are added to one or more diamonds. A facet’s functions are executed in the diamond’s context via 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.
An introspection function is a function that returns information about the facets and/or functions used by a diamond or facet.
For the purposes of this specification, a mapping refers to a conceptual association between two items and does not refer to a specific implementation.
Diamond Diagram
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.
Fallback
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:
errorFunctionNotFound(bytes4_selector);// Executes function call on facet using `delegatecall`.
// Returns function call return data or revert data.
fallback()externalpayable{// Get facet address from function selector
addressfacet=selectorToFacet[msg.sig];if(facet==address(0)){revertFunctionNotFound(msg.sig);}// Execute external function on facet using `delegatecall` and return any value.
assembly{// Copy function selector and any arguments from calldata to memory.
calldatacopy(0,0,calldatasize())// Execute function call using the facet.
letresult:=delegatecall(gas(),facet,0,calldatasize(),0,0)// Copy all return data from the previous call into memory.
returndatacopy(0,0,returndatasize())// Return any return value or error back to the caller.
switchresultcase0{revert(0,returndatasize())}default{return(0,returndatasize())}}}
Function Not Found
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).
Inspecting Diamonds
An ERC-8153 diamond MUST implement the same introspection functions as defined in ERC-2535.
Specifically, these functions MUST be implemented:
interfaceIDiamondInspect{structFacet{addressfacetAddress;bytes4[]functionSelectors;}/// @notice Gets all facet addresses and their four byte function selectors.
/// @return facets_ Facet
functionfacets()externalviewreturns(Facet[]memoryfacets_);/// @notice Gets all the function selectors supported by a specific facet.
/// @param _facet The facet address.
/// @return facetFunctionSelectors_
functionfacetFunctionSelectors(address_facet)externalviewreturns(bytes4[]memoryfacetFunctionSelectors_);/// @notice Get all the facet addresses used by a diamond.
/// @return facetAddresses_
functionfacetAddresses()externalviewreturns(address[]memoryfacetAddresses_);/// @notice Gets the facet that supports the given selector.
/// @dev If facet is not found return address(0).
/// @param _functionSelector The function selector.
/// @return facetAddress_ The facet address.
functionfacetAddress(bytes4_functionSelector)externalviewreturns(addressfacetAddress_);}
Typically, these functions are implemented in a facet and the facet is added to diamonds.
Inspecting Facets
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.
Facet-Based Events
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:
/**
* @notice Emitted when a facet is added to a diamond.
* @dev The function selectors this facet handles can be retrieved by calling
* `IFacet(_facet).exportSelectors()`
*
* @param _facet The address of the facet that handles function calls to the diamond.
*/eventFacetAdded(addressindexed_facet);/**
* @notice Emitted when an existing facet is replaced with a new facet.
* @dev
* - Selectors that are present in the new facet but not in the old facet are added to the diamond.
* - Selectors that are present in both the new and old facet are updated to use the new facet.
* - Selectors that are not present in the new facet but are present in the old facet are removed from
* the diamond.
*
* The function selectors handled by these facets can be retrieved by calling:
* - `IFacet(_oldFacet).exportSelectors()`
* - `IFacet(_newFacet).exportSelectors()`
*
* @param _oldFacet The address of the facet that previously handled function calls to the diamond.
* @param _newFacet The address of the facet that now handles function calls to the diamond.
*/eventFacetReplaced(addressindexed_oldFacet,addressindexed_newFacet);/**
* @notice Emitted when a facet is removed from a diamond.
* @dev The function selectors this facet handles can be retrieved by calling
* `IFacet(_facet).exportSelectors()`
*
* @param _facet The address of the facet that previously handled function calls to the diamond.
*/eventFacetRemoved(addressindexed_facet);
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.
Optional Upgrade Events
Recording Non-Fallback delegatecalls
This 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.
/**
* @notice Emitted when a diamond's constructor function or function from a
* facet makes a `delegatecall`.
*
* @param _delegate The contract that was the target of the `delegatecall`.
* @param _delegateCalldata The function call, including function selector and
* any arguments.
*/eventDiamondDelegateCall(addressindexed_delegate,bytes_delegateCalldata);
Diamond Metadata
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.
/**
* @notice Emitted to record information about a diamond.
* @dev This event records any arbitrary metadata.
* The format of `_tag` and `_data` are not specified by the
* standard.
*
* @param _tag Arbitrary metadata, such as a release version.
* @param _data Arbitrary metadata.
*/eventDiamondMetadata(bytes32indexed_tag,bytes_data);
Implementation Requirements
A facet-based diamond MUST implement the following:
Diamond Structure
A fallback() function.
Function Association
It MUST associate function selectors with facet addresses.
Function Execution
When an external function is called on a diamond:
The diamond’s fallback function is executed.
The fallback function MUST find the facet associated with the function selector.
The fallback function MUST execute the function on the facet using delegatecall.
If no facet is associated with the function selector, the diamond MAY execute a default function or apply another handling mechanism.
If no facet, default function, or other handling mechanism exists, execution MUST revert with the error FunctionNotFound(bytes4 _selector).
Events
The following events MUST be emitted:
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.
Diamond Introspection
A diamond MUST implement the following introspection functions:
facets()
facetFunctionSelectors(address _facet)
facetAddresses()
facetAddress(bytes4 _functionSelector)
Facet Introspection
Each facet MUST implement the exportSelectors() function, which returns a bytes array.
receive() function
A diamond MAY have a receive() function.
upgradeDiamond Function
Implementing 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:
Adding a Facet
Call exportSelectors() on the facet to obtain its function selectors.
Add each selector to the diamond, mapping it to the facet address.
Replacing a Facet
Call exportSelectors() on the old facet to obtain its packed selectors.
Call exportSelectors() on the new facet to obtain its packed selectors.
For selectors present in the new facet but not the old facet: add them.
For selectors present in both: replace them to point to the new facet.
For selectors present in the old facet but not the new facet: remove them.
Removing a Facet
Call exportSelectors() on the facet to obtain its packed selectors.
Remove each selector from the diamond.
Errors and Types
/**
* @notice The upgradeDiamond function below detects and reverts
* with the following errors.
*/errorNoSelectorsForFacet(address_facet);errorNoBytecodeAtAddress(address_contractAddress);errorCannotAddFunctionToDiamondThatAlreadyExists(bytes4_selector);errorCannotRemoveFacetThatDoesNotExist(address_facet);errorCannotReplaceFacetWithSameFacet(address_facet);errorFacetToReplaceDoesNotExist(address_oldFacet);errorDelegateCallReverted(address_delegate,bytes_delegateCalldata);errorExportSelectorsCallFailed(address_facet);/**
* @dev This error means that a function to replace exists in a
* facet other than the facet that was given to be replaced.
*/errorCannotReplaceFunctionFromNonReplacementFacet(bytes4_selector);/**
* @notice This struct is used to replace old facets with new facets.
*/structFacetReplacement{addressoldFacet;addressnewFacet;}
Function Signature
/**
* @notice Upgrade the diamond by adding, replacing, or removing facets.
*
* @dev
* Facets are added first, then replaced, then removed.
*
* These events are emitted to record changes to facets:
* - `FacetAdded(address indexed _facet)`
* - `FacetReplaced(address indexed _oldFacet, address indexed _newFacet)`
* - `FacetRemoved(address indexed _facet)`
*
* If `_delegate` is non-zero, the diamond performs a `delegatecall` to
* `_delegate` using `_delegateCalldata`. The `DiamondDelegateCall` event is
* emitted.
*
* The `delegatecall` is done to alter a diamond's state or to
* initialize, modify, or remove state after an upgrade.
*
* However, if `_delegate` is zero, no `delegatecall` is made and no
* `DiamondDelegateCall` event is emitted.
*
* If _tag is non-zero or if _metadata.length > 0 then the
* `DiamondMetadata` event is emitted.
*
* @param _addFacets Facets to add.
* @param _replaceFacets (oldFacet, newFacet) pairs, to replace old with new.
* @param _removeFacets Facets to remove.
* @param _delegate Optional contract to delegatecall (zero address to skip).
* @param _delegateCalldata Optional calldata to execute on `_delegate`.
* @param _tag Optional arbitrary metadata, such as release version.
* @param _metadata Optional arbitrary data.
*/functionupgradeDiamond(address[]calldata_addFacets,FacetReplacement[]calldata_replaceFacets,address[]calldata_removeFacets,address_delegate,bytescalldata_delegateCalldata,bytes32_tag,bytescalldata_metadata);
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
Add facets
Replace facets
Remove facets
Event Emission
Every change to a facet MUST emit exactly one of:
FacetAdded
FacetReplaced
FacetRemoved
Error Conditions
The implementation MUST detect and revert with the specified error when:
Adding a selector that already exists: CannotAddFunctionToDiamondThatAlreadyExists.
Removing a facet that does not exist: CannotRemoveFacetThatDoesNotExist.
Replacing a facet with itself: CannotReplaceFacetWithSameFacet.
Replacing a facet that does not exist: FacetToReplaceDoesNotExist.
Replacing a selector that exists in the diamond but is mapped to a facet different than the facet being replaced: CannotReplaceFunctionFromNonReplacementFacet.
Facet Validation
If any facet address contains no contract bytecode, revert with NoBytecodeAtAddress.
If exportSelectors() is missing, reverts, or cannot be called successfully, revert with ExportSelectorsCallFailed.
If exportSelectors() returns zero selectors, revert with NoSelectorsForFacet.
Delegate Validation
If _delegate is non-zero but contains no bytecode, revert with NoBytecodeAtAddress.
Delegatecall Execution
If _delegate is non-zero, the diamond MUST delegatecall_delegate with _delegateCalldata.
If the delegatecall fails and returns revert data, the diamond MUST revert with the same revert data.
If the delegatecall fails and returns no revert data, revert with DelegateCallReverted.
If a delegatecall is performed, the diamond MUST emit the DiamondDelegateCall event.
_delegateCalldata MAY be empty. If empty, the delegatecall executes with no calldata.
Metadata Event
If _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).
Rationale
Eliminating Selector Management
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.
Reducing Deployment Gas Costs
Reducing Calldata
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.
Reducing Storage
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 Value
exportSelectors() returns bytes rather than bytes4[] for two reasons:
bytes4[] Wastes Memory
Each 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.
Simple Syntax For Facets
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.
Diamond Upgrades
This upgrade function specified by this standard is optional.
This means a couple things:
1. Diamonds Can Be Immutable
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.
2. You Can Create Your Own Upgrade Functions
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.
Runtime Gas Considerations
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.
Storage Layout
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 Sharing Storage & Functionality
Facets are separately deployed, independent units, but can share state and functionality in the following ways:
Facets can share state variables by using the same structs at the same storage positions.
Facets can share internal functions by importing them or inheriting contracts.
On-chain Facets can be Reused and Composed
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.
Backwards Compatibility
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.
Security Considerations
Arbitrary Execution with upgradeDiamond
The upgradeDiamond function allows arbitrary execution with access to the diamond’s storage (through delegatecall). Access to this function must be restricted carefully.
Use Only Trusted and Verified Facets
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.
Upgrade Integrity Checks
The specified upgradeDiamond behavior prevents a number of upgrade mistakes. Upgrades revert when:
A facet is added that already exists in the diamond.
A facet is replaced or removed that does not exist in the diamond.
A selector is added that already exists in the diamond.
A selector is replaced that exists in the diamond but it is from a different facet than the facet being replaced.
A facet address contains no bytecode.
A facet does not implement exportSelectors() successfully.
A facet provides zero selectors.
A facet is replaced with itself (same contract address).
Selector collisions (two different signatures with the same 4-byte selector) are handled as “selector already exists” and are therefore prevented.
Do Not Self Destruct
Use of selfdestruct in a facet is heavily discouraged. Misuse of it can delete a diamond or a facet.
Transparency
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.