A Hands-On Guide to Modular Smart Contracts with Diamond Standard (EIP2535)

A Hands-On Guide to Modular Smart Contracts with Diamond Standard (EIP2535)

·

6 min read

Modular smart contract systems allow for the creating of flexible and scalable decentralized applications (DApps) on blockchain platforms like Ethereum. ERC-2535 supports creating modular smart contracts enabling developers to extend and upgrade functionalities even after deployment.

In this tutorial, we will dive deeper into this Diamond standard explaining its core smart contracts with practical implementation. Let's get started!

What exactly is Diamond Standard?

Nick Mudge introduced EIP-2535, introducing the concept of function selectors, facets, and diamond contracts. This standard revolutionizes smart contract architecture by enabling modularity and upgradeability through the segmentation of contracts into smaller components known as Facets. These Facets provide specialized functionalities to a single smart contract entity known as the Diamond. Using the Solidity delegateCall function, the Diamond contract executes logic from its Facets, allowing developers to easily add, replace, or remove functionalities without affecting the overall contract.

For those curious about delegate calls, it is a feature in Solidity that lets one contract call function from another contract, while keeping the data storage of the calling contract.

Now, you might be wondering, why the name "Diamond"? Well, we know a Diamond is not just a single piece of gemstone, but rather a complex structure made up of many facets, each contributing to its overall beauty and strength. Similarly, in the world of smart contracts, a diamond is a single contract that consists of multiple smaller contracts called facets. Each facet serves a specific purpose or function, just like each facet of a diamond plays a role in its overall appearance.

So, how does the Diamond Standard differ from traditional proxy standards? Unlike conventional proxy standards, the Diamond Standard offers unparalleled modularity and flexibility, empowering smart contract development to seamlessly adapt to evolving requirements and simplifying management processes.

Key Features

  • Upgradeability and Extensibility: Diamond Standard enables developers to continuously upgrade and extend the functionalities of smart contracts, even after deployment. This facilitates iterative development and ensures that contracts can evolve to meet changing requirements over time.

  • No contract size limit: Unlike traditional smart contracts, diamonds do not have a maximum contract size limitation. This enables the aggregation of multiple functionalities into a single contract address, simplifying deployment and integration.

  • Reduce Gas cost: Diamonds promote gas efficiency by condensing multiple contracts into a single entity and optimizing function calls. This results in cost savings, particularly for complex DApps with extensive functionalities.

  • Modularity and Flexibility: The modular architecture of diamonds allows for the organization of contract code into separate units called facets. Facets can be independently deployed and upgraded, enhancing code maintainability and reusability.

Core Components in Diamond Standard

The Diamond

This interface sets the rules for making changes to facets. Simply it outlines how you can add, replace, or remove functionalities in a diamond contract, ensuring consistency and clarity in the modification process.

interface IDiamond {
enum FacetCutAction {Add, Replace, Remove}
// Add=0, Replace=1, Remove=2

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

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

IDiamondCut

By implementing this interface, a contract ensures it can modify its functionality by adding, replacing, or removing functions in a flexible and upgradeable manner.

interface IDiamondCut is IDiamond {    
    function diamondCut(
        FacetCut[] calldata _diamondCut,
        address _init,
        bytes calldata _calldata
    ) external;    
}

IDiamondLoupe

It is an interface that provides methods for inspecting and retrieving information about the facets of a diamond contract. It provides a way to explore and query the facets of a diamond contract, allowing developers to understand its structure and available functionalities.

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_);
}

The Diamond Storage

The Library implements storage management for diamond contracts following the Diamond Standard allowing developers to easily access and manipulate diamond-specific storage variables.

library LibDiamond {
    bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

    struct FacetAddressAndSelectorPosition {
        address facetAddress;
        uint16 selectorPosition;
    }

    struct DiamondStorage {
        // function selector => facet address and selector position in selectors array
        mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition;
        bytes4[] selectors;
        mapping(bytes4 => bool) supportedInterfaces;
        // owner of the contract
        address contractOwner;
    }

    function diamondStorage() internal pure returns (DiamondStorage storage ds) {
        bytes32 position = DIAMOND_STORAGE_POSITION;
        assembly {
            ds.slot := position
        }
    }
}

Implementing Diamond Standard Smart Contract

Here is a step-by-step procedure for creating a simple implementation to diamond standard with Hardhat x BuildBear that lets you create your private testnet, forked from the mainnet, with your own Native and ERC20 Token faucet and blockchain Explorer.

  • Run the command on your terminal to clone the repository
git clone <URL>
  • Now change the directory and install the dependencies
cd diamond
npm install
  • create your private testnet by running
npm run createTestnet

Once the Testnet is live, its RPC, Explorer, and Faucet details are added to the testnet.json file

  • We'll create a sample storage facet that updates and returns a message string, which will help us understand how diamonds work and how state variables are shared between the diamond and its facet.

    Now, Create a new file inside the facet folder naming it Storage.sol, and add the below code to it.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Storage {

    function getMessage() public view returns (string memory) {
        return libStorage.getMessage();
    }

    function setMessage(string memory _newMsg) public {
        libStorage.setMessage(_newMsg);
    }
}

library libStorage {
    bytes32 constant STORAGE_POSITION = keccak256("diamond.standard.storage");

    struct MStorage {
        string message;
    }

    function getMessage() internal view returns (string memory) {
        return diamondStorage().message;
    }

    function setMessage(string memory _newMsg) internal {
        diamondStorage().message = _newMsg;
    }

    function diamondStorage() internal pure returns (MStorage storage ds) {
        bytes32 position = STORAGE_POSITION;
        assembly {
            ds.slot := position
        }
    }
}
  • Deploy the contracts by running scripts
npx hardhat run scripts/deploy.js

Yeeey! Our diamond contract is deployed and verified on etherscan, You will get an output like this after a successful deployment.

  • Now go to testnet.json for important URLs like faucet or directly navigate with the Explorer URL. Add funds to your account using the BuildBear faucet and interact with contracts using the write and read functionality.

Conclusion

The Diamond Standard revolutionizes smart contract development by enabling modularity and upgradeability. It helps developers add new features, and fix bugs to their contracts even after they are deployed. It is a big step forward in how smart contracts are built for more dynamic and adaptable blockchain solutions.

About BuildBear:

BuildBear is a platform tailored for DApp development and testing. Developers gain the freedom to construct a personalized Private Testnet sandbox across a variety of blockchain networks. The liberty to mint unlimited Native and ERC20 tokens, coupled with rapid transaction times on BuildBear (under 3 seconds!), enhances the DApp development lifecycle manifold. The platform comes equipped with tools designed for real-time testing and debugging, ensuring developers can keep tabs on intricate blockchain transactions with unparalleled ease.

Connect with us on Twitter | LinkedIn | Telegram | GitHub

Author: Sana