Skip to main content

The Diamond Standard (EIP-2535) Explained - Part 1

Created on
Updated on
Nov 26, 2024

6 min read

Overview

Smart contract development can get quite complex if you have a lot of contract logic to maintain. The Diamond standard (EIP-2535) makes it easy to modularize and efficiently upgrade and manage your smart contracts. In part one of this two-part series, you will learn what the Diamond standard is and how it works. Then, in Part 2, you will learn how to create and deploy an EIP-2535 compliant Diamond smart contract using Hardhat.

What You Will Do

  • Learn what the Diamond Standard is
  • Learn about each component in the Diamond Standard
  • Cover the benefits of the Diamond Standard

What You Will Need

What are Diamonds?

The Diamond standard is a finalized Ethereum Improvement Proposal (EIP-2535) that aims to make it easier for developers to modularize and upgrade their smart contracts. The core idea of the Diamond standard is similar to upgradeable smart contracts, such as the proxy pattern but with the benefit that you can control many implementation contracts (i.e., logic contracts) from your single Diamond contract (i.e., proxy contract). Some key features of the Diamond standard include:

  • A single gateway to make proxy calls to n number of implementation contracts
  • Upgrade a single or multiple smart contract atomically
  • No storage limit to how many implementation contracts you can add to your Diamond
  • A log history of all the upgrades made on the Diamond
  • Can reduce gas costs (i.e., by reducing the number of external function calls)

The three core components of a Diamond contract include the DiamondCut, DiamondStorage, and the standard functions and events, which allow you to see what's in the Diamond and when it's been upgraded. Some protocols that have implemented the Diamond standard include, Aavegotchi, BarnBridge, DerivaDEX and Oncyber.

Additionally, there are different types of Diamonds, such as:

  • Upgradeable Diamond: A mutable contract that can be upgraded
  • Finished Diamond: An immutable contract due to the upgradeability feature being removed
  • Single Cut Diamond: An immutable contract that can no longer be upgraded

Now that you have a high-level understanding of the standard, let us dive into some of the components that make up the standard.

Anatomy of the Diamond Standard

Diamond

A Diamond is a smart contract that uses other smart contracts (aka Facets) to make delegatecall calls. What's a delegate call and Facet? In short, a delegate call is a special external function call where a proxy contract borrows code from another smart contract in its own context (i.e., its own state variables). The other core component of a Diamond contract is Facets, which allow you to modularize your implementation logic into separate solidity files and upgrade as needed. Let's learn about Facets more in the next section.

Facets

A Facet can be compared to an implementation contract or library as it holds the external function logic the proxy (e.g., Diamond) contract will make calls too. There is no limit to how many Facets you can add to a Diamond which isn't doable with the standard proxy pattern. The benefit of Facets within a Diamond contract is that it allows you to modularize your code and only update what's needed. Unlike a common proxy pattern where you would need to redeploy a new implementation contract, with Facets since you can have many implementation contracts, you can update multiple Facets (e.g., multiple implementation contracts) or an individual Facet.

You may be wondering how Facets are stored in a Diamond. Good question. Facets are deployed separately from your Diamond contract and can be added into your Diamond using DiamondCut (which we will get to soon). This way, you can efficiently organize your code and only update the Facet you need by pointing to a new Facet address. These addresses are stored in a mapping (usually called selectorToFacet) which holds a (bytes4 => address) mapping. This bytes4 value represents the function signature of the function you want to call at the Facet address it's pointing to.

Now let's talk about what goes on internally under the hood. When an external function is called on a Diamond, the fallback function of the Diamond contract looks to see what function is being called by using the selectorToFacet mapping. It then makes a delegatecall to execute the function on the corresponding Facet address. If the external function call has return values, the Diamond contract will return those to you.

A typical Facet contract could look like this:

contract FacetA {

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

function getDataA() external view returns (bytes32) {
return LibA.diamondStorage().dataA;
}
}

DiamondCut

DiamondCut is a standard function that must be implemented in a Diamond contract. It allows you to add, replace or move functions from your Diamond contract. DiamondCut can be considered the "upgrade" functionality where you update the mapping (e.g., selectorToFacet) held in storage for your diamond contract to point to a new function signature and/or address. Whenever you call the DiamondCut function, an event will be emitted, which can be used to track the history of your upgrades.

Here's an example from the EIP-2535 webpage that displays how a DiamondCut interface can look:

interface IDiamondCut {
enum FacetCutAction {Add, Replace, Remove}

struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}

function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;

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

Next, you will learn about the storage options for state variables in your Diamond contract.

AppStorage and DiamondStorage

There are multiple ways to incorporate storage in your Diamond. The two most common are using AppStorage and DiamondStorage. Since Part 2 of this guide will implement AppStorage, we will only briefly go over DiamondStorage. The DiamondStorage technique consists of declaring your state variables inside structs. Each of those structs gets a specific position in contract storage.

AppStorage is an alternative method of managing your state variables and storage layout. It is more efficient because it helps you share state variables between facets. With AppStorage, you create a smart contract that has a struct of all your state variables. Once deployed, you can import that AppStorage into multiple Facets.

An example of a Facet implementing AppStorage would look like this:

import "./AppStorage.sol"

contract StakingFacet {
AppStorage internal s;

function myFacetFunction(uint256 _nextVar) external {
s.total = s.firstVar + _nextVar;
}

Note: AppStorage is usually defined as s

DiamondLoupe

A Diamond contract implements the DiamondLoupe interface. DiamondLoupe helps us see what Facets, function selectors, and facet addresses are pointing to in the Diamond contract. Here's how a DiamondLoupe interface can look:

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

function facets() external view returns (Facet[] memory facets_);
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);
function facetAddresses() external view returns (address[] memory facetAddresses_);
function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}

These functions are intended to be used off-chain and do not require gas fees.

Final Thoughts

Kudos! You now have a better understanding of the Diamond standard. Diamonds should see more usage in the future once contract systems require more sophistication and tools are supporting it. In part 2, you will learn how to deploy a Diamond-compliant smart contract and interact with it. If you still want to learn more about the Diamond standard, check out the Learning & Resources section on the EIP2535 repository, which was created by Nick Mudge (the creator of the diamond standard!).

We ❤️ Feedback!

If you have any feedback or questions on this guide, let us know. We'd love to hear from you!

Share this guide