Ir al contenido principal

Explicación del estándar Diamond (EIP-2535) - Parte 1

Actualizado el
26 de noviembre de 2025

6 minutos de lectura

Resumen

El desarrollo de contratos inteligentes puede resultar bastante complejo si hay que mantener una gran cantidad de lógica de contrato. El estándar Diamond (EIP-2535) facilita la modularización, así como la actualización y la gestión eficientes de tus contratos inteligentes. En la primera parte de esta serie de dos entregas, aprenderás qué es el estándar Diamond y cómo funciona. A continuación, en la segunda parte, aprenderás a crear e implementar un contrato inteligente Diamond compatible con EIP-2535 utilizando Hardhat.

Tus funciones

  • Descubre qué es el «Diamond Standard»
  • Descubre cada uno de los componentes del Diamond Standard
  • Explica las ventajas del «Diamond Standard»

Lo que necesitarás

¿Qué son los diamantes?

El estándar Diamond es una Propuesta de Mejora de Ethereum (EIP-2535) ya aprobada cuyo objetivo es facilitar a los desarrolladores la modularización y actualización de sus contratos inteligentes. La idea central del estándar Diamond es similar a la de los contratos inteligentes actualizables, como el patrón proxy, pero con la ventaja de que permite controlar múltiples contratos de implementación (es decir, contratos lógicos) desde un único contrato Diamond (es decir, contrato proxy). Algunas de las características clave del estándar Diamond son:

  • Una única pasarela para realizar llamadas proxy a un número n de contratos de implementación
  • Actualizar uno o varios contratos inteligentes de forma atómica
  • No hay límite en cuanto al número de contratos de implementación que puedes añadir a tu cuenta Diamond.
  • Un historial de todas las actualizaciones realizadas en el Diamond
  • Puede reducir los costes de gas (es decir, al reducir el número de llamadas a funciones externas)

Los tres componentes principales de un contrato Diamond son DiamondCut, DiamondStorage y las funciones y eventos estándar, que permiten ver qué contiene el Diamond y cuándo se ha actualizado. Algunos protocolos que han implementado el estándar Diamond son, entre otros, Aavegotchi, BarnBridge, DerivaDEX y Oncyber.

Además, existen diferentes tipos de diamantes, como por ejemplo:

  • Diamond actualizable: un contrato mutable que se puede actualizar
  • Finished Diamond: un contrato inmutable debido a que se ha eliminado la función de actualización
  • Single Cut Diamond: un contrato inmutable que ya no se puede actualizar

Ahora que ya tienes una visión general de la norma, profundicemos en algunos de los componentes que la conforman.

Anatomía del estándar del diamante

Diamante

Un Diamond es un contrato inteligente que utiliza otros contratos inteligentes (también conocidos como «Facets») para realizar llamadas de delegado. ¿Qué son una «delegatecall» y una «Facet»? En resumen, una «delegatecall» es una llamada a una función externa especial en la que un contrato proxy toma prestado código de otro contrato inteligente en su propio contexto (es decir, sus propias variables de estado). El otro componente fundamental de un contrato Diamond son las «Facets», que permiten modularizar la lógica de implementación en archivos Solidity independientes y actualizarla según sea necesario. Profundicemos en el tema de las «Facets» en la siguiente sección.

Facetas

Una «Facet» puede compararse con un contrato de implementación o una biblioteca, ya que contiene la lógica de la función externa a la que el contrato proxy (por ejemplo, Diamond) realizará llamadas. No hay límite en cuanto al número de «Facets» que se pueden añadir a un Diamond, algo que no es posible con el patrón proxy estándar. La ventaja de las «Facets» dentro de un contrato Diamond es que permiten modularizar el código y actualizar solo lo necesario. A diferencia del patrón de proxy habitual, en el que habría que volver a implementar un nuevo contrato de implementación, con las «Facets» —dado que se pueden tener muchos contratos de implementación— se pueden actualizar varias «Facets» (por ejemplo, varios contratos de implementación) o una sola «Facet».

Quizá te preguntes cómo se almacenan las facetas en un Diamond. Buena pregunta. Las facetas se implementan por separado del contrato de tu Diamond y se pueden añadir a tu Diamond mediante DiamondCut (del que hablaremos en breve). De esta forma, puedes organizar tu código de manera eficiente y actualizar solo la Facet que necesites apuntando a una nueva dirección de Facet. Estas direcciones se almacenan en una asignación (normalmente denominada «selectorToFacet») que contiene una asignación (bytes4 => dirección). Este valor bytes4 representa la firma de la función que deseas llamar en la dirección de Facet a la que apunta.

