Los contratos de poder son una herramienta importante para los desarrolladores de contratos inteligentes. Hoy en día, existen múltiples modelos de agencia y las correspondientes reglas de uso en el sistema de contratos. Anteriormente hemos descrito las mejores prácticas de seguridad para contratos de proxy actualizables.

En este artículo presentaremos otro modelo de proxy que es popular en la comunidad de desarrolladores, el modelo de proxy diamante.

El Contrato de Agencia Diamante, también conocido como "Diamante", es un patrón de diseño para contratos inteligentes de Ethereum introducido por la Propuesta de Mejora de Ethereum (EIP) 2535.

El modelo Diamond permite que un contrato tenga una funcionalidad ilimitada al dividir su funcionalidad en contratos más pequeños (también conocidos en sentido figurado como "porciones"). Los diamantes actúan como representantes, enrutando llamadas a funciones a los aspectos apropiados.

El modo diamante está diseñado para resolver el problema del límite máximo de tamaño de contrato de la red Ethereum. Al dividir un contrato grande en facetas más pequeñas, el patrón Diamond permite a los desarrolladores crear contratos inteligentes más complejos y ricos en funciones sin estar sujetos a restricciones de tamaño.

Los Agentes Diamante ofrecen una tremenda flexibilidad en comparación con los contratos actualizables tradicionales. Permiten actualizar partes del contrato, agregando, reemplazando o eliminando funciones parciales seleccionadas sin tocar otras partes.

Este artículo proporciona una descripción general de EIP-2535, incluidas comparaciones con el modo proxy transparente y el modo proxy UUPS ampliamente utilizados, así como sus consideraciones de seguridad para la comunidad de desarrolladores.

En el contexto de EIP-2535, "Diamante" es un contrato de proxy cuya implementación funcional es proporcionada por diferentes contratos lógicos, llamados "aspectos".

Imagine que los diamantes reales tienen lados diferentes, llamados facetas, entonces el contrato de diamantes Ethereum correspondiente también tiene facetas diferentes. Cada contrato de función de préstamo de diamantes tiene diferentes aspectos o facetas.

Diamond Standard amplía las capacidades de Diamond Cut por analogía para agregar, reemplazar o eliminar facetas y características.

Además, Diamond Standard proporciona una función llamada "Lupa de diamante" que devuelve información sobre las facetas y la presencia de diamantes.

Comparado con el modelo de agencia tradicional, "diamante" equivale al contrato de agencia, mientras que diferentes "aspectos" corresponden al contrato de implementación. Diferentes aspectos de un Diamond Agent pueden compartir funciones internas, bibliotecas y variables de estado. Los componentes clave de un diamante son los siguientes:

Un contrato central que actúa como proxy y enruta las llamadas de función a los aspectos apropiados. Contiene una asignación de selectores de funciones a direcciones de "aspecto".

Un contrato único que implementa una función específica. Cada aspecto contiene un conjunto de funciones que el diamante puede llamar.

es un conjunto de funciones estándar definidas en EIP-2535 que proporciona información sobre las facetas y selectores de funciones utilizados en los diamantes. Diamond Magnifier permite a los desarrolladores y usuarios inspeccionar y comprender la estructura de los diamantes.

Funciones para agregar, reemplazar o eliminar facetas y sus correspondientes selectores de características en un diamante. Sólo las direcciones autorizadas (por ejemplo, el propietario del diamante o un contrato de firma múltiple) pueden realizar el corte de diamantes.

De manera similar a los agentes tradicionales, cuando hay una llamada de función en el agente diamante, se activará la función de respaldo del agente (función de respaldo). La principal diferencia con el proxy diamante es que en la función alternativa, hay un mapeo selectorToFacet que almacena y determina qué dirección de contrato lógica tiene la implementación de la función llamada. Luego usa delegarcall para ejecutar la función, como un delegado tradicional.

Todos los servidores proxy utilizan la función fallback() para delegar llamadas a funciones a direcciones externas. La siguiente es la implementación del agente diamante y la implementación del agente tradicional.

Vale la pena señalar que sus bloques de código ensamblador son muy similares, por lo que la única diferencia es la dirección de aspecto en la llamada del delegado de proxy de diamante y la dirección implícita en la llamada del delegado de proxy tradicional.

La principal diferencia es que en el proxy diamante, la dirección del aspecto está determinada por el hashmap del msg.sig (selector de función) de la persona que llama a la dirección del aspecto, mientras que en el proxy tradicional, la dirección impl no depende de la entrada de la persona que llama.

