Patrones comunes de vulnerabilidades en smart contracts Solidity
Revisión de fallos clásicos detectados en auditorías reales: reentrada, desbordamiento matemático controlado y manipulación de oráculos de precio.

Reentrancy todavía existe en 2025
A pesar de quince años de literatura, sigo encontrando reentrancy explotables en auditorías nuevas. La razón es que el patrón clásico (call externo antes de actualizar estado) ha mutado: hoy aparece en hooks ERC-777, en callbacks de flash-loans, y especialmente en cross-function reentrancy donde la víctima no es la misma función que hace el call.
La defensa estructural es Checks-Effects-Interactions estricto y, cuando el flujo lo permite, un ReentrancyGuard. Pero más importante: modelar explícitamente qué funciones comparten estado y cuáles pueden ser reentrantes entre sí.
// MAL
function withdraw(uint amount) external {
require(balances[msg.sender] >= amount);
(bool ok,) = msg.sender.call{value: amount}("");
require(ok);
balances[msg.sender] -= amount; // tarde
}
// BIEN
function withdraw(uint amount) external nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // effects
(bool ok,) = msg.sender.call{value: amount}(""); // interaction
require(ok, "transfer failed");
}Oráculos: el vector más rentable
La mayoría de explotaciones de ocho cifras de los últimos dos años no han sido bugs de lógica del contrato auditado, sino manipulación de la fuente de precio que ese contrato consultaba. Un Uniswap V2 spot price es trivialmente manipulable dentro del mismo bloque mediante flash-loan; usarlo como referencia para liquidaciones es entregar el protocolo.
La regla: TWAP de ventana suficientemente amplia, o feeds Chainlink con validación de staleness (block.timestamp - updatedAt < heartbeat) y de desviación contra una segunda fuente. Nunca un único oráculo, nunca precio instantáneo de un AMM para decisiones financieras.
Aritmética: Solidity 0.8 no es una bala de plata
El built-in overflow check de 0.8 cubre el caso obvio, pero no protege contra pérdida de precisión por divisiones prematuras, ni contra rounding direction equivocado en favor del usuario en lugar del protocolo. En vaults y curvas de bonding, el orden de operaciones decide quién gana micro-arbitrajes acumulados a lo largo de millones de transacciones.
Regla operativa: multiplicar antes de dividir, redondear siempre en contra del usuario en operaciones de mint/redeem, y testear con fuzzing — Foundry y Echidna deberían formar parte del ciclo, no de la auditoría final.
Lo que un auditor mira primero
Antes de leer una línea de lógica reviso: control de acceso (¿hay onlyOwner sobre funciones que mueven valor?), inicialización (¿hay riesgo de re-init en proxies?), pausabilidad y planes de upgrade, y el grafo de llamadas externas. Si esos cuatro puntos están bien, la auditoría se vuelve productiva. Si están mal, no hace falta leer más para emitir un informe accionable.
