Skip to content

xxxijustwei/erc2535-hardhat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

18 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’Ž Diamond Hardhat v3 Implementation

A modern implementation of the EIP-2535 Diamond Standard using Hardhat v3 and Viem

Solidity Hardhat Viem TypeScript License: MIT


πŸ“‹ Table of Contents


🌟 Overview

This repository provides a production-ready implementation of the EIP-2535 Diamond Standard, also known as the "Multi-Facet Proxy" pattern. Diamonds enable smart contracts to exceed the 24KB bytecode limit while providing fine-grained upgradeability control.

Key Benefits

  • πŸš€ Gas Optimized: Loupe functions optimized for on-chain transactions
  • πŸ”§ Modern Stack: Built with Hardhat v3, Viem, and TypeScript
  • πŸ“¦ Modular Design: Easy to add, replace, or remove functionality
  • πŸ›‘οΈ Battle Tested: Comprehensive test coverage for all diamond operations
  • ⚑ Fast Development: Uses pnpm package manager for blazing fast builds

✨ Features

  • EIP-2535 Compliant: Full implementation of the Diamond Standard
  • Loupe Functions: All four standard loupe functions included
  • Role-Based Access Control: Advanced permission system with multiple roles
  • TypeScript Support: Full type safety for scripts and tests
  • Gas Efficient: Optimized storage patterns and function selectors
  • Flexible Upgrades: Add, replace, or remove functions in a single transaction

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         User/DApp                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Diamond Proxy                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚                  Fallback Function                  β”‚    β”‚
β”‚  β”‚            (Delegates to Facet Functions)           β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β–Ό                  β–Ό                  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ DiamondCut   β”‚  β”‚ DiamondLoupe β”‚  β”‚    Roles     β”‚
β”‚    Facet     β”‚  β”‚    Facet     β”‚  β”‚    Facet     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β–Ό                 β–Ό
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚ Custom Facet β”‚ β”‚ Custom Facet β”‚
          β”‚      #1      β”‚ β”‚      #2      β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸš€ Quick Start

Prerequisites

Installation

# Clone the repository
git clone git@github.com:xxxijustwei/erc2535-hardhat.git
cd erc2535-hardhat

# Install dependencies
pnpm install

πŸ“ Project Structure

erc2535-hardhat/
β”‚
β”œβ”€β”€ πŸ“ contracts/                   # Smart contracts
β”‚   β”œβ”€β”€ πŸ’Ž Diamond.sol              # Main diamond proxy contract
β”‚   β”œβ”€β”€ πŸ“ facets/                  # Facet implementations
β”‚   β”‚   β”œβ”€β”€ DiamondCutFacet.sol     # Diamond upgrade functions facet
β”‚   β”‚   β”œβ”€β”€ DiamondLoupeFacet.sol   # Introspection functions facet
β”‚   β”‚   β”œβ”€β”€ RoleFacet.sol           # Role-based access control facet
β”‚   β”‚   β”œβ”€β”€ Test1Facet.sol          # Test1 facet (for testing)
β”‚   β”‚   └── Test2Facet.sol          # Test2 facet (for testing)
β”‚   β”œβ”€β”€ πŸ“ interfaces/              # Contract interfaces
β”‚   β”‚   β”œβ”€β”€ IDiamondCut.sol         # Diamond cut interface
β”‚   β”‚   β”œβ”€β”€ IDiamondLoupe.sol       # Diamond loupe interface
β”‚   β”‚   └── IERC165.sol             # ERC165 interface
β”‚   β”œβ”€β”€ πŸ“ libraries/               # Shared libraries
β”‚   β”‚   β”œβ”€β”€ LibDiamond.sol          # Diamond storage and helpers
β”‚   β”‚   └── LibAccessControl.sol    # Role-based access control storage
β”‚   └── πŸ“ upgradeInitializers/     # Initialization contracts
β”‚       └── DiamondInit.sol         # Diamond initialization logic
β”‚
β”œβ”€β”€ πŸ“ ignition/                    # Hardhat Ignition deployment
β”‚   └── πŸ“ modules/
β”‚       └── diamond.ts              # Diamond deployment module
β”‚
β”œβ”€β”€ πŸ“ scripts/                     # Deployment scripts and utilities
β”‚   β”œβ”€β”€ deploy.ts                   # Programmatic deployment script
β”‚   └── utils.ts                    # Helper functions (selectors, etc.)
β”‚
β”œβ”€β”€ πŸ“ test/                        # Test suite
β”‚   β”œβ”€β”€ cacheBug.test.ts            # Cache bug tests
β”‚   β”œβ”€β”€ diamond.test.ts             # Comprehensive diamond tests
β”‚   └── rbac.test.ts                # Role-based access control tests
β”‚
β”œβ”€β”€ βš™οΈ hardhat.config.ts            # Hardhat configuration
β”œβ”€β”€ πŸ“¦ package.json                 # Dependencies
β”œβ”€β”€ πŸ“ biome.json                   # Biome linter configuration
└── πŸ“ README.md                    # This file

πŸ“– Usage Guide

Deployment Process

This project supports two deployment methods:

Method 1: Hardhat Ignition (Recommended)

Use ignition/modules/diamond.ts for declarative, reproducible deployments:

# Deploy to local network
npx hardhat ignition deploy ignition/modules/diamond.ts

