Notional Finance V1: cómo una conversión de uint128 habría ocultado una deuda de 2^128
Un análisis técnico del incidente reportado en Notional Finance V1, donde una conversión insegura habría hecho que una deuda extrema se valorara como cero y permitiera retirar cerca de 1,73 millones de dólares.
Notional Finance V1: cómo una conversión de uint128 habría ocultado una deuda de 2^128
Tiempo de lectura: 8 minutos
El 3 de septiembre de 2026, aproximadamente 1,73 millones de dólares en DAI y USDC salieron del contrato Escrow de Notional Finance V1. CryptoTimes informó ese monto reportado y describió el movimiento posterior de los fondos. La transacción de retiro es pública en Ethereum, pero por sí sola no demuestra toda la secuencia previa ni la intención de quien la ejecutó.
La hipótesis técnica publicada por EtherWorld, contrastable con la inspección del código público y de las transacciones señaladas, apunta a una conversión directa de un entero de gran tamaño a uint128 dentro de una ruta crítica para calcular la solvencia. Al construir una deuda de exactamente 2^128, el atacante habría conseguido que el sistema la interpretara como cero.
El incidente todavía no cuenta con un post-mortem oficial y autoritativo de Notional Finance. Por eso, la pérdida, la causa raíz y la secuencia descritas en este artículo deben entenderse como información reportada por terceros y evidencia pública disponible al momento de escribir; no como una reconstrucción independiente que pruebe cada paso del ataque.
Qué está confirmado y qué sigue pendiente
La transacción de retiro reportada figura como exitosa en Ethereum. CryptoTimes reportó que el contrato pagó aproximadamente:
- 69.257,37 DAI
- 1.658.524,86 USDC
CryptoTimes también informó la conversión posterior de esos activos a aproximadamente 689,2 ETH y su envío a Tornado Cash. Esas cifras y ese recorrido se presentan aquí como reporte de ese medio, aunque las transacciones públicas permiten examinar movimientos concretos.
Sin embargo, todavía faltan datos autoritativos sobre:
- La pérdida definitiva reconocida por Notional Finance.
- El alcance sobre usuarios o posiciones históricas de V1.
- Las medidas de contención adoptadas por el equipo.
- La causa raíz validada mediante un post-mortem oficial.
- La posible recuperación de fondos.
Cómo funcionaba la comprobación de solvencia
Notional V1 representaba préstamos a plazo fijo mediante posiciones llamadas fCash. Una posición positiva representaba el derecho a recibir fondos al vencimiento. Una posición negativa representaba una obligación de pago.
La documentación de Notional describe el free collateral como el valor disponible tras considerar activos, deudas, descuentos y márgenes de seguridad.
Ese cálculo protegía una propiedad esencial:
Una cuenta insolvente no debe poder crear posiciones ni retirar activos reales del protocolo.
La conversión que habría transformado una deuda en cero
El código público de ExchangeRate.sol contiene esta operación dentro de _convertToETH():
uint128 absBalance = uint128(balance.abs());
balance.abs() produce un valor sin signo de hasta 256 bits. Después, el código lo convierte directamente a uint128, un tipo que solo puede representar valores entre 0 y 2^128 - 1.
Si el valor supera ese límite, una conversión explícita de este tipo no conserva los bits superiores. El resultado equivale a quedarse únicamente con los 128 bits inferiores.
EtherWorld reconstruyó una preparación con dos obligaciones cuya suma era exactamente:
1 + (2^128 - 1) = 2^128
La transacción de preparación reportada puede inspeccionarse públicamente. La interpretación de que esas operaciones formaron parte de esta ruta de explotación sigue siendo una reconstrucción de EtherWorld, no una conclusión oficial de Notional.
Los 128 bits inferiores de 2^128 son todos cero. Por eso, al reducir el valor a uint128, la deuda habría quedado representada como 0.
La operación crítica habría sido una conversión estrecha sin validación de límites, también conocida como unsafe narrowing cast o truncamiento.
El mismo archivo utiliza SafeCast.toUint128() para otras conversiones. Esa variante revierte cuando el valor no entra en 128 bits. Aplicar una comprobación equivalente al saldo habría impedido que 2^128 continuara como cero.
Por qué una sola conversión no explica todo el incidente
El truncamiento habría sido explotable por la interacción entre varios supuestos.
El contrato Portfolios.sol permitía crear pares fCash mediante mintfCashPair(). La función registraba una obligación para el pagador y una posición positiva para el receptor.
Después verificaba el free collateral del pagador:
(int256 fc, , ) = freeCollateral(payer);
require(fc >= 0, "insufficient free collateral");
El receptor no pasaba por la misma comprobación porque el diseño asumía que recibir una posición positiva siempre mejoraba su solvencia.
La reconstrucción de EtherWorld, junto con la inspección de los contratos y transacciones públicas enlazados, plantea que el ataque habría combinado:
- La posibilidad de crear pares
fCashcon valores controlados. - Una deuda construida exactamente en el límite
2^128. - Una conversión a
uint128que habría truncado esa deuda a cero. - Una comprobación de solvencia aplicada solo al pagador.
- La liquidación de posiciones fabricadas para convertirlas en saldos retirables de DAI y USDC.
La compatibilidad de estas piezas con el código público no sustituye una atribución oficial de causalidad. Bajo esa hipótesis, el sistema económico habría confiado en un valor que ya había perdido información durante la conversión.
Qué deberían auditar los protocolos DeFi
Este caso deja controles concretos para cualquier protocolo que calcule deuda, colateral o capacidad de retiro.
1. Prohibir conversiones estrechas sin validación
Toda conversión desde un tipo más amplio hacia uno más pequeño debe revertir si el valor queda fuera de rango.
uint128 absBalance = SafeCast.toUint128(balance.abs());
No alcanza con revisar sumas y multiplicaciones. Los casts explícitos también forman parte de la superficie aritmética.
2. Probar los límites matemáticos
Las pruebas deben incluir, como mínimo:
012^128 - 12^1282^128 + 1- Valores negativos equivalentes cuando se usan enteros con signo
3. Expresar invariantes de solvencia
Una prueba debería verificar propiedades como:
- Crear posiciones compensadas no puede generar valor neto retirable.
- Una deuda mayor nunca puede producir una valoración menor por truncamiento.
- Una cuenta con free collateral negativo no puede retirar fondos.
- La suma de derechos emitidos no puede superar los activos respaldados por el sistema.
4. Revisar ambos lados de una operación
Cuando una función crea simultáneamente una obligación y un derecho, la auditoría debe analizar al pagador, al receptor y la relación global entre ambos. Un supuesto válido para transferencias normales puede dejar de serlo cuando un atacante controla todas las cuentas participantes.
5. No ignorar contratos heredados
El incidente afectó código de Notional V1, una versión heredada. El software antiguo sigue siendo parte de la superficie de ataque mientras conserve fondos, permisos, liquidez o rutas ejecutables.
La lección central
En sistemas DeFi, una comprobación de solvencia es tan confiable como cada conversión que alimenta su resultado.
La revisión correcta no consiste únicamente en buscar reentradas, oráculos manipulables o controles de acceso. También exige preguntarse si cada valor conserva su significado económico al cambiar de tipo, precisión o signo.
Este artículo será actualizado cuando Notional Finance publique un post-mortem técnico autoritativo con la causa raíz y el impacto definitivo.
Fuentes
- Notional Finance V1, código de ExchangeRate.sol
- Notional Finance V1, código de Portfolios.sol
- Notional Finance, documentación de free collateral
- Etherscan, transacción de retiro reportada
- Etherscan, transacción de preparación reportada
- CryptoTimes, Notional Finance Escrow Contract Faces Reported $1.7M Exploit
- EtherWorld, Notional Finance Exploit Drains $1.7M