EIP-2535: Diamond Standard Source

AuthorNick Mudge
Discussions-Tohttps://github.com/ethereum/EIPs/issues/2535
StatusDraft
TypeStandards Track
CategoryERC
Created2020-02-22

Simple Summary

A new smart contract paradigm that works today.

A diamond is a contract with functionality from multiple contracts that can share internal functions, libraries and state variables.

Diamonds are powerful, organized, modular, optionally upgradeable, flexible, unlimited in size, and transparent.

Diamonds enable you to build efficient, powerful, flexible, modular smart contract systems.

A diamond is a contract that implements the Specification in this standard.

Terminology from the diamond industry.

What is a Diamond?

A diamond is a contract with external functions that are supplied by contracts called facets.

Facets are separate, independent contracts that can share internal functions, libraries and state variables.

How a Diamond Works

A diamond stores within it a mapping of function selector to facet address, for example selectorToFacet.

When an external function is called on a diamond its fallback function is executed. The fallback function finds in the selectorToFacet mapping which facet has the function that has been called and then executes that function from the facet using delegatecall.

A diamond’s fallback function and delegatecall enable a diamond to execute a facet’s external function as its own external function. The msg.sender and msg.value values do not change and only the diamond’s contract storage is read and written to.

Here is a simple example of a diamond’s fallback function:

// Find facet for function that is called and execute the
// function if a facet is found and return any value.
fallback() external payable {  
  address facet = selectorTofacet[msg.sig];  
  require(facet != address(0));
  // Execute external function from facet using delegatecall and return any value.
  assembly {    
    calldatacopy(0, 0, calldatasize())
    let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
    returndatacopy(0, 0, returndatasize())
    switch result
      case 0 {revert(0, returndatasize())}
      default {return (0, returndatasize())}
  }
}   

A diamond can use a diamondCut function to add/replace/remove any number of functions from any number of facets in a single transaction. diamondCut updates the mapping of function selector to facet address. Other such functions can be used.

An event is emitted any time external functions are added, replaced or removed to report what changed.

A diamond has four standard external functions that can be called to show what facets and functions it currently has.

Upgrades and Immutability

A diamond can be immutable from inception by not adding any external functions that can add/replace/remove functions. There are number of reasons to do this.

A diamond that is mutable can be made immutable by removing such functions.

A diamond can be upgraded if it has a diamondCut external function or other function(s) that can add/replace/remove functions.

Facets and State Variables

A facet defines external functions and can define or use internal functions, libraries, and state variables.

A facet can declare and use state variables but it does it in a way that is safer, easier and more flexible for diamonds.

Facet state variables are declared in structs. Each struct is given a specific position in contract storage. This technique is called Diamond Storage.

Here is a simple example that shows diamond storage and its use in a facet:

// A contract that implements diamond storage.
// This contract is inherited and used by facets.
contract DiamondStorageContract {
  
  // This struct contains state variables we care about.
  struct DiamondStorage {
    address owner;
    bytes32 dataA;
  }

  // Returns the struct from a specified position in contract storage
  // ds is short for DiamondStorage
  function diamondStorage() internal pure returns(DiamondStorage storage ds) {    
    // Specifies a random position from a hash of a string    
    bytes32 storagePosition = keccak256("diamond.storage")
    // Set the position of our struct in contract storage    
    assembly {ds.slot := storagePosition}
  }
}

// Our facet inherits and uses the diamond storage defined above.
contract FacetA is DiamondStorageContract {

  function setDataA(bytes32 _dataA) external {
    DiamondStorage storage ds = diamondStorage();
    require(ds.owner == msg.sender, "Must be owner.");
    ds.dataA = _dataA
  }
    
  function getDataA() external view returns (bytes32) {
    return diamondStorage().dataA
  }
}

Any number of structs, each with a different storage position, can be used by a facet.

By using diamond storage facets can declare their own state variables that do not conflict with the storage locations of state variables declared in other facets.

By using diamond storage facets can be developed independently, without connection or concern for other facets.

Solidity libraries can also use diamond storage.

Facets Can Share State and Functionality

Facets can share state variables by using the same structs at the same storage positions.

Facets can share internal functions and libraries by inheriting the same contracts or using the same libraries.

In these ways facets are separate, independent units but can share state and functionality.

Facets are Reusable and Composable

A deployed facet can be used by any number of diamonds.

Different combinations of facets can be used with different diamonds.

It is possible to create and deploy a set of facets that are reused by different diamonds over time.

The ability to use the same deployed facets for many diamonds reduces deployment costs.