Ahora hablemos de lo que ocurre internamente, «bajo el capó». Cuando se invoca una función externa en un Diamond, la función de reserva del contrato Diamond comprueba qué función se está invocando mediante la asignación «selectorToFacet ». A continuación, realiza una llamada delegada para ejecutar la función en la dirección correspondiente de Facet. Si la llamada a la función externa tiene valores de retorno, el contrato Diamond te los devolverá.

Un contrato típico de Facet podría tener el siguiente aspecto:

contract FacetA {

function setDataA(bytes32 _dataA) external {
LibA.DiamondStorage storage ds = LibA.diamondStorage();
require(ds.owner == msg.sender, "Must be owner.");
ds.dataA = _dataA;
}

function getDataA() external view returns (bytes32) {
return LibA.diamondStorage().dataA;
}
}

DiamondCut

DiamondCut es una función estándar que debe implementarse en un contrato Diamond. Permite añadir, sustituir o mover funciones de tu contrato Diamond. DiamondCut puede considerarse la funcionalidad de «actualización», mediante la cual actualizas la asignación (por ejemplo, selectorToFacet) almacenada en tu contrato Diamond para que apunte a una nueva firma de función y/o dirección. Cada vez que se llame a la función DiamondCut, se emitirá un evento que puede utilizarse para realizar un seguimiento del historial de las actualizaciones.

Aquí hay un ejemplo de la página web de EIP-2535 que muestra cómo puede ser una interfaz DiamondCut:

interface IDiamondCut {
enum FacetCutAction {Add, Replace, Remove}

struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}

function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;

event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}

A continuación, aprenderás cuáles son las opciones de almacenamiento de las variables de estado en tu contrato Diamond.

AppStorage y DiamondStorage

Hay varias formas de incorporar almacenamiento en tu Diamond. Las dos más habituales son utilizar AppStorage y DiamondStorage. Dado que en la parte 2 de esta guía se implementará AppStorage, solo repasaremos brevemente DiamondStorage. La técnica DiamondStorage consiste en declarar tus variables de estado dentro de estructuras. Cada una de esas estructuras ocupa una posición específica en el almacenamiento del contrato.

AppStorage es un método alternativo para gestionar tus variables de estado y la estructura de almacenamiento. Es más eficiente porque te permite compartir variables de estado entre facetas. Con AppStorage, creas un contrato inteligente que contiene una estructura con todas tus variables de estado. Una vez desplegado, puedes importar ese AppStorage a varias facetas.

Un ejemplo de una faceta que implemente AppStorage tendría este aspecto:

import "./AppStorage.sol"

contract StakingFacet {
AppStorage internal s;

function myFacetFunction(uint256 _nextVar) external {
s.total = s.firstVar + _nextVar;
}

Nota: AppStorage suele definirse como s

DiamondLoupe

Un contrato Diamond implementa la interfaz DiamondLoupe. DiamondLoupe nos ayuda a ver a qué apuntan las facetas, los selectores de funciones y las direcciones de facetas en el contrato Diamond. Así es como puede tener aspecto una interfaz DiamondLoupe:

interface IDiamondLoupe {
struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}

function facets() external view returns (Facet[] memory facets_);
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);
function facetAddresses() external view returns (address[] memory facetAddresses_);
function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}

Estas funciones están pensadas para utilizarse fuera de la cadena y no requieren el pago de comisiones de gas.

Reflexiones finales

¡Enhorabuena! Ahora ya entiendes mejor el estándar Diamond. Se prevé que los diamantes se utilicen cada vez más en el futuro, a medida que los sistemas de contratos requieran mayor sofisticación y las herramientas lo admitan. En la parte 2, aprenderás a implementar un contrato inteligente compatible con Diamond e interactuar con él. Si aún quieres saber más sobre el estándar Diamond, echa un vistazo a la sección «Learning & Resources» del repositorio EIP2535, creado por Nick Mudge (¡el creador del estándar Diamond!).

¡Nos encanta recibir comentarios!

Si tienes algún comentario o pregunta sobre esta guía, háznoslo saber. ¡Nos encantaría saber tu opinión!

Comparte esta guía