Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-8109: Diamonds, Simplified

A simplified diamond architecture for modular smart contract systems.

Authors Nick Mudge (@mudgen)
Created 2025-12-21
Discussion Link https://ethereum-magicians.org/t/erc-8109-diamonds-simplified/27119

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 specific events.

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

Motivation for Diamond Contracts

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

Unlike monolithic contracts, a diamond’s external functions are commonly determined by runtime routing (selector → facet mapping) rather than being wholly represented in the diamond’s source code or bytecode. To accurately determine or display the full set of functionality a diamond has, tooling must rely on standardized introspection functions and events.

Block explorers, indexers, development tools, and user interfaces need a standard way to inspect which functions and facets a diamond possesses. Additionally, tooling can be built to reconstruct and display the full development or upgrade history of diamond contracts using event logs.

ERC-2535 standardized introspection functions and events. ERC-8109 re-standardizes these to make diamond contracts easier to implement and easier to understand.

ERC-8109 improves upon ERC-2535 Diamonds in the following ways:

  1. Simplified terminology.
  2. Fewer and simpler to implement introspection functions.
  3. Replaces a single monolithic event, with per-function events. This makes it easier to implement a variety of functions that add, replace and remove functions in a diamond. It also makes it easier for tools to search for and process events.

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. A diamond implements the requirements in the Implementation Requirements section.
  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())}
    }
}

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

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

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.
*/
event DiamondDelegateCall(address indexed _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.
*/
event DiamondMetadata(bytes32 indexed _tag, bytes _data);

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:

  1. Diamond Structure
    • A diamond MUST implement a fallback() function.
  2. Function Association
    • A diamond MUST associate function selectors with facet addresses.
  3. 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).
  4. Events
    • The following events MUST be emitted:
      • DiamondFunctionAdded — when a function is added to a diamond.
      • DiamondFunctionReplaced — when a function is replaced in a diamond.
      • DiamondFunctionRemoved — when a function is removed from a diamond.
  5. Introspection
    • A diamond MUST implement the following introspection functions:
      • facetAddress(bytes4 _functionSelector)
      • functionFacetPairs()

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.

Rationale

This standard provides standard events 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:

  • Simplified terminology.
  • Simplified introspection functions.
  • Standardized events that are simpler to use and consume.
  • Optional upgrade path for existing ERC-2535 diamonds.

Diamond Upgrades

This standard does not specify an upgrade function.

This means several 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. Other Standards Can Build on ERC-8109

Other standards can build on ERC-8109 by specifying an upgrade function(s), while remaining compliant with this standard.

3. 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 events section, and that the introspection functions defined in the Inspecting Diamonds section continue to accurately return function and facet information.

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:

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

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.

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.

Any other upgrade details are implementation specific.

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

  • The reference implementation for the facetAddress(bytes4 _functionSelector) and functionFacetPairs() introspection functions is here: DiamondInspectFacet.sol
  • An example implementation of an ERC-8109 diamond is here: DiamondExample.sol

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.

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 and related rights waived via CC0.

Citation

Please cite this document as:

Nick Mudge (@mudgen), "ERC-8109: Diamonds, Simplified [DRAFT]," Ethereum Improvement Proposals, no. 8109, December 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-8109.