Skip to main content

Diamond Pattern (EIP-2535)

note

Prerequisites: You should understand how proxies work in Ethereum and have reviewed Modality 1.

Audience: For architects and senior developers.

Usage Context: When designing complex modular contracts or updating existing functionalities.

The Diamond Pattern (EIP-2535) is the mandatory architecture in ISBE for all business contracts. It allows building modular, upgradeable contracts without size limits through facets (independent modules).

What is the Diamond Pattern

An architecture that allows a single contract (diamond) to route calls to multiple modules (facets):

User → Diamond → Facet 1 (HashTimestamp)
├→ Facet 2 (AccessControl)
├→ Facet 3 (Pausable)
└→ Facet N (BusinessLogic)

Features:

  • One entry point: Unique address of the diamond.
  • Multiple facets: Modular functionalities.
  • Shared storage: All facets access the same storage.
  • Upgradeable: Without changing the contract address.
  • No size limits: Surpasses the 24KB EVM limit.

Why Diamond in ISBE

ISBE adopts Diamond as the only standard.

Advantages:

AspectBenefit
ModularityEach functionality is a self-contained module
ScalabilityNo size limits
UpgradeabilityUpdate modules without migration
GovernanceCentralized control of changes
Global PauseAll facets pausable from one point
Unified RBACRoles and permissions managed centrally
AuditOne address, full traceability

Diamond Architecture in ISBE

1. Diamond Contract (Core)

Receives all calls and routes them to facets via delegatecall:

abstract contract EIP2535 is FacetAddressResolver {
receive() external payable {}

fallback() external payable {
// Get facet from function selector
address facet = _resolveFacetAddress(msg.sig);

// Execute external function from facet using delegatecall
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())
}
}
}

function _resolveFacetAddress(bytes4 _sig) private view returns (address) {
address facet = _facetAddress(_sig);
require(facet != address(0), FunctionNotFound(_sig));
return facet;
}
}

2. Facets (Modules)

FacetPurpose
DiamondLoupeFacetIntrospection (query structure)
DiamondCutFacetFacet management (add/update/remove)
Business Logic FacetsUse case specific logic

3. Diamond Storage

Shared storage using unstructured storage (unique slots calculated with keccak256):

// Diamond storage structure
struct DiamondStorage {
mapping(bytes4 => FacetAddressAndItemPosition) facetAddressAndSelectorPosition;
bytes4[] selectors;
mapping(bytes4 => FacetAddressAndItemPosition) facetAddressAndInterfacePosition;
bytes4[] interfaces;
}

// Fixed storage position
bytes32 constant _DIAMOND_STORAGE_POSITION =
keccak256("isbe.contracts.diamond.storage");

function _diamondStorage()
private
pure
returns (DiamondStorage storage storage_)
{
bytes32 position = _DIAMOND_STORAGE_POSITION;
assembly {
storage_.slot := position
}
}

Facet Storage: Each facet uses its own unique slot to avoid collisions:

struct MyFacetStorage {
mapping(bytes32 => uint256) data;
}

bytes32 constant _MY_FACET_STORAGE_POSITION =
keccak256("isbe.contracts.myfacet.storage");

function _myFacetStorage()
internal
pure
returns (MyFacetStorage storage storage_)
{
bytes32 position = _MY_FACET_STORAGE_POSITION;
assembly {
storage_.slot := position
}
}

Facet Structure in ISBE

Mandatory Interface: IEIP2535Introspection

interface IEIP2535Introspection {
// Supported ERC-165 interfaces
function interfacesIntrospection() external pure returns (bytes4[] memory);

// Unique identifier of the facet
function businessIdIntrospection() external pure returns (bytes32);

// Exposed functions
function selectorsIntrospection() external pure returns (bytes4[] memory);
}

Internal/External Pattern:

Facets separate internal logic from public functions:

Internal: Storage and internal logic with _ prefix.

abstract contract HashTimestampInternal {
struct HashTimestampStorage {
mapping(bytes32 => uint256) hashTimestamps;
}

function _hashTimestampStorage()
internal pure
returns (HashTimestampStorage storage storage_)
{
bytes32 position = _HASH_TIMESTAMP_STORAGE_POSITION;
assembly {
storage_.slot := position
}
}

function _timestampHash(bytes32 _hash) internal {
uint256 timestamp = _blockTimestamp();
_hashTimestampStorage().hashTimestamps[_hash] = timestamp;
emit IHashTimestamp.HashTimestamped(_hash, msg.sender, timestamp);
}
}

External: Public functions using internal ones.

abstract contract HashTimestamp is HashTimestampInternal {
function timestampHash(bytes32 _hash)
external
override
whenNotPaused
onlyRole(_HASH_TIMESTAMP_ROLE)
{
_timestampHash(_hash);
}
}

Facet: Combines external + introspection.

contract HashTimestampFacet is HashTimestamp, IEIP2535Introspection {
function businessIdIntrospection() external pure returns (bytes32) {
return _HASH_TIMESTAMP_RESOLVER_KEY;
}

function selectorsIntrospection() external pure returns (bytes4[] memory) {
// ... returns selectors
}
}

Key pattern: External functions use internal methods. They NEVER directly access storage.

Facet Management (DiamondCut)

Operations:

enum ItemCutAction {
Add, // Add new selectors
Replace, // Replace existing selectors
Remove // Remove selectors
}

struct ItemCut {
address facetAddress;
ItemCutAction action;
bytes4[] items;
}

Functions:

interface IDiamondCut {
function diamondCut(
ItemCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
}

Security: Only DEFAULT_ADMIN_ROLE can execute diamondCut.

Introspection (DiamondLoupe)

Query diamond structure:

interface IDiamondLoupe {
function facets() external view returns (Facet[] memory);
function facetFunctionSelectors(address facet) external view returns (bytes4[] memory);
function facetAddresses() external view returns (address[] memory);
function facetAddress(bytes4 selector) external view returns (address);
}

Interaction with Diamond

From Solidity:

import {IHashTimestamp} from '@isbe/contracts/hashtimestamp/IHashTimestamp.sol';

IHashTimestamp hashRegistry = IHashTimestamp(diamondAddress);
hashRegistry.timestampHash(hash);

From Web3/Ethers:

const diamondAbi = [...hashTimestampAbi, ...accessControlAbi];
const diamond = new ethers.Contract(diamondAddress, diamondAbi, signer);
await diamond.timestampHash(documentHash);

Comparison with Other Patterns

FeatureStandaloneUUPS ProxyDiamond
Maximum size24KB24KBUnlimited
UpdateMigrationMono-implementationGranular multi-facet
AddressChangesFixedFixed
ModularityLowLowHigh
IntrospectionNoLimitedFull

Diamond Summary

AspectDescription
ArchitectureOne diamond + multiple facets
StorageUnstructured, shared
UpdateGranular, without address change
SizeUnlimited
IntrospectionFull (DiamondLoupe)
Mandatory in ISBEYes, only permitted pattern

Diamond allows ISBE to maintains centralized governance control with maximum modular flexibility.


Other technical reference topics: