Cómo evitar que tu primer contrato inteligente en Solidity se convierta en una máquina tragamonedas de gas: una guía de optimización paso a paso
15 de septiembre de 2026
¿Sabes esa sensación de tirar una moneda en una máquina tragamonedas y ver cómo se la traga sin darte nada a cambio? Pues eso mismo sentí yo la primera vez que desplegué un contrato en Solidity. Cada función era un trago más de gas. Cada transacción, una palanca que bajaba y me dejaba más pobre. Y lo peor: el contrato funcionaba. Simplemente funcionaba carísimo.
Hoy te cuento cómo pasé de tener un contrato que parecía diseñado por alguien que odiaba a los usuarios, a uno que optimicé hasta dejarlo eficiente y hasta elegante. Porque sí, optimizar gas también puede ser bonito. Vamos allá.
El caso: "RegistroDeUsuarios.sol" o cómo vaciar la cartera de tus usuarios
Todo empezó con un contrato sencillo. Un registro de usuarios con nombre, correo y una lista de permisos. Nada del otro mundo. Yo venía del mundo de JavaScript, donde guardar un array de strings es gratis y nadie te juzga. En Solidity, ese mismo array puede costarte más que una cena en un restaurante caro.
El contrato original tenía esto:
solidity struct Usuario { string nombre; string email; string[] permisos; }
mapping(address => Usuario) public usuarios; address[] public listaUsuarios;
function registrar(string memory _nombre, string memory _email) public { usuarios[msg.sender].nombre = _nombre; usuarios[msg.sender].email = _email; usuarios[msg.sender].permisos.push("basico"); listaUsuarios.push(msg.sender); }
Inocente, ¿verdad? Pues cada llamada a registrar costaba alrededor de 180.000 gas. En una red congestionada, eso podía ser el equivalente a varios dólares por usuario. Y yo, como un genio, había puesto una función para actualizar permisos que hacía un bucle sobre el array. Un bucle. Sobre un array que crecía. En blockchain. Sí, puedes reírte. Yo lo hice después.
La gota que colmó el vaso fue cuando un amigo intentó registrarse y me dijo: "Oye, me ha costado más el gas que el café que me estoy tomando mientras espero". Ahí supe que tenía que arreglarlo.
Paso 1: Entender que el gas no es una métrica abstracta, es dinero real
Lo primero que hice fue dejar de pensar en gas como "unidades de computación". Empecé a pensar en gas como céntimos que salen del bolsillo de alguien. Cada SSTORE es un euro. Cada bucle es una ruleta rusa. Cada string almacenado en cadena es un impuesto que alguien paga.
En Solidity, escribir en almacenamiento es carísimo. Leer también tiene coste, pero mucho menor. Y si puedes evitar tocar la cadena, mejor que mejor. La primera lección: no todo tiene que vivir on-chain. Los permisos, por ejemplo, podían ser un bitmask. Los nombres y correos, eventos. La lista de usuarios, un contador.
Paso 2: Cambiar strings por bytes32 y eventos
Los strings en Solidity son arrays de bytes con longitud variable. Ocupan espacio, cuestan gas y son un dolor de cabeza. Si tus datos no van a superar los 32 bytes, usa bytes32. Es fijo, eficiente y se almacena en una sola palabra.
Reescribí el struct:
solidity struct Usuario { bytes32 nombre; bytes32 email; uint256 permisos; // bitmask }
Y para la lista de usuarios, en lugar de un array on-chain, emitía un evento:
solidity event UsuarioRegistrado(address indexed usuario, bytes32 nombre);
Los eventos no se almacenan en el estado del contrato. Se escriben en logs, que son mucho más baratos. Y si necesitas consultarlos, tienes indexadores off-chain. ¿Magia? No, diseño.
El coste de registrar bajó de 180.000 a unos 45.000 gas. Solo con eso.
Paso 3: Adiós a los bucles sobre arrays dinámicos
Aquí viene la parte que más me dolió. Tenía una función actualizarPermisos que hacía:
solidity function actualizarPermisos(address _usuario, string memory _permiso) public { for (uint i = 0; i < usuarios[_usuario].permisos.length; i++) { // ... } }
Un bucle que crece con cada permiso. En una máquina tragamonedas, eso es como tirar de la palanca y que la máquina se quede sin monedas. El coste podía dispararse sin límite.
Lo cambié por un bitmask. Cada permiso es un bit. Activar un permiso es un OR. Desactivarlo, un AND con negación. Sin bucles, sin arrays, sin dramas.
solidity uint256 constant PERMISO_BASICO = 1 << 0; uint256 constant PERMISO_ADMIN = 1 << 1;
function activarPermiso(address _usuario, uint256 _permiso) public { usuarios[_usuario].permisos |= _permiso; }
Coste: unos 5.000 gas. Y escalable hasta 256 permisos sin despeinarse.
Paso 4: El poder de calldata y immutable
Otro error de novato: usar memory para parámetros que no se modifican. Si una función solo lee un array o un string, usa calldata. Es más barato porque no copia los datos a memoria.
Y para valores que no cambian, como direcciones de contratos auxiliares o constantes de configuración, usa immutable o constant. Se guardan directamente en el bytecode, no en almacenamiento. Leerlos es prácticamente gratis.
solidity address immutable public tesoreria; uint256 constant TASA_BASE = 100;
Parece una tontería, pero en un contrato con muchas llamadas, esos ahorros se acumulan.
Paso 5: Medir, medir y volver a medir
No puedes optimizar lo que no mides. Yo usaba una herramienta de perfilado de gas que me decía exactamente cuánto costaba cada función. Y no, no vale con "creo que esto es más barato". Hay que probarlo.
Hice una tabla con antes y después. Ver el número bajar es adictivo. Pasé de un contrato que costaba 180.000 gas por registro a uno que cuesta 28.000. Y la función de permisos, de un bucle impredecible a un OR de 5.000 gas.
Paso 6: La guinda: patrones de acceso y almacenamiento en frío
Aprendí que no todo tiene que estar en el mismo contrato. Separar la lógica de almacenamiento en contratos más pequeños puede reducir el coste de despliegue y de ejecución. También descubrí los mappings anidados y cómo evitar recorrerlos.
Y algo que me voló la cabeza: el almacenamiento en frío vs caliente. La primera escritura en una variable cuesta más que las siguientes. Si puedes agrupar escrituras, mejor. Si puedes evitarlas, mejor todavía.
La reflexión: optimizar no es solo una cuestión de gas, es de respeto
Cuando empecé, pensaba que la optimización era para obsesionados. Ahora sé que es una forma de respeto hacia quien usa tu contrato. Cada vez que evitas un bucle innecesario, cada vez que cambias un string por un bytes32, cada vez que mides antes de desplegar, estás diciendo: "Tu dinero importa. Tu tiempo importa. No quiero que esta máquina te trague las monedas".
La blockchain no es rápida ni barata por diseño. Es segura y descentralizada. Pero eso no significa que tengamos que resignarnos a contratos que parecen sacados de una subasta de gas. Con un poco de cabeza, puedes escribir contratos que la gente use sin sentir que están pagando un peaje abusivo.
Y oye, si tu primer contrato resulta ser una máquina tragamonedas, no pasa nada. El mío lo fue. Lo importante es que aprendas a mirar cada línea de código y preguntarte: "¿Esto
Comentarios
Deja un comentario