Saltar al contenido principal

Patrón Diamond (EIP-2535)

nota

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:

AspectoBeneficio
ModularidadCada funcionalidad es un módulo autocontenido
EscalabilidadSin límites de tamaño
UpgradeabilidadActualizar módulos sin migración
GobernanzaControl centralizado de cambios
Pausado globalTodas las facets pausables desde un punto
RBAC unificadoRoles y permisos gestionados centralmente
AuditoríaUna 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)

FacetPropósito
DiamondLoupeFacetIntrospección (consultar estructura)
DiamondCutFacetGestión de facets (añadir/actualizar/eliminar)
Business Logic FacetsLó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ísticaStandaloneProxy UUPSDiamond
Tamaño máximo24KB24KBIlimitado
ActualizaciónMigraciónMono-implementaciónMulti-facet granular
DirecciónCambiaFijaFija
ModularidadBajaBajaAlta
IntrospecciónNoLimitadaCompleta

Resumen Diamond

AspectoDescripción
ArquitecturaUn diamond + múltiples facets
StorageUnstructured, compartido
ActualizaciónGranular, sin cambio de dirección
TamañoIlimitado
IntrospecciónCompleta (DiamondLoupe)
Obligatorio en ISBESí, ú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: