ERC-8109 - Diamonds, Simplified

Created 2025-12-21
Status Draft
Category ERC
Type Standards Track
Authors

Abstract

A diamond is a proxy contract that delegatecalls to multiple implementation contracts called facets.

Diagram showing how a diamond contract works

Diamond contracts were originally standardized by ERC-2535. This standard refines that specification by simplifying terminology, reducing the implementation complexity of introspection functions, and standardizing events that are easier for block explorers, indexers, and other tooling to consume.

This standard preserves the full capabilities of diamond contracts while reducing complexity. It also specifies an optional upgrade path for existing ERC-2535 diamonds.

Motivation

Obligatory diamondThrough 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 arbitrarily 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.

Specification

Terms

  1. 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.
  2. 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.
  3. An introspection function is a function that returns information about the facets and functions used by a diamond.
  4. 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.

Diagram showing structure of 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:

error FunctionNotFound(bytes4 _selector);

// Executes function call on facet using `delegatecall`.
// Returns function call return data or revert data.
fallback() external payable {
    // Get facet address from function selector
    address facet = selectorToFacet[msg.sig];
    if (facet == address(0)) {
        revert FunctionNotFound(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.
        let result := 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.
        switch result
        case 0 {revert(0, returndatasize())}
        default {return (0, returndatasize())}
    }
}

Events

Adding/Replacing/Removing Functions

These events are REQUIRED.

For each function selector that is added, replaced, or removed, the corresponding event MUST be emitted.

/**
* @notice Emitted when a function is added to a diamond.
*
* @param _selector The function selector being added.
* @param _facet    The facet address that will handle calls to `_selector`.
*/
event DiamondFunctionAdded(bytes4 indexed _selector, address indexed _facet);

/**
* @notice Emitted when changing the facet that will handle calls to a function.
* 
* @param _selector The function selector being affected.
* @param _oldFacet The facet address previously responsible for `_selector`.
* @param _newFacet The facet address that will now handle calls to `_selector`.
*/
event DiamondFunctionReplaced(
    bytes4 indexed _selector,
    address indexed _oldFacet,
    address indexed _newFacet
);

/**
* @notice Emitted when a function is removed from a diamond.
*
* @param _selector The function selector being removed.
* @param _oldFacet The facet address that previously handled `_selector`.
*/
event DiamondFunctionRemoved(
    bytes4 indexed _selector, 
    address indexed _oldFacet
);

Recording Non-Fallback delegatecalls

When the standard upgradeDiamond function defined in this specification is implemented, it MUST emit DiamondDelegateCall as called for in that function. Otherwise 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.

/**
* @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 _functionCall The function call, including function selector and 
*                      any arguments.
*/
event DiamondDelegateCall(address indexed _delegate, bytes _functionCall);

Diamond Metadata

When the standard upgradeDiamond function defined in this specification is implemented, it MUST emit DiamondMetadata as called for in that function. Otherwise 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.
*/
event DiamondMetadata(bytes32 indexed _tag, bytes _data);

Diamond Upgrades

The following upgrade function is OPTIONAL.

This means two important things:

1. Diamonds Can Be Immutable

2. You Can Create Your Own Upgrade Functions

Instead of, or in addition to the upgrade function specified below, 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 events section, and that the introspection functions defined in the Inspecting Diamonds section continue to accurately return function and facet information.

upgradeDiamond Function

This upgrade function is designed for interoperability with tools, such as GUIs and command line tools, which can be used to perform upgrades on diamonds.

This upgrade function adds/replaces/removes any number of functions from any number of facets in a single transaction. In addition, it can optionally execute a function with delegatecall.

/**
 * @notice The upgradeDiamond function below detects and reverts
 *         with the following errors.
 */
error NoSelectorsProvidedForFacet(address _facet);
error NoBytecodeAtAddress(address _contractAddress);
error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector);
error CannotReplaceFunctionThatDoesNotExist(bytes4 _selector);
error CannotRemoveFunctionThatDoesNotExist(bytes4 _selector);
error CannotReplaceFunctionWithTheSameFacet(bytes4 _selector);
error DelegateCallReverted(address _delegate, bytes _functionCall);

struct FacetFunctions {
    address facet;
    bytes4[] selectors;
}

/**
* @notice Upgrade the diamond by adding, replacing, or removing functions.
*
* @dev
* ### Function Changes:
* - `_addFunctions` maps new selectors to their facet implementations.
* - `_replaceFunctions` updates existing selectors to new facet addresses.
* - `_removeFunctions` removes selectors from the diamond.
*
* Functions are added first, then replaced, then removed.
*
* These events are emitted to record changes to functions:
* - `DiamondFunctionAdded`
* - `DiamondFunctionReplaced`
* - `DiamondFunctionRemoved`
*
* ### `delegatecall`:
* If `_delegate` is non-zero, the diamond performs a `delegatecall` to
* `_delegate` using `_functionCall`. 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.
*
* ### Metadata:
* If _tag is non-zero or if _metadata.length > 0 then the
* `DiamondMetadata` event is emitted.
*
* @param _addFunctions     Selectors to add, grouped by facet.
* @param _replaceFunctions Selectors to replace, grouped by facet.
* @param _removeFunctions  Selectors to remove.
* @param _delegate         Optional contract to `delegatecall` (zero address to skip).
* @param _functionCall     Optional calldata to execute on `_delegate`.
* @param _tag              Optional arbitrary metadata, such as release version.
* @param _metadata         Optional arbitrary data.
*/
function upgradeDiamond(
    FacetFunctions[] calldata _addFunctions,
    FacetFunctions[] calldata _replaceFunctions,
    bytes4[] calldata _removeFunctions,           
    address _delegate,
    bytes calldata _functionCall,
    bytes32 _tag,
    bytes calldata _metadata
) external;

The upgradeDiamond function MUST adhere to the following implementation requirements:

The complete definitions of the events and custom errors referenced below are given earlier in this document.

  1. Function Inputs _addFunctions maps new selectors to their facet implementations. _replaceFunctions updates existing selectors to new facet addresses. _removeFunctions removes selectors from the diamond.
  2. Execution Order
  3. Functions are added first,
  4. then replaced,
  5. then removed.
  6. Function Event Emission
  7. Every change to a function selector MUST be recorded by emitting the corresponding event:
    • DiamondFunctionAdded
    • DiamondFunctionReplaced
    • DiamondFunctionRemoved
  8. Function Error Conditions The following error conditions MUST be detected and execution reverted with the given error:
  9. CannotAddFunctionToDiamondThatAlreadyExists
  10. CannotReplaceFunctionThatDoesNotExist
  11. CannotReplaceFunctionWithTheSameFacet
  12. CannotRemoveFunctionThatDoesNotExist

Implementations are not required to detect duplicate selectors across input arrays beyond enforcing the above error conditions. 5. Facet Validation - If any facet address in _addFunctions or _replaceFunctions contains no contract bytecode, execution MUST revert with NoBytecodeAtAddress. - If a facet address is provided in _addFunctions or _replaceFunctions but no function selectors are given with it, execution MUST revert with NoSelectorsProvidedForFacet. 6. Delegate Validation - If _delegate is non-zero but contains no contract code, then revert with the NoBytecodeAtAddress error. 7. delegatecall Execution - If _delegate is non-zero, the diamond MUST execute _functionCall on _delegate using delegatecall. - 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, the diamond MUST revert with DelegateCallReverted. - If the delegatecall is performed, the diamond MUST emit the DiamondDelegateCall event. - _functionCall MAY be empty. If empty, the delegatecall executes with no calldata. 8. Metadata Event - If _tag is non-zero or _metadata.length > 0, the diamond MUST emit the DiamondMetadata event.

After adding/replacing/removing functions a delegatecall can be done to initialize, modify, or remove state after an upgrade.

It is also possible to call this function just to perform a delegatecall to modify a diamond's state without adding/replacing/removing any functions.

A reference implementation of this function is provided in DiamondUpgradeFacet.sol.

Inspecting Diamonds

Diamond introspection functions return information about what functions and facets are used in a diamond.

These functions MUST be implemented and are required by the standard:

/** @notice Gets the facet that handles the given selector.
 *
 *  @dev If facet is not found return address(0).
 *  @param _functionSelector The function selector.
 *  @return The facet address associated with the function selector.
 */
function facetAddress(bytes4 _functionSelector) external view returns (address);

struct FunctionFacetPair {
    bytes4 selector;
    address facet;
}

