---
title: "Optimización de gas en Solidity: 12 técnicas para hacer tus smart contracts más económicos y eficientes"
description: "¿Quieres escribir mejor código y reducir tus fees de gas?"
---

# Optimización de gas en Solidity: 12 técnicas para hacer tus smart contracts más económicos y eficientes

<ImageBlock
  src="https://media.alchemy.com/1763564597-blog-gas-optimization.png"
  alt="Ilustración de una gasolinera que representa la optimización de gas en Solidity"
  width={2880}
  height={1620}
  priority
/>

Las comisiones de gas de Ethereum han sido históricamente un punto de dolor para el ecosistema: ¿qué usuario quiere pagar $20\+ en gas por una simple transacción onchain? Si bien las nuevas actualizaciones de Ethereum en los últimos años han reducido sustancialmente los costos de gas para los usuarios, optimizar el gas en tu código de [Solidity](https://www.alchemy.com/overviews/solidity) sigue siendo la principal manera de habilitar acciones onchain complejas en tu app sin arruinar a tus usuarios.

Ya sea que estés minimizando riesgos en revisiones de código o simplemente escribiendo contratos más limpios, hacer bien la optimización de gas significa habilitar aplicaciones seguras y económicas que escalen a millones de usuarios. En esta guía, repasaremos los conceptos básicos de gas, por qué importa la optimización \(spoiler: puede reducir costos entre 20-50% en comparación con código no optimizado\), y compartiremos 12 técnicas de optimización con ejemplos de código.

## ¿Qué es el gas y la optimización de gas en Solidity?

[Gas](https://ethereum.org/developers/docs/gas/) es la unidad de medida del esfuerzo computacional requerido para realizar operaciones específicas en Ethereum, y la optimización de gas en Solidity es el proceso de hacer que tu código de smart contracts en Solidity sea menos costoso de ejecutar.

Al transaccionar en Ethereum, cada transacción tiene un costo, el costo de escribir datos en storage o procesar una transacción, y ese costo se llama justamente "gas." Si no pagas comisiones de gas, no pasa nada, así como un auto necesita gas para moverse.

También necesitas ese gas para los contratos. Esto es lo que sucede cuando despliegas y ejecutas un smart contract:

1. Escribes [**código Solidity**](https://www.alchemy.com/overviews/solidity-smart-contract). Este es el lenguaje de programación de alto nivel y legible para humanos para smart contracts de Ethereum.
1. **El compilador lo convierte a bytecode.** Cuando compilas tu código Solidity, el compilador lo traduce a bytecode, una representación hexadecimal de instrucciones de bajo nivel. Ese bytecode es lo que efectivamente se almacena en la blockchain.
1. **El bytecode contiene opcodes.** Ese bytecode está compuesto por opcodes \(códigos de operación\), que son el conjunto de instrucciones del EVM: piénsalos como lenguaje ensamblador para Ethereum. Cada opcode representa una operación específica como "sumar dos números," "leer del storage," o "saltar a otra instrucción."
1. **El EVM ejecuta los opcodes.** Cuando alguien llama a tu smart contract, la [Ethereum Virtual Machine](https://www.alchemy.com/overviews/what-is-the-ethereum-virtual-machine-evm) \(EVM\) que corre en los nodos de la red lee el bytecode, lo decodifica en opcodes individuales, y los ejecuta uno por uno. Cada opcode tiene un costo de gas fijo.

Por ejemplo, el opcode `ADD` \(que suma dos números\) cuesta 3 gas, mientras que `SSTORE` \(que escribe en storage\) cuesta al menos 20,000 gas. El EVM suma el costo de gas de cada opcode que ejecuta durante tu transacción.

Para más información sobre el modelo de gas de Ethereum, consulta la [documentación oficial](https://ethereum.org/en/developers/docs/gas/).

La optimización de gas consiste en ajustar tu código para que haga el mismo trabajo, pero con menos operaciones, disminuyendo los costos de ejecución. Como mencionamos antes, cada transacción necesita gas \(pagado en ETH o equivalentes en distintas chains\). Después de [Dencun](https://consensys.io/ethereum-dencun-upgrade), la disponibilidad de datos es más económica gracias a los blobs, pero el gas de ejecución \(los costos de cómputo\) todavía puede acumularse. Los contratos optimizados no solo ahorran dinero a los usuarios, sino que también protegen contra ataques DoS al ser bases de código más livianas. Profundiza en los opcodes en la [documentación de "Gas Optimizer"](https://docs.soliditylang.org/en/latest/internals/optimizer.html).

## ¿Por qué la optimización de gas es importante para los desarrolladores?

Gas alto = UX frustrante que puede hacer que los usuarios dejen de usar tu app, especialmente durante picos de tráfico donde las comisiones de gas pueden dispararse.

Optimizar tu código para un uso bajo de gas puede disminuir las comisiones para tus usuarios, ofrecer una mejor UX y habilitar transacciones de bajo valor que de otro modo serían antieconómicas por las comisiones, además de manejar un uso alto sin alcanzar los límites de bloque.

Los contratos no optimizados pueden gastar [20-50% de gas extra](https://www.cs.toronto.edu/~fanl/papers/gas-brain21.pdf), elevando costos y abriendo puertas a exploits. Con el TVL de DeFi acercándose a [$150 mil millones a octubre de 2025](https://defillama.com/), los contratos eficientes en gas no son solo algo deseable, son una ventaja competitiva que puede definir la adopción de usuarios.

O eres la app con lógica de smart contract compleja que hace que los usuarios paguen las altas comisiones asociadas con esa complejidad, o optimizas tu código para hacer que la experiencia final de tus usuarios sea mejor y más económica.

## Las 12 mejores técnicas de optimización de gas en Solidity

Aquí tienes formas probadas de optimizar el uso de gas en tu código. Explicaremos cada ejemplo, cómo ahorra gas, y mostraremos el código. Te recomendamos probar esto en Remix o Hardhat para ver las diferencias por ti mismo.

### 1. Usa mappings en lugar de arrays

Solidity ofrece dos estructuras de datos principales para almacenar listas de datos: arrays y mappings. Aunque su sintaxis se ve similar, sirven para propósitos muy distintos y tienen costos de gas drásticamente diferentes.

Los arrays son colecciones ordenadas e iterables que almacenan elementos secuencialmente en memory o storage. Son útiles cuando necesitas recorrer todos los elementos o mantener un orden específico. Sin embargo, encontrar un elemento específico en un array requiere iteración: el EVM debe revisar cada elemento hasta encontrar una coincidencia. Esto significa que las operaciones de búsqueda tienen complejidad O\(n\), volviéndose más costosas a medida que tu array crece.

Los mappings \(también llamados tablas hash\) funcionan de manera completamente distinta. Usan una estructura clave-valor donde puedes recuperar instantáneamente cualquier valor por su clave, con búsquedas de tiempo constante O\(1\) sin importar cuántos elementos estén almacenados. Esto sucede porque Solidity calcula el storage slot directamente a partir de la clave usando una función hash, eliminando la necesidad de buscar entre los datos. La contrapartida es que los mappings no son iterables, no puedes recorrer todas las entradas ni saber qué claves existen sin llevar un registro por separado.

**El impacto en gas**: Crear y acceder a entradas de un mapping es significativamente más económico que las operaciones con arrays porque no hay overhead de iteración. Usa arrays solo cuando necesites específicamente iterar sobre todos los elementos o mantener el orden de inserción. Para todos los demás casos, especialmente balances de usuarios, registros de propiedad, o cualquier búsqueda basada en claves: los mappings son la opción clara.

Aquí un ejemplo usando un array \(más costoso para acceder\):

<CodeSnippet language="solidity" code={`string[] public cars = ["ford", "audi", "chevrolet"];

// To find "audi", you'd need to loop through the array
function findCar(string memory target) public view returns (bool) {
for (uint i = 0; i < cars.length; i++) {
if (keccak256(bytes(cars[i])) == keccak256(bytes(target))) {
return true; // Cost increases with array size
}
}
return false;
}`} />

Aquí los mismos datos usando un mapping \(mucho más económico para búsquedas\):

<CodeSnippet language="solidity" code={`mapping(uint => string) public cars;

constructor() {
cars[101] = "Ford";
cars[102] = "Audi";
cars[103] = "Chevrolet";
}

// Direct access - O(1) constant time regardless of data size
function getCar(uint id) public view returns (string memory) {
return cars[id]; // Single storage read, minimal gas
}`} />

Usar claves enteras te permite simular listas ordenadas sin los costos de iteración de los arrays. Esto es especialmente útil para datos de usuarios como balances o registros de propiedad donde necesitas acceso rápido y directo por ID. Para más información sobre cómo funcionan los mappings internamente, consulta la [documentación de mappings de Solidity](https://docs.soliditylang.org/en/latest/types.html#mapping-types).

### 2. Activa el optimizador del compilador de Solidity

El [optimizador del compilador de Solidity](https://docs.soliditylang.org/en/latest/internals/optimizer.html) es una herramienta poderosa que puede reducir significativamente los costos de gas, pero requiere configuración según tu caso de uso específico. El optimizador funciona analizando tu código y aplicando varias transformaciones: simplifica expresiones, elimina código muerto, inlinea funciones pequeñas para eliminar operaciones de salto costosas, y reutiliza segmentos de código duplicados. Todos estos cambios reducen la cantidad de opcodes que el EVM necesita ejecutar.

Sin embargo, hay una contrapartida importante controlada por el parámetro "runs." Este número le indica al optimizador cuántas veces esperas que cada opcode de tu contrato se ejecute durante su vida útil. El optimizador usa esto para balancear dos objetivos que compiten entre sí: minimizar los costos de deployment \(que ocurren una vez\) versus minimizar los costos de ejecución en runtime \(que ocurren repetidamente con cada llamada a función\).

Cómo funciona el parámetro runs:

- **Runs bajos \(por ejemplo, 200\)**: El optimizador prioriza un bytecode más pequeño, resultando en un deployment más económico. Esto significa menos duplicación de código y más saltos entre segmentos de código reutilizables. Ideal para contratos que desplegarás con frecuencia pero llamarás poco, como contratos factory o scripts de deployment de un solo uso.
- **Runs altos \(por ejemplo, 10,000\+\)**: El optimizador prioriza la eficiencia en runtime duplicando código para evitar saltos e inlineando funciones extensivamente. Esto genera un bytecode más grande \(deployment más costoso\) pero una ejecución de funciones más rápida y económica. Ideal para contratos con alto volumen de transacciones: como routers de DEX, contratos de staking, o marketplaces de NFT.

Aquí un ejemplo para apps con muchos deployments y runs bajos \(200\):

<CodeSnippet
  language="javascript"
  code={`module.exports = {  solidity: {    version: "0.8.9",    settings: {      optimizer: {        enabled: true, // Set to true for opt        runs: 200,      },    },  },};`}
/>

Y aquí un ejemplo para apps con pocos deployments optimizadas para runs altos \(10000\):

<CodeSnippet
  language="javascript"
  code={`module.exports = {  solidity: {    version: "0.8.9",    settings: {      optimizer: {        enabled: true,        runs: 10000,      },    },  },};`}
/>

#### Elegir el valor correcto de runs

Ambos enfoques tienen contrapartidas. Piensa en el ciclo de vida de tu contrato. Un token de gobernanza que se despliega una vez pero tiene millones de transacciones de transferencia debería usar runs altos. Un factory de deployment que crea contratos constantemente pero esos contratos rara vez se llaman debería usar runs bajos. En caso de duda, 200 es un valor por defecto seguro que balancea razonablemente bien ambas preocupaciones. Puedes probar con el perfil de tu contrato, y puedes configurar cualquiera de las dos opciones en [Hardhat config](https://hardhat.org/hardhat-runner/docs/config#solidity) o Remix.

### 3. Minimiza los datos onchain

El storage onchain es la operación individual más costosa en Solidity. Cada opcode `SSTORE` \(escribir en storage\) puede costar 20,000\+ gas \(medido en Gwei\), y modificar slots de storage existentes cuesta entre 2,900-5,000 gas. Compara eso con las operaciones de memory que cuestan apenas 3 gas, y rápidamente ves por qué la optimización de storage es crítica. El principio fundamental aquí es simple: almacena solo lo absolutamente esencial onchain, y maneja todo lo demás offchain a través de APIs, oráculos, o servicios de indexación.

Más allá de reducir las escrituras a storage, también deberías agrupar inteligentemente las operaciones para evitar costos redundantes. Esto mantiene tu contrato ligero, reduciendo tanto las comisiones de deployment como las de runtime, mientras dificulta que los atacantes exploten funciones computacionalmente pesadas que podrían usarse en ataques DoS.

#### Guardar datos en variables de storage

Sé implacable respecto a qué merece storage. Por ejemplo, los balances de usuarios, registros de propiedad, y el estado del contrato que debe ser inalterable y accesible globalmente: eso pertenece onchain. Si estás optimizando para gas, todo lo demás debería vivir offchain. Por ejemplo, si necesitas datos de precios externos, usa oráculos como Chainlink para obtenerlos durante la ejecución en lugar de almacenar precios históricos. Si necesitas rastrear el historial de transacciones para un frontend, emite eventos en lugar de almacenar arrays.

Un detalle importante sobre los eventos: aunque los eventos son económicos de emitir \(alrededor de 375 gas base \+ 375 gas por topic\), los contratos no pueden leer sus propios eventos. Los eventos existen puramente para consumo offchain por indexadores y frontends. Nunca uses eventos como sustituto de storage al que tu lógica de contrato necesita acceder.

#### Agrupar operaciones (batching)

En lugar de requerir que los usuarios envíen múltiples transacciones separadas, agrupa acciones relacionadas en una sola transacción. Esto ahorra la comisión base de transacción de 21,000 gas \(que se paga en cada transacción sin importar qué haga\) y también reduce operaciones redundantes como verificar `msg.sender` múltiples veces, cargar las mismas variables de storage repetidamente, o pagar costos de calldata por múltiples envíos de transacción.

Este patrón es especialmente útil para procesos de varios pasos como aprobaciones de tokens seguidas de transferencias, o ejecutar múltiples operaciones DeFi de forma atómica \(hacer swap, luego stake, luego reclamar recompensas\).

Aquí un ejemplo de una función de envío por lotes:

<CodeSnippet language="solidity" code={`Struct Call {
address recipient;
uint256 gas;
uint256 value;
bytes data;
}

function batchSend(Call[] memory \_calls) public payable {
for(uint256 i = 0; i < \_calls.length; i++) {
(bool \_success, bytes memory \_data) = \_calls[i].recipient.call{
gas: \_calls[i].gas,
value: \_calls[i].value
}(\_calls[i].data);

    if (!_success) {
        assembly {
            revert(add(0x20, _data), mload(_data))
        }
    }

}

}`} />

Este patrón ahorra significativamente en gas al eliminar verificaciones repetidas de `msg.sender`, reducir el overhead de calldata \(solo pasas el selector de función una vez\), y pagar la comisión base de transacción una sola vez en lugar de una vez por operación.

#### Loops

Los loops son multiplicadores de gas, cada iteración repite las mismas operaciones, y los costos se acumulan linealmente. Un loop sobre 100 elementos que realiza operaciones de storage podría fácilmente consumir 500,000\+ gas, y los loops sobre arrays sin límite pueden incluso exceder los límites de gas del bloque, haciendo tu función permanentemente imposible de llamar.

La solución casi siempre es eliminar el loop por completo. Usa mappings para búsquedas de tiempo constante O\(1\) en lugar de iteración de array O\(n\). Si absolutamente debes iterar, limita estrictamente el tamaño de los arrays, o mejor aún, mueve la iteración offchain y haz que los usuarios envíen índices o claves específicas.

#### Una nota sobre los reembolsos de gas

Solidity solía ofrecer reembolsos de gas por limpiar storage \(fijar valores en cero\), pero EIP-3529 redujo significativamente estos reembolsos. Aunque todavía obtienes un pequeño reembolso por limpiar storage, ya no es una estrategia de optimización mayor. Para detalles sobre la mecánica actual de reembolsos, consulta la [propuesta de reembolsos de gas de Ethereum](https://eips.ethereum.org/EIPS/eip-3529).

### 4. Usa eventos indexados

Los eventos son un mecanismo de logging ligero que cuesta una fracción de las operaciones de storage, alrededor de 375 gas base más 375 gas por parámetro indexado, comparado con 20,000\+ gas por escribir en storage. Los eventos se escriben en el receipt trie de la transacción, que es separado del storage de estado del contrato, haciéndolos perfectos para registrar información que las aplicaciones offchain necesitan rastrear.

La limitación crítica: los eventos son de solo escritura desde la perspectiva del contrato. Una vez emitidos, tu código de contrato no puede volver a leerlos. Los eventos existen puramente para consumo externo por frontends, indexadores, y herramientas de monitoreo. Usa eventos para notificaciones y registros históricos que los sistemas offchain necesitan, pero nunca para datos de los que tu lógica de contrato dependa.

Esto libera cantidades masivas de gas. En lugar de almacenar cada transacción en un array de storage costoso, emite un evento y deja que indexadores offchain \(como [The Graph](https://www.alchemy.com/dapps/the-graph) o las APIs de Alchemy\) construyan ese historial para tu frontend.

Aquí cómo declarar y emitir un evento:

<CodeSnippet language="solidity" code={`event MyFirstEvent(address indexed sender, uint256 indexed amount, string message);

function doSomething(uint256 \_amount, string memory \_message) public {
// Your contract logic here

    // Emit the event - cheap logging instead of expensive storage
    emit MyFirstEvent(msg.sender, _amount, _message);

}`} />

#### Parámetros indexados

Puedes marcar hasta 3 parámetros como `indexed`, lo que los hace buscables en consultas de logs. Por ejemplo, con `sender` y `amount` indexados, puedes filtrar rápidamente "todos los eventos donde sender = 0x123..." sin escanear cada evento. Los parámetros no indexados como `message` se siguen registrando pero no son directamente buscables.

Los casos de uso comunes incluyen transferencias de tokens, cambios de propiedad, transiciones de estado, y seguimiento de actividad de usuarios: cualquier lugar donde necesites un registro para consumo offchain pero no necesites acceso onchain. Para más detalles, consulta la [documentación de eventos de Solidity](https://docs.soliditylang.org/en/latest/contracts.html#events).

### 5. Empaqueta tus variables

El EVM almacena datos en slots de 32 bytes, y cada storage slot cuesta gas al escribir \(20,000\+ gas para slots nuevos, 2,900-5,000 gas para actualizaciones\). Al agrupar estratégicamente variables pequeñas, puedes ajustar múltiples variables en un solo slot, reduciendo drásticamente el número de operaciones `SSTORE` que tu contrato necesita.

Piénsalo como empacar una maleta eficientemente: el orden en que acomodas los elementos determina cuánto espacio desperdicias. Las variables se empaquetan en el orden en que las declaras, así que un acomodo cuidadoso es crucial.

Antes \(desperdicia espacio en 3 slots\):

<CodeSnippet
  language="solidity"
  code={`contract MyContract {
    uint128 c; // Slot 0 (uses 16 bytes, wastes 16 bytes)
    uint256 b; // Slot 1 (uses full 32 bytes)
    uint128 a; // Slot 2 (uses 16 bytes, wastes 16 bytes)
}`}
/>

Esto usa 3 storage slots aunque los datos solo necesitan 2.5 slots de espacio.

Después \(compacta en 2 slots\):

<CodeSnippet
  language="solidity"
  code={`contract MyContract {
    uint128 a; // Slot 0 (first 16 bytes)
    uint128 c; // Slot 0 (second 16 bytes) - packed together!
    uint256 b; // Slot 1 (full 32 bytes)
}`}
/>

Al declarar las dos variables `uint128` consecutivamente, comparten un solo slot, ahorrando una operación `SSTORE` completa cada vez que escribes en ambas variables.

Reglas clave de empaquetado:

- Las variables se empaquetan en el orden de declaración
- Un nuevo slot comienza cuando la siguiente variable no cabe en el slot actual
- Tipos más pequeños como `uint8`, `uint128`, `address` \(20 bytes\) son candidatos perfectos para empaquetar
- Aunque un tipo pequeño esté solo, sigue consumiendo un slot completo de 32 bytes, así que siempre intenta emparejarlos

Por ejemplo, dos variables `address` \(20 bytes cada una\) no se empaquetarán en un solo slot ya que 40 bytes excede los 32 bytes. Pero un `address` \(20 bytes\) más un `uint96` \(12 bytes\) encaja perfectamente en un slot de 32 bytes.

Para más detalles sobre cómo Solidity organiza el storage, consulta la [documentación de storage layout](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html).

### 6. Libera storage no utilizado

Cuando limpias variables de storage restableciéndolas a sus valores por defecto \(0 para enteros, `address\(0\)` para addresses, false para booleanos\), el EVM ofrece un reembolso de gas. Aunque EIP-3529 redujo significativamente estos reembolsos respecto a sus montos originales, todavía recuperas 4,800 gas por storage slot liberado: una recuperación significativa al limpiar datos obsoletos.

Esto funciona porque restablecer storage slots reduce el tamaño del estado de la blockchain, así que Ethereum incentiva este comportamiento de limpieza. Es una forma de recuperar algunos costos cuando los datos se vuelven obsoletos, manteniendo el estado de tu contrato ligero y eficiente.

Aquí cómo limpiar variables:

<CodeSnippet
  language="solidity"
  code={`delete myVariable; // Resets to default value and triggers refund// Or explicitly:
myInt = 0;
myAddress = address(0);
myBool = false;`}
/>

Nota importante sobre mappings: la palabra clave `delete` no funciona en mappings completos porque los mappings no rastrean qué claves existen. En cambio, debes eliminar entradas individuales del mapping:

<CodeSnippet language="solidity" code={`mapping(address => uint256) public balances;

// This won't work - can't delete entire mapping// delete balances;// Instead, delete specific keys:
delete balances[msg.sender]; // Clears this specific entry`} />

#### Casos de uso prácticos

Los reembolsos de gas son más útiles en contratos donde los datos tienen un ciclo de vida claro, piensa en contratos de escrow que se pueden limpiar después de completarse, autorizaciones temporales que expiran, o datos en caché que se vuelven obsoletos. No fuerces la lógica de tu contrato solo para perseguir reembolsos, pero cuando los datos naturalmente se vuelven obsoletos, limpiarlos es beneficioso para todos.

Para la mecánica y limitaciones actuales de reembolsos, consulta la [documentación de reembolsos de gas EIP-3529](https://eips.ethereum.org/EIPS/eip-3529).

### 7. Almacena datos en calldata en lugar de memory para ciertos parámetros de función

Al declarar parámetros de función, tienes la opción entre `memory` y `calldata` para tipos de referencia como arrays, strings, y structs. Entender la diferencia puede ahorrar gas significativo, especialmente para funciones externas con parámetros grandes.

**Calldata** es storage de solo lectura que vive directamente en los datos de la transacción. Cuando usas `calldata`, la función lee los argumentos directamente de la transacción sin copiarlos a ningún lado. Esta es la opción más económica porque evita por completo la asignación de memoria y las operaciones de copia.

**Memory**, por otro lado, requiere que el EVM asigne espacio y copie los datos de calldata a memory, ejecutando múltiples operaciones `MLOAD` y `MSTORE`. Este overhead de copiado se vuelve costoso con arrays o strings grandes: cada elemento copiado cuesta gas adicional.

**La regla**: Usa `calldata` para parámetros de funciones externas que solo necesitas leer. Usa `memory` solo cuando necesites modificar los datos dentro de tu función.

Aquí un ejemplo usando `calldata` \(más económico para acceso de solo lectura\):

<CodeSnippet
  language="solidity"
  code={`function processNumbers(uint[] calldata nums) external {
    for (uint i = 0; i < nums.length; i++) {
        // Read-only operations - no copy needed
        uint value = nums[i];
        // Do something with value
    }
}`}
/>

Compáralo con `memory` \(más costoso debido a la copia\):

<CodeSnippet
  language="solidity"
  code={`function processNumbers(uint[] memory nums) external {
    // Data gets copied from calldata to memory first (costs gas)
    for (uint i = 0; i < nums.length; i++) {
        uint value = nums[i];
    }
}`}
/>

El ahorro de gas escala con el tamaño de los datos, un array de 100 elementos pasado como `calldata` puede ahorrar miles de gas comparado con `memory`.

Cuándo debes usar memory: si tu función necesita modificar el array, agregarle elementos, o construir nuevas estructuras de datos, entonces `memory` es necesario ya que `calldata` es inmutable. Pero para operaciones de solo lectura, `calldata` siempre es la mejor opción.

Para más información sobre las diferencias entre ubicaciones de storage, consulta la [documentación de calldata vs memory](https://docs.soliditylang.org/en/latest/types.html#data-location).

### 8. Usa valores fijos immutable y constant

Las variables marcadas como `constant` o `immutable` no usan storage slots en absoluto: sus valores se incrustan directamente en el bytecode del contrato durante el deployment. Esto elimina operaciones costosas de `SLOAD` \(2,100 gas cada una\) cada vez que las accedes, reemplazando lecturas de storage con lecturas económicas de bytecode.

**Constant**: El valor debe fijarse en tiempo de compilación y no puede cambiar. Úsalo para valores fijos que nunca variarán entre deployments.

**Immutable**: El valor se fija una vez en el constructor y no puede cambiar después. Úsalo para valores que difieren entre deployments \(como direcciones de tokens o direcciones de owner\) pero permanecen fijos una vez desplegados.

Aquí cómo usar ambos:

<CodeSnippet
  language="solidity"
  code={`contract MyContract {
    uint256 constant FEE_RATE = 10; // Set at compile time
    address immutable owner;        // Set once at deployment
    
    constructor(address _owner) {
        owner = _owner; // Can only set in constructor
    }
    
    function calculateFee(uint256 amount) public pure returns (uint256) {
        return amount * FEE_RATE / 100; // No SLOAD - reads from bytecode
    }
}`}
/>

Ejemplo de ahorro de gas: si lees una variable de storage normal 10 veces en una función, eso son 21,000 gas en operaciones `SLOAD`. Con `constant` o `immutable`, esas lecturas cuestan esencialmente nada: solo el gas para ejecutar operaciones aritméticas básicas.

Casos de uso comunes:

- Tasas o porcentajes de comisiones de protocolo \(`constant`\)
- Constantes matemáticas como decimales o factores de escala \(`constant`\)
- Direcciones de tokens desde argumentos del constructor \(`immutable`\)
- Direcciones de owner o admin del contrato \(`immutable`\)
- Direcciones de contratos externos que no cambiarán \(`immutable`\)

Tanto las variables `constant` como `immutable` también pueden declararse a nivel de archivo fuera de los contratos, haciéndolas reutilizables entre múltiples contratos en el mismo archivo.

Para más detalles, consulta la [documentación de constants e immutables](https://docs.soliditylang.org/en/latest/contracts.html#constant-and-immutable-state-variables).

### 9. Usa el modificador de visibilidad external

Los modificadores de visibilidad de funciones afectan cómo el EVM maneja las llamadas a funciones y pueden impactar los costos de gas. La diferencia clave: las funciones `external` están optimizadas específicamente para llamadas desde fuera del contrato, mientras que las funciones `public` deben manejar tanto llamadas externas como internas, agregando overhead.

**Las funciones external** solo pueden llamarse desde fuera del contrato \(mediante transacciones u otros contratos\). Al llamarse externamente, leen parámetros directamente de calldata sin copiarlos, haciéndolas ligeramente más eficientes. No puedes llamar una función external internamente usando `functionName\(\)`necesitarías usar `this.functionName\(\)`, lo cual crea una costosa llamada externa.

**Las funciones public** pueden llamarse tanto externa como internamente. Esta flexibilidad requiere que el compilador genere código adicional para manejar ambos tipos de llamada, agregando un pequeño overhead de gas incluso cuando se llaman externamente.

Una regla general: usa `external` para funciones que forman parte de la API pública de tu contrato y solo serán llamadas por usuarios u otros contratos. Usa `internal` o `private` para funciones auxiliares que solo las propias funciones de tu contrato necesitan llamar.

Aquí un ejemplo de una función external \(optimizada para llamadas externas\):

<CodeSnippet
  language="solidity"
  code={`function updateMessage(string calldata _newMessage) external returns (string memory) {
    message = _newMessage;
    return message;
}`}
/>

Compáralo con public \(maneja tanto interno como externo\):

<CodeSnippet
  language="solidity"
  code={`function updateMessage(string memory _newMessage) public returns (string memory) {
    message = _newMessage;
    return message;
}`}
/>

El ahorro de gas es modesto: típicamente alrededor de 20-50 gas por llamada, pero se acumulan en miles de transacciones. Más importante aún, usar `external` señala claramente la intención: esta función está pensada para llamarse desde fuera del contrato.

Tip adicional: ¿Notaste cómo la versión `external` usa `calldata` para el parámetro string? Las funciones external combinan perfectamente con argumentos calldata ya que ambos están optimizados para llamadas externas.

Para más información sobre visibilidad de funciones y buenas prácticas, consulta la [documentación de visibilidad](https://docs.soliditylang.org/en/latest/contracts.html#visibility-and-getters).

### 10. Usa aritmética unchecked de forma segura

A partir de Solidity 0.8.0, el compilador agrega automáticamente verificaciones de overflow y underflow a todas las operaciones aritméticas. Aunque esto previene bugs, cuesta aproximadamente 30-40 gas por operación. Cuando tengas certeza de que el overflow/underflow es matemáticamente imposible, envuelve las operaciones en bloques `unchecked` para saltarte estas verificaciones y ahorrar gas.

**Cuándo es seguro**: contadores de loop con límites conocidos, aritmética donde has validado los inputs, u operaciones donde el overflow es matemáticamente imposible.

**Cuándo evitarlo**: valores proporcionados por usuarios sin validar, cálculos financieros, o cualquier lugar donde el overflow pueda crear vulnerabilidades.

Aquí un ejemplo básico:

<CodeSnippet
  language="solidity"
  code={`function add(uint x, uint y) external pure returns (uint) {
    unchecked {
        return x + y; // Skips overflow check - saves ~30 gas
    }
}`}
/>

Aquí otro ejemplo mostrando contadores de loop, el caso de uso más común para esta técnica:

<CodeSnippet
  language="solidity"
  code={`function processArray(uint[] calldata items) external {
    for (uint i = 0; i < items.length;) {
        // Process item
        
        unchecked {
            ++i; // i can never realistically overflow
        }
    }
}`}
/>

En este loop, `i` comienza en 0 e incrementa en 1 cada iteración. Para que `i` haga overflow, el array necesitaría 2^256 elementos, lo cual es físicamente imposible dadas las restricciones de blockchain. Este es un candidato perfecto para `unchecked`.

Nota importante de seguridad: si usas `unchecked`, agrega declaraciones `require` manuales para validar inputs que podrían causar problemas:

<CodeSnippet
  language="solidity"
  code={`function subtract(uint x, uint y) external pure returns (uint) {
    require(x >= y, "Underflow prevented");
    unchecked {
        return x - y; // Safe because validated above
    }
}`}
/>

El ahorro de gas de los bloques `unchecked` se acumula rápidamente en loops o funciones llamadas con frecuencia. Para más detalles y casos límite, consulta la [documentación de unchecked](https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic).

### 11. Minimiza las llamadas externas

Cada llamada a otro contrato cuesta al menos 100 gas por el opcode `CALL`, más costos adicionales por calldata y cualquier cambio de estado en el contrato llamado. Estas llamadas también pueden fallar de forma impredecible si el contrato externo revierte, haciéndolas tanto costosas como riesgosas. Cuando necesites datos de contratos externos, agrupa múltiples llamadas o cachea resultados para evitar llamadas repetidas dentro de la misma transacción.

Enfoque eficiente en gas:

<CodeSnippet language="solidity" code={`interface IExternal {
    function getData() external view returns (uint);
}

function processData(address addr) external {
// Call once and cache the result
uint data = IExternal(addr).getData();

    // Reuse cached value multiple times - no additional calls
    uint result1 = data * 2;
    uint result2 = data + 100;
    uint result3 = data / 5;

}`} />

Enfoque ineficiente \(evítalo\):

<CodeSnippet
  language="solidity"
  code={`function processData(address addr) external {
    // Calling three separate times - wastes ~300 gas
    uint result1 = IExternal(addr).getData() * 2;
    uint result2 = IExternal(addr).getData() + 100;
    uint result3 = IExternal(addr).getData() / 5;
}`}
/>

Si necesitas los mismos datos múltiples veces en una transacción, siempre cachéalos en una variable local. Si necesitas datos externos en múltiples transacciones, considera almacenarlos \(aunque debes sopesar el costo de 20,000 gas de `SSTORE` contra la frecuencia de llamadas externas\).

Para patrones y consideraciones de seguridad en torno a llamadas externas, consulta las [mejores prácticas de llamadas externas](https://docs.soliditylang.org/en/latest/security-considerations.html#use-the-checks-effects-interactions-pattern).

### 12. Usa assembly para rutas críticas

Para código crítico en rendimiento como loops estrechos o funciones llamadas con frecuencia, puedes bajar a assembly Yul para optimizar manualmente más allá de lo que el compilador de Solidity puede lograr. El assembly te da control directo sobre la gestión de memoria, te permite saltarte verificaciones de seguridad, y elimina el overhead de abstracción. Sin embargo, es un arma de doble filo, el assembly evita todas las características de seguridad de Solidity, haciendo el código más difícil de leer y extremadamente propenso a errores.

**Cuándo considerar assembly**: operaciones de alta frecuencia, manipulación compleja de bits, layouts de memoria personalizados, o loops que se ejecutan miles de veces donde cada unidad de gas cuenta.

**Cuándo evitar assembly**: en cualquier otro caso. El ahorro de gas rara vez justifica el mayor riesgo de bugs, vulnerabilidades de seguridad, y carga de mantenimiento.

Aquí un ejemplo de sumar un array usando assembly:

<CodeSnippet
  language="solidity"
  code={`function sum(uint[] memory arr) public pure returns (uint s) {
    assembly {
        let len := mload(arr)                    // Load array length
        let data := add(arr, 0x20)               // Skip length, point to data
        
        for { let i := 0 } lt(i, len) { i := add(i, 1) } {
            s := add(s, mload(add(data, mul(i, 0x20))))  // Load and sum each element
        }
    }
}`}
/>

Esta versión en assembly ahorra gas manipulando directamente punteros de memoria y saltándose verificaciones de límites, pero es significativamente más difícil de entender y auditar comparada con el código Solidity equivalente.

#### Prácticas críticas de seguridad

- Prueba exhaustivamente el código assembly con casos límite
- Agrega comentarios extensos explicando cada operación
- Haz que expertos en seguridad auditen las secciones de assembly
- Usa assembly solo como último recurso después de agotar las optimizaciones de Solidity

Para la mayoría de los desarrolladores y casos de uso, las otras 11 técnicas de esta guía brindarán mejores ahorros de gas con mucho menos riesgo. Solo recurre a assembly cuando hayas perfilado tu contrato, identificado cuellos de botella específicos, y confirmado que el ahorro de gas justifica la complejidad adicional.

Para aprender la sintaxis y capacidades de Yul, consulta la [documentación de Yul](https://docs.soliditylang.org/en/latest/yul.html).

## Prueba tu smart contract antes del deployment

Antes de desplegar a mainnet, prueba rigurosamente tu uso de gas usando herramientas de desarrollo. [Hardhat](https://hardhat.org/) ofrece plugins de reporte de gas que generan desgloses detallados de costos de gas por función. [Remix IDE](https://remix.ethereum.org/) muestra estimaciones de gas en tiempo real mientras pruebas. Enfoca tus esfuerzos de optimización en funciones de cara al usuario como mints, transferencias, y swaps: estas son las que se llaman con más frecuencia y tienen el mayor impacto en la experiencia de usuario.

Para pruebas más profundas, usa las capacidades de fuzzing de [Foundry](https://book.getfoundry.sh/) para hacer benchmark de tus optimizaciones a través de miles de inputs aleatorios, asegurando que tu ahorro de gas se mantenga bajo condiciones reales y casos límite.

## Para cerrar: a optimizar

Ahora tienes 12 técnicas probadas para hacer tus contratos de Solidity más ligeros y económicos de ejecutar. La optimización de gas no se trata solo de ahorrar dinero: se trata de construir mejores experiencias para tus usuarios y crear aplicaciones con las que realmente quieran interactuar.

Comienza implementando lo más sencillo: activa el optimizador del compilador, usa mappings en lugar de arrays, y marca los valores fijos como constant o immutable. Luego perfila tus contratos para encontrar cuellos de botella y aplica las técnicas más avanzadas donde tengan mayor impacto.

**Recursos para seguir aprendiendo**:

- [Guías de Solidity de Alchemy](https://www.alchemy.com/overviews/solidity-tutorial)
- [Contratos estandarizados de OpenZeppelin](https://docs.openzeppelin.com/contracts/)
- [Guía de campo sobre seguridad de smart contracts](https://scsfg.io/)

Ahora ve y empieza a optimizar, ¡tus usuarios \(y sus billeteras\) te lo agradecerán!

## Preguntas frecuentes

### ¿Cuáles son algunas de las formas más efectivas de reducir los costos de gas en smart contracts de Solidity?

Las técnicas de mayor impacto incluyen usar mappings en lugar de arrays para búsquedas, activar el optimizador del compilador de Solidity con la configuración de runs adecuada, marcar valores fijos como `constant` o `immutable`, minimizar operaciones de storage, y usar `calldata` en lugar de `memory` para parámetros de función de solo lectura.

### ¿Cómo ayuda el uso de variables `constant` y `immutable` con la optimización de gas?

Los valores `constant` y `immutable` se incrustan directamente en el bytecode del contrato en lugar de almacenarse en costosos storage slots, eliminando operaciones costosas de `SLOAD` \(2,100 gas cada una\) y reemplazándolas con lecturas económicas de bytecode.

### ¿Por qué debería usar mappings en lugar de arrays para búsquedas de datos?

Los mappings brindan búsquedas de tiempo constante O\(1\) sin importar el tamaño de los datos, mientras que los arrays requieren iteración O\(n\) que se vuelve más costosa a medida que el array crece. Los mappings son significativamente más económicos para patrones de acceso basados en claves como balances de usuarios o registros de propiedad.

### ¿Cómo funciona el optimizador del compilador de Solidity y qué configuración de runs debería usar?

El optimizador reduce el gas simplificando expresiones, eliminando código muerto, e inlineando funciones. Usa runs bajos \(200\) para contratos que desplegarás con frecuencia pero llamarás poco, y runs altos \(10,000\+\) para contratos con alto volumen de transacciones donde la eficiencia en runtime importa más que el costo de deployment.

### ¿Cuál es la diferencia entre usar `calldata`, `memory`, y `storage` para optimización de gas?

Usa `calldata` para parámetros de funciones externas que solo lees \(evita costos de copia\), `memory` para manipulación temporal de datos, y `storage` solo cuando necesites estado persistente. `calldata` siempre es más económico que `memory` para operaciones de solo lectura.

### ¿Cuándo debería usar bloques de aritmética `unchecked` de forma segura?

Usa `unchecked` para operaciones donde el overflow es matemáticamente imposible, como contadores de loop con límites conocidos u operaciones aritméticas validadas. Esto ahorra ~30-40 gas por operación al saltarse las verificaciones automáticas de overflow introducidas en Solidity 0.8.0.

### ¿Cómo puedo optimizar las operaciones de storage para reducir costos de gas?

Empaqueta múltiples variables pequeñas en storage slots individuales de 32 bytes, elimina storage no utilizado para obtener reembolsos de gas \(4,800 gas por slot liberado\), y minimiza las escrituras a storage cacheando valores en memory durante la ejecución de la función.

### ¿Cuáles son los beneficios de usar el modificador de visibilidad `external` sobre `public`?

Las funciones `external` están optimizadas específicamente para llamadas desde fuera del contrato y funcionan eficientemente con parámetros `calldata`, ahorrando 20-50 gas por llamada comparado con las funciones `public` que deben manejar tanto llamadas internas como externas.
