Solo con fines educativos; no constituye asesoramiento de inversión. Un fallo de reentrada puede causar una pérdida rápida e irreversible de activos del contrato, y ninguna defensa o auditoría aislada demuestra que un contrato sea seguro.
Respuesta directa
Un ataque de reentrada puede ocurrir cuando la lógica de un contrato realiza una llamada externa y el destinatario vuelve a llamarlo antes de que termine la ejecución original y se restauren sus invariantes. Si siguen visibles saldos, participaciones, permisos o precios antiguos, el callback puede repetir una acción o afectar otra usando un estado incoherente.
El callback no tiene que entrar en la misma función ni transferir moneda nativa. Los hooks de tokens, callbacks de receptores NFT, llamadas a bóvedas o estrategias y callbacks de préstamos flash pueden transferir el control a código no confiable. La reentrada puede cruzar funciones, contratos y módulos; la reentrada de solo lectura también puede hacer que otro protocolo consuma un valor transitorio.
Cómo funciona
Una explotación típica sigue esta secuencia:
- El atacante entra en una función que cambia el estado y supera sus comprobaciones iniciales.
- El contrato vulnerable llama a una dirección, token o protocolo antes de completar su contabilidad.
- El código controlado por el atacante entra en el contrato original o conectado mientras el estado antiguo sigue visible.
- La llamada anidada repite un efecto o cambia estado compartido, y la ejecución retorna con una invariante rota.
La transferencia de control externa puede ser explícita, como una llamada de bajo nivel, u ocultarse tras una transferencia de tokens, hook de receptor, acuñación segura, adaptador de estrategia o interfaz de llamada arbitraria. Un revert suele deshacer el árbol de llamadas afectado, pero el atacante puede construir una secuencia anidada exitosa que retorne normalmente después de extraer valor o corromper la contabilidad.
Las defensas deben combinarse:
- Aplicar comprobaciones-efectos-interacciones: validar primero, confirmar todos los efectos internos relevantes y dejar la interacción externa para el final.
- Usar una protección en cada punto de entrada que comparta la invariante protegida, no solo en la función con la llamada evidente.
- Preferir cobros iniciados por el receptor cuando corresponda y reducir llamadas a tokens, receptores, hooks, proxies e integraciones no confiables.
- Definir invariantes entre contratos y probar callbacks maliciosos, rutas entre funciones, multicalls anidadas y consumidores de solo lectura.
- Combinar la revisión de implementación con monitoreo, pausa y respuesta a incidentes cuando el diseño lo permita.
Ejemplo
Considere una bóveda que envía valor y borra el saldo del usuario solo después de la llamada:
function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "empty balance");
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "transfer failed");
balances[msg.sender] = 0;
}El receptor obtiene el control mientras balances[msg.sender] aún contiene el importe anterior. Su función receptora puede volver a llamar a withdraw(), superar la misma comprobación y pedir otra transferencia. Actualizar el saldo antes de la llamada cierra esta ventana; una protección puede rechazar la entrada anidada. Ningún cambio demuestra que todo el sistema sea seguro, pues otro punto de entrada o contrato conectado puede exponer la misma invariante incompleta.
Riesgos
- Una protección cubre una función mientras otra expone el mismo estado.
- El orden local es correcto, pero una invariante entre contratos sigue incoherente durante el callback.
- Un token, receptor, callback de préstamo flash o adaptador de estrategia ejecuta código externo inesperadamente.
- Una función view publica un precio o tipo de cambio transitorio que otro protocolo usa en la misma transacción.
- Una actualización, módulo o cambio de distribución de almacenamiento elude o corrompe el bloqueo original.
- Las pruebas cubren el retiro recursivo, pero omiten rutas entre funciones, contratos y de solo lectura.
Para los usuarios, un sello de auditoría o protección de reentrada demuestra que existe un control, no que haya una garantía. Las actualizaciones, integraciones y acciones de emergencia privilegiadas pueden cambiar la superficie de ataque. Limite permisos y exposición, verifique la implementación desplegada y no suponga que las pérdidas se pueden revertir.
Errores comunes
- La reentrada solo repite una función de retiro. Puede entrar en otra función o contrato, o mostrar datos incoherentes a un lector.
- Solo las transferencias de moneda nativa activan callbacks. Los estándares de tokens, hooks de receptores e integraciones también pueden ejecutar código externo.
- Una protección o comprobaciones-efectos-interacciones hacen seguro el contrato. Su alcance, todos los puntos de entrada compartidos y las invariantes entre contratos aún requieren revisión.
- Una auditoría satisfactoria descarta la reentrada. Las auditorías tienen alcance limitado; cambios posteriores e integraciones no revisadas pueden crear rutas nuevas.
Temas relacionados
- Contrato inteligente
- Auditoría de contratos inteligentes
- Máquina Virtual de Ethereum
- ERC-1155
- Estándar de bóveda ERC-4626
Fuentes
- Consideraciones de seguridad - Solidity Documentation (consultado: 2026-08-21)
- Seguridad de contratos inteligentes - ethereum.org (consultado: 2026-08-21)
- ReentrancyGuard - OpenZeppelin Documentation (consultado: 2026-08-21)
- SC08:2026 Ataques de reentrada - OWASP Smart Contract Security (consultado: 2026-08-21)