Patrón Diamond (EIP-2535)
Prerrequisitos: Debes entender cómo funcionan los proxies en Ethereum y haber revisado la Modalidad 1.
Público: Para arquitectos y desarrolladores senior.
Contexto de uso: Al diseñar contratos modulares complejos o actualizar funcionalidades existentes.
El Patrón Diamond (EIP-2535) es la arquitectura obligatoria en ISBE para todos los contratos de negocio. Permite construir contratos modulares, actualizables y sin límites de tamaño mediante facets (módulos independientes).
Qué es el Patrón Diamond
Arquitectura que permite que un único contrato (diamond) enrute llamadas a múltiples módulos (facets):
Usuario → Diamond → Facet 1 (HashTimestamp)
├→ Facet 2 (AccessControl)
├→ Facet 3 (Pausable)
└→ Facet N (BusinessLogic)
Características:
- Un punto de entrada: Dirección única del diamond
- Múltiples facets: Funcionalidades modulares
- Storage compartido: Todas las facets acceden al mismo storage
- Actualizable: Sin cambiar dirección del contrato
- Sin límites de tamaño: Supera el límite de 24KB de la EVM
Por qué Diamond en ISBE
ISBE adopta Diamond como único estándar.
Ventajas:
| Aspecto | Beneficio |
|---|---|
| Modularidad | Cada funcionalidad es un módulo autocontenido |
| Escalabilidad | Sin límites de tamaño |
| Upgradeabilidad | Actualizar módulos sin migración |
| Gobernanza | Control centralizado de cambios |
| Pausado global | Todas las facets pausables desde un punto |
| RBAC unificado | Roles y permisos gestionados centralmente |
| Auditoría | Una dirección, trazabilidad completa |
Arquitectura Diamond en ISBE
1. Diamond Contract (Núcleo)
Recibe todas las llamadas y enruta a las facets mediante 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 (Módulos)
| Facet | Propósito |
|---|---|
| DiamondLoupeFacet | Introspección (consultar estructura) |
| DiamondCutFacet | Gestión de facets (añadir/actualizar/eliminar) |
| Business Logic Facets | Lógica específica del caso de uso |
3. Diamond Storage
Storage compartido usando unstructured storage (slots únicos calculados con keccak256):
// Estructura del storage del diamond
struct DiamondStorage {
mapping(bytes4 => FacetAddressAndItemPosition) facetAddressAndSelectorPosition;
bytes4[] selectors;
mapping(bytes4 => FacetAddressAndItemPosition) facetAddressAndInterfacePosition;
bytes4[] interfaces;
}
// Posición fija del storage
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
}
}
Storage de Facets: Cada facet usa su propio slot único para evitar colisiones:
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
}
}
Estructura de Facets en ISBE
Interfaz Obligatoria: IEIP2535Introspection
interface IEIP2535Introspection {
// Interfaces ERC-165 soportadas
function interfacesIntrospection() external pure returns (bytes4[] memory);
// Identificador único de la facet
function businessIdIntrospection() external pure returns (bytes32);
// Funciones expuestas
function selectorsIntrospection() external pure returns (bytes4[] memory);
}
Patrón Internal/External:
Las facets separan lógica interna de funciones públicas:
Internal: Storage y lógica interna con prefijo _
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: Funciones públicas que usan las internas
abstract contract HashTimestamp is HashTimestampInternal {
function timestampHash(bytes32 _hash)
external
override
whenNotPaused
onlyRole(_HASH_TIMESTAMP_ROLE)
{
_timestampHash(_hash);
}
}
Facet: Combina external + introspección
contract HashTimestampFacet is HashTimestamp, IEIP2535Introspection {
function businessIdIntrospection() external pure returns (bytes32) {
return _HASH_TIMESTAMP_RESOLVER_KEY;
}
function selectorsIntrospection() external pure returns (bytes4[] memory) {
// ... retorna selectores
}
}
Patrón clave: Funciones externas usan métodos internos. NUNCA acceden directamente al storage.
Gestión de Facets (DiamondCut)
Operaciones:
enum ItemCutAction {
Add, // Agregar nuevos selectores
Replace, // Reemplazar selectores existentes
Remove // Eliminar selectores
}
struct ItemCut {
address facetAddress;
ItemCutAction action;
bytes4[] items;
}
Funciones:
interface IDiamondCut {
function diamondCut(
ItemCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
}
Seguridad: Solo DEFAULT_ADMIN_ROLE puede ejecutar diamondCut.
Introspección (DiamondLoupe)
Consultar estructura del diamond:
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);
}
Interacción con Diamond
Desde Solidity:
import {IHashTimestamp} from '@isbe/contracts/hashtimestamp/IHashTimestamp.sol';
IHashTimestamp hashRegistry = IHashTimestamp(diamondAddress);
hashRegistry.timestampHash(hash);
Desde Web3/Ethers:
const diamondAbi = [...hashTimestampAbi, ...accessControlAbi];
const diamond = new ethers.Contract(diamondAddress, diamondAbi, signer);
await diamond.timestampHash(documentHash);
Comparación con Otros Patrones
| Característica | Standalone | Proxy UUPS | Diamond |
|---|---|---|---|
| Tamaño máximo | 24KB | 24KB | Ilimitado |
| Actualización | Migración | Mono-implementación | Multi-facet granular |
| Dirección | Cambia | Fija | Fija |
| Modularidad | Baja | Baja | Alta |
| Introspección | No | Limitada | Completa |
Resumen Diamond
| Aspecto | Descripción |
|---|---|
| Arquitectura | Un diamond + múltiples facets |
| Storage | Unstructured, compartido |
| Actualización | Granular, sin cambio de dirección |
| Tamaño | Ilimitado |
| Introspección | Completa (DiamondLoupe) |
| Obligatorio en ISBE | Sí, único patrón permitido |
Diamond permite a ISBE mantener control centralizado de gobernanza con máxima flexibilidad modular.
Otros temas de la referencia técnica: