
Introducción
Para superar estas limitaciones, se han desarrollado varias soluciones de capa 2, como Base, que están controladas por la escalabilidad y la reducción de los gastos de gas. Aunque las L2 tienen recortes significativos en las tarifas con respecto a la red principal de Ethereum, los desarrolladores de contratos inteligentes no pueden ignorar la optimización del gas en su desarrollo.
Este tutorial tratará de consolidar la experiencia del usuario y crear aplicaciones descentralizadas más competitivas mediante estrategias avanzadas que reduzcan el coste del gas en los contratos inteligentes que se probarán. Los ejemplos que proporcionan se simulan con contratos sencillos y tienen en cuenta principalmente los precios del gas en tiempo de ejecución, ya que los costes de implementación varían mucho en función del tamaño del contrato.
La optimización del gas es importante para los desarrolladores, los usuarios y el éxito a largo plazo del proyecto. El uso económico del gas de los contratos inteligentes puede garantizar que los protocolos sean más rentables y escalables, y menos propensos a amenazas de seguridad como los ataques de denegación de servicio.
La aplicación práctica de los contratos inteligentes requiere una auditoría exhaustiva y sistemática de cada contrato inteligente.
La importancia de la optimización del gas de Solidity
La optimización del gas permite que los contratos inteligentes, los protocolos y los proyectos funcionen en condiciones de congestión de la red y los hace más baratos y eficientes. Al mejorar el código del contrato, existe la posibilidad de revelar posibles vulnerabilidades y protocolos, y los usuarios se sienten más seguros.
La optimización del gas también es un aspecto importante del desarrollo. No se trata simplemente de una característica agradable de tener, sino de una clave para el éxito y la seguridad a largo plazo de los contratos inteligentes. El hecho de que sea posible construir en L2 con tarifas comparativamente bajas no implica que las tarifas de gas no vayan a ser significativamente más altas que las de la competencia.
Todas las pruebas utilizan Foundry y Solidity versión 0.8.13, el nodo local de cadena de bloques Anvil, comandos de prueba forge y 100 ejecuciones de optimización.
Las empresas de auditoría profesionales deben participar en las comprobaciones de seguridad completas; esta guía debe ser complementaria.
Reducir los datos en cadena
Los tokens no fungibles se hicieron populares en 2021 y despertaron el interés por los NFT totalmente en cadena. Los NFT en cadena, en comparación con los NFT tradicionales que utilizan datos fuera de cadena, incluidos metadatos y referencias de imágenes, colocan toda la información directamente en la cadena de bloques.
Estas tokens son muy costosas para comunicarse y las soluciones híbridas son la opción predeterminada cuando los usuarios se ven afectados por las tarifas. Independientemente de si estás creando NFT, juegos o protocolos DeFi, siempre debes pensar en qué datos es realmente necesario almacenar en la cadena de bloques y en las ventajas e inconvenientes de ambas alternativas.
Reducir drásticamente el uso de gas almacenando la información fuera de la cadena, lo que requiere menos almacenamiento de variables. Un método específico consiste en utilizar eventos para almacenar datos fuera de la cadena en lugar de en el almacenamiento real dentro de la cadena.
Los costes de gas de las transacciones aumentan debido a las funciones de emisión adicionales que generan los eventos; sin embargo, como la información no se almacena en la cadena, el ahorro suele superar los costes.
Considera un contrato inteligente que permite a los usuarios votar verdadero o falso. El primero es aquel que almacena los votos de los usuarios en estructuras en cadena. Una demostración de la prueba de la función de voto con Foundry con más de 100 iteraciones muestra ciertos costes de gas.
Compáralo con un contrato inteligente que no almacenaría información en la cadena, pero que emitiría un evento cuando se invocara la función de votación. Una prueba en la que se utilizó Foundry más de 100 veces en la nueva función de votación proporciona resultados.
La reducción de los datos en cadena redujo el gas medio en un 90,34 %.
Para acceder a datos fuera de la cadena en la cadena, las funciones de Chainlink, soluciones de soporte, están disponibles para integrarse con las redes L2 más populares, como Base.
Usa asignaciones sobre matrices
Hay dos estructuras de datos principales en Solidity: matrices y asignaciones.
- Las matrices se utilizan para almacenar conjuntos de elementos que se atribuyen a determinados índices.
- Las asignaciones clave-valor son estructuras de datos que permiten el acceso directo a los datos mediante claves únicas.
Aunque las matrices pueden ser útiles para almacenar vectores y otros datos similares, normalmente se prefieren las asignaciones debido a su eficiencia en cuanto al consumo de gas. Se adaptan de forma óptima a situaciones en las que se necesitan datos bajo demanda, por ejemplo, por nombre, dirección de monedero o saldo de cuenta.
Para comprender el uso de gas al utilizar matrices o mapeos, es posible que tengas que consultar el gas utilizado por los códigos de operación EVM asociados. Los códigos de operación son instrucciones de bajo nivel que la máquina virtual Ethereum ejecuta en la ejecución de contratos inteligentes, y cada código de operación tiene un coste de gas.
Para acceder a los valores de la matriz, debes pagar por unidad de gas utilizada por los códigos de operación EVM. Almacenar las direcciones de los usuarios y los saldos correspondientes en forma de matriz requiere recorrer todos los elementos de la matriz, comprobar si la dirección de usuario y el argumento proporcionado coinciden y devolver el saldo en caso de coincidencia.
Pasar directamente al saldo específico del usuario evitaría la necesidad de revisar todos los elementos de la matriz. Una vez que las matrices se sustituyeron por mapeos, se utilizó un 89 % menos de gas para acceder a los datos.
Comparación del uso de gas: matrices frente a mapeos
| Método | Antes de la optimización | Después de la optimización | Ahorro de combustible |
|---|---|---|---|
| Acceso a los datos | 30 586 | 3081 | 89 % |
| Adición de datos | - | - | 93 % |
Utiliza constantes e inmutables para reducir los costes de gas de los contratos inteligentes
Otro consejo de optimización sería utilizar variables constantes e inmutables. Al declarar las variables como inmutables o constantes, sus valores solo se proporcionan en el momento de la creación del contrato y no más allá.
No ocupan espacio de almacenamiento en la EVM en comparación con otras variables. Es posible codificar directamente los valores en el código byte del contrato inteligente, lo que significa que el coste de almacenamiento de los datos se reduce por el hecho de que variables como maxSupply y owner no tienen las palabras clave constant o immutable.
Cuando las pruebas se ejecutan 100 veces, el coste medio del gas es de 112 222 unidades. Dado que maxSupply y owner son valores conocidos que no se pretende cambiar, la mejor opción es declarar el suministro máximo y el propietario que no utilizan espacio de almacenamiento.
Las pruebas con variables constantes o inmutables dan como resultado un ahorro medio del 35,89 % (de 112 222 a 71 940 unidades de gas).
Optimiza las variables no utilizadas
Una recomendación obvia para optimizar el gas es optimizar las variables en los contratos inteligentes. Sin embargo, en la mayoría de los casos, las variables que no son útiles se conservan en la ejecución del contrato, lo que conduce a un uso innecesario de gas.
La eliminación de variables únicas no utilizadas puede servir para ahorrar costes de gas, una media del 18 % si se tienen en cuenta los contratos con variables no utilizadas definidas y manipuladas en funciones, pero que nunca se invocan en otros lugares.
Resultados de la optimización de variables no utilizadas
| Fase de optimización | Consumo de gas | Ahorros |
|---|---|---|
| Antes de la optimización | 32 513 | - |
| Después de la optimización | 27 429 | 18 % |
Reembolso de gas de Solidity: eliminación de variables no utilizadas.
La eliminación de variables no utilizadas es un intento de asignar valores predeterminados a las variables después de que se hayan calculado por completo sin que los datos se almacenen en la memoria. A modo de ejemplo, el valor predeterminado de las variables del tipo uint es 0.
Al no destruir las variables de la función cuando esta finaliza, el coste medio de gas es de 100 300 unidades para asignar variables a los datos. Ahorro medio de gas del 19 % utilizando la tecla de borrar para eliminar variables de datos (para establecer las variables en 0).
Utiliza matrices de tamaño fijo en lugar de matrices dinámicas para minimizar los costes de gas de los contratos inteligentes
Utiliza mapeos siempre que sea posible para reducir los costes de gas de los contratos inteligentes. No obstante, cuando se requieran matrices, las matrices de tamaño fijo son más económicas en términos de gas que las matrices de tamaño dinámico, ya que estas últimas pueden expandirse indefinidamente, lo que conlleva mayores costes de gas.
Las matrices dinámicas se pueden ampliar, por lo que la EVM debe supervisar las longitudes y actualizarlas cuando se añadan nuevos elementos.
Considera el código que define matrices de tamaño dinámico y las actualiza con funciones updateArray. Las instrucciones require se asegurarán de que los índices proporcionados estén dentro de un rango de matriz de tamaño fijo. Cuando se ejecutan 100 repeticiones de una prueba, la cantidad promedio de gas que se utilizó es 12 541.
Cambiar las matrices a un tamaño fijo de 5 crea matrices de tamaño fijo de longitud 5, tipo uint256. La EVM sabe que fixedArray con tamaño de variable de estado 5 tiene 5 ranuras, y la longitud no se almacena en el almacenamiento. Sustituir las matrices dinámicas por fijas ahorra un 17,99 % de gas.
Optimiza tus contratos inteligentes hoy mismo.
Empieza a implementar estas técnicas de optimización de gas para reducir los costes hasta en un 90 % en Base y otras cadenas L2.
Uso de uint8 como sustituto de uint256
Esto es menos eficiente y puede resultar más caro de usar que uint256 debido al funcionamiento de la EVM. La EVM tiene tamaños de palabra de 256 bits. Las operaciones con enteros de 256 bits (uint256) suelen ser las más eficientes, ya que se ajustan al tamaño de palabra de la EVM, mientras que los tipos más pequeños (como uint8) requieren que el compilador Solidity cree operaciones adicionales para ajustar los tipos más pequeños a una sola ranura de almacenamiento de 256 bits.
En general, aunque los tipos pequeños como uint8 pueden ser beneficiosos en lo que respecta al almacenamiento (se pueden agrupar varios tipos pequeños en una sola operación con una ranura de almacenamiento de 256 bits), se ha observado que los tipos más pequeños solo ayudan con el almacenamiento. El ahorro de almacenamiento puede anularse al convertir a uint256 y viceversa para realizar cálculos.
Combinación de variables menores de 256 bits
Combinar variables de menos de 256 bits de tamaño no suele ser tan eficiente como las variables de 256 bits. Sin embargo, las situaciones de falta de interoperabilidad obligan a utilizar tipos menos potentes en tamaños más pequeños, por ejemplo, booleanos que consumen 1 byte u 8 bits para su almacenamiento.
Cuando se utilizan tipos menos potentes, es posible declarar variables de estado teniendo en cuenta el espacio de almacenamiento, y Solidity puede agruparlas y almacenarlas utilizando la misma ranura de almacenamiento. La ventaja del empaquetado de variables se suele considerar en las operaciones de almacenamiento, más que en las operaciones de memoria o de pila.
Dado que los tamaños de combinación bidireccionales son de 16 bits, o 240 bits menos que la capacidad para almacenar una sola ranura de almacenamiento, Solidity se puede utilizar para empaquetar variables en la misma ranura y minimizar así el uso de gas de implementación, ya que no se necesitan muchas ranuras para almacenar variables de estado.
Reorganizar las declaraciones de variables permite optimizar un 13 % de gas de media.
Resultados del empaquetado de variables
| Etapa | Consumo de gas | Ahorros |
|---|---|---|
| Preoptimización | 1678 | - |
| Optimización posterior | 1447 | 13 % |
Selecciona el modificador de visibilidad externa
Para maximizar el gas del contrato inteligente, es razonable seleccionar la visibilidad adecuada de las funciones. Los modificadores de visibilidad externa pueden ser más eficientes en el uso del gas que los públicos debido a la forma en que una función pública gestiona sus argumentos y a la manera en que los datos se transfieren a las funciones.
Las funciones externas pueden acceder a calldata, que es un espacio temporal de solo lectura en el EVM que contiene los parámetros de una llamada a una función. Se pueden llamar funciones públicas:
- Externamente (cuando son llamados externamente por una transacción utilizando calldata)
- Internamente (cuando se llaman dentro de un contrato utilizando calldata).
Dado que las funciones son públicas, deben recibir matrices en la memoria, lo que puede suponer un coste elevado de gas en caso de que sean grandes. Cambiar las funciones a externas permite la recepción de matrices en calldata, lo que resulta más rentable en situaciones que implican matrices grandes, ya que se ahorra una media del 0,3 % de unidades de gas por llamada.
Almacenamiento para volver a leer el mismo valor.
Una buena técnica para ahorrar gas es no leer el mismo valor en el almacenamiento muchas veces. Leer desde el almacenamiento tiene un coste mayor que leer desde la memoria. Releer las mismas variables en el almacenamiento muchas veces y escribir en el almacenamiento cuando no es necesario puede suponer un desperdicio de gas en los contratos inteligentes.
Esto se puede evitar definiendo variables que estén en la memoria al inicio de las funciones y asignándoles valores de variables numéricas.
Esto ahorra un 17 % del gas consumido por las funciones sumNumbers, pero cuanto mayor sea la matriz, mayores serán los números de iteración y el gas utilizado.
Variables de almacenamiento en caché Resultados
| Etapa | Consumo de gas | Ahorros |
|---|---|---|
| Antes de la optimización | 3527 | - |
| Después de la optimización | 2905 | 17 % |
No inicialices las variables con valores predeterminados
Cuando se declaran variables de estado sin inicializarlas (es decir, sin asignarles un valor inicial), estas variables se inicializan automáticamente con valores predeterminados:
- uint: 0
- bool: false
- dirección: dirección(0)
Esto último es más barato que declarar valores por defecto y simplemente actualizarlos cuando el usuario interactúa con el sistema, lo cual no es eficiente en términos de gas. Sin inicialización de variables, la optimización indica un ahorro medio de gas del 4 %.
Compatibilidad con la optimización del compilador Solidity
Solidity incluye compiladores con ajustes que son fáciles de modificar para optimizar el código compilado. La compilación optimizada se ejecuta cientos de veces, optimizando el código y convirtiéndolo a formas más económicas que requieren menos gas para ejecutarse.
Los compiladores se pueden ajustar para lograr el equilibrio adecuado entre los costes de implementación y de tiempo de ejecución. Definición del número estimado de contratos que se ejecutarán con el uso de los comandos de ejecución:
- Los niveles más altos son óptimos en términos de precios bajos del gas cuando se aplica el contrato
- Un número menor es óptimo en términos de reducción de los costes de gas en la implementación del contrato
Optimizar indicadores más el valor de run=200 indica a los compiladores que optimicen el código para ahorrar gas al ejecutar las funciones incrementCount 200 veces. Clasifica estos ajustes según los requisitos específicos de la aplicación.
Bonificación Optimización de gas de Solidity: ensamblaje
Cuando escribes contratos inteligentes en Solidity, estos se compilan en códigos de bytes, una secuencia de códigos de operación EVM. A través del ensamblador, puedes simplemente escribir código que se ejecute en niveles más compatibles con los códigos de operación y, en algunos casos, la capacidad de optimizar manualmente los códigos de operación le da una ventaja sobre el código de bytes de Solidity.
Aunque escribir código a un nivel tan bajo no es una tarea sencilla, tiene la ventaja de permitir optimizar los códigos de operación manualmente, por lo que, en general, es mejor que el código byte de Solidity. Este nivel de optimización le confiere mayor eficiencia y eficacia en la ejecución del contrato.
Incluso en casos sencillos en los que se supone que las dos funciones suman dos números, las versiones simples de Solidity y Assembly tendrán algunas diferencias, pero las versiones de Assembly son más baratas.
La implementación de proyectos en L2 como Base supondrá que los usuarios tendrán un menor coste por utilizar los protocolos, pero es responsabilidad de los desarrolladores garantizar que implementan las técnicas de optimización de gas. Estas sugerencias pueden ahorrar significativamente los costes de las transacciones, añadir escalabilidad y mejorar la eficiencia del contrato en general.
Se puede utilizar Assembly para optimizar la ejecución de contratos inteligentes utilizando gas, pero puede haber casos en los que las versiones de Assembly den lugar a la creación de código inseguro. Existen sugerencias sólidas para involucrar a los expertos en seguridad de contratos inteligentes en la revisión de los contratos antes de su implementación.


