Los contratos proxy son una herramienta importante para los desarrolladores de contratos inteligentes. Hoy en día, hay muchos modos de proxy y reglas de uso correspondientes en el sistema de contratos. Anteriormente, describimos las mejores prácticas de seguridad de contrato de proxy actualizable.
En este artículo, presentaremos otro modelo de proxy que es popular en la comunidad de desarrolladores, el modelo de proxy de diamante.
Los contratos proxy de diamantes, también conocidos como "diamantes", son un patrón de diseño para los contratos inteligentes de Ethereum introducidos por la Propuesta de mejora de Ethereum (EIP) 2535.
El modo Diamante permite que un contrato tenga una funcionalidad ilimitada al dividir su funcionalidad en contratos más pequeños (también llamados acertadamente "aspectos"). Diamond actúa como un proxy, enrutando las llamadas de función al aspecto apropiado.
El diseño del modelo de diamante puede resolver el problema de la limitación del tamaño máximo del contrato de la red Ethereum. Al dividir un contrato grande en aspectos más pequeños, el patrón de diamante permite a los desarrolladores crear contratos inteligentes más complejos y ricos en funciones sin verse afectados por las restricciones de tamaño.
Diamond Brokerage ofrece una gran flexibilidad en comparación con los contratos actualizables tradicionales. Permiten actualizar partes del contrato, agregar, reemplazar o eliminar partes seleccionadas de funciones sin tocar otras partes.
Este artículo proporciona una descripción general de EIP-2535, incluida una comparación con el modo de proxy transparente ampliamente utilizado y el modo de proxy UUPS, y sus consideraciones de seguridad para la comunidad de desarrolladores.
En el contexto de EIP-2535, un "diamante" es un contrato proxy cuya implementación funcional la proporcionan diferentes contratos lógicos, denominados "aspectos".
Imagine que un diamante real tiene diferentes lados, llamados facetas, y los contratos de diamantes de Ethereum correspondientes también tienen diferentes facetas. Cada contrato de función de préstamo de diamantes es un lado o faceta diferente.
El estándar de diamante utiliza una analogía para ampliar las capacidades del "corte de diamante" para agregar, reemplazar o eliminar facetas y características.
Además, Diamond Standard proporciona una característica llamada "Lupa de diamantes" que devuelve información sobre las facetas y la presencia del diamante.
En comparación con el modelo de poder tradicional, el "diamante" es equivalente al contrato de poder, y los diferentes "aspectos" corresponden a la realización del contrato. Diferentes aspectos de un agente de diamantes 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, enrutando las llamadas de función al aspecto apropiado. Contiene una asignación de selectores de función a direcciones de "aspecto".
Un solo contrato que implementa una función específica. Cada faceta contiene un conjunto de funciones que el diamante puede llamar.
es un conjunto de funciones estándar definidas en EIP-2535 que proporcionan información sobre los selectores de facetas y funciones que se utilizan en los diamantes. Diamond Loupe permite a los desarrolladores y usuarios inspeccionar y comprender la estructura de los diamantes.
Funciones para agregar, reemplazar o eliminar facetas en un diamante y sus correspondientes selectores de características. Solo las direcciones autorizadas (por ejemplo, el propietario del diamante o un contrato de múltiples firmas) pueden realizar el corte de diamantes.
De manera similar a los agentes tradicionales, cuando se realiza una llamada de función en el agente de diamante, se activa la función de respaldo del agente (función de respaldo). La principal diferencia con el proxy de diamante es que en la función de respaldo, hay una asignación de selectorToFacet, que almacena y determina qué dirección de contrato lógica tiene la implementación de la función llamada. Luego usa una llamada de delegado para ejecutar la función, como un proxy tradicional.
Todos los proxies usan la función fallback() para delegar llamadas a funciones a direcciones externas. A continuación se muestra la implementación del proxy de diamante y la implementación del proxy tradicional.
Vale la pena señalar que sus bloques de código de ensamblaje son muy similares, por lo que la única diferencia es la dirección de aspecto en la llamada de delegado de proxy de diamante y la dirección impl en la llamada de delegado de proxy tradicional.
La principal diferencia es que en el proxy de diamante, la dirección del aspecto está determinada por el hashmap desde el msg.sig (selector de función) de la persona que llama hasta 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 alternativa de agente de diamante
Función alternativa de proxy tradicional
El mapeo SelectorToFacet determina qué contrato contiene la implementación de cada selector de función. Los trabajadores del proyecto a menudo necesitan agregar, reemplazar o eliminar esta asignación de contrato de selector de función a implementación. EIP-2535 estipula: Para lograr este propósito, debe haber una función diamondCut(). A continuación se muestra una interfaz de ejemplo.
Cada estructura FacetCut contiene una dirección de faceta y una matriz de selección de funciones de cuatro bytes que se actualizará en el contrato de proxy de diamante. FaceCutAction permite agregar, reemplazar y eliminar selectores de funciones. La implementación de la función diamondCut() debe incluir un control de acceso adecuado, evitar colisiones de ranuras, recuperarse en caso de falla, etc.
Para consultar qué funciones y facetas tiene un agente de diamantes, utilizamos la "lupa de diamantes". "Diamond Loupe" 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() debe devolver todos los selectores de función admitidos por un aspecto en particular. La función facetAddresses() debe devolver todas las direcciones de facetas utilizadas por un diamante.
La función facetAddress() debe devolver un aspecto que admita 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 los proxies de diamante delegan llamadas de funciones diferentes a diferentes contratos de implementación, es fundamental administrar las ranuras de almacenamiento correctamente 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 usar cualquier número 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 las ubicaciones de almacenamiento de las variables de estado declaradas por otros aspectos. En EIP-2535 se proporciona una biblioteca de muestra y un contrato de almacenamiento de diamantes, como se muestra en la siguiente figura:
El almacenamiento de aplicaciones es una versión más especializada del almacenamiento de diamantes. Este patrón se utiliza para compartir variables de estado de aspectos de forma más cómoda y sencilla. Una estructura de tienda de aplicaciones se define para contener cualquier número y tipo de variables de estado requeridas por una aplicación. Un aspecto siempre declara la estructura de AppStorage como la primera y única variable de estado, en la posición 0 de la ranura de almacenamiento. Diferentes aspectos pueden entonces acceder a las variables de esta estructura.
También hay otras estrategias de administración de ranuras de almacenamiento, incluido un híbrido de Diamond Storage y AppStorage. Por ejemplo, algunas estructuras se comparten entre diferentes aspectos y otras son específicas de un aspecto específico. En todos los casos, es muy importante evitar colisiones accidentales de ranuras.
Comparación con Proxy Transparente y Proxy UUPS
Los dos principales modos de proxy utilizados actualmente por la comunidad de desarrolladores de Web3 son el modo de proxy transparente y el modo de proxy UUPS. En esta sección, comparamos brevemente el modo de proxy de diamante con los modos de proxy transparente y UUPS.
1.EPI-2535:
2.EPI-1967:
3.Implementación de referencia de proxy de diamante:
4.Implementación de OpenZeppelin:
Las soluciones escalables y de proxy son sistemas más complejos, y OpenZeppelin proporciona bases de código y documentación completa para los proxies escalables UUPS, Transparent y Beacon. Sin embargo, para el modo 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 esta solución ellos mismos deben implementarla con extrema precaución. Aquí hemos compilado una lista de verificación de las 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 aspectos específicos de la creación y mantenimiento de contratos, en lugar de administrar una base de código monolítica y compleja. 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, 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 usa para agregar, eliminar o reemplazar facetas y funciones, sin DiamondCutFacet y diamondCut(), el agente de diamantes no puede funcionar correctamente.
Fuente: Mugen's Diamond-3-Hardhat
Al agregar una nueva variable de estado a una estructura de almacenamiento en un contrato inteligente, debe agregarse 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 memoria incorrecta.
El patrón de AppStorage requiere que se declare una y solo una estructura para el proxy de diamante, y que esta estructura sea compartida por todos los aspectos. Si se requieren varias estructuras, se debe usar el patrón DiamondStorage.
No coloque una estructura directamente dentro de otra estructura a menos que esté seguro de que no tiene la intención de agregar más variables de estado a la estructura interna. No es posible agregar nuevas variables de estado a estructuras internas en una actualización sin sobrescribir las ranuras de almacenamiento de variables declaradas después de la estructura.
La solución es agregar nuevas variables de estado a la estructura mapeada en memoria en lugar de colocar la "estructura" directamente en la "estructura". Las ranuras de almacenamiento de variables en un mapa se calculan de manera diferente y no son contiguas 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 esa estructura.
Esto puede causar problemas si la estructura se usa como un elemento en una matriz. Si el tamaño y el diseño de la estructura cambian, 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 un diseño coherentes de la estructura.
Al igual que otros patrones de proxy, cada variable debe tener una ranura de almacenamiento única. De lo contrario, dos estructuras diferentes en la misma ubicación se sobrescribirían entre sí.
La función initialize() generalmente se usa para establecer variables importantes, como direcciones de roles privilegiados. Si no se inicializa cuando se implementa el contrato, un actor malicioso puede llamar y controlar el contrato.
Se recomienda agregar un control de acceso adecuado a la función de inicialización/configuración, o asegurarse de que se llame a la función 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(), tiene el potencial de destruir todo el contrato, lo que resulta en la pérdida de fondos o datos. Esto es extremadamente peligroso en el modo de proxy de diamante, ya que varios aspectos pueden acceder al almacenamiento y los datos del contrato de proxy.
Actualmente, vemos cada vez más proyectos que adoptan el modelo de proxy de diamante en sus contratos inteligentes. Ofrece flexibilidad y otras ventajas sobre los proxies tradicionales.
Sin embargo, la flexibilidad adicional también puede significar una superficie de ataque más amplia para los atacantes. Esperamos que este artículo ayude a la comunidad de desarrolladores a comprender la mecánica del modelo 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 de los contratos de agencias de diamantes.
Ver originales
El contenido es solo de referencia, no una solicitud u oferta. No se proporciona asesoramiento fiscal, legal ni de inversión. Consulte el Descargo de responsabilidad para obtener más información sobre los riesgos.
Mejores prácticas de seguridad para contratos de agencias de diamantes
Los contratos proxy son una herramienta importante para los desarrolladores de contratos inteligentes. Hoy en día, hay muchos modos de proxy y reglas de uso correspondientes en el sistema de contratos. Anteriormente, describimos las mejores prácticas de seguridad de contrato de proxy actualizable.
En este artículo, presentaremos otro modelo de proxy que es popular en la comunidad de desarrolladores, el modelo de proxy de diamante.
Los contratos proxy de diamantes, también conocidos como "diamantes", son un patrón de diseño para los contratos inteligentes de Ethereum introducidos por la Propuesta de mejora de Ethereum (EIP) 2535.
El modo Diamante permite que un contrato tenga una funcionalidad ilimitada al dividir su funcionalidad en contratos más pequeños (también llamados acertadamente "aspectos"). Diamond actúa como un proxy, enrutando las llamadas de función al aspecto apropiado.
El diseño del modelo de diamante puede resolver el problema de la limitación del tamaño máximo del contrato de la red Ethereum. Al dividir un contrato grande en aspectos más pequeños, el patrón de diamante permite a los desarrolladores crear contratos inteligentes más complejos y ricos en funciones sin verse afectados por las restricciones de tamaño.
Diamond Brokerage ofrece una gran flexibilidad en comparación con los contratos actualizables tradicionales. Permiten actualizar partes del contrato, agregar, reemplazar o eliminar partes seleccionadas de funciones sin tocar otras partes.
Este artículo proporciona una descripción general de EIP-2535, incluida una comparación con el modo de proxy transparente ampliamente utilizado y el modo de proxy UUPS, y sus consideraciones de seguridad para la comunidad de desarrolladores.
En el contexto de EIP-2535, un "diamante" es un contrato proxy cuya implementación funcional la proporcionan diferentes contratos lógicos, denominados "aspectos".
Imagine que un diamante real tiene diferentes lados, llamados facetas, y los contratos de diamantes de Ethereum correspondientes también tienen diferentes facetas. Cada contrato de función de préstamo de diamantes es un lado o faceta diferente.
El estándar de diamante utiliza una analogía para ampliar las capacidades del "corte de diamante" para agregar, reemplazar o eliminar facetas y características.
Además, Diamond Standard proporciona una característica llamada "Lupa de diamantes" que devuelve información sobre las facetas y la presencia del diamante.
En comparación con el modelo de poder tradicional, el "diamante" es equivalente al contrato de poder, y los diferentes "aspectos" corresponden a la realización del contrato. Diferentes aspectos de un agente de diamantes 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, enrutando las llamadas de función al aspecto apropiado. Contiene una asignación de selectores de función a direcciones de "aspecto".
Un solo contrato que implementa una función específica. Cada faceta contiene un conjunto de funciones que el diamante puede llamar.
es un conjunto de funciones estándar definidas en EIP-2535 que proporcionan información sobre los selectores de facetas y funciones que se utilizan en los diamantes. Diamond Loupe permite a los desarrolladores y usuarios inspeccionar y comprender la estructura de los diamantes.
Funciones para agregar, reemplazar o eliminar facetas en un diamante y sus correspondientes selectores de características. Solo las direcciones autorizadas (por ejemplo, el propietario del diamante o un contrato de múltiples firmas) pueden realizar el corte de diamantes.
De manera similar a los agentes tradicionales, cuando se realiza una llamada de función en el agente de diamante, se activa la función de respaldo del agente (función de respaldo). La principal diferencia con el proxy de diamante es que en la función de respaldo, hay una asignación de selectorToFacet, que almacena y determina qué dirección de contrato lógica tiene la implementación de la función llamada. Luego usa una llamada de delegado para ejecutar la función, como un proxy tradicional.
Todos los proxies usan la función fallback() para delegar llamadas a funciones a direcciones externas. A continuación se muestra la implementación del proxy de diamante y la implementación del proxy tradicional.
Vale la pena señalar que sus bloques de código de ensamblaje son muy similares, por lo que la única diferencia es la dirección de aspecto en la llamada de delegado de proxy de diamante y la dirección impl en la llamada de delegado de proxy tradicional.
La principal diferencia es que en el proxy de diamante, la dirección del aspecto está determinada por el hashmap desde el msg.sig (selector de función) de la persona que llama hasta 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 alternativa de agente de diamante
Función alternativa de proxy tradicional
El mapeo SelectorToFacet determina qué contrato contiene la implementación de cada selector de función. Los trabajadores del proyecto a menudo necesitan agregar, reemplazar o eliminar esta asignación de contrato de selector de función a implementación. EIP-2535 estipula: Para lograr este propósito, debe haber una función diamondCut(). A continuación se muestra una interfaz de ejemplo.
Cada estructura FacetCut contiene una dirección de faceta y una matriz de selección de funciones de cuatro bytes que se actualizará en el contrato de proxy de diamante. FaceCutAction permite agregar, reemplazar y eliminar selectores de funciones. La implementación de la función diamondCut() debe incluir un control de acceso adecuado, evitar colisiones de ranuras, recuperarse en caso de falla, etc.
Para consultar qué funciones y facetas tiene un agente de diamantes, utilizamos la "lupa de diamantes". "Diamond Loupe" 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() debe devolver todos los selectores de función admitidos por un aspecto en particular. La función facetAddresses() debe devolver todas las direcciones de facetas utilizadas por un diamante.
La función facetAddress() debe devolver un aspecto que admita 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 los proxies de diamante delegan llamadas de funciones diferentes a diferentes contratos de implementación, es fundamental administrar las ranuras de almacenamiento correctamente 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 usar cualquier número 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 las ubicaciones de almacenamiento de las variables de estado declaradas por otros aspectos. En EIP-2535 se proporciona una biblioteca de muestra y un contrato de almacenamiento de diamantes, como se muestra en la siguiente figura:
El almacenamiento de aplicaciones es una versión más especializada del almacenamiento de diamantes. Este patrón se utiliza para compartir variables de estado de aspectos de forma más cómoda y sencilla. Una estructura de tienda de aplicaciones se define para contener cualquier número y tipo de variables de estado requeridas por una aplicación. Un aspecto siempre declara la estructura de AppStorage como la primera y única variable de estado, en la posición 0 de la ranura de almacenamiento. Diferentes aspectos pueden entonces acceder a las variables de esta estructura.
También hay otras estrategias de administración de ranuras de almacenamiento, incluido un híbrido de Diamond Storage y AppStorage. Por ejemplo, algunas estructuras se comparten entre diferentes aspectos y otras son específicas de un aspecto específico. En todos los casos, es muy importante evitar colisiones accidentales de ranuras.
Comparación con Proxy Transparente y Proxy UUPS
Los dos principales modos de proxy utilizados actualmente por la comunidad de desarrolladores de Web3 son el modo de proxy transparente y el modo de proxy UUPS. En esta sección, comparamos brevemente el modo de proxy de diamante con los modos de proxy transparente y UUPS.
1.EPI-2535:
2.EPI-1967:
3.Implementación de referencia de proxy de diamante:
4.Implementación de OpenZeppelin:
Las soluciones escalables y de proxy son sistemas más complejos, y OpenZeppelin proporciona bases de código y documentación completa para los proxies escalables UUPS, Transparent y Beacon. Sin embargo, para el modo 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 esta solución ellos mismos deben implementarla con extrema precaución. Aquí hemos compilado una lista de verificación de las 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 aspectos específicos de la creación y mantenimiento de contratos, en lugar de administrar una base de código monolítica y compleja. 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, 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 usa para agregar, eliminar o reemplazar facetas y funciones, sin DiamondCutFacet y diamondCut(), el agente de diamantes no puede funcionar correctamente.
Fuente: Mugen's Diamond-3-Hardhat
Al agregar una nueva variable de estado a una estructura de almacenamiento en un contrato inteligente, debe agregarse 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 memoria incorrecta.
El patrón de AppStorage requiere que se declare una y solo una estructura para el proxy de diamante, y que esta estructura sea compartida por todos los aspectos. Si se requieren varias estructuras, se debe usar el patrón DiamondStorage.
No coloque una estructura directamente dentro de otra estructura a menos que esté seguro de que no tiene la intención de agregar más variables de estado a la estructura interna. No es posible agregar nuevas variables de estado a estructuras internas en una actualización sin sobrescribir las ranuras de almacenamiento de variables declaradas después de la estructura.
La solución es agregar nuevas variables de estado a la estructura mapeada en memoria en lugar de colocar la "estructura" directamente en la "estructura". Las ranuras de almacenamiento de variables en un mapa se calculan de manera diferente y no son contiguas 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 esa estructura.
Esto puede causar problemas si la estructura se usa como un elemento en una matriz. Si el tamaño y el diseño de la estructura cambian, 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 un diseño coherentes de la estructura.
Al igual que otros patrones de proxy, cada variable debe tener una ranura de almacenamiento única. De lo contrario, dos estructuras diferentes en la misma ubicación se sobrescribirían entre sí.
La función initialize() generalmente se usa para establecer variables importantes, como direcciones de roles privilegiados. Si no se inicializa cuando se implementa el contrato, un actor malicioso puede llamar y controlar el contrato.
Se recomienda agregar un control de acceso adecuado a la función de inicialización/configuración, o asegurarse de que se llame a la función 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(), tiene el potencial de destruir todo el contrato, lo que resulta en la pérdida de fondos o datos. Esto es extremadamente peligroso en el modo de proxy de diamante, ya que varios aspectos pueden acceder al almacenamiento y los datos del contrato de proxy.
Actualmente, vemos cada vez más proyectos que adoptan el modelo de proxy de diamante en sus contratos inteligentes. Ofrece flexibilidad y otras ventajas sobre los proxies tradicionales.
Sin embargo, la flexibilidad adicional también puede significar una superficie de ataque más amplia para los atacantes. Esperamos que este artículo ayude a la comunidad de desarrolladores a comprender la mecánica del modelo 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 de los contratos de agencias de diamantes.