A limitation is that two external functions with the same function signature can’t be added to a diamond at the same time because a diamond, or any contract, cannot have two external functions with the same function signature.

Diagrams

Diamond Structure

This diagram shows the structure of a diamond:

Diamond Storage

The diagram below shows facets with their own data and data shared between them.

Notice that all data is stored in the diamond. But different facets have different access to data.

In this diagram

  • Only FacetA can access DataA
  • Only FacetB can access DataB
  • Only the diamond’s own code can access DataD.
  • FacetA and FacetB share access to DataAB.
  • The diamond’s own code, FacetA and FacetB share access to DataABD.

Deployed Facets Can Be Reused

A deployed facet can be used by any number of diamonds.

The diagram below shows two diamonds using the same two facets.

  • FacetA is used by Diamond1
  • FacetA is used by Diamond2
  • FacetB is used by Diamond1
  • FacetB is used by Diamond2

Some Diamond Benefits

  1. A stable contract address that provides needed functionality.
  2. A single address with the functionality of multiple contracts (facets) that are independent from each other but can share internal functions, libraries and state variables.
  3. A way to add, replace and remove multiple external functions atomically (in the same transaction).
  4. Fine-grained upgrades, so you can change just the parts of a diamond that need to be changed.
  5. Have greater control over when and what functions exist.
  6. Decentralized Autonomous Organizations (DAOs) and other governance systems can be used to upgrade diamonds.
  7. An event that shows what functions are added, replaced and removed.
  8. The ability to show all changes made to a diamond.
  9. Increase trust over time by showing all changes made to a diamond.
  10. A way to look at a diamond to see its current facets and functions.
  11. Have an immutable, trustless diamond.
  12. Solves the 24KB maximum contract size limitation. Diamonds can be any size.
  13. Separate functionality can be implemented in separate facets and used together in a diamond.
  14. Larger contracts have to reduce their size by removing error messages and other things. You can keep your error messages and the full functionality that you need by implementing a diamond.
  15. Enables zero, partial or full diamond immutability as desired, and when desired.
  16. The ability to develop and improve an application over time with an upgradeable diamond and then make it immutable and trustless if desired.
  17. Develop incrementally and let your diamond grow with your application.
  18. Upgrade diamonds to fix bugs, add functionality and implement new standards.
  19. Organize your code with a diamond and facets.
  20. Diamonds can be large (have many functions) but still be modular because they are compartmented with facets.
  21. Contract architectures that call multiple contracts in a single transaction can save gas by condensing those contracts into a single diamond and accessing state variables directly.
  22. Save gas by creating external functions for specific use cases, such as bulk transfers.
  23. Diamonds are designed for tooling and user-interface software.

New User-Interface Software & Libraries

User-interface software can be written to show all documentation, functions and source code used by a diamond.

Diamond events can be filtered from the Ethereum blockchain to show all changes to a diamond.

Existing and new programming libraries and software can be used to deploy, show, upgrade and use diamonds.

Upgradeable Diamond vs. Centralized Private Database

Why have an upgradeable diamond instead of a centralized, private, mutable database?

  1. Decentralized Autonomous Organizations (DAOs) and other governance systems can be used to upgrade diamonds.
  2. Wide interaction and integration with the Ethereum ecosystem.
  3. With open storage data and verified source code it is possible to show a provable history of trustworthiness.
  4. With openness bad behavior can be spotted and reported when it happens.
  5. Independent security and domain experts can review the change history of contracts and vouch for their history of trustworthiness.
  6. It is possible for an upgradeable diamond to become immutable and trustless.

Different Kinds of Diamonds

Many designs of diamonds are possible. Here are a few kinds of diamonds and their uses.

Upgradeable Diamond An upgradeable diamond has the diamondCut function and/or possibly other functions to add/replace/remove functions. It is useful for iterative development or improving an application over time.

Finished Diamond A finished diamond was an upgradeable diamond and had a number of upgrades. Then its diamondCut function and/or other upgrade functions were removed and upgrades are no longer possible. It is no longer possible to add/replace/remove functions. It has become an immutable diamond.

Single Cut Diamond A single cut diamond adds all functions to itself in its constructor function, but it does not add the diamondCut function or any other function that can add/replace/remove functions. This means that a single cut diamond is fully created in its constructor and once created can never be upgraded. It has the same immutability and trustless guarantees as a regular vanilla contract. Why would someone do this? There may be a number of reasons. The two use cases below are good reasons.

  1. Your contract hits the max contract size limit. Make it into a single cut diamond. You still break your big contract into smaller facets, modularizing your code.
  2. You start with an upgradeable diamond in your development and testing and upgrade it to your heart’s delight. Reap the advantages of easy upgrading and a stable address as you work out new features, bugs and kinks. Release the upgradeable diamond on a test network with your application for beta testing and upgrade it when needed. This is iterative development. When it is solid then deploy it as a single cut diamond on the main network.

Specification

Note: The solidity delegatecall opcode enables a contract to execute a function from another contract, but it is executed as if the function was from the calling contract. Essentially delegatecall enables a contract to “borrow” another contract’s function. Functions executed with delegatecall affect the contract storage of the calling contract, not the contract where the functions are defined.

Note: This specification specifies what needs to be implemented for a contract to be a diamond.

Terms

  1. A diamond is a contract that uses functions from its facets to execute function calls. A diamond can have one or more facets.
  2. The word facet comes from the diamond industry. It is a side, or flat surface of a diamond. A diamond can have many facets. In this standard a facet is a contract with one or more functions that executes functionality of a diamond.
  3. A loupe is a magnifying glass that is used to look at diamonds. In this standard a loupe is a facet that provides functions to look at a diamond and its facets.
  4. An immutable function is a function that is defined directly in a diamond and so cannot be replaced or removed. Or it is a function that is defined in a facet that cannot be replaced or removed because all upgrade functions have been removed from a diamond.

General Summary

A diamond calls functions from its facets using delegatecall.

In the diamond industry diamonds are created and shaped by being cut, creating facets. In this standard diamonds are cut by adding, replacing or removing facets and their functions.

The diamondCut Function

The standard diamondCut function specified below can be used to add/replace/remove any number of functions from/to a diamond in a single transaction. It has been designed for maximum flexibility while using the least amount of gas.

The standard diamondCut function below is specified for the purpose of interoperability. Diamond tools, software and user-interfaces should expect and use the standard diamondCut function. Diamonds that might work with diamond specific tooling to add/replace/remove functions should implement the standard diamondCut function.

The diamondCut function is optional. It does not have to be implemented in a diamond. For example an immutable diamond wouldn’t have this function.

You can implement your own custom functions that add or replace or remove functions. You can also implement your own non-standard versions of diamondCut that have different parameters.

If you want to create your own custom function(s) for adding/replacing/removing functions you might also want to implement the standard diamondCut function for interoperability with tools.

In ALL cases any function or code that adds or replaces or removes one or more functions MUST emit the standard DiamondCut event specified below.

The DiamondCut event records all changes to a diamond.

Diamond Interface

interface IDiamondCut {
    struct Facet {
        address facetAddress;
        bytes4[] functionSelectors;
    }

    /// @notice Add/replace/remove any number of functions and optionally execute
    ///         a function with delegatecall
    /// @param _diamondCut Contains the facet addresses and function selectors
    /// @param _init The address of the contract or facet to execute _calldata
    /// @param _calldata A function call, including function selector and arguments
    ///                  _calldata is executed with delegatecall on _init
    function diamondCut(
        Facet[] calldata _diamondCut,
        address _init,
        bytes calldata _calldata
    ) external;

    event DiamondCut(Facet[] _diamondCut, address _init, bytes _calldata);
}

Adding/Replacing/Removing Functions

A diamond contains within it a mapping of function selectors to facet addresses. Functions are added/replaced/removed by modifying this mapping.

The _diamondCut argument is an array of Facet structs.

Each Facet struct contains a facet address and an array of one or more function selectors. The mapping in a diamond is updated by each Facets struct. The selectors in a Facet struct are associated with the facet address in the same Facet struct. This is how functions are added or replaced.

However if a facet address in a Facet struct is address(0) then the function selectors in the same Facet struct are removed. This is how functions are removed from a diamond.

The diamondCut function does nothing if a supplied function selector is already associated with the supplied facet address. The diamondCut function does nothing if a supplied function selector can’t be removed because it already does not exist.

Executing _calldata

After adding/replacing/removing functions the _calldata argument is executed with delegatecall on _init. This execution is done to initialize data or setup or remove anything needed or no longer needed after adding, replacing and/or removing functions.

If the _init value is address(0) then _calldata must contain 0 bytes or the transaction reverts.

If the _init value is not address(0) then _calldata must contain more than 0 bytes or the transaction reverts.

If _init is address(0) and _calldata contains 0 bytes then _calldata execution is skipped. _calldata is not executed and the diamondCut call can complete successfully.

DiamondCut Event

The _diamondCut, _init, and _calldata arguments are passed directly to the DiamondCut event.

Any time one or more functions are added, replaced or removed the DiamondCut event MUST be emitted to record changes.

Diamond Loupe

A loupe is a small magnifying glass used to look at diamonds. These functions look at diamonds.

The function selectors used by a diamond are queried to get what functions the diamond has and what facets are used.

A diamond loupe is a facet that implements this interface:

// A loupe is a small magnifying glass used to look at diamonds.
// These functions look at diamonds
interface IDiamondLoupe {
    /// These functions are expected to be called frequently
    /// by tools.

    struct Facet {
        address facetAddress;
        bytes4[] functionSelectors;
    }

    /// @notice Gets all facet addresses and their four byte function selectors.
    /// @return facets_ Facet
    function facets() external view returns (Facet[] memory facets_);

    /// @notice Gets all the function selectors supported by a specific facet.
    /// @param _facet The facet address.
    /// @return facetFunctionSelectors_
    function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);

    /// @notice Get all the facet addresses used by a diamond.
    /// @return facetAddresses_
    function facetAddresses() external view returns (address[] memory facetAddresses_);

    /// @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.
    function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}

See the reference implementation to see an example of how this can be implemented.

The loupe functions are designed for user-interface software. A user interface calls these functions to provide information about and visualize diamonds.

In some implementations the loupe functions may not be gas efficient and so should not be called in on-chain transactions. In some implementations the loupe functions may be gas efficient and can be called in on-chain transactions.

Implementation Points

A diamond implements the following implementation points:

  1. A diamond contains a fallback function and zero or more immutable functions that are defined directly within it.
  2. A diamond associates function selectors with facets.
  3. When a function is called on a diamond it executes immediately if it is an “immutable function” defined directly in the diamond. Otherwise the diamond’s fallback function is executed. The fallback function finds the facet associated with the function and executes the function using delegatecall. If there is no facet for the function and no other mechanism to handle it then execution reverts.
  4. Each time functions are added, replaced or removed a DiamondCut event is emitted to record it.
  5. A diamond implements the DiamondLoupe interface.
  6. A diamond implements ERC165. If a diamond has the diamondCut function then the interface ID used for it is IDiamondCut.diamondCut.selector. The interface ID used for the diamond loupe interface is IDiamondLoupe.facets.selector ^ IDiamondLoupe.facetFunctionSelectors.selector ^ IDiamondLoupe.facetAddresses.selector ^ IDiamondLoupe.facetAddress.selector.

The diamond address is the address that users interact with. The diamond address does not change. Only facet addresses can change by using the diamondCut function, or other function.

Implementation

A reference implementation is given in the Diamond repository. Use that implementation to get started implementing diamonds.

Rationale

Using Function Selectors

User interface software can be used to retrieve function selectors and face addresses from a diamond in order show what functions a diamond has.

This standard is designed to make diamonds work well with user-interface software. Function selectors with the ABI of a contract provide enough information about functions to be useful for user-interface software.

Gas Considerations

Delegating function calls does have some gas overhead. This is mitigated in several ways:

  1. Because diamonds do not have a max size limitation it is possible to add gas optimizing functions for use cases. For example someone could use a diamond to implement the ERC721 standard and implement batch transfer functions from the ERC1412 standard to reduce gas (and make batch transfers more convenient).
  2. Some contract architectures require calling many contracts in one transaction. Gas savings can be realized by condensing those contracts into a single diamond and accessing contract storage directly.
  3. Facets can be small, reducing gas costs. Because it costs more gas to call a function in a contract with many functions than a contract with few functions.

  4. The Solidity optimizer can be set to a high setting causing more bytecode to be generated but the facets will use less gas when executed.

Diamond Storage

Since Solidity 0.6.4 it is possible to create pointers to structs in arbitrary places in contract storage. This enables diamonds and their facets to create their own storage layouts that are separate from each other and do not conflict with each other, but can still be shared between them. See this blog post for more information: New Storage Layout For Proxy Contracts and Diamonds. The reference implementation for the diamond standard uses diamond storage.

Diamond storage is not the same thing as unstructured storage. Unstructured storage reads and writes specific values like unsigned integers and addresses at specified locations in contract storage. Diamond storage uses structs at specified locations in contract storage for reading and writing. Structs can hold any number of state variables of any type.

Versions of Functions

Software or a user can verify what version of a function is called by getting the facet address of the function. This can be done by calling the facetAddress function from the DiamondLoupe interface. This function takes a function selector as an argument and returns the facet address where it is implemented.