# Deploy to specific network
npx hardhat ignition deploy ignition/modules/diamond.ts --network <network-name>

Method 2: Programmatic Script

Use scripts/deploy.ts for more control over the deployment process:

npx hardhat run scripts/deploy.ts --network <network-name>

Deployment Steps

Both methods follow the same deployment flow:

  1. Deploy DiamondCutFacet - Provides the diamondCut function for upgrades
  2. Deploy Diamond - Creates the main proxy with owner and DiamondCutFacet address
  3. Deploy DiamondInit - Initialization contract for setting initial state
  4. Deploy Facets - Deploy additional facets (DiamondLoupeFacet, RoleFacet, etc.)
  5. Execute diamondCut - Add all facet functions to the diamond and call init function in one transaction

Working with Facets

Creating a New Facet

// contracts/facets/MyFacet.sol
contract MyFacet {
    // Using diamond storage pattern
    bytes32 constant STORAGE_POSITION = keccak256("my.facet.storage");

    struct MyStorage {
        uint256 value;
        mapping(address => bool) users;
    }

    function getStorage() internal pure returns (MyStorage storage ms) {
        bytes32 position = STORAGE_POSITION;
        assembly {
            ms.slot := position
        }
    }

    function setValue(uint256 _value) external {
        MyStorage storage ms = getStorage();
        ms.value = _value;
    }
}

Calling Diamond Functions with Viem

import { getContractAt } from "@nomicfoundation/hardhat-viem";

// Get diamond instance with specific facet ABI
const diamondAddress = "0x...";
const myFacet = await getContractAt("MyFacet", diamondAddress);

// Call function through diamond proxy
const tx = await myFacet.setValue(42);
await tx.wait();

πŸ§ͺ Testing

Run the comprehensive test suite:

# Run all tests
pnpm test

# Run all tests with coverage
pnpm test --coverage

# Run all tests with gas stats
pnpm test --gas-stats

# Run specific test file
pnpm test test/diamond.test.ts

Test Coverage

  • βœ… Diamond deployment
  • βœ… Facet addition/replacement/removal
  • βœ… Function selector management
  • βœ… Loupe function queries
  • βœ… Role-based access control
  • βœ… Initialization patterns
  • βœ… Edge cases and error handling

πŸ’Ž Diamond Upgrading

Adding Functions

const { request } = await publicClient.simulateContract({
  address: diamondAddress,
  abi: dCutFacet.abi,
  functionName: "diamondCut",
  args: [
    [
      {
        facetAddress: facet.address,
        action: FacetCutAction.Add,
        functionSelectors: selectors,
      },
    ],
    zeroAddress,
    zeroHash,
  ],
});
const tx = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash: tx });

Replacing Functions

const { request } = await publicClient.simulateContract({
  address: diamondAddress,
  abi: dCutFacet.abi,
  functionName: "diamondCut",
  args: [
    [
      {
        facetAddress: addr,
        action: FacetCutAction.Replace,
        functionSelectors: selectors,
      },
    ],
    zeroAddress,
    zeroHash,
  ],
});
const tx = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash: tx });

Removing Functions

const { request } = await publicClient.simulateContract({
  address: diamondAddress,
  abi: dCutFacet.abi,
  functionName: "diamondCut",
  args: [
    [
      {
        facetAddress: zeroAddress,
        action: FacetCutAction.Remove,
        functionSelectors: selectors,
      },
    ],
    zeroAddress,
    zeroHash,
  ],
});
const tx = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash: tx });

Important Notes

  • ⚑ All changes happen in a single transaction
  • πŸ”’ Optional upgrade functionality (can create immutable diamonds)
  • 🎯 Initialization function can be called during upgrades
  • ✨ Maintains consistent state throughout the upgrade process

πŸ› οΈ Advanced Topics

Diamond Storage Pattern

The Diamond Storage pattern allows facets to share storage without conflicts:

library LibAppStorage {
    bytes32 constant STORAGE_POSITION = keccak256("app.storage");

    struct AppStorage {
        uint256 totalSupply;
        mapping(address => uint256) balances;
        address admin;
    }

    function appStorage() internal pure returns (AppStorage storage ds) {
        bytes32 position = STORAGE_POSITION;
        assembly {
            ds.slot := position
        }
    }
}

Security Considerations

  • πŸ” Access Control: Role-based system protects critical functions
  • πŸ›‘οΈ Facet Validation: Verify facet addresses before adding
  • ⚠️ Selector Clashes: Ensure no function selector conflicts
  • πŸ” Audit Trail: Log all diamond upgrades for transparency

🀝 Contributing

We welcome contributions! Please follow these steps:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Development Guidelines

  • Write comprehensive tests for new features
  • Follow existing code style and conventions
  • Update documentation as needed
  • Ensure all tests pass before submitting PR

πŸ“š Resources

Official Documentation

Tutorials & Articles

Community & Support


πŸ‘₯ Authors

  • Nick Mudge - Original Implementation - @mudgen
  • xxxijustwei - Hardhat v3 Migration - @xxxijustwei

🌟 Star this repo if you find it helpful!

Made with ❀️ by the Diamond Standard Community

About

πŸ’Ž ERC2535 Diamond Contract | Hardhat v3 + Viem

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors