Ethereum: de historia a labs
Artículo 24 de 48
50%
completado al llegar aquí
Sistemas distribuidosNota de investigación16 min de lectura
Oráculos: datos externos con dientes
Cómo llega un precio a un contrato: feeds agregados de Chainlink, heartbeat y desviación, chequeos de frescura, TWAP frente a precio spot y manipulación en una transacción.
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.
Testing serio: unit, fork, fuzz e invariantes
Unit, fuzz, invariant y fork tests en Foundry: qué evidencia da cada uno, cómo escribir handlers y ghost variables, y un bug real que solo encuentra el fuzzer.
Blockchain15 min de lectura→ERC-20: simple por fuera, tramposo por dentro
La interfaz ERC-20 paso a paso: balances, approve y transferFrom, la carrera al cambiar un allowance, tokens que no devuelven bool y permisos que nunca caducan.
Blockchain12 min de lectura→
Un protocolo de préstamos acepta ETH como colateral y presta dólares digitales. Para decidir cuánto prestar necesita saber cuánto vale ese ETH ahora. Pero un contrato Ethereum no puede abrir una conexión HTTP a un exchange: solo ve lo que está en la cadena. Alguien tiene que escribir el precio en la cadena, y el contrato tiene que decidir cuánto confiar en ese número.
Ese "alguien" es un oráculo: un servicio que lleva datos externos a la cadena para que los contratos los usen. Este capítulo trata el precio como una entrada no confiable que hay que validar: de dónde viene, qué tan viejo es, qué tan fácil es moverlo y qué hace el contrato cuando el dato falla.
Brújula de lectura
Actor, evidencia y estado
Actor
Red de oráculos o pool DEXQuien escribe el precio que tu contrato va a leer.
Evidencia
answer y updatedAtEl valor y el momento de su última actualización, ambos leídos en la misma llamada.
Estado
Decisiones que dependen del precioColateral, préstamos y liquidaciones que cambian según el número leído.
Preguntas guía
- Si el contrato no puede consultar internet, ¿quién escribe el precio en la cadena y cada cuánto?
- ¿Qué debe hacer tu contrato si el último precio tiene seis horas?
- ¿Por qué leer el precio actual de un pool de un DEX es peligroso dentro de la misma transacción?
- ¿Qué cambia cuando tu contrato vive en un rollup cuyo secuenciador se detuvo?
- ¿Cuál es el comportamiento seguro cuando el oráculo falla: revertir, pausar o usar un respaldo?
El problema del oráculo: determinismo y confianza
Ethereum es determinista: dado el mismo estado y la misma entrada, todos los nodos calculan el mismo resultado. Por eso los contratos solo pueden razonar sobre datos que ya están en la cadena. ethereum.org llama "problema del oráculo" a la tensión que aparece cuando un contrato necesita un dato externo: el dato debe ser correcto para que el contrato funcione bien, y confiar en quien lo trae debilita la propiedad de no depender de terceros. [1]
En términos de backend, un oráculo es una dependencia externa cuyo fallo se convierte en un fallo de negocio, sin reintentos ni timeouts por defecto. Un precio incorrecto no produce un error 500: produce un préstamo demasiado grande o una liquidación injusta, ejecutados y finales. La única defensa es que el contrato valide el dato antes de usarlo.
Traza de un feed agregado: proxy, aggregator y rondas
Chainlink Data Feeds es un ejemplo concreto de oráculo descentralizado, y su documentación ilustra bien las preguntas correctas. Cada feed lo actualizan varios operadores independientes; un contrato aggregator recibe sus reportes y guarda en la cadena una respuesta agregada. Si el aggregator no recibe respuestas de un número mínimo de oráculos, la última respuesta no se actualiza. [2]
El consumidor no llama al aggregator directamente, sino a un proxy que apunta a él, de modo que el aggregator se puede reemplazar sin cambiar la dirección que usa tu contrato. El mismo patrón de proxy del capítulo de upgrades aparece aquí, con la misma consecuencia: alguien controla a qué aggregator apunta. [2]
¿Cuándo se actualiza el precio? No en cada bloque. Una nueva ronda empieza cuando el valor fuera de cadena se desvía del valor en cadena más que un umbral de desviación, o cuando pasa un tiempo fijo desde la última actualización, el heartbeat. La primera condición que se cumpla dispara la ronda. [2] Consecuencia práctica: en un mercado tranquilo, el precio en cadena puede tener casi un heartbeat de antigüedad y seguir siendo correcto. Tu chequeo de frescura debe derivarse del heartbeat del feed concreto que integras, no de un número genérico.
La lectura se hace con latestRoundData(), que devuelve roundId, answer, startedAt, updatedAt y answeredInRound. El precio es answer, un entero con la cantidad de decimales que informa decimals(). La referencia marca answeredInRound como obsoleto, así que los chequeos basados en ese campo ya no aportan. [3]
Invariante de lectura: precio positivo, fresco y dentro de límites
Este contrato encapsula la lectura y revierte si el dato no es utilizable. Escala el precio a 18 decimales para que el resto del sistema no dependa de decimals(). Lo ejecutamos con Foundry 1.5.1 contra un feed simulado; es ilustrativo, no un contrato auditado:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
contract PriceReader {
AggregatorV3Interface public immutable feed;
uint256 public immutable maxStaleness; // segundos; derivado del heartbeat del feed
uint256 public immutable minPrice; // límites de cordura, en 18 decimales
uint256 public immutable maxPrice;
error InvalidPrice(int256 answer);
error StalePrice(uint256 updatedAt, uint256 nowTs);
error PriceOutOfBounds(uint256 price);
constructor(AggregatorV3Interface _feed, uint256 _maxStaleness, uint256 _minPrice, uint256 _maxPrice) {
feed = _feed;
maxStaleness = _maxStaleness;
minPrice = _minPrice;
maxPrice = _maxPrice;
}
function price() public view returns (uint256) {
(, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
if (answer <= 0) revert InvalidPrice(answer);
if (updatedAt == 0 || block.timestamp - updatedAt > maxStaleness) {
revert StalePrice(updatedAt, block.timestamp);
}
uint8 dec = feed.decimals();
uint256 scaled = dec <= 18
? uint256(answer) * 10 ** (18 - dec)
: uint256(answer) / 10 ** (dec - 18);
if (scaled < minPrice || scaled > maxPrice) revert PriceOutOfBounds(scaled);
return scaled;
}
}Los tests cubren cuatro casos: un precio fresco de 8 decimales se escala bien; uno con más antigüedad que maxStaleness revierte; uno fuera de límites revierte; y uno igual a cero revierte. Los límites de cordura no reemplazan al oráculo: atrapan errores groseros, como un feed mal configurado o un activo que dejó de cotizar, antes de que el contrato actúe sobre ellos.
Chainlink pone la responsabilidad de estos controles en quien integra: la documentación dice que los desarrolladores son los únicos responsables de chequeos de calidad de datos, circuit breakers y lógica de contingencia. [9] También recomienda circuit breakers que pausen el contrato ante precios viejos, saltos bruscos o valores cerca de mínimos y máximos predefinidos, y advierte que algunos feeds solo se actualizan en horario de mercado. [4]
Superficie de manipulación: el precio spot dentro de una transacción
Ahora la alternativa tentadora: leer el precio directamente de un pool de un DEX, dividiendo sus reservas. Es gratis, está en la cadena y se actualiza con cada swap. Ese último punto es el problema. Un atacante puede mover el precio de un pool con un swap grande, hacer que tu contrato lo lea y deshacer el swap, todo en la misma transacción.
¿De dónde saca el capital? De un flash loan, un préstamo sin colateral que debe devolverse dentro de la misma transacción. En Aave v3, flashLoanSimple permite a un contrato usar la liquidez del pool dentro de una transacción, siempre que devuelva el monto más una comisión. [8] Si el préstamo no se devuelve, toda la transacción revierte; si se devuelve, el atacante solo pagó la comisión y el gas.
Manipulación de precio spot dentro de una transacción
Cómo leer esta figura
Diagrama en dos partes. Arriba, un marco titulado una sola transacción del atacante contiene cinco cajas en secuencia: 1, flash loan de un activo; 2, swap grande en pool chico; 3, la víctima lee el spot; 4, préstamo con colateral inflado; 5, swap inverso y devolución. Abajo, un gráfico cualitativo con el eje horizontal marcado paso, de 1 a 5, y el eje vertical marcado precio. Una línea roja, precio spot del pool, parte baja en el paso 1, sube en el paso 2, se mantiene alta en los pasos 3 y 4 y vuelve a bajar en el paso 5. Una línea verde punteada, TWAP o feed agregado, se mantiene plana en todos los pasos, con la nota igual en todo el bloque.
La documentación de Uniswap v2 reconoce que muchos hackeos de alto perfil tuvieron al oráculo como vector principal. Su respuesta fue medir el precio al comienzo de cada bloque, antes de cualquier operación, y acumularlo ponderado por el tiempo en una variable. Con dos lecturas de ese acumulador se calcula un TWAP, un precio promedio ponderado por tiempo en un intervalo. [6]
Mover ese precio exige operar al final de un bloque anterior y sostener el desvío en el tiempo. Según la misma documentación, el costo de manipular un TWAP simple crece de forma aproximadamente lineal con la liquidez del pool y con la longitud del intervalo, y se estima como lo perdido ante arbitrajistas y comisiones en cada bloque del período. [6] Uniswap v3 guarda un historial de observaciones consultable con observe; promediar el tick acumulado da una media geométrica del precio. [7]
Un TWAP no es gratis: un intervalo largo resiste mejor la manipulación pero reacciona tarde a una caída real del mercado, justo cuando un protocolo de préstamos necesita liquidar. Y la documentación de v3 aclara que Uniswap v4 no incluye oráculo integrado. [7]
Tres fuentes de precio y su riesgo dominante
Riesgo operacional: secuenciadores, liquidez y respaldo
Si tu contrato vive en un rollup, hay un modo de falla adicional. Los rollups dependen de un secuenciador que ordena las transacciones; si se detiene, los usuarios comunes no pueden operar, aunque la seguridad siga anclada en L1. Chainlink ofrece feeds de disponibilidad del secuenciador que informan 0 si está activo y 1 si está caído, y recomienda aplicar un período de gracia al volver, para evitar liquidaciones masivas cuando los usuarios no pudieron reaccionar. [5]
La liquidez del activo también es parte del oráculo. Chainlink clasifica sus feeds por riesgo de precio de mercado y advierte que activos con poca liquidez o volumen son más vulnerables a manipulación; para feeds de fuente única exige salvaguardas adicionales como límites, chequeos de frescura, comportamiento de respaldo y controles de pausa manual. [4]
Antes de que un precio mueva dinero
0 de 6 revisados
El último punto es una decisión de diseño, no un detalle. Revertir es lo más simple, pero puede bloquear liquidaciones necesarias. Un respaldo agrega una segunda dependencia con sus propios riesgos. Lo importante es que la elección quede escrita y probada antes del despliegue, no improvisada durante un incidente.
Conexiones dentro de la serie
- Antes: Testing serio: unit, fork, fuzz e invariantes da las técnicas para simular feeds viejos, saltos de precio y secuencias de ataque.
- Antes: ERC-20: simple por fuera, tramposo por dentro explica los decimales de tokens, que se combinan con los decimales del feed.
- Antes: Proxies y upgrades: cambiar el motor con el auto andando, porque el proxy de un feed también tiene quien lo controla.
- Después: Cómo auditar un contrato sin jugar al checklist teatral integra la revisión de oráculos en una auditoría completa.
- Más adelante: AMMs: x*y=k sin vender humo matemático y Lending y liquidaciones: cuando el colateral respira mal muestran dónde el precio decide quién pierde.
Bibliografía académica
- [1]
ethereum.org, "Oracles," Ethereum Developer Documentation.
Determinismo, problema del oráculo, oráculos centralizados y descentralizados, patrones publish-subscribe y request-response.
- [2]
Chainlink, "Data Feeds Architecture," Chainlink Documentation.
Agregación por varios operadores, mínimo de respuestas, contratos proxy y aggregator, disparadores por umbral de desviación y heartbeat.
- [3]
Chainlink, "Data Feeds API Reference," Chainlink Documentation.
AggregatorV3Interface: decimals, latestRoundData, getRoundData; campos roundId, answer, startedAt, updatedAt; answeredInRound obsoleto.
- [4]
Chainlink, "Selecting Quality Data Feeds," Chainlink Documentation.
Categorías de riesgo de mercado, horarios de mercado, circuit breakers, liquidez, feeds de fuente única y deprecación de feeds.
- [5]
Chainlink, "L2 Sequencer Uptime Feeds," Chainlink Documentation.
Estado del secuenciador (0 activo, 1 caído), período de gracia tras una caída y funcionamiento en Arbitrum, OP y otras redes.
- [6]
Uniswap Labs, "Oracles," Uniswap v2 Protocol Documentation, Uniswap/docs repository.
Precio medido al inicio de cada bloque, acumulador de precio ponderado por tiempo, construcción de TWAP y costo aproximado de manipulación.
- [7]
Uniswap Labs, "Price Oracles," Uniswap v3 Protocol Documentation, Uniswap/docs repository.
Observaciones, observe(secondsAgos), acumulador de tick, media geométrica, actualización como máximo una vez por bloque y ausencia de oráculo integrado en v4.
- [8]
Aave, "IPool.sol: flashLoanSimple," aave-v3-core source code, GitHub repository aave/aave-v3-core.
flashLoanSimple: acceso a la liquidez del pool dentro de una transacción si se devuelve el monto más una comisión.
- [9]
Chainlink, "Developer Responsibilities," Chainlink Data Feeds Documentation.
Riesgos de integridad de mercado y de código de aplicación; responsabilidad del integrador por chequeos de calidad, circuit breakers y contingencias.
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.
ERC-20: simple por fuera, tramposo por dentro
Token fungible, balances, transfers, approvals, allowances y riesgos comunes del estándar ERC-20.
ERC-4626: vaults, shares y contabilidad peligrosa
Tokenized vaults, assets, shares, exchange rate, rounding y ataques de inflación en vaults.
Proxies y upgrades: cambiar el motor con el auto andando
Delegatecall, transparent proxies, UUPS, storage collisions y gobernanza de upgrades.
Testing serio: unit, fork, fuzz e invariantes
Cómo probar contratos con unit tests, fork tests, fuzzing e invariants antes de confiar en un deploy.