Ethereum: de historia a labs

Artículo 13 de 48

27%

completado al llegar aquí

Sistemas distribuidosNota de investigación14 min de lectura

Foundry, Anvil y Cast: taller local para Ethereum

Forge, Anvil y Cast paso a paso: compilar, probar con fuzzing, desplegar en una cadena local 31337 y leer storage por RPC sin gastar gas 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.

Imagina que quieres comprobar algo muy concreto: después de llamar setNumber(42) en un contrato, ¿el valor 42 está de verdad en el slot 0 de su storage? Con una testnet pública necesitas ETH de prueba, esperar bloques y confiar en un proveedor RPC. Con Foundry lo haces en tu máquina en menos de un minuto, y puedes repetirlo cien veces con el mismo resultado.

Foundry es un conjunto de herramientas de línea de comandos para desarrollar contratos de Ethereum. Trae cuatro binarios: forge compila, prueba, despliega y verifica contratos; cast habla con cualquier nodo por JSON-RPC; anvil levanta un nodo Ethereum local; chisel es un REPL de Solidity . Para alguien que viene de Rust la sensación es familiar: Foundry está escrito en Rust y se instala con un gestor de versiones propio.

Ciclo local

Del código fuente a la evidencia leída por RPC

Cada paso deja un artefacto que se puede inspeccionar: artefactos de compilación, trazas de prueba, un receipt y un slot de storage.
  1. forge buildCompila src/ y escribe ABI y bytecode en out/.
  2. forge testEjecuta pruebas en Solidity dentro de una EVM en memoria, sin nodo.
  3. anvilLevanta un nodo local en 127.0.0.1:8545 con cuentas precargadas.
  4. forge script --broadcastSimula el despliegue y luego envía las transacciones firmadas al nodo.
  5. cast call / cast storageLee el resultado desde fuera: retorno de una función y slot crudo.

Brújula de lectura

Actor, evidencia y estado

Antes de avanzar, ubica quién actúa, qué prueba queda y qué parte del sistema cambia.

Actor

Desarrollador con una cuenta de Anvil

Firma con una clave de prueba pública; nadie más compite por el bloque.

Evidencia

Receipt, retorno y slot

Cast lee por JSON-RPC lo mismo que leería de un nodo real.

Estado

Cadena efímera en memoria

Desaparece al cerrar Anvil, salvo que la vuelques a un archivo.

Preguntas guía

  • ¿Qué parte del ciclo corre sin nodo y qué parte necesita uno escuchando en un puerto?
  • ¿Qué diferencia hay entre una prueba que pasa en forge test y un contrato desplegado en Anvil?
  • ¿Por qué el chain id de la red local importa para la seguridad de tus firmas?
  • ¿Qué comportamientos de mainnet no aparecen nunca en una cadena local?

Cuatro binarios y una cadena de juguete

La instalación oficial usa foundryup, un instalador que también actúa como gestor de versiones. Ejecutar foundryup instala la última versión estable de forge, cast, anvil y chisel; foundryup --install <versión> fija una versión concreta . En un equipo, fijar la versión es la primera decisión de reproducibilidad: si dos personas usan versiones distintas, las trazas y los errores pueden no coincidir.

Bash
curl -L https://foundry.paradigm.xyz | bash   # instala foundryup
foundryup                                     # última versión estable
forge --version && anvil --version && cast --version

Anvil es un nodo Ethereum que vive solo en memoria. Al arrancar sin flags crea diez cuentas de desarrollo con 10.000 ETH cada una, derivadas de la frase mnemónica pública test test test test test test test test test test test junk . Por defecto escucha en 127.0.0.1:8545 y usa el chain id 31337 . El chain id es el número que identifica la red y que entra en la firma de cada transacción desde EIP-155, para que una transacción firmada para una red no sea válida en otra .

