Bird
Raised Fist0
Blockchain / Solidityprogramming~15 mins

Diamond pattern (EIP-2535) in Blockchain / Solidity - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Diamond pattern (EIP-2535)
What is it?
The Diamond pattern (EIP-2535) is a way to build smart contracts that can be split into many smaller parts called facets. Each facet holds some functions, and together they form one big contract called a diamond. This pattern helps manage complex contracts by allowing easy upgrades and adding new features without losing data or changing the contract address.
Why it matters
Without the Diamond pattern, smart contracts become very large and hard to manage or upgrade. If you want to fix bugs or add features, you often have to deploy a new contract and move data, which is risky and costly. The Diamond pattern solves this by letting developers upgrade parts of the contract safely and keep the same address, making blockchain apps more reliable and flexible.
Where it fits
Before learning the Diamond pattern, you should understand basic smart contracts, how functions and storage work in Solidity, and the concept of contract upgradeability. After mastering the Diamond pattern, you can explore advanced upgrade patterns, proxy contracts, and modular smart contract architectures.
Mental Model
Core Idea
The Diamond pattern splits a big contract into smaller pieces (facets) that can be added, replaced, or removed independently while sharing the same storage and address.
Think of it like...
Imagine a large office building (the diamond) made of many rooms (facets). Each room has its own purpose and staff (functions). You can renovate, add, or replace rooms without tearing down the whole building, and everyone still uses the same building address.
┌─────────────────────────────┐
│          Diamond            │
│  (Main contract interface)  │
│                             │
│  ┌─────────┐  ┌─────────┐   │
│  │ Facet A │  │ Facet B │   │
│  │ (Funcs) │  │ (Funcs) │   │
│  └─────────┘  └─────────┘   │
│          ...                │
│  Shared Storage & Address   │
└─────────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Smart Contract Limits
🤔
Concept: Smart contracts have size and complexity limits that make big contracts hard to manage and upgrade.
Smart contracts on blockchains like Ethereum have a maximum size limit and once deployed, their code cannot be changed. Large contracts become expensive to deploy and difficult to fix or improve. This creates a need for patterns that allow modularity and upgradeability.
Result
Learners see why big contracts are problematic and why modular design is needed.
Knowing the limits of smart contracts explains why patterns like Diamond exist to overcome these challenges.
2
FoundationBasics of Contract Upgradeability
🤔
Concept: Upgradeability lets contracts change behavior after deployment without losing stored data or changing addresses.
Traditional upgrade methods use proxy contracts that delegate calls to logic contracts. This separates data storage from logic, allowing logic to be swapped. However, proxies can become complex and limited when contracts grow large.
Result
Learners understand the general idea of upgradeable contracts and their challenges.
Understanding upgradeability sets the stage for why Diamond pattern improves on existing proxy methods.
3
IntermediateIntroducing Facets in Diamond Pattern
🤔
Concept: Facets are small contracts each holding a set of functions that the diamond contract uses.
In the Diamond pattern, the main diamond contract delegates function calls to facets. Each facet implements part of the contract's functionality. This modular approach breaks a large contract into manageable pieces.
Result
Learners grasp how facets split contract logic and how the diamond routes calls.
Seeing facets as modular pieces clarifies how the Diamond pattern achieves flexibility and scalability.
4
IntermediateFunction Selector and Diamond Storage
🤔
Concept: The diamond uses a mapping from function selectors to facet addresses, and all facets share the same storage layout.
Each function in Solidity has a unique selector. The diamond keeps a map from these selectors to the facet that implements them. When a function is called, the diamond forwards the call to the correct facet. Storage is centralized in the diamond to keep data consistent across facets.
Result
Learners understand how function calls are routed and how storage is shared.
Knowing the selector mapping and shared storage is key to understanding how the diamond acts as one contract despite many facets.
5
IntermediateAdding, Replacing, and Removing Facets
🤔Before reading on: Do you think facets can be changed without redeploying the diamond contract? Commit to your answer.
Concept: The diamond can be upgraded by adding, replacing, or removing facets dynamically.
Using the diamondCut function, developers can modify which facets handle which functions. This allows upgrading parts of the contract without changing the diamond's address or losing data. The diamondCut function takes instructions to add, replace, or remove facets and updates the selector mapping accordingly.
Result
Learners see how the diamond supports flexible upgrades safely.
Understanding dynamic facet management reveals how the Diamond pattern enables safe, modular upgrades.
6
AdvancedStorage Collision and Diamond Storage Pattern
🤔Before reading on: Do you think each facet has its own separate storage? Commit to your answer.
Concept: All facets share the same storage space using a special storage pattern to avoid conflicts.
Facets use a fixed storage slot (diamond storage) to store their data structures. This avoids storage collisions that happen if facets use normal storage variables. Each facet accesses its own storage through a unique namespace inside the diamond's storage.
Result
Learners understand how storage is safely shared and isolated among facets.
Knowing the diamond storage pattern prevents bugs from storage collisions in modular contracts.
7
ExpertGas Costs and Delegatecall Nuances
🤔Before reading on: Does using delegatecall in the diamond increase or decrease gas costs compared to a monolithic contract? Commit to your answer.
Concept: Delegatecall allows facets to run in the diamond's context but adds gas overhead and subtle risks.
The diamond uses delegatecall to execute facet functions in the diamond's storage context. This adds some gas cost overhead compared to direct calls. Also, delegatecall can cause security risks if facets are not carefully designed, such as unexpected storage writes or reentrancy. Proper facet design and testing are critical.
Result
Learners appreciate the tradeoffs and risks of delegatecall in the diamond pattern.
Understanding delegatecall's costs and risks helps experts design safer, more efficient diamonds.
Under the Hood
The diamond contract holds a mapping from function selectors (4-byte identifiers) to facet addresses. When a function is called, the diamond looks up the selector, then uses delegatecall to forward the call to the facet. Delegatecall runs the facet's code but uses the diamond's storage and context. Storage is organized using a fixed slot pattern so facets do not overwrite each other's data. The diamondCut function modifies the selector-to-facet mapping to add, replace, or remove facets dynamically.
Why designed this way?
The Diamond pattern was designed to overcome the size and upgrade limits of monolithic contracts and traditional proxies. By splitting logic into facets and sharing storage, it allows modular upgrades without changing contract addresses or losing data. Delegatecall was chosen to preserve storage context, and the diamond storage pattern prevents storage collisions. This design balances flexibility, safety, and efficiency.
┌───────────────┐
│   User Call   │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│   Diamond     │
│  (Main Proxy) │
│  Selector Map │─────────────┐
└──────┬────────┘             │
       │ delegatecall          │
       ▼                      ▼
┌───────────────┐        ┌───────────────┐
│   Facet A     │        │   Facet B     │
│ (Functions)   │        │ (Functions)   │
└───────────────┘        └───────────────┘
       ▲                      ▲
       │                      │
       └────── Shared Storage ─┘
Myth Busters - 4 Common Misconceptions
Quick: Does each facet have its own separate storage on the blockchain? Commit to yes or no.
Common Belief:Each facet in the Diamond pattern has its own independent storage like separate contracts.
Tap to reveal reality
Reality:All facets share the same storage space in the diamond contract using a special storage pattern to avoid collisions.
Why it matters:Believing facets have separate storage leads to bugs where data is overwritten or lost, causing contract malfunction.
Quick: Can you upgrade a diamond contract by deploying a new diamond and copying data? Commit to yes or no.
Common Belief:Upgrading a diamond means deploying a new diamond contract and migrating all data manually.
Tap to reveal reality
Reality:The diamond pattern allows upgrading facets dynamically within the same diamond contract without redeployment or data migration.
Why it matters:Misunderstanding upgradeability causes unnecessary redeployments, increasing cost and risk.
Quick: Does using delegatecall in the diamond pattern make function calls cheaper than normal calls? Commit to cheaper or more expensive.
Common Belief:Delegatecall makes function calls cheaper because it reuses the diamond's context.
Tap to reveal reality
Reality:Delegatecall adds gas overhead compared to direct calls due to context switching and extra steps.
Why it matters:Underestimating gas costs can lead to inefficient contract designs and unexpected high fees.
Quick: Is the diamond pattern only useful for very large contracts? Commit to yes or no.
Common Belief:The diamond pattern is only needed for extremely large contracts with many functions.
Tap to reveal reality
Reality:Even medium-sized contracts benefit from modularity and upgradeability that the diamond pattern provides.
Why it matters:Ignoring the pattern for smaller projects misses out on safer upgrades and better code organization.
Expert Zone
1
Facets must carefully manage storage layout to avoid overwriting each other's data despite shared storage.
2
The diamondCut function requires precise control and validation to prevent accidental removal of critical facets.
3
Delegatecall context means that facets cannot use certain Solidity features like msg.sender in the usual way without care.
When NOT to use
The Diamond pattern is not ideal for very simple contracts where upgradeability is unnecessary or for contracts where gas cost must be minimized at all costs. Alternatives include simple proxies or immutable contracts.
Production Patterns
In production, diamonds are used to build modular DeFi protocols, NFT platforms, and DAOs that require frequent upgrades. Developers often combine diamonds with access control facets and use automated testing to ensure safe upgrades.
Connections
Proxy Pattern
The Diamond pattern builds on and extends the proxy pattern by allowing multiple logic contracts (facets) instead of one.
Understanding proxies helps grasp how diamonds delegate calls and manage upgradeability more flexibly.
Modular Programming
Diamond pattern applies modular programming principles to smart contracts by splitting functionality into independent facets.
Knowing modular programming concepts clarifies why splitting contract logic improves maintainability and scalability.
Microservices Architecture (Software Engineering)
Both diamonds and microservices break a large system into smaller, independently deployable units that communicate to form a whole.
Seeing the diamond pattern as a blockchain version of microservices helps understand its design goals of flexibility and upgradeability.
Common Pitfalls
#1Storage collision causing data corruption between facets.
Wrong approach:contract FacetA { uint256 count; function increment() external { count++; } } contract FacetB { uint256 count; function decrement() external { count--; } }
Correct approach:library LibFacetA { struct Storage { uint256 count; } bytes32 constant STORAGE_POSITION = keccak256("diamond.storage.facetA"); function storage() internal pure returns (Storage storage s) { bytes32 position = STORAGE_POSITION; assembly { s.slot := position } } } contract FacetA { function increment() external { LibFacetA.Storage storage s = LibFacetA.storage(); s.count++; } }
Root cause:Assuming each facet has isolated storage like normal contracts, ignoring that all facets share diamond storage.
#2Removing a facet that handles critical functions without checks.
Wrong approach:diamondCut([{facetAddress: address(0), action: Remove, functionSelectors: selectors}]);
Correct approach:Require thorough validation and backups before removing facets, and use access control to restrict diamondCut calls.
Root cause:Not enforcing strict controls on diamondCut leads to accidental or malicious removal of essential functionality.
#3Using msg.sender directly in facets without considering delegatecall context.
Wrong approach:function doSomething() external { address user = msg.sender; // use user }
Correct approach:function doSomething() external { address user = msg.sender; // works as expected because delegatecall preserves msg.sender // but be cautious with tx.origin and other globals }
Root cause:Misunderstanding how delegatecall affects context and global variables can cause security issues.
Key Takeaways
The Diamond pattern (EIP-2535) modularizes smart contracts into facets to enable flexible upgrades and manage complexity.
All facets share the same storage in the diamond contract using a special storage pattern to avoid collisions.
Function calls are routed by the diamond contract using a selector-to-facet mapping and delegatecall to execute facet code.
Upgrading the diamond involves adding, replacing, or removing facets dynamically without redeploying the whole contract.
Understanding delegatecall's gas costs and risks is essential for designing safe and efficient diamond contracts.

Practice

(1/5)
1.

What is the main purpose of the Diamond pattern (EIP-2535) in blockchain smart contracts?

easy
A. To split a large contract into smaller, manageable facets
B. To increase the gas cost of contract deployment
C. To prevent any contract upgrades
D. To combine multiple unrelated contracts into one

Solution

  1. Step 1: Understand the Diamond pattern concept

    The Diamond pattern divides a big contract into smaller parts called facets to organize code better.
  2. Step 2: Identify the main benefit

    This splitting allows easier upgrades and management of smart contracts.
  3. Final Answer:

    To split a large contract into smaller, manageable facets -> Option A
  4. Quick Check:

    Diamond pattern = splitting contract into facets [OK]
Hint: Remember: Diamond pattern breaks big contracts into smaller parts [OK]
Common Mistakes:
  • Thinking it prevents upgrades
  • Assuming it increases deployment cost
  • Confusing it with contract merging
2.

Which of the following is the correct Solidity syntax to declare a facet interface in the Diamond pattern?

interface IFacet {
    function myFunction() external;
}
easy
A. contract IFacet { function myFunction() public {} }
B. interface IFacet { function myFunction() external; }
C. library IFacet { function myFunction() internal; }
D. struct IFacet { function myFunction() external; }

Solution

  1. Step 1: Identify correct Solidity declaration for interface

    Interfaces use the keyword interface and declare functions without bodies.
  2. Step 2: Match function visibility and syntax

    Function in interface must be external and end with a semicolon, no body.
  3. Final Answer:

    interface IFacet { function myFunction() external; } -> Option B
  4. Quick Check:

    Interface syntax = interface IFacet { function myFunction() external; } [OK]
Hint: Interfaces have no function bodies and use 'external' functions [OK]
Common Mistakes:
  • Using contract instead of interface
  • Adding function bodies in interface
  • Using wrong visibility like public or internal
3.

Given the following Solidity snippet using the Diamond pattern, what will be the output when calling diamond.facetFunction()?

contract FacetA {
    function facetFunction() external pure returns (string memory) {
        return "Facet A called";
    }
}

contract Diamond {
    FacetA facetA;
    constructor() {
        facetA = new FacetA();
    }
    function facetFunction() external view returns (string memory) {
        return facetA.facetFunction();
    }
}
medium
A. "Facet A called"
B. Compilation error due to missing function
C. "Diamond called"
D. Runtime error: function not found

Solution

  1. Step 1: Understand contract interaction

    The Diamond contract creates an instance of FacetA and calls its facetFunction.
  2. Step 2: Trace the function call and return value

    Calling diamond.facetFunction() returns the string from FacetA: "Facet A called".
  3. Final Answer:

    "Facet A called" -> Option A
  4. Quick Check:

    Diamond calls FacetA function = "Facet A called" [OK]
Hint: Diamond delegates calls to facets returning their outputs [OK]
Common Mistakes:
  • Assuming Diamond returns its own string
  • Expecting compilation error due to delegation
  • Confusing runtime errors with correct delegation
4.

Identify the error in this simplified Diamond pattern Solidity code snippet:

contract Diamond {
    mapping(bytes4 => address) public facets;

    function addFacet(bytes4 selector, address facetAddress) public {
        facets[selector] = facetAddress;
    }

    fallback() external {
        address facet = facets[msg.sig];
        (bool success, ) = facet.delegatecall(msg.data);
        require(success, "Delegatecall failed");
    }
}
medium
A. Using delegatecall instead of call
B. Fallback function must be external payable
C. Mapping key type should be bytes32, not bytes4
D. Missing return statement in fallback function

Solution

  1. Step 1: Analyze fallback function behavior

    The fallback uses delegatecall but does not return data to the caller.
  2. Step 2: Identify missing return data forwarding

    Delegatecall returns data that must be returned by fallback to preserve call behavior.
  3. Final Answer:

    Missing return statement in fallback function -> Option D
  4. Quick Check:

    Fallback must return delegatecall data [OK]
Hint: Fallback must return delegatecall results to caller [OK]
Common Mistakes:
  • Ignoring return data in fallback
  • Confusing delegatecall with call
  • Assuming payable is mandatory for fallback
5.

You want to upgrade a Diamond contract by adding a new facet with a function selector that already exists in another facet. What will happen if you do not remove the old selector before adding the new one?

hard
A. The Diamond will route calls to the new facet for that selector
B. The Diamond will have two facets for the same selector causing ambiguity
C. The old facet's function will still be called, ignoring the new one
D. The contract will fail to compile due to duplicate selectors

Solution

  1. Step 1: Understand selector uniqueness in Diamond pattern

    Each function selector maps to exactly one facet address in the Diamond.
  2. Step 2: Analyze what happens when adding duplicate selectors

    If you add a selector without removing the old one, the mapping still points to the old facet, so calls route there.
  3. Final Answer:

    The old facet's function will still be called, ignoring the new one -> Option C
  4. Quick Check:

    Duplicate selector without removal = old facet called [OK]
Hint: Remove old selector before adding new to update facet [OK]
Common Mistakes:
  • Assuming Diamond supports multiple facets per selector
  • Expecting compile-time errors for duplicates
  • Thinking new facet automatically overrides old without removal