The Diamond pattern helps build big smart contracts by splitting them into smaller parts. It keeps code organized and easy to update.
Diamond pattern (EIP-2535) in Blockchain / Solidity
Start learning this pattern below
Jump into concepts and practice - no test required
contract Diamond {
// Holds addresses of facets (modules)
mapping(bytes4 => address) public facets;
// Fallback function to delegate calls to facets
fallback() external payable {
address facet = facets[msg.sig];
require(facet != address(0), "Function does not exist");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}The Diamond contract holds references to smaller contracts called facets.
Calls to functions are forwarded to the right facet using delegatecall.
contract Diamond {
mapping(bytes4 => address) public facets;
fallback() external payable {
address facet = facets[msg.sig];
require(facet != address(0), "Function does not exist");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}contract FacetA {
function greet() external pure returns (string memory) {
return "Hello from Facet A!";
}
}// Adding a facet to the diamond
facets[bytes4(keccak256("greet()"))] = address(facetA);This program shows a Diamond contract that forwards the greet() call to FacetA. When you call greet() on Diamond, it returns the greeting from FacetA.
pragma solidity ^0.8.0; contract FacetA { function greet() external pure returns (string memory) { return "Hello from Facet A!"; } } contract Diamond { mapping(bytes4 => address) public facets; constructor(address _facetA) { facets[bytes4(keccak256("greet()"))] = _facetA; } fallback() external payable { address facet = facets[msg.sig]; require(facet != address(0), "Function does not exist"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } }
Diamond pattern uses delegatecall to run code in the context of the main contract.
Function selectors (first 4 bytes of function signature hash) are used to route calls.
Updating facets lets you add or fix features without redeploying the whole contract.
The Diamond pattern splits big contracts into smaller parts called facets.
It forwards function calls to the right facet using delegatecall and function selectors.
This makes contracts easier to manage, update, and save gas.
Practice
What is the main purpose of the Diamond pattern (EIP-2535) in blockchain smart contracts?
Solution
Step 1: Understand the Diamond pattern concept
The Diamond pattern divides a big contract into smaller parts called facets to organize code better.Step 2: Identify the main benefit
This splitting allows easier upgrades and management of smart contracts.Final Answer:
To split a large contract into smaller, manageable facets -> Option AQuick Check:
Diamond pattern = splitting contract into facets [OK]
- Thinking it prevents upgrades
- Assuming it increases deployment cost
- Confusing it with contract merging
Which of the following is the correct Solidity syntax to declare a facet interface in the Diamond pattern?
interface IFacet {
function myFunction() external;
}Solution
Step 1: Identify correct Solidity declaration for interface
Interfaces use the keywordinterfaceand declare functions without bodies.Step 2: Match function visibility and syntax
Function in interface must beexternaland end with a semicolon, no body.Final Answer:
interface IFacet { function myFunction() external; } -> Option BQuick Check:
Interface syntax = interface IFacet { function myFunction() external; } [OK]
- Using contract instead of interface
- Adding function bodies in interface
- Using wrong visibility like public or internal
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();
}
}Solution
Step 1: Understand contract interaction
The Diamond contract creates an instance of FacetA and calls itsfacetFunction.Step 2: Trace the function call and return value
Callingdiamond.facetFunction()returns the string from FacetA: "Facet A called".Final Answer:
"Facet A called" -> Option AQuick Check:
Diamond calls FacetA function = "Facet A called" [OK]
- Assuming Diamond returns its own string
- Expecting compilation error due to delegation
- Confusing runtime errors with correct delegation
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");
}
}Solution
Step 1: Analyze fallback function behavior
The fallback uses delegatecall but does not return data to the caller.Step 2: Identify missing return data forwarding
Delegatecall returns data that must be returned by fallback to preserve call behavior.Final Answer:
Missing return statement in fallback function -> Option DQuick Check:
Fallback must return delegatecall data [OK]
- Ignoring return data in fallback
- Confusing delegatecall with call
- Assuming payable is mandatory for fallback
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?
Solution
Step 1: Understand selector uniqueness in Diamond pattern
Each function selector maps to exactly one facet address in the Diamond.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.Final Answer:
The old facet's function will still be called, ignoring the new one -> Option CQuick Check:
Duplicate selector without removal = old facet called [OK]
- Assuming Diamond supports multiple facets per selector
- Expecting compile-time errors for duplicates
- Thinking new facet automatically overrides old without removal