/**
* @notice Returns an array of all function selectors and their 
*         corresponding facet addresses.
*
* @dev    Iterates through the diamond's stored selectors and pairs
*         each with its facet.
* @return pairs An array of `FunctionFacetPair` structs, each containing
*         a selector and its facet address.
*/
function functionFacetPairs() external view returns(FunctionFacetPair[] memory pairs);

The essence of a diamond is its function -> facet mapping. functionFacetPairs() returns that mapping as an array of (selector, facet) pairs.

These functions were chosen because they provide all necessary facet and function data about a diamond. They are very simple to implement and are computationally efficient.

Block explorers, GUIs, tests, and other tools may rely on their presence.

A reference implementation exists for these introspection functions here: DiamondInspectFacet.sol

Other introspection functions may be added to a diamond. The above two functions are the only ones required by this standard.

Implementation Requirements

A diamond MUST implement the following to be compliant with this standard:

  1. Diamond Structure

A diamond MUST implement a fallback() function.

  1. Function Association

A diamond MUST associate function selectors with facet addresses.

  1. Function Execution

When an external function is called on a diamond:

The following events MUST be emitted:

A diamond MUST implement the following introspection functions:

receive() function

A diamond MAY have a receive() function.

Immutable Functions

Definition:

An immutable function is an external or public function defined directly in a diamond contract, not in a facet. This definition does not apply to a diamond's constructor or the special fallback() and receive() functions.

A diamond can have zero or more immutable functions.

A diamond with immutable functions has the following additional requirements that MUST be followed:

  1. The DiamondFunctionAdded event MUST be emitted for each immutable function.
  2. Immutable functions MUST be returned by the introspection functions facetAddress(bytes4 _functionSelector) and functionFacetPairs(), where the facet address is the diamond’s own address.
  3. Any upgrade function MUST revert on an attempt to replace or remove an immutable function. The following custom errors MAY be used:
  4. error CannotReplaceImmutableFunction(bytes4 _selector);
  5. error CannotRemoveImmutableFunction(bytes4 _selector);

Rationale

This standard provides standard events, an upgrade function and introspection functions so that GUIs, block explorers, command line programs, and other tools and software can detect and interoperate with diamond contracts.

Software can retrieve function selectors and facet addresses from a diamond in order to use and show what functions a diamond has. Function selectors and facet addresses, combined with contract ABIs and verified source code, provide sufficient information for tooling and user interfaces.

ERC-8109 Diamonds vs ERC-2535 Diamonds

This standard is a simplification and refinement of ERC-2535 Diamonds.

A diamond compliant with ERC-8109 is NOT required to implement ERC-2535.

Here are changes in ERC-8109 Diamonds:

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:

  1. 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.

  2. 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.

  3. 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.

functionFacetPairs() Gas Usage

The functionFacetPairs() function is meant to be called off-chain. At this time major RPC providers have a maximum gas limit of about 550 million gas. Gas benchmark tests show that the functionFacetPairs() function can return 60,000 (selector, facet) pairs using less gas than that.

ERC-8109 implementations are free to add iteration or pagination-based introspection functions, but they are not required by this standard.

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:

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.

Function Signature Limitation

A function signature is the name of a function and its parameter types. Example function signature: myfunction(uint256). A limitation is that two external functions with the same function signature can’t be added to the same diamond at the same time because a diamond, or any contract, cannot have two external functions with the same function signature.

Immutable Functions Considerations

Immutable functions offer minor gas savings by avoiding fallback logic and a delegatecall. However, they introduce a second implementation alongside facets, resulting in two ways to provide similar functionality. This increases implementation complexity and cognitive overhead.

A diamond is simpler to implement and understand without immutable functions.

In upgradeable diamonds, immutable functions reduce flexibility, as they cannot be replaced or removed. This limits the ability to evolve, fix, or improve functionality over time.

Immutable functions that read from or write to storage are not isolated from upgrades. Other functions, including upgrade logic, may modify the same storage relied upon by immutable functions.

Diamond Poetry

Modularity

The brilliance of a diamond emerges from the precise alignment of its distinct facets. Each facet is individually cut, yet together they reflect a unified purpose.

Where monolithic contracts blur responsibility, diamonds draw sharp edges. Each facet reflects a single concern, allowing complexity to be reduced through separation. The result is not fragmentation, but harmony.

A diamond is not defined by excess, but by intention. Every facet exists for a reason, cut precisely to serve the whole. This standard favors deliberate structure over accidental complexity, ensuring that each function has a clear place and purpose.

Immutable Functions

To support flawless architecture, this standard discourages immutable functions. Like inclusions within a diamond, they cloud structural clarity and cannot be polished away.

Immutable Diamonds

An immutable diamond is cut once, with no margin for error. Every facet must be placed with intention, for no edge can be reworked after the stone is set. Its strength comes from certainty, not change.

Immutability improves clarity. When no future changes are possible, the design is complete. Nothing is deferred, and nothing is concealed behind upgrade intent.

An immutable diamond is still modular. Its facets are cut separately, inspected individually, and set only once. Immutability does not remove structure — it freezes a structure already proven.

True permanence is chosen, not imposed. A diamond may be set forever, but only after it has been examined, and graded. This standard preserves the freedom to refine before committing to final form.

Upgrades

Software, like a rough stone, requires shaping. This standard grants the freedom to polish and refine, turning a raw idea into a flawless diamond through the iterative process of upgrades.

A diamond is not perfected in a single strike. It is shaped through measured cuts, evaluated under light, and refined over time. This standard embraces evolution, allowing systems to improve without disturbing what already shines.

Single Contract Address

Behind many facets stands a solitary, enduring interface. It presents a singular, cohesive identity to the world, regardless of the depth of logic beneath the surface.

Composability

Deployed facets may be reused, recombined, and set into new designs. From a shared library of facets, countless diamonds may emerge — distinct in purpose, consistent in form. Reuse strengthens the ecosystem, as proven facets bring their clarity wherever they are set.

Transparency

True value requires transparency. Unlike opaque proxies that hide their functions, this standard ensures the architecture remains crystal clear, granting users an unobstructed view of its composition.

A diamond is judged by how it handles light. This standard ensures the architecture remains pure, allowing the light of public scrutiny to pass through every facet without obstruction or distortion.

Light reveals flaws before it reveals brilliance. By making structure explicit and changes observable, this standard invites scrutiny as a form of strength, not risk. Trust emerges not from obscurity, but from clarity.

Time

Trends fade, tooling changes, but structure endures. A diamond that is well cut today will remain sound tomorrow. This standard aims not for novelty, but for designs that withstand time, upgrades, and inspection.

Backwards Compatibility

Existing, deployed ERC-2535 Diamonds implementations MAY upgrade to this standard by performing an upgrade that does the following:

  1. Removes the existing upgrade function and adds a new upgrade function that uses the new events.
  2. Adds the new functionFacetPairs() introspection function.
  3. Emits a DiamondFunctionAdded event for every function currently in the diamond, including the new upgrade function and the new functionFacetPairs() function.

After this upgrade, the diamond is considered compliant with this standard and SHOULD be indexed and treated as a diamond of this standard going forward.

This upgrade acts as a 'state snapshot'. Indexers only interested in the current state of the diamond can start indexing from this transaction onwards, without needing to parse the legacy DiamondCut history.

To reconstruct the complete upgrade history requires retrieving all the past DiamondCut events as well as all new events defined in this standard.

ERC-2535 Diamonds with Immutable Functions

An ERC-2535 diamond that upgrades to this standard and has immutable functions MUST comply with the Immutable Functions section of the Specification.

If the ERC-2535 diamond upgrade function is immutable, then it can't be removed. If possible, disable the upgrade function by making its authentication always fail.

Reference Implementation

Security Considerations

Ownership and Authentication

The design and implementation of diamond ownership/authentication is not part of this standard.

It is possible to create many different authentication or ownership schemes with diamonds. Authentication schemes can be very simple or complex, fine grained or coarse. This proposal does not limit it in any way. For example ownership/authentication could be as simple as a single account address having the authority to add/replace/remove functions. Or a decentralized autonomous organization could have the authority to add/replace/remove certain functions.

The development of standards and implementations of ownership, control and authentication of diamonds is encouraged.

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.

Function Selector Clash

A function selector clash occurs when two different function signatures hash to the same four-byte hash. This has the unintended consequence of replacing an existing function in a diamond when the intention was to add a new function. This scenario is not possible with a standard upgradeDiamond function because it prevents adding function selectors that already exist.

Transparency

A diamond emits an event every time a function 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

Copyright and related rights waived via CC0.