Cast es el cliente. Cada subcomando se traduce a una o varias llamadas JSON-RPC, y lee el endpoint de --rpc-url o de la variable ETH_RPC_URL . Esa es la idea que conecta este capítulo con el anterior: Cast no tiene un canal especial hacia Anvil. Habla con él igual que hablaría con un nodo de mainnet, así que lo que aprendes aquí sirve después contra cualquier red.

Traza de ejecución: de forge init a un slot leído con Cast

forge init hello_foundry crea un proyecto con src/, test/, script/ y lib/, más un contrato Counter con su prueba y su script de despliegue . El contrato guarda un único uint256 public number y expone setNumber e increment. Es pequeño a propósito: con una sola variable sabemos exactamente qué slot debe cambiar.

Bash
forge init hello_foundry && cd hello_foundry
forge build          # artefactos en out/
forge test           # pruebas en EVM local, sin nodo
anvil                # en otra terminal: nodo en 127.0.0.1:8545

El script generado (script/Counter.s.sol) envuelve new Counter() entre vm.startBroadcast() y vm.stopBroadcast(). Sin --broadcast, forge script solo simula; con --broadcast firma y envía las transacciones al RPC indicado . La clave de abajo es la de la primera cuenta de Anvil, publicada en la documentación; nunca debe usarse fuera de una red local.

Bash
export ETH_RPC_URL=http://127.0.0.1:8545
export PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
forge script script/Counter.s.sol --broadcast --rpc-url $ETH_RPC_URL --private-key $PK

Si es la primera transacción de esa cuenta (nonce 0) en un Anvil recién iniciado, la dirección del contrato es determinista: 0x5FbDB2315678afecb367f032d93F642f64180aa3. Con CREATE, la dirección depende solo del emisor y de su nonce, así que la puedes calcular antes de desplegar. Ahora leemos y escribimos desde fuera.

Bash
C=0x5FbDB2315678afecb367f032d93F642f64180aa3
cast call $C "number()(uint256)"               # 0
cast send $C "setNumber(uint256)" 42 --private-key $PK
cast call $C "number()(uint256)"               # 42
cast storage $C 0                              # 0x...002a (42 en hexadecimal)

Las dos últimas líneas cuentan la misma historia desde capas distintas. cast call usa el ABI: codifica la llamada, ejecuta eth_call y decodifica el retorno. cast storage ignora el ABI y pide el contenido crudo del slot 0. Que ambos coincidan es la evidencia que buscábamos al principio, y es la base del capítulo sobre storage layout.

Figura 1

El ciclo local de Foundry y dónde queda cada evidencia

Fuentes:

[1][3][6][7]
El ciclo local de Foundry y dónde queda cada evidencia
Cómo leer esta figura

Diagrama en dos filas. Fila superior, de izquierda a derecha: src/Counter.sol pasa por forge build a out/ con ABI y bytecode; out/ pasa por forge script --broadcast a Anvil, que escucha en 127.0.0.1:8545 con chain id 31337; Anvil pasa por cast call y cast storage a una caja de evidencia con retorno, slot y receipt. Fila inferior: desde out/ baja una flecha a forge test, que ejecuta la EVM dentro del proceso sin nodo ni RPC, y de ahí a trazas -vvv y fuzzing de 256 casos. A la derecha, una caja cast rpc anvil_* para impersonar, mover el tiempo y tomar snapshots apunta hacia Anvil. Una nota indica que la cadena local es efímera y no toca mainnet.

Visual original de TrautsLab. forge build produce artefactos; forge test los ejecuta sin nodo; forge script --broadcast los despliega en Anvil y Cast lee el resultado por JSON-RPC.

Pruebas que eligen sus propios datos

En Foundry las pruebas se escriben en Solidity. Un archivo .t.sol define un contrato que hereda de Test; setUp() corre antes de cada prueba y cada función que empieza por test es un caso . Para quien viene de JUnit o de cargo test el modelo es conocido. Lo nuevo es que la prueba vive dentro de la misma EVM que el contrato, así que puede llamar funciones, leer storage y comprobar reverts sin capa de traducción.

