Marketplace has launched, further enabling blockchain developers! Learn more

The Diamond Standard (EIP-2535) Explained: Part 2

October 24, 2022

Overview

In Part one of the Diamond standard series, we learned about the Diamond contracts (i.e., Multi-Facet Proxy) and how it works. In this guide, you will learn how to create, deploy, and test an EIP-2535 compliant Diamond smart contract using the diamond-1-hardhat repository and louper.dev, which is a tool for inspecting diamond smart contracts on EVM-based chains.

What You Will Do

  • Configure and examine the diamond-1-hardhat repository
  • Create and Deploy the set of Diamond smart contracts
  • Test the Diamond smart contract functionality

What You Will Need

Setting Up The Project

For this tutorial, we will be creating a Diamond smart contract using the diamond-1-hardhat repository (created by Nick Mudge, the author of EIP-2535). If you want to see other implementations, check out these other resources. Note there are also diamond-2-hardhat and diamond-3-hardhat repositories which demonstrate alternative methods of implementing and deploying a Diamond smart contract (e.g., gas optimized and implementing loupe functions).

Now, let us get started with setting up our project. Open a terminal window and run the following command to clone the repository and navigate inside it:

setting up the project

Copy
git clone https://github.com/mudgen/diamond-1-hardhat && cd diamond-1-hardhat

Then, installed the required dependencies:

setting up the project

Copy
npm install && npm install --save-dev @nomiclabs/hardhat-etherscan

Open up the project in your code editor of choice and open the hardhat.config.js file. Add the following import at the top of the file:

setting up the project

Copy
require("@nomiclabs/hardhat-etherscan");
Requires the plugin for automatic smart contract verification with Etherscan

Then, add the following code to the end of the module.exports object in the hardhat.config.js file:

setting up the project

Copy
networks: {
goerli: {
    url: "YOUR_QUICKNODE_HTTP_ENDPOINT",
    accounts: ["YOUR_PRIVATE_KEY"]
}
},
etherscan: {
    // Your API key for Etherscan
    // Obtain one at https://etherscan.io/
    apiKey: "YOUR_ETHERSCAN_API_KEY"
}

Now we will take a moment to ensure we have enough testnet ETH (on Goerli) and will fill in the placeholder values above with our actual RPC URL, private key, and Etherscan API key.

