WEB3DEV Español

Cover image for Alpha: Hackeo de Optimización de Gas
Gabriella Alexandra Martinez Viloria
Gabriella Alexandra Martinez Viloria

Posted on

Alpha: Hackeo de Optimización de Gas

Esta guía comprensiva te guiará a estrategias para la minimización del uso de gas para tus contratos inteligentes de Ethereum.

La maestría en esto es vital porque los altos costos de despliegue, puede afectar la adopción de la blockchain y los costos de ejecución puede ahuyentar a los usuarios a que interactúen con tu aplicación descentralizada (dApp).

Hemos consolidado conocimientos desde fuentes confiables, los créditos estarán al final, para asegurar que tengas las tácticas más efectivas a tu disposición.

Estos tips serán clasificados bajo las categorías D y E. D representa las estrategias para salvar los costos de despliegue y E se enfoca en los métodos para minimizar los costos de ejecución en tus contratos inteligentes.

Punto #1: Usa la Optimización de Solidity (D || E)

En los entornos de desarrollo blockchain como Hardhat, Truffle o Foundry, está la habilidad para activar el optimizador y especificar el número de sus ejecuciones.

// En hardhat.config.js
Enter fullscreen mode Exit fullscreen mode
module.exports = {
 solidity: {
   version: "0.8.20",
   settings: {
     optimizer: {
       enabled: true,
       runs: 1000,  <------------------
     },
   },
 },
};
Enter fullscreen mode Exit fullscreen mode

Como explica la documentación oficial de Solidity, existe una compensación entre el número de ejecuciones y los costos asociados asociados al despliegue y ejecución. Pocas ejecuciones pueden llevar a bajos costos de despliegue, a costa de mayores costos de ejecución. A cambio, un mayor número de ejecuciones puede incrementar los costos de despliegue pero considerablemente reducen los costos de ejecución.

El número de ejecuciones (--optimize-runs) específica, aproximadamente, con cuánta frecuencia cada opcode del código desplegado será ejecutado entre el tiempo de vida del contrato. Esto quiere decir que esto es un parámetro compensado entre el tamaño del código (costo del despliegue) y el costo del código de ejecución (costo luego del despliegue). Un parámetro “runs” de “1” se producirá con más tiempo pero con un código de gas más eficiente. -Documentación de Solidity- The Optimizer

Punto #2: Uso Inmutable y Keywords Constantes (D && E)

Cuando un proyecto usa variables para almacenar datos, es aconsejable usar el keyword constant si el valor es identificable durante el desarrollo del código y no aplicado a cambios dentro del ciclo de vida del contrato, o el keyword immutable si el valor será conocido durante el despliegue del contrato (para que sea establecido en el constructor). De esta forma, el contenido de la variable está almacenado en el bytecode del contrato, no en el almacenaje.

Image description

El costo de despliegue puede ser ligeramente minimizado a través de este método pero, más importante, reduce significativamente los costos de ejecución. Esto es porque el valor de la variable se retira directamente desde el bytecode, no desde el almacenaje. Por lo tanto, salva el gas asociado con la operación SLOAD como si evitara el proceso costoso de leerlo desde el almacenaje.

Punto #3: Borra las variables sin usar

Dentro de las blockchains EVM, un reembolso de gas se garantiza cuando se despeja el espacio de almacenamiento. El acto de borrar una variable, da una gran cantidad de reembolso de gas, maximizado a la mitad del gasto de gas de toda la transacción.

Usar el keyword delete para remover una variable es equivalente a establecer la variable de vuelta a su valor inicial específico para sus tipos de datos, como cero para los números enteros o bytes32 (0) para bytes32.

Pronto, tendré otro artículo específicamente relacionado al Reembolso de Gas, los valores actuales e históricos.

Punto #4: Variables del paquete de almacenamiento (D)

Los datos almacenados de la EVM dentro de las ranuras de memoria de 256 bit. Los datos pequeños que son los 256 bits están contenidos dentro de una ranura simple, mientras que los datos más grandes están distribuidos junto a múltiples ranuras. Ya que actualizar cada ranura consume ranura de gas, los variables de paquete pueden optimizar el uso de gas, reduciendo el número de ranuras para actualizarlo dentro del contrato.

Por ejemplo, el compilador de Solidity sólo puede almacenar cuatro variables uint64 dentro de las mismas ranuras de memoria de 256-bit si están empaquetados. Si no, cada variable uint64 ocupará una ranura uint256 individual, desperdiciando el resto de los bits por ranura.

Punto #5: Evita almacenar datos innecesarios en la cadena

Para los casos donde los datos no son cruciales o no requieren accesibilidad de otros contratos inteligentes, es aceptable y usualmente beneficial emitir estos datos en la forma de un evento. La emisión de datos en los eventos permiten que sean documentados en el historial de bloques de la blockchain sin que sea accesible a otros contratos inteligentes.

Los eventos pueden que no sean confiables en el futuro para acceder a los valores históricos sino acceder los datos de forma instantánea ya que Ethereum puede que introduzca chain-pruning. Pruning es el proceso que envuelve borrar datos antiguos para optimizar la utilización del disco de espacio. Implementando la incrementación del pruning, los clientes Ethereum pueden administrar eficientemente sus necesidades de almacenamiento mientras aún mantienen los datos históricos necesarios, requeridos para las operaciones de la red.

Punto #6: Usa Assembly (D && E)

Cuando compilas un contrato inteligente de Solidity, es transformado en una serie de opcodes EVM (Ethereum Virtual Machine, Máquina Virtual de Ethereum).

Con Assemble, puedes escribir código muy cercano al nivel del opcode. No es fácil escribir código a ese nivel tan bajo, pero el beneficio es que puedes optimizar el opcode manualmente y superar el bytecode de Solidity en algunos casos.

Punto #7: Usa Calldata en vez de Memory (E)

Para los parámetros de función, usa calldata en vez de memory, ya que cuando memory es usado, las variables son copiadas a la memoria, haciendo el runtime sea caso. Sin embargo, si calladata se usa, el contenido de la variable no será copiado y sólo será leído desde calldata.

Punto #8: Usa Mapping en vez de Arrays (E)

Solidity ofrece dos tipos de datos distintos: arrays y mappings, para representar las listas de datos. Sin embargo, su sintaxis y la estructura divergen significativamente.

Mientras que los mapeos tienden a ser más eficientes y económicos en la mayoría de los escenarios, los arrays cuentan con iterabilidad y empaquetabilidad. Consecuentemente, la recomendación es usar los mapeos para manejar las listas de datos, a menos que la situaciones demanden iteración o la fiabilidad del paquete de tipo de datos.

Punto #9: Las Variables del Almacenamiento Caché antes de Usarlos (E)

Es esencial almacenar las variables del almacenamiento caché en la memoria antes de usarlas (usarlas varias veces o iterar entre ellas), cada vez que una variable de almacenamiento se lee o es escrita. Usa SLOAD/SSTORE respectivamente.

Sin embargo, cuando el valor es cacheado, usa MLOAD/MSTORE el cual es más barato que el mencionado arriba.

Punto #9: Usa el EIP-1167 Minimal Proxies (D)

Cuando un contrato es esperado que sea desplegado muchas veces, se espera que el contrato debería ser desplegado usando el patrón Proxies, uno de los patrones proxy más barato en términos de salvar despliegue es EIP-1167 Minimal Proxy, donde el contrato tendrá un bytecode mínimo que delegatecalls cualquier calldata que recibe a la implementación del contrato hardcodeada.

Nota: el uso de los proxies EIP-1167 serán ligeramente más caros en términos de costos runtime ya que cada vez que una llamada se hace, el costo de delegatecall se añade a la implementación del contrato.

Punto #10: Usa Custom Errors (D)

Custom Errors fueron introducidos en Solidity 0.8.4, como reemplazo para los errores de cadena, los cuales son más caros.

Punto #11: Limita los Errores de Cadena a menos de 32 caracteres (D)

Si los errores de cadena son usados, y no los errores personalizados, una forma de optimizar el uso de los errores de almacenamiento es hacer que la cadena del error sea menos que 32 caracteres.

Punto #12: Usa Unchecked (E)

Después de Solidity 0.8.0, las revisiones incorporadas introducidas en el compilador para evitar los desbordamientos o los flujos bajos. Estas revisiones cuestan gas, cuando el desarrollador está seguro que la variable no llegará al valor que hará que haya desbordamientos o flujos bajos, es posible envolver ese código en un bloque sin marcar.

Por ejemplo, para los bucles que usa uint256 para incrementar, no es posible llegar al máximo de uint256. De esta forma, puedes envolver la iteración dentro del bloque sin revisar.

Punto #13: Usa ++i en vez de i++ (E)

++i usa menos gas que i++. Sin embargo, ambos retornan valores distintos en la ejecución.

Punto #14: Usa Vyper (E)

Es importante tomar en cuenta que el uso de Vyper, generalmente, puede llevar a ejecuciones de código altamente optimizados, a veces sobrepasando la optimización del nivel de código escrito en Yul, el nivel de ensamblaje de Solidity. Vyper está orientado al contrato, el idioma de programación “pythonico” que apunta la Máquina Virtual de Ethereum (EVM).

https://twitter.com/0xz80/status/1674891842020622340?

Consejo

  • Usa un reporte de gas para rastrear el código de gas del código que escribes
  • Mira el tamaño del límite del código, definido en el EIP-170
  • No todas las optimizaciones que están escritas online son verdaderas, el compilador alguna veces optimizan algunos casos edge.

Conéctate Conmigo en Github, Twitter y LinkedId

Image description

Este artículo es una traducción de 0xAdnan, hecha por Gabriella Martínez. Puedes encontrar el artículo original aquí
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)