Si una función de prueba recibe parámetros, Forge la convierte en una prueba de fuzzing: genera entradas aleatorias y la ejecuta 256 veces por defecto. La plantilla ya trae una: testFuzz_SetNumber(uint256 x) comprueba que setNumber(x) seguido de number() devuelve x para cualquier x. Para acotar entradas existen vm.assume, que descarta casos, y bound, que los recorta a un rango.

Cuando algo falla, la verbosidad es la herramienta de depuración. -vvv muestra la traza de las pruebas que fallan, -vvvv la de todas y -vvvvv añade los cambios de storage. Si el fuzzer encuentra un fallo, informa la semilla; fijarla con --fuzz-seed ayuda a reproducirlo, aunque la documentación advierte que también hay que mantener la versión de Foundry, la configuración y el corpus.

Un detalle que confunde al principio: Forge ejecuta cada llamada externa de primer nivel de una prueba como una transacción separada. Esto da una contabilidad de gas más precisa, pero significa que una prueba no siempre equivale a una sola transacción con accesos "calientes" compartidos. Si tu aserción depende de eso, la documentación indica --no-isolate.

Manipular la cadena local sin engañarte

Anvil acepta métodos RPC propios, fuera del estándar, para preparar escenarios difíciles de reproducir en una red real . Con anvil_impersonateAccount envías transacciones como cualquier dirección sin conocer su clave. Con evm_increaseTime y evm_mine adelantas el reloj, útil para probar un timelock. Con evm_snapshot y evm_revert guardas un punto y vuelves a él; el snapshot se consume al revertir.

Bash
SNAP=$(cast rpc evm_snapshot)                  # punto de retorno
cast rpc evm_increaseTime 3600 && cast rpc evm_mine
cast rpc anvil_setStorageAt $C 0x0 0x0000000000000000000000000000000000000000000000000000000000000007
cast call $C "number()(uint256)"               # 7: el slot 0 se escribió a mano
cast rpc evm_revert $SNAP                      # vuelve al estado guardado

Estas palancas son potentes y peligrosas para el razonamiento. Escribir un slot a mano demuestra cómo se lee el storage, pero también puede producir un estado que ninguna transacción válida alcanzaría. La regla práctica: usa la manipulación para preparar precondiciones y deja que el contrato produzca la postcondición mediante llamadas normales.

Para fijar el punto de partida de un lab, Anvil puede volcar y cargar estado con --dump-state, --load-state o --state, y bifurcar una red real con --fork-url y --fork-block-number . Un fork añade una dependencia externa: el bloque elegido y el proveedor RPC que sirve el estado histórico. Fijar el número de bloque es lo que hace repetible un fork.

Riesgo operativo: lo que Anvil no puede prometer

Las cuentas por defecto de Anvil son públicas. La documentación lo advierte y recomienda una mnemónica propia al bifurcar mainnet o hablar con proveedores RPC públicos . Cualquier fondo enviado a esas direcciones en una red real puede ser retirado por cualquiera. Para redes reales, la guía de scripting recomienda un keystore cifrado (--account) o una cartera hardware (--ledger) en lugar de --private-key .

El segundo riesgo es de interpretación. Por defecto Anvil mina en cuanto llega una transacción lista; existen modos por intervalo (--block-time) y manual (--no-mining) para modelar bloques y orden . Aun así no hay otros usuarios en el mempool, no hay buscadores de MEV, la latencia es cero y el precio del gas no sube porque tu transacción sea urgente. Una prueba local verde demuestra que el código cumple la propiedad escrita bajo esas condiciones; no demuestra que sea seguro en mainnet.

El tercer riesgo es de evidencia. Un lab que termina en "los tests pasan" no deja nada que otra persona pueda verificar. Lo mínimo que conviene guardar es la versión de Foundry, la dirección desplegada, el hash de la transacción, el estado leído antes y después, y cómo se apaga el entorno. Los scripts ya escriben los receipts en broadcast/, así que esa evidencia sale casi gratis.

Riesgo operativo

