0
0
Blockchain / Solidityprogramming~15 mins

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

Choose your learning style9 modes available
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.