First, retrieve your private key and replace it with YOUR_PRIVATE_KEY above (if you're using MetaMask, find instructions here). Next, navigate to the QuickNode Faucet to retrieve some testnet ETH (you will need to sign in with Twitter). You will also need to input your API keys from Etherscan.io (which we will need later when verifying our Facet on louper.dev).

Lastly, we will need access to the Goerli Testnet blockchain. You're welcome to use public nodes or deploy and manage your own infrastructure; however, if you'd like up to 8x faster response times, you can leave the heavy lifting to us. Sign up for a free account here. Once you have your RPC url, replace it with YOUR_QUICKNODE_HTTP_ENDPOINT in hardhat.config.js and save the file.

With all those tasks accomplished, you now have our Hardhat project configured to deploy to Goerli Testnet and verify source code using the Hardhat-etherscan plugin. Next, we will create and deploy the set of diamond smart contracts.

Create and Deploy the Diamond Smart Contract

The diamond-1-hardhat repository is a reference implementation for EIP-2535 Diamonds. It has the boilerplate code needed to create a Diamond contract. It even has test Facets you can use; however, for the purpose of learning, we will create our own Facet.

In your contracts/facets folder, create a FacetA.sol file and add the following code:

create and deploy the diamond smart contract

Copy
pragma solidity ^0.8.0;

library LibA {

struct DiamondStorage {
    address owner;
    bytes32 dataA;
}


function diamondStorage() internal pure returns(DiamondStorage storage ds) {
    bytes32 storagePosition = keccak256("diamond.storage.LibA");
    assembly {
    ds.slot := storagePosition
    }
}
}

contract FacetA {
    function setDataA(bytes32 _dataA) external {
        LibA.DiamondStorage storage ds = LibA.diamondStorage();
        ds.dataA = _dataA;
    }

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

Let's recap the code!

  • Line 1: Set our pragma version. Note setting the version instructs the compiler to check if the version matches the one required by the pragma. If it does not match, the compiler issues an error.

  • Line 3-8: Create a library that implements DiamondStorage. The library contains a struct that stores the Facets variables and a diamondStorage function which specifies a random position from a hash of a string, and sets the position of our struct in contract storage.

  • Line 19-28: Create a contract that implements two functions, setDataA, which takes a bytes32 value and sets diamond storage defined above, and getDataA, which reads from the set storage.

Now that we have our Facet created, we can move on to compiling and deploying the smart contracts. The process for deploying a Diamond smart contract and adding our Facet will look like this:

  • Deploy DiamondInit.sol
  • Deploy DiamondCutFacet.sol
  • Deploy DiamondLoupeFacet.sol
  • Deploy OwnershipFacet.sol
  • Deploy Diamond.sol
  • Deploy FacetA.sol
  • Call diamondCut function to add FacetA

In your terminal, navigate to your root project directory and run the command npx hardhat compile, you should get a similar response to this:



Then to deploy the contracts, run the command: npx hardhat run scripts/deploy.js --network goerli. It may take a couple of minutes to deploy but when its finished you'll see each deployed contract address outputted in the terminal.



Next, we will deploy the Facet contract. In your scripts folder, create a file called deployFacet.js and input the following code:

create and deploy the diamond smart contract

Copy
/* global ethers */
/* eslint prefer-const: "off" */

async function deployFacet() {
  const FacetA = await ethers.getContractFactory('FacetA');
  const facetA = await FacetA.deploy();
  await facetA.deployed();
  console.log('facetA deployed:', facetA.address);

  return facetA.address;
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
deployFacet().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Save the file and run the command npx hardhat run scripts/FacetA.js --network goerli to deploy. Your ouput should contain the address of your deployed FacetA contract:



Before adding our Facet to the Diamond, let's verify the source code on Etherscan. Run the following command with your FacetA smart contract address:

create and deploy the diamond smart contract

Copy
npx hardhat verify --network goerli DEPLOYED_CONTRACT_ADDRESS

You should see the following output:



Now that all the necessary contracts are deployed, you'll need to use the diamondCut function to add your Facet contract (FacetA.sol) to your Diamond. We will use louper.dev (a tool for inspecting and interacting with Diamond smart contracts) to do this. Navigate to louper.dev and click the Sign-In button at the top-right of the page. Once logged in, select the Goerli testnet under the dropdown and then input the smart contract address of your Diamond contract (this should be listed in your terminal as "Diamond deployed: ").



Then, click the Upgrade Facet button and click Connect. You will need to input the address of your Facet contract (i.e., FacetA) that you previously deployed.



After clicking Fetch Facet Info, select the two functions and click Add Facet (you'll need to approve the transaction in your wallet).



Once your transaction is confirmed, you can move on to the next section, where you will test the newly added Facet on your Diamond contract.

Testing the Diamond Contract

Now that our Diamond contract has context about our FacetA contract, we can test the setDataA and getDataA functions. Navigate back to the homepage of your diamond contract on louper.dev and go to the FacetA section and click the Write button and then click Connect. Select the setDataA from the dropdown and input the value 0x68656c6c6f206469616d6f6e6473000000000000000000000000000000000000 (which converts to "hello diamonds" in decoded string format). Finally, sign the transaction in your MetaMask wallet.

Once your transaction is confirmed, head back over to the homepage of your diamond contract and this time, click the Read button under your Facet and select the getDataA function. Click the Read button, and you should see your previously set string!

Final Thoughts

Kudos! You now understand more about the Diamond standard and how to implement it. Got ideas, questions, or want to show off what you have learned? Tell us on Discord or reach out to us via Twitter.

We ❤️ Feedback!

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

Related articles 26

Solidity vs Vyper
Published: Aug 18, 2021
Updated: Sep 23, 2022

With the introduction to smart contracts on the Ethereum blockchain, it was only a matter of time until a language other than Solidity was made to write smart contract code. Vyper is one such...

Continue reading
How to Create a BEP20 Token
Published: Jul 3, 2021
Updated: Sep 23, 2022

BEP20 is the Binance Smart Chain equivalent to the popular ERC20 specification for tokens on the Ethereum network. The difference is the BEP20 Token isn't run on the Ethereum Network, but the...

Continue reading
What is an ABI?
Published: Mar 20, 2021
Updated: Sep 23, 2022

While interacting with a smart contract ABI is one of the essential components. In this guide, let us understand what the ABI of smart contracts is.

Continue reading
How to create and deploy an ERC20 token
Published: Feb 3, 2021
Updated: Sep 23, 2022

Ethereum network’s launch in 2015 created a lot of buzz in the developer community and sprouted a lot of tokens on the network. Initially there weren’t any templates or guidelines for token...

Continue reading
How to Create and Deploy an ERC-1155 NFT
Published: Mar 14, 2022
Updated: Sep 23, 2022

ERC1155 has emerged as a gold standard to create NFTs; every major marketplace lists new tokens as an ERC1155 standard. In this guide, we will learn about the ERC1155 token standard and how to...

Continue reading
Como crear un Token BEP20
Published: Jan 10, 2022
Updated: Sep 23, 2022

BEP20 es en Binance Smart Chain el equivalente al popular token ERC20 de la red de Ethereum. La diferencia es que el token BEP20 no corre en la red de Ethereum sino en la red de Binance Smart...

Continue reading