Qué demuestra cada entorno

Recomendado

forge test

Propiedades del código en una EVM en memoria, con fuzzing y trazas.

Ideal para
Lógica, reverts, invariantes y regresiones rápidas.
Ojo con
Sin red, sin mempool y con llamadas aisladas por defecto.

Anvil local

Despliegue real por JSON-RPC, receipts y lectura de storage con Cast.

Ideal para
Scripts de despliegue, integración con frontends y labs reproducibles.
Ojo con
Minado instantáneo y claves públicas: no modela competencia ni MEV.
Precaución

Anvil con fork

Estado de una red real en un bloque fijo, modificable en local.

Ideal para
Probar integraciones con contratos ya desplegados.
Ojo con
Depende del RPC remoto y del bloque elegido; sin bloque fijo no es repetible.
La misma prueba significa cosas distintas según dónde corre.

Conexiones dentro de la serie

Referencias

Bibliografía académica

Formato IEEENumerado para ingeniería, sistemas y protocolos.
  1. [1]

    Foundry contributors, "Getting Started," Foundry Documentation.

    tool docsID: FOUNDRY-GETTING-STARTEDAbrir fuente (abre en otra pestaña)

    Tabla de las cuatro herramientas, forge init/build/test, anvil y cast send con la cuenta 0 de Anvil.

  2. [2]

    Foundry contributors, "Installation," Foundry Documentation.

    tool docsID: FOUNDRY-INSTALLATIONAbrir fuente (abre en otra pestaña)

    foundryup, versiones fijas, verificación SHA-256 y ruta ~/.foundry.

  3. [3]

    Foundry contributors, "Anvil," Foundry Documentation.

    tool docsID: FOUNDRY-ANVILAbrir fuente (abre en otra pestaña)

    Nodo en memoria, 10 cuentas con 10.000 ETH, mnemónica por defecto y advertencia sobre claves públicas.

  4. [4]

    Foundry contributors, "crates/anvil/src/config.rs," foundry-rs/foundry, GitHub.

    source codeID: FOUNDRY-ANVIL-CONFIGAbrir fuente (abre en otra pestaña)

    Constantes NODE_PORT = 8545, CHAIN_ID = 31337 y DEFAULT_MNEMONIC.

  5. [5]

    V. Buterin, "EIP-155: Simple replay attack protection," Ethereum Improvement Proposals, no. 155, 2016.

    Incluye chainid en el hash firmado para evitar replays entre redes.

  6. [6]

    Foundry contributors, "Reading chain data," Foundry Documentation.

    tool docsID: FOUNDRY-CAST-READINGAbrir fuente (abre en otra pestaña)

    ETH_RPC_URL, cast call con firma de retorno, cast storage, cast receipt y overrides de estado.

  7. [7]

    Foundry contributors, "Scripting," Foundry Documentation.

    tool docsID: FOUNDRY-FORGE-SCRIPTINGAbrir fuente (abre en otra pestaña)

    vm.startBroadcast, simulación frente a --broadcast, opciones de firma y archivos en broadcast/.

  8. [8]

    Foundry contributors, "Testing," Foundry Documentation.

    tool docsID: FOUNDRY-FORGE-TESTINGAbrir fuente (abre en otra pestaña)

    Convenciones .t.sol, setUp, aislamiento de llamadas, niveles -v, fuzzing con 256 ejecuciones, semilla, vm.assume y bound.

  9. [9]

    Foundry contributors, "Custom methods," Foundry Documentation.

    tool docsID: FOUNDRY-ANVIL-CUSTOM-METHODSAbrir fuente (abre en otra pestaña)

    anvil_impersonateAccount, evm_increaseTime, evm_snapshot/evm_revert, anvil_setStorageAt y control de minado.

Fuentes primarias, papers seminales y especificaciones técnicas citadas en esta investigación.

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.

Sigue leyendo

Ideas conectadas con este artículo

Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.

Los comentarios se cargan al final de la lectura para no meter peso antes de tiempo.