Sharing Functions Between Facets

In some cases it might be necessary to call a function defined in a different facet. Here are some solutions to this:

  1. Copy internal function code in one facet to the other facet.
  2. Put common internal functions in a contract that is inherited by multiple facets.
  3. Put common internal functions in a Solidity library and use the library in facets.
  4. Use delegatecall to call external functions defined in other facets. Here is an example of doing that:
    DiamondStorage storage ds = diamondStorage();
    bytes4 functionSelector = bytes4(keccak256("myFunction(uint256)"));
    // get facet address of function
    address facet = ds.selectorToFacet[functionSelector];
    bytes memory myFunctionCall = abi.encodeWithSelector(functionSelector, 4);
    (bool success, uint result) = address(facet).delegatecall(myFunctionCall);
    require(success, "myFunction failed");
    
  5. Instead of using delegatecall to call an external function defined in another facet you can instead create an internal function version of the external function and use that.

Security Considerations

Ownership and Authentication

Note: The design and implementation of diamond ownership/authentication is not part of this standard. The examples given in this standard and in the reference implementation are just examples of how it could be done.

It is possible to create many different authentication or ownership schemes with the diamond standard. Authentication schemes can be very simple or complex, fine grained or coarse. The diamond standard 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 only add/replace/remove certain functions.

Consensus functionality could be implemented such as an approval function that multiple different people call to approve changes before they are executed with the diamondCut function. These are just examples.

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

Security of Diamond Storage

If a person can add/replace functions then that person can alter storage willy-nilly. This is very powerful and very dangerous. However the capability can be used while eliminating or reducing the danger. The danger is eliminated or reduced by limiting who can add/replace/remove functions, limiting when functions can be added/replaced/removed and by transparency.

Who Here are some ways who can be limited:

  1. Only allow a trusted individual or organization to make diamond upgrades.
  2. Only allow a distributed autonomous organization to make diamond upgrades.
  3. Only allow multi-signature upgrades.
  4. Only allow the end user who owns his own diamond to make upgrades. This enables users to opt-in to upgrades.
  5. Don’t allow anybody to make upgrades by making a single cut diamond.

When Here are some ways when can be limited:

  1. Only allow upgrades during development and testing. Make a single cut diamond for main network release.
  2. Use an upgradeable diamond until it is certain that no new features are needed and then make it a finished diamond by removing the ability to add/replace/remove functions.
  3. Program into the diamondCut function certain periods of time that the diamond can be upgraded. For example the diamondCut function could be programmed so that a diamond could only be upgraded during a specific five hour period each year. Attention and transparency could be applied to that five hour period to ensure upgrades are done right.

Transparency Transparency provides certainty and proof that upgrades are done correctly and honestly.

  1. Publish and make available verified source code used by diamonds and facets.
  2. Provide documentation for diamonds, facets, upgrade plans, and results of upgrades.
  3. Provide tools and/or user interfaces that make your diamonds more visible and understandable.

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. Function selector clashes are very rare but should be prevented by using tools that check for function selector clashes before calling the diamondCut function or similar function.

Transparency

Diamonds emit an event every time one or more functions are added, replaced or removed. All source code can be verified. This enables people and software to monitor changes to a contract. If any bad acting function is added to a diamond then it can be seen.

Security and domain experts can review the history of change of a diamond to detect any history of foul play.

Backwards Compatibility

This standard makes upgradeable diamonds compatible with future standards and functionality because new functions can be added and existing functions can be replaced or removed.

Learning & References

Diamond Articles

Ethereum’s Maximum Contract Size Limit is Solved with the Diamond Standard

Understanding Diamonds

New Storage Layout For Proxy Contracts and Diamonds

The Diamond Standard: A new paradigm for upgradeability

Upgradeable smart contracts using the Diamond Standard

Diamond Tools

buidler-deploy

Diamond Setter

Implementations

Diamond Reference Implementation

Nayms Contracts

Help

Diamond Standard Discord

Inspiration & Development

The Diamond Standard is an improved design over EIP-1538 using ABIEncoderV2 and function selectors.

The Diamond Standard replaces EIP-1538.

This standard was inspired by EIP-1538 and ZeppelinOS’s implementation of Upgradeability with vtables.

This standard was also inspired by the design and implementation of the Mokens contract.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Nick Mudge, "EIP-2535: Diamond Standard [DRAFT]," Ethereum Improvement Proposals, no. 2535, February 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2535.