Diamond Pattern (EIP-2535)
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:
| Aspect | Benefit |
|---|---|
| Modularity | Each functionality is a self-contained module |
| Scalability | No size limits |
| Upgradeability | Update modules without migration |
| Governance | Centralized control of changes |
| Global Pause | All facets pausable from one point |
| Unified RBAC | Roles and permissions managed centrally |
| Audit | One 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)
| Facet | Purpose |
|---|---|
| DiamondLoupeFacet | Introspection (query structure) |
| DiamondCutFacet | Facet management (add/update/remove) |
| Business Logic Facets | Use 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
| Feature | Standalone | UUPS Proxy | Diamond |
|---|---|---|---|
| Maximum size | 24KB | 24KB | Unlimited |
| Update | Migration | Mono-implementation | Granular multi-facet |
| Address | Changes | Fixed | Fixed |
| Modularity | Low | Low | High |
| Introspection | No | Limited | Full |
Diamond Summary
| Aspect | Description |
|---|---|
| Architecture | One diamond + multiple facets |
| Storage | Unstructured, shared |
| Update | Granular, without address change |
| Size | Unlimited |
| Introspection | Full (DiamondLoupe) |
| Mandatory in ISBE | Yes, only permitted pattern |
Diamond allows ISBE to maintains centralized governance control with maximum modular flexibility.
Other technical reference topics: