Ethereum: de historia a labs
Artículo 10 de 48
21%
completado al llegar aquí
Sistemas distribuidosNota de investigación16 min de lectura
EVM: stack, memory, storage y calldata
Dónde vive cada dato en la EVM, cuánto dura y cuánto cuesta: stack, memory, storage, transient storage, calldata y logs, con una traza de bytecode real.
Ruta Ethereum: 0/48
Prerequisitos recomendados
Si alguno de estos conceptos todavía está medio nebuloso, empieza por aquí. Te ahorra tropiezos y un par de cejas fruncidas.
Un contrato que funcionaba en pruebas empieza a costar diez veces más gas en producción. Al revisar, el equipo descubre que un bucle escribe en storage en cada vuelta, cuando bastaba con acumular en memoria y escribir una vez al final. El bug no está en la lógica, sino en no saber dónde vive cada dato. Este capítulo da ese mapa.
La Ethereum Virtual Machine (EVM) es la máquina que ejecuta el código de los contratos. Es una máquina de pila: las instrucciones toman sus operandos de una pila y dejan ahí el resultado, en lugar de usar registros con nombre. Cada elemento de la pila es una palabra de 256 bits, un tamaño elegido para manejar con comodidad hashes Keccak-256 y valores de secp256k1. [1]
Brújula de lectura
Actor, evidencia y estado
Actor
Bytecode del contratoCada opcode consume gas y mueve datos entre las regiones de la EVM.
Evidencia
Traza de opcodes y gasUsedEl receipt da el total; una traza muestra el costo de cada paso.
Estado
Solo storage persisteStack, memory y transient storage desaparecen; los logs quedan fuera del estado.
Preguntas guía
- ¿Qué regiones de datos tiene la EVM y cuánto vive cada una?
- ¿Por qué escribir un slot nuevo de storage cuesta miles de veces más que una suma?
- ¿Qué cambia cuando un contrato llama a otro: qué se comparte y qué empieza de cero?
- ¿Para qué sirve transient storage y en qué se diferencia de memory?
Conexiones dentro de la serie
- Antes: Estado, storage y Merkle Patricia Trie explica dónde queda escrito el storage.
- Después: ABI, calldata y eventos muestra cómo se codifican los bytes de calldata y de los logs.
- Más adelante: Storage layout aplica estos costos a Solidity, y Reentrancy muestra qué pasa cuando una llamada externa vuelve a entrar mientras el primer marco sigue vivo.
Seis lugares para un dato
Cada vez que un contrato recibe una llamada, la EVM crea un marco de ejecución: un contexto con su propio contador de programa, su gas disponible y sus regiones de trabajo. [2] Los datos pueden estar en seis lugares:
| Región | Qué es | Cuánto vive | Costo típico |
|---|---|---|---|
| Stack | pila de hasta 1024 palabras de 256 bits | el marco actual | 2 o 3 gas por operación simple |
| Memory | arreglo de bytes, empieza en cero | el marco actual | 3 gas por palabra más un término cuadrático |
| Calldata | bytes de entrada de la llamada, solo lectura | el marco actual | 3 gas por CALLDATALOAD |
| Storage | mapa persistente de 32 a 32 bytes por contrato | entre transacciones | 2100 frío, 100 caliente; 20 000 al pasar de cero a no cero |
| Transient storage | mapa de 32 a 32 bytes por contrato | la transacción | 100 gas por lectura o escritura |
| Logs | registros emitidos con LOG0 a LOG4 | quedan en el receipt | según topics y bytes |
El tope de 1024 elementos de pila y los costos de memoria y storage están en el yellow paper; los de transient storage, en EIP-1153; la tabla de opcodes de ethereum.org los resume. [2] [3] [4] Los accesos fríos y calientes a storage vienen de EIP-2929: el primer acceso a un slot dentro de una transacción es frío y los siguientes son calientes. [5]
Cuánto vive cada región de datos de la EVM
Cómo leer esta figura
Eje horizontal de tiempo con dos transacciones. En la transacción 1, el marco A llama al marco B, que ocupa una franja central. Fila calldata y fila stack y memory: barras azules para el marco A antes y después de la franja, y una barra violeta distinta para el marco B, que empieza vacío. Fila transient storage: una barra verde continua durante toda la transacción 1 que termina con el rótulo se borra, y una barra nueva en la transacción 2. Fila storage: una barra ocre continua que atraviesa ambas transacciones. Fila logs: un punto LOG en B y otro LOG en A, con una flecha hacia el receipt, si no revierte.
Traza de ejecución: ocho bytes que escriben un slot
El capítulo anterior desplegó el código de inicialización 0x602a5f5560015ff3. Leído byte a byte con la tabla de opcodes, son seis instrucciones. [4] PUSH0 (0x5f) existe desde EIP-3855 y empuja un cero sin bytes de datos. [6]
| Byte(s) | Opcode | Pila después (cima a la izquierda) | Gas |
|---|---|---|---|
60 2a | PUSH1 0x2a | [42] | 3 |
5f | PUSH0 | [0, 42] | 2 |
55 | SSTORE | []: slot 0 pasa de 0 a 42 | 22 100 |
60 01 | PUSH1 0x01 | [1] | 3 |
5f | PUSH0 | [0, 1] | 2 |
f3 | RETURN | devuelve memory[0:1] = 0x00 | 3 por expandir memoria |
SSTORE toma de la cima la clave y debajo el valor. [4] Su costo aquí suma dos partes del yellow paper: 2100 por acceso frío al slot y 20 000 por pasar un valor de cero a no cero. [2] RETURN toma un desplazamiento y una longitud y devuelve ese tramo de memoria; en una creación, esos bytes se convierten en el código del contrato. Como la memoria empieza en cero, el código resultante es el byte 0x00, que es STOP.
La lección de la traza es de proporción. Cinco de las seis instrucciones cuestan entre 2 y 3 gas; la escritura en storage cuesta más de siete mil veces eso. Cualquier optimización seria empieza por contar escrituras de storage.
Memory y calldata: lo que se borra al terminar la llamada
Calldata son los bytes que acompañan a la llamada. El contrato los lee con CALLDATALOAD, que copia 32 bytes desde una posición a la pila, o con CALLDATACOPY, que los copia a memoria. No se pueden modificar. [4]
Memory es un arreglo de bytes que empieza en cero en cada marco y crece a medida que se usa. Su costo no es lineal: para palabras de 32 bytes, el yellow paper cobra gas en total, y cada expansión paga la diferencia. [2] Usar unos pocos kilobytes es barato; usar megabytes se vuelve prohibitivo, y ese término cuadrático es lo que acota la memoria de un marco en la práctica.
Solidity organiza la memoria con una convención propia: los bytes 0x00 a 0x3f son espacio temporal para hashes, 0x40 guarda el puntero de memoria libre y 0x60 es una palabra que siempre vale cero. Los objetos nuevos empiezan en 0x80. [7] Un bloque assembly que escribe en memoria sin respetar ese puntero puede pisar datos que el compilador cree intactos.
Storage y transient storage: lo que sobrevive a la llamada
Storage es el mapa persistente de cada contrato que vimos dentro del trie. Es el único lugar donde un dato sobrevive a la transacción. Además del costo de escritura, cada lectura fría cuesta 2100 gas. [5] En un bucle que lee el mismo slot en cada vuelta, la primera lectura es fría y las demás cuestan 100 gas cada una; copiar el valor a una variable local antes del bucle deja una sola lectura de storage y convierte el resto en operaciones de pila de 2 o 3 gas.
Transient storage se añadió con EIP-1153 en Dencun, activado en mainnet el 13 de marzo de 2024. [8] Tiene la forma de storage, un mapa de 32 a 32 bytes por contrato, pero se descarta al final de cada transacción. Se accede con TSTORE (0x5d) y TLOAD (0x5c) por 100 gas. [3]
La diferencia con memory es el alcance. La memoria es privada de cada marco: si A llama a B y B vuelve a llamar a A, el segundo A empieza con memoria vacía. El transient storage, en cambio, se comparte entre todas las llamadas internas de la misma transacción. [1] Eso lo hace útil para un cerrojo contra reentrada o para pasar un valor entre llamadas sin pagar storage. El propio EIP advierte que, si los datos solo deben vivir dentro de un marco, conviene usar memory. [3]
Dónde guardar un dato según cuánto debe vivir
Reglas de decisión rápida
Solo dentro de esta función
Variable temporal, buffer, resultado intermedio.
→ Stack o memory
Entre llamadas internas de una misma transacción
Cerrojo de reentrada, contexto compartido entre callbacks.
→ Transient storage (TSTORE/TLOAD)
Entre transacciones
Saldos, configuración, propiedad.
→ Storage, minimizando escrituras
Solo para observadores fuera de la cadena
Historial que un indexador o una UI necesita leer.
→ Eventos (logs), nunca como fuente de verdad del contrato
Logs y llamadas entre contratos: invariantes a respetar
Los logs son registros que el contrato emite con LOG0 a LOG4: hasta cuatro topics de 32 bytes, que sirven para filtrar, y un bloque de datos. [4] Quedan en el receipt, no en el estado. Ningún contrato puede leerlos, ni siquiera el que los emitió. [9] Por eso un evento es una notificación para el mundo exterior, no una forma de guardar información para el propio contrato.
Cuando un contrato llama a otro con CALL, se abre un marco nuevo con stack, memory y calldata propios, y el código llamado trabaja sobre su propio storage. DELEGATECALL es la excepción importante: ejecuta el código de otro contrato sobre el storage del que llama. [4] [2] Ese es el mecanismo de los proxies y la razón por la que un cambio de layout entre versiones puede corromper datos.
Si un marco revierte, se descartan sus cambios de storage, de transient storage y sus logs, y el que llamó recibe un indicador de fallo que debe comprobar. [2] [3] Dos límites más acotan lo que un contrato puede ser: la pila no puede superar 1024 elementos, o la ejecución se detiene con error, y el código desplegado no puede superar 24 576 bytes. [2] [10]
Riesgo operativo: costos que cambian con el contexto
El mismo código no siempre cuesta lo mismo. Una lectura de storage cuesta 2100 gas si es el primer acceso al slot en la transacción y 100 si no lo es. [5] Una estimación de gas hecha con una transacción aislada puede quedarse corta cuando esa llamada forma parte de un lote, o sobrar cuando otro paso ya calentó el slot.
Tres prácticas reducen sorpresas: estimar con el mismo contexto en que se va a ejecutar, dejar margen en gas_limit, y revisar en cada auditoría de gas cuántos SSTORE hace cada camino de código. Los números concretos cambian con los upgrades, así que conviene leerlos en el EIP vigente y no en una tabla copiada hace años.
Taller de bytecode: desensamblar y repetir la traza
Este taller reutiliza el contrato de 8 bytes. Todos los comandos se ejecutan contra Anvil, el nodo local de Foundry, con su cuenta de prueba pública.
# Desensamblar el código de inicialización (no necesita nodo)
cast disassemble 0x602a5f5560015ff3
# Desplegarlo en Anvil y leer el slot que escribió
export ETH_RPC_URL=http://127.0.0.1:8545
PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
cast send --private-key $PK --create 0x602a5f5560015ff3
cast storage <DIRECCION_DEL_CONTRATO> 0
# Pedir la traza paso a paso de la transacción de despliegue (método de depuración que Anvil expone)
cast rpc debug_traceTransaction <TX_HASH>Qué observar: el desensamblado debe listar las seis instrucciones de la tabla; la traza debe mostrar SSTORE como el paso más caro, con 42 y 0 en la pila justo antes de ejecutarse. debug_traceTransaction no forma parte de la especificación estándar eth_*; es un método de depuración que ofrecen algunos clientes y Anvil, y su formato de salida puede variar entre implementaciones.
Bibliografía académica
- [1]
ethereum.org, "Ethereum Virtual Machine (EVM)," Ethereum developer documentation. [Online]. Available: https://ethereum.org/developers/docs/evm/
Máquina de pila, memoria y transient storage.
- [2]
G. Wood, "Ethereum: A secure decentralised generalised transaction ledger," Yellow Paper, Shanghai version efc5f9a, 2025.
Especificación formal de estado, transacciones, firma (apéndice F), gas y EVM.
- [3]
A. Akhunov and M. Salem, "EIP-1153: Transient storage opcodes," Ethereum Improvement Proposals, 2018.
TSTORE y TLOAD: almacenamiento que se descarta al final de la transacción.
- [4]
ethereum.org, "Opcodes for the EVM," Ethereum developer documentation. [Online]. Available: https://ethereum.org/developers/docs/evm/opcodes/
Tabla de opcodes con entradas, salidas y gas.
- [5]
V. Buterin and M. Swende, "EIP-2929: Gas cost increases for state access opcodes," Ethereum Improvement Proposals, 2020.
Accesos fríos y calientes a cuentas y slots.
- [6]
A. Beregszaszi, H. De la cruz and P. Bylica, "EIP-3855: PUSH0 instruction," Ethereum Improvement Proposals, 2021.
Opcode 0x5f que empuja cero.
- [7]
Solidity Team, "Layout in memory," Solidity documentation.
Scratch space, free memory pointer y zero slot.
- [8]
T. Beiko, "EIP-7569: Hardfork meta - Dencun," Ethereum Improvement Proposals, 2023.
EIPs de Dencun y activación en mainnet.
- [9]
Solidity Team, "Contracts: events," Solidity documentation.
Logs inaccesibles desde contratos y hasta tres parámetros indexed.
- [10]
V. Buterin, "EIP-170: Contract code size limit," Ethereum Improvement Proposals, 2016.
Límite de 24576 bytes de código desplegado.
Rutas vecinas
Si este tema te abrió hambre, sigue por aquí.
Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.
Series relacionadas
HotStuff: de cero a estado del arte
Ruta progresiva para entender consenso BFT moderno: quórums, sincronía parcial, locks, taxonomía one/two/three-chain, QC/highQC, multipipeline, view-change, responsiveness, AggQC, Fast-HotStuff, sync y frontera DAG-BFT.
Substrate: del runtime al consenso propio
Serie de 20 capítulos, de neófito a experto, sobre Substrate (Polkadot SDK): runtime en Wasm, FRAME, extrinsics, estado, pesos, actualizaciones sin fork, Aura, BABE, GRANDPA, sincronización, parachains, XCM, Sassafras, BEEFY y cómo conectar un consenso BFT propio a un nodo.
Ruta práctica: activos digitales en la operación
Nueve módulos, de menor a mayor dependencia, para decidir cómo usar custodia, wallets, llaves, firmas multifirma y MPC, AML/KYT y stablecoins con Fireblocks, BitGo, Kaleido, Circle y Tether.
Labs para convertir lectura en práctica
Prueba de trabajo y reajuste de dificultad
Laboratorio determinista para medir el costo de la prueba de trabajo y ver cómo el reajuste por altura de bloque estabiliza el tiempo entre bloques.
Consensus benchmark
Banco reproducible para comparar consenso BFT y documentar los trade-offs de HotStuff-2.
Ethereum local: Anvil, wallet y primera transacción
Laboratorio base para levantar una red local, crear cuentas, firmar transacciones y observar receipts sin gastar ETH real.
RPC y Cast: leer Ethereum con lupa
Ejercicios con JSON-RPC y Cast para consultar bloques, nonces, gas, receipts, logs y errores reproducibles.
Sigue leyendo
Ideas conectadas con este artículo
Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.
Metropolis a Istanbul: Ethereum madura antes del Merge
Lectura histórica de Byzantium, Constantinople e Istanbul como preparación técnica para una red más segura y escalable.
Estado, storage y Merkle Patricia Trie
Cómo pensar el estado global de Ethereum, storage de contratos, state root y pruebas de pertenencia.
Storage layout: donde Solidity cobra la factura
Slots, packing, mappings, arrays, structs y por qué storage layout importa para gas y upgrades.
Ethereum: mapa de lectura antes de tocar Solidity
Ruta completa para aprender Ethereum desde historia y arquitectura hasta EVM, proof-of-stake, L2, seguridad, DeFi y labs reproducibles.