LogoLaunch App

Reducción de los Cuellos de Botella en el Desarrollo de Pruebas de Solidity Fork

December 19, 2023
solidity fork testing

La necesidad de velocidad

El propósito del desarrollo de software es lograr que funcione según lo previsto. Se requieren múltiples iteraciones de prueba para lograrlo, y el tiempo entre iteraciones es de importancia crítica cuando se intenta completar la tarea en cuestión lo antes posible.

En Origin, utilizamos pruebas unitarias y de bifurcación para el desarrollo. La ejecución de pruebas de bifurcación puede ser tediosamente lenta, pero hemos logrado reducir el tiempo de ejecución de las pruebas de bifurcación (en algunos casos) en un factor de 4. A través de este artículo, explicaré cómo puedes mejorar la velocidad de tus pruebas de bifurcación para que pueda hacer que su código funcione según lo previsto en poco tiempo. 

¿Prueba unitaria o prueba de bifurcación?

Normalmente, el uso de una prueba de bifurcación o una prueba unitaria depende de la arquitectura del protocolo que se está desarrollando. Algunos equipos solo usan pruebas unitarias y otros, tanto pruebas unitarias como de bifurcación. En Origin, nuestros contratos interactúan con varios protocolos de terceros (Curve, Uniswap, Balancer, Convex…) y los errores en la integración pueden causar pérdidas catastróficas de fondos. 

Utilizando pruebas unitarias, nos burlamos del ABI del protocolo de terceros y probamos que nuestros contratos se integren correctamente mediante programación. Pero probar el comportamiento de un protocolo de terceros con todas las complejidades y varios estados (legítimos o manipulados) en los que puede encontrarse el protocolo es donde brillan las pruebas de bifurcación. Y para algunos ingenieros de nuestro equipo, el desarrollo basado en pruebas mediante pruebas de bifurcación es el modo de desarrollo principal. Reducir el tiempo de “cambio de código” al tiempo de “ejecución de prueba” es esencial. 

Ciclo de desarrollo de pruebas de bifurcación

En Origin, utilizamos Hardhat para nuestra pila de desarrollo de Solidity. Es importante que comprendamos los distintos pasos que componen una ejecución de prueba de bifurcación para reducir el tiempo del ciclo de desarrollo. Para simplificar, ejecutaremos solo una prueba de bifurcación a la vez, que suele ser el enfoque cuando se trabaja en un contrato inteligente. 

  • Compilar: este es un paso necesario al realizar cambios de código en contratos de Solidity. Sucede tanto con las pruebas unitarias como con las bifurcaciones y el tiempo consumido depende de la cantidad de contratos modificados desde la última compilación.
  • Implementaciones: para las implementaciones, utilizamos el complemento hardhat-deploy. Los archivos de implementación se utilizan para publicar cambios en el repositorio de protocolos en la red principal. Debido a que queremos que el entorno bifurcado imite lo más fielmente posible a la red principal, estos archivos se ejecutan casi sin modificaciones en el entorno bifurcado. También hay algunas cosas a considerar:
  • Dependiendo de la altura del bloque de bifurcación, solo se ejecutan las implementaciones que aún no se reflejan en el nodo bifurcado.
  • Debido a que OUSD y OETH se rigen por un DAO, cada implementación debe pasar por el procedimiento de gobernanza. Primero se publica una propuesta, luego hay un período de votación para emitir votos y, después de eso, la propuesta se pone en cola y se ejecuta. Simular todos esos pasos en el nodo requiere un tiempo de ciclo precioso.
  • Post-implementación: un paso global para todas las pruebas donde se financian las cuentas de prueba y se restablecen sus asignaciones.
  • Dispositivo de prueba: algunas pruebas requieren que el protocolo se coloque en un estado específico para que una prueba sea viable (por ejemplo, habilitar y financiar una estrategia que actualmente está deshabilitada en la red principal).
  • Prueba: la prueba de la bifurcación en sí 

El enfoque más lento

El enfoque más lento es ejecutar las pruebas de bifurcación con la bifurcación de nodos del último bloque. De esta manera, cada vez que se ejecutan pruebas se elige una altura de bloque diferente y el casco no puede realizar ningún almacenamiento en caché leyendo las ranuras de almacenamiento del proveedor de la red principal. Esto se refleja en la lenta velocidad de ejecución de 101 segundos.

Esta prueba de bifurcación en particular envía fondos a una de nuestras estrategias de extracción de liquidez que despliega activos en un grupo/pool de Balancer. Esto provoca una gran cantidad de lecturas de ranuras de almacenamiento y es la causa principal de la parte lenta de "prueba" de la ejecución (parte naranja de la barra de arriba).

Una alternativa más rápida

La fruta más fácil y obvia es fijar la altura del bloque para que Hardhat pueda ahorrar una gran cantidad de tiempo utilizando un caché local de lecturas de ranuras de almacenamiento. El tiempo de compilación no se ve afectado por esta optimización, pero sí todos los demás pasos. Reduciendo en gran medida el tiempo del ciclo a 27 segundos (esto se aplica a todas las ejecuciones de la prueba que no sean las primeras, donde el caché del casco ya está calentado).

¿Puede ser aún más rápido?

El enfoque anterior mejora significativamente la situación, pero aún está lejos de la velocidad de las pruebas unitarias y, a veces, todavía puede parecer frustrantemente lento. Sólo hemos cambiado un contrato. ¿Por qué necesitamos volver a ejecutar las mismas implementaciones y posteriores a la implementación cuando, idealmente, solo es necesario cambiar el código de bytes de 1 (o más) contratos de Solidity? Dado que, por supuesto, no cambiaremos el diseño o el estado de las ranuras de almacenamiento entre compilaciones. Ingrese a las implementaciones en caliente.

Implementaciones en caliente: El demonio de la velocidad

Hot-deploy implementa el contrato que se está modificando y recupera su código de bytes. Utilizando instantáneas del conjunto de pruebas de casco, encuentra la versión anterior de ese contrato en un estado antes de que se ejecutaran las pruebas y reemplaza su código de bytes con el recién compilado. Además de eso, ejecuta los dispositivos de prueba y la prueba en sí, omitiendo todos los pasos (posteriores) a la implementación.

 El tiempo del ciclo ahora se reduce a sólo 7 segundos. Este cambio en el ciclo de desarrollo ha ayudado a Origin Protocol a mejorar en gran medida la productividad y la velocidad a la que podemos desarrollar funciones. 

Solución técnica

Desafortunadamente, las implementaciones en caliente no se pueden activar como un conmutador (al menos no todavía) y requieren cierta configuración. La forma en que hemos implementado la solución en Origin es la siguiente: 

  • Tenemos una función que puede construir cualquier contrato implementable en caliente con los parámetros exactos del constructor presentes en la red principal. Esto no se puede evitar ya que el constructor puede definir variables constantes o inmutables que se almacenan en el código de bytes del contrato en lugar de ranuras de almacenamiento.
  • Al utilizar indicadores ambientales, el desarrollador puede especificar qué grupos (si los hay) de contratos se implementan en caliente en cada ejecución de prueba. Esto es muy específico de nuestro protocolo y probablemente diferirá de otras implementaciones.
  • Se encuentra la versión anterior del contrato en el nodo bifurcado y su código de bytes de implementación se reemplaza por el recién compilado

Un requisito importante es que un nodo de casco independiente se ejecute por separado del entorno de prueba de la bifurcación. Puede que no sea inmediatamente obvio, pero la ejecución de prueba de bifurcación creará otro tiempo de ejecución de nodo y utilizará el otro nodo en ejecución independiente como proveedor. El nodo independiente ejecutará todas las implementaciones (posteriores), y el nodo de prueba de bifurcación compilará los cambios, ejecutará dispositivos de prueba reemplazando cualquier código de bytes de contrato requerido y, finalmente, ejecutará la prueba. 

Es bueno tener en cuenta los cambios en los archivos de implementación, las ranuras de almacenamiento del contrato y su estado de configuración. Debe considerar que la ejecución de dispositivos de prueba requiere un reinicio del nodo independiente. 

Conclusión

A veces podemos sentirnos tentados a utilizar pruebas unitarias para partes de la plataforma que se integran con protocolos de terceros solo para poder desarrollarnos más rápido... a costa de la seguridad adicional que puede proporcionar una prueba de bifurcación. Para los desarrolladores de Origin, las implementaciones en caliente son un paso importante para cerrar la brecha de tiempo del ciclo y tratar de tener nuestro pastel y comérselo también. Espero que otros prueben esto y espero que usted pueda beneficiarse de este enfoque para reducir los tiempos del ciclo de desarrollo.

Moises
Moises
Origin
Stay in touch
Be the first to hear about important product updates. Your email will be kept private.
Organization
Team
Careers
Hiring!
Originally released by Origin Protocol
Privacy policyTerms of service