Función de reserva del agente diamante

Función de respaldo de proxy tradicional

El mapeo SelectorToFacet determina qué contrato contiene la implementación de cada selector de función. El personal del proyecto a menudo necesita agregar, reemplazar o eliminar esta asignación de selectores de funciones a los contratos de implementación. EIP-2535 estipula: Para lograr este propósito, debe haber una función DiamondCut(). A continuación se muestra una interfaz de muestra.

Cada estructura FacetCut contiene una dirección de faceta y una matriz de cuatro bytes de selectores de funciones que se actualizarán en el contrato del agente diamante. FaceCutAction permite a las personas agregar, reemplazar y eliminar selectores de funciones. La implementación de la función DiamondCut() debe incluir un control de acceso adecuado para evitar colisiones de ranuras de almacenamiento, recuperación en caso de falla, etc.

Para comprobar qué funciones tiene un agente de diamante y qué facetas utiliza, utilizamos la "Lupa de Diamante". "Diamond Magnifying Glass" es un aspecto especial que implementa la siguiente interfaz definida en EIP-2535:

La función facets() debería devolver las direcciones de todas las facetas y sus selectores de funciones de cuatro bytes. La función facetFunctionSelectors() debería devolver todos los selectores de funciones admitidos por un aspecto específico. La función facetAddresses() debería devolver las direcciones de todas las facetas utilizadas por un diamante.

La función facetAddress() debe devolver la faceta que admite el selector dado, o la dirección (0) si no se encuentra. Tenga en cuenta que no debe haber más de una dirección de aspecto con el mismo selector de funciones.

Dado que el Agente Diamante delega diferentes llamadas de funciones a diferentes contratos de implementación, es crucial administrar adecuadamente los espacios de almacenamiento para evitar conflictos. EIP-2535 menciona varios métodos de administración de ranuras de almacenamiento.

Este aspecto puede declarar variables de estado en la estructura. Este aspecto puede utilizar cualquier cantidad de estructuras, cada una con una ubicación de almacenamiento diferente. Cada estructura tiene una ubicación específica en el almacenamiento por contrato. Los aspectos pueden declarar sus propias variables de estado, pero no pueden entrar en conflicto con la ubicación de almacenamiento de las variables de estado declaradas por otros aspectos. EIP-2535 proporciona un contrato de muestra de biblioteca y almacenamiento de diamantes, como se muestra en la siguiente figura:

App Storage es una versión más especializada de Diamond Storage. Este patrón se utiliza para que sea más conveniente y fácil compartir variables de estado de aspecto. La estructura de almacenamiento de una aplicación se define para contener cualquier número y tipo de variables de estado requeridas por una aplicación. Un aspecto siempre declara la estructura AppStorage como la primera y única variable de estado, ubicada en la ranura 0. Luego, diferentes aspectos pueden acceder a variables de esta estructura.

También existen otras estrategias de gestión de espacios de almacenamiento, incluida una combinación de Diamond Storage y AppStorage. Por ejemplo, algunas estructuras son compartidas entre diferentes aspectos y otras son exclusivas de aspectos específicos. En todos los casos, es importante evitar colisiones accidentales con los tanques de almacenamiento.

Comparación con proxies transparentes y proxies UUPS

Los dos modos de proxy principales utilizados actualmente por la comunidad de desarrolladores Web3 son el modo de proxy transparente y el modo de proxy UUPS. En esta sección, comparamos brevemente el patrón de proxy Diamond con los patrones de proxy Transparente y UUPS.

1.EPI-2535:https://eips.ethereum.org/EIPS/eip-2535 #Facets,% 20 State% 20 Variables% 20 and% 20 Diamond% 20 Almacenamiento

2.EPI-1967: https://eips.ethereum.org/EIPS/eip-1967    

3.Implementación de referencia del proxy Diamond: https://github.com/mudgen/Diamond    

4.Implementación de OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.7.0/contracts/proxy

Los agentes y las soluciones escalables son sistemas más complejos, y OpenZeppelin proporciona bibliotecas de códigos y documentación completa para UUPS, agentes escalables transparentes y Beacon. Sin embargo, para el patrón de proxy de diamante, aunque OpenZeppelin afirmó sus beneficios, decidieron no incluir la implementación de diamante EIP-2535 en su biblioteca.

Por lo tanto, los desarrolladores que utilicen bibliotecas de terceros existentes o que implementen la solución ellos mismos deben extremar las precauciones al implementarla. Aquí hemos compilado una lista de mejores prácticas de seguridad para que las considere la comunidad de desarrolladores.

Al dividir la lógica del contrato en módulos más pequeños y manejables, los desarrolladores pueden probar y auditar su código más fácilmente.

Además, este enfoque permite a los desarrolladores centrarse en crear y mantener aspectos específicos del contrato en lugar de gestionar una base de código compleja y monolítica. El resultado final es una base de código más flexible y modular que se puede actualizar y modificar fácilmente sin afectar otras partes del contrato.

Fuente: Aavegotchi Github

Cuando se implementa el contrato de proxy de diamantes, se debe agregar la dirección del contrato DiamondCutFacet al contrato de proxy de diamantes e implementar la función DiamondCut (). La función DiamondCut() se utiliza para agregar, eliminar o reemplazar aspectos y funciones. Sin DiamondCutFacet y DiamondCut(), el agente diamante no puede funcionar correctamente.

Fuente: Diamond-3-Hardhat de Mugen

Al agregar una nueva variable de estado a una estructura de almacenamiento en un contrato inteligente, se debe agregar al final de la estructura. Agregar una nueva variable de estado al principio o en medio de una estructura hará que la nueva variable de estado sobrescriba los datos de la variable de estado existente, y cualquier variable de estado después de la nueva variable de estado puede hacer referencia a la ubicación de almacenamiento incorrecta.

El patrón AppStorage requiere que se declare una y solo una estructura para el agente diamante, y esta estructura es compartida por todos los aspectos. Si se requieren varias estructuras, se debe utilizar el patrón DiamondStorage.

No coloque una estructura directamente dentro de otra estructura a menos que esté seguro de que no planea agregar más variables de estado a la estructura interna. No puede agregar nuevas variables de estado a una estructura interna en una actualización sin sobrescribir las ranuras de almacenamiento de variables declaradas después de la estructura.

La solución es agregar la nueva variable de estado a la estructura del mapa de almacenamiento en lugar de colocar la "estructura" directamente en la "estructura". Los espacios de almacenamiento variables en el mapa se calculan de manera diferente y no son contiguos en el almacenamiento.

El tamaño de la matriz se verá afectado por el tamaño de la estructura. Cuando se agrega una nueva variable de estado a una estructura, cambia el tamaño y el diseño de la estructura.

Esto puede causar problemas si la estructura se utiliza como elemento en una matriz. Si el tamaño y el diseño de la estructura cambian, entonces el tamaño y el diseño de la matriz también cambiarán, lo que puede causar problemas con la indexación u otras operaciones que dependen de un tamaño y diseño consistentes de la estructura.

Al igual que con otros patrones de proxy, cada variable debe tener una ranura de almacenamiento única. De lo contrario, dos estructuras diferentes en el mismo lugar se sobrescribirían entre sí.

La función inicializar() se usa generalmente para establecer variables importantes, como la dirección de un rol privilegiado. Si el contrato no se inicializa cuando se implementa, un actor malintencionado puede llamar y tomar el control del contrato.

Se recomienda agregar un control de acceso adecuado a la función de inicialización/configuración, o asegurarse de que la función se llame cuando se implemente el contrato y no se pueda volver a llamar.

Si algún aspecto del contrato puede llamar a la función selfdestruct(), puede destruir todo el contrato, lo que resultará en la pérdida de fondos o datos. Esto es extremadamente peligroso en el modelo de proxy diamante porque múltiples aspectos pueden acceder al almacenamiento y a los datos del contrato de proxy.

Actualmente, vemos cada vez más proyectos que adoptan el modelo de agencia de diamantes en sus contratos inteligentes. Ofrece flexibilidad y otras ventajas sobre los agentes tradicionales.

Sin embargo, una flexibilidad adicional también puede significar dar a los atacantes una superficie de ataque más amplia. Esperamos que este artículo sea útil para que la comunidad de desarrolladores comprenda la mecánica del patrón de proxy de diamante y sus consideraciones de seguridad.

Al mismo tiempo, el equipo del proyecto debe realizar pruebas rigurosas y auditorías de terceros para reducir el riesgo de vulnerabilidades relacionadas con la implementación del contrato de agencia de diamantes.

CertiK seguirá publicando este tipo de artículos técnicos para ayudar a más desarrolladores a desarrollarse de forma segura. ¡Síguenos para obtener más información e información similar!