← Volver al blog
Mariano Salazar16 de junio de 2026

Delegatecall no mueve dinero: la sutileza que abrió un bug crítico de *Infinite Spend* en Aurora Bridge

> Cómo una mala interpretación de `msg.value`, combinada con `delegatecall`, puso en riesgo la invariante económica del bridge de Aurora, y cómo el hallazgo temprano de un auditor evitó una explotación multimillonaria.

En auditoría de smart contracts hay primitivas que obligan a frenar y mirar dos veces. delegatecall es una de ellas.

No porque sea incorrecta por naturaleza, sino porque altera algo fundamental: el código que se ejecuta no necesariamente corre en el contexto económico que muchos asumen.

Cuando un protocolo crítico, como un bridge, construye lógica sensible sobre una suposición equivocada de ese contexto, el resultado puede ser devastador.

Eso fue exactamente lo que ocurrió en el caso de Aurora Bridge. El problema no nació de una línea “obviamente rota”, sino de una confusión mucho más peligrosa: creer que msg.value > 0 implicaba que el contrato correcto había recibido ETH legítimamente.

Ese supuesto era falso bajo delegatecall, y esa sutileza abrió la puerta a un bug de inflación o Infinite Spend con impacto potencial multimillonario.

Lo más importante es que esta historia no terminó en desastre. La vulnerabilidad fue identificada y reportada de forma responsable antes de que pudiera ser explotada. Y esa diferencia cambió por completo el desenlace.


Un bridge no mueve solo mensajes, conserva valor

Para entender por qué este bug era tan grave, primero hay que entender qué protege realmente un bridge.

Un bridge no es solamente un conjunto de contratos que emiten eventos y coordinan mensajes entre cadenas. En esencia, es una máquina de conservación de valor.

Su invariante fundamental suele verse así:

Activos bloqueados o quemados = activos acuñados o liberados

Por ejemplo:

  • En Ethereum se bloquea 1 ETH
  • En NEAR o Aurora se acuña 1 nETH o su representación equivalente

Mientras esa relación se mantenga, el sistema conserva integridad económica.

Cuando se rompe, el protocolo empieza a emitir representación de valor sin respaldo real. Y ahí el problema deja de ser una mala validación aislada para convertirse en inflación artificial de activos.

Eso fue precisamente lo que estuvo en juego en Aurora.


Qué es delegatecall, y por qué importa tanto

delegatecall es una instrucción de bajo nivel de la EVM que permite ejecutar el código de otro contrato, pero manteniendo el contexto del contrato llamador.

En otras palabras, el contrato externo “presta” su lógica, pero no su contexto de ejecución.

call vs delegatecall

Con call

  • Se ejecuta el código del contrato destino
  • Se usa el storage del contrato destino
  • El contexto pertenece al contrato destino
  • Si se envía valor, ese valor se transfiere al destino en esa llamada

Con delegatecall

  • Se ejecuta el código del contrato destino
  • Pero se usa el storage del contrato llamador
  • Se preservan valores como msg.sender y msg.value
  • La ejecución ocurre como si ese código perteneciera al caller

La idea clave es esta:

delegatecall preserva el contexto de ejecución, pero no equivale a una transferencia económica normal hacia el contrato cuyo código se está reutilizando.

Ese matiz es el corazón del caso Aurora.


Por qué existe delegatecall

delegatecall no es una rareza académica. Es una pieza central del ecosistema EVM moderno.

La mayoría de los proxies upgradeables se apoyan en esta idea:

  • El proxy conserva el storage
  • La implementation aporta la lógica

Esto permite actualizar comportamiento sin migrar los datos.

El problema es que ese mismo poder también lo vuelve peligroso.

Cada vez que aparece delegatecall, conviene revisar con mucho cuidado:

  • qué contrato controla la ejecución real
  • qué storage se modifica
  • qué se asume sobre msg.sender
  • qué se asume sobre msg.value
  • si la lógica depende de que un valor haya sido recibido por un contrato específico

Porque si cualquiera de esas suposiciones falla, el protocolo puede terminar validando una ilusión de contexto en lugar de una realidad económica.


El problema en Aurora Bridge

Aurora contaba con contratos especiales, entre ellos ExitToNear y ExitToEthereum, encargados de gestionar retiros hacia otras redes dentro de la lógica del bridge.

El flujo esperado era razonable en apariencia:

  1. Un usuario inicia un retiro
  2. Los activos se queman o bloquean en Aurora
  3. Se genera la señal correcta para el bridge
  4. En la cadena destino se liberan o acuñan los activos correspondientes

La suposición implícita era esta:

Si msg.value > 0, entonces el contrato especial recibió ETH legítimamente.

Suena intuitivo. Pero deja de ser seguro cuando esa lógica puede alcanzarse mediante delegatecall.

Y ahí estaba el problema real: no era solo una rareza técnica de EVM, era una debilidad que afectaba directamente la seguridad económica del bridge de Aurora.


El error conceptual de fondo

El caso Aurora dejó una lección muy importante:

Ver un msg.value positivo no siempre significa que el contrato que ejecuta la lógica sensible recibió realmente el ETH en el sentido económico que el sistema asume.

Con delegatecall, el contexto de la llamada original se preserva. Eso incluye msg.value.

Entonces, si un protocolo interpreta ese msg.value como prueba suficiente de que ocurrió un depósito o una recepción legítima en el contrato correcto, puede aceptar una operación que parece válida desde la ejecución, pero no lo es desde la contabilidad económica del sistema.

Y en un bridge, esa diferencia lo cambia todo.


Cómo podía romperse la lógica del bridge

Según el análisis público del caso, un atacante podía utilizar un contrato bajo su control para ejecutar vía delegatecall la lógica asociada a ExitToNear.

Durante esa ejecución ocurría algo extremadamente delicado:

  • msg.value seguía siendo positivo
  • la lógica de salida interpretaba eso como una operación legítima
  • se emitía un evento válido para el bridge
  • pero el contrato especial no había recibido realmente el ETH como parte de una llamada económica normal hacia él

En otras palabras:

El sistema veía una señal válida, pero la transición económica que esa señal debía representar no había ocurrido realmente.

Ese era el punto de ruptura.

Y en un bridge, si la señal y la realidad económica dejan de coincidir, la invariante central del sistema empieza a quebrarse.


Un evento válido no prueba una transición económica válida

Esta es, probablemente, la lección más fuerte del caso.

Un evento no es una prueba económica. Es solo un log.

Un evento puede demostrar que una cierta ruta de código se ejecutó. No demuestra por sí solo que:

  • el activo fue bloqueado
  • el balance correcto disminuyó
  • el contrato correcto recibió valor
  • la quema o inmovilización realmente ocurrió
  • la invariante del bridge sigue intacta

En el caso Aurora, el bridge podía interpretar un evento emitido desde la lógica esperada como prueba suficiente de un retiro válido.

Pero esa confianza era equivocada.

El evento existía.
La realidad económica equivalente, no necesariamente.


Por qué esto se convertía en un bug de Infinite Spend

El impacto no era simplemente ejecutar un retiro inválido una sola vez.

Era peor.

La consecuencia era poder reutilizar el mismo valor económico múltiples veces.

A grandes rasgos, el flujo era este:

  1. Un atacante enviaba ETH hacia Aurora
  2. Desplegaba un contrato malicioso
  3. Usaba delegatecall para ejecutar la lógica de ExitToNear
  4. Obtenía nETH en NEAR como si hubiera ocurrido un retiro legítimo
  5. Reingresaba ese nETH
  6. Repetía el proceso

Resultado:

  • el valor originalmente respaldado no se consumía como debía
  • pero el sistema seguía permitiendo emitir o liberar representación equivalente

Eso rompe la invariante del bridge y genera el escenario más peligroso para este tipo de sistemas:

el sistema termina contabilizando más activos de los que realmente están respaldados

Y ahí aparece la inflación artificial.

Por eso el hallazgo fue descrito como un bug de Infinite Spend: el mismo valor base podía ser contabilizado más de una vez.


Lo que evitó el desastre

Acá está el punto más importante del artículo.

Este caso no es relevante solo por la gravedad técnica del bug, sino porque no llegó a convertirse en una explotación real.

De acuerdo con el reporte público, la vulnerabilidad fue identificada y reportada responsablemente por pwning.eth a través de Immunefi antes de que un atacante la explotara en producción.

Eso permitió que Aurora corrigiera el problema a tiempo.

Y esa diferencia es enorme:

  • no hubo drenaje masivo de fondos
  • no hubo colapso del respaldo económico del bridge
  • no hubo explotación en cadena del mecanismo de inflación

Este caso también muestra por qué la auditoría, la investigación de seguridad y los programas de bug bounty importan de verdad.

Cuando funcionan bien, no solo encuentran bugs. Evitan catástrofes antes de que se conviertan en pérdidas irreversibles.


Por qué el impacto era tan crítico

El impacto potencial estimado era enorme:

  • 70.000 ETH
  • alrededor de 200 millones de dólares en otros activos

Eso importa porque demuestra algo clave:

Los bugs más caros no siempre nacen de aritmética rota o de access control débil. A veces nacen de entender mal una primitiva de ejecución.

En este caso, el error fue confundir:

  • contexto de ejecución
  • con
  • transferencia económica real

Y en un bridge, esa confusión puede destruir la integridad del sistema entero.


Qué debería revisar un auditor cuando ve delegatecall

Cada vez que aparece delegatecall, conviene revisar como mínimo esta checklist:

1. Quién controla el target

¿La dirección del contrato cuyo código se ejecuta está estrictamente controlada?

2. Qué se asume sobre msg.sender

¿La autorización depende de una identidad que puede preservarse de forma engañosa por contexto?

3. Qué se asume sobre msg.value

¿La lógica trata msg.value como prueba suficiente de recepción legítima de fondos?

4. Qué storage layout espera la lógica

¿La ejecución delegada escribe en slots compatibles o puede generar corrupción o colisiones?

5. Si hay colisiones de contexto

¿La función fue diseñada para ejecutarse solo en un contrato específico?

6. Si existen protecciones anti-delegatecall

¿Hay guards que invaliden ejecuciones delegadas en rutas sensibles?

7. Si el protocolo confía en eventos como señal de verdad

¿El sistema downstream valida balances y estado, o simplemente consume logs?

8. Si puede romperse una invariante económica

¿Existe alguna forma de emitir, liberar o acuñar activos sin que el respaldo real cambie como corresponde?


Mitigaciones

El caso Aurora también deja lecciones concretas de diseño defensivo.

No confiar solo en msg.value

Un msg.value > 0 no demuestra, por sí mismo, que el contrato correcto recibió fondos bajo el modelo económico esperado.

No tratar eventos como prueba suficiente

Los bridges deben validar:

  • balances reales
  • cambios de estado
  • quema o bloqueo efectivo
  • pruebas criptográficas cuando corresponda

Bloquear rutas sensibles contra delegatecall

Si una función fue diseñada para ejecutarse únicamente en su propio contexto, debe existir una protección explícita contra ejecución delegada.

Auditar invariantes, no solo funciones

El problema no siempre está en una línea aislada. Muchas veces está en una suposición sistémica que nadie escribió, pero todos dieron por válida.


Sobre el snippet de código

Los snippets low level, como llamadas manuales con assembly o invocaciones a contratos especiales, son un excelente punto de entrada para el análisis.

Sirven para hacerse preguntas correctas:

  • ¿qué pasa si esta llamada falla?
  • ¿qué contrato está ejecutando realmente la lógica?
  • ¿qué significa acá msg.value?
  • ¿esta señal representa una transición económica real o solo una ejecución?

Lo importante es no sobre-interpretar el fragmento aislado.

Un snippet puede mostrar la superficie de riesgo, pero el problema real aparece cuando esa superficie se combina con supuestos erróneos del protocolo completo.


Conclusión

El bug de Aurora no fue solo un error de implementación.

Fue, sobre todo, una ruptura potencial de la invariante económica del bridge causada por una comprensión incorrecta de delegatecall y de lo que realmente representa msg.value durante una ejecución delegada.

Y esa es la enseñanza más valiosa del caso.

delegatecall no mueve dinero por sí solo.
Mueve lógica.

Si el protocolo confunde esa lógica reutilizada con una transferencia económica real, puede empezar a emitir valor sin respaldo.

En smart contracts, y especialmente en bridges, ese tipo de error no es un detalle técnico. Es una catástrofe potencial.

En este caso, por suerte, también fue una catástrofe evitada: un hallazgo crítico identificado a tiempo por un investigador de seguridad antes de que pudiera convertirse en una explotación real.

Si estás buscando una auditoría de seguridad para tu protocolo, puedes contactarnos a través de nuestro sitio web seclat.xyz.

También puedes encontrar más contenido sobre seguridad Web3 en nuestro blog.

SECLAT - Security & Audit

SECLAT - Expertos en seguridad blockchain y auditorías de contratos inteligentes.

Estadísticas Clave

200+
Contratos Auditados
0
Incidentes Críticos
20+
Clientes Satisfechos
$100M+
Protegidos

Contactanos

Si buscas una auditoría de seguridad o una consulta, Contactanos.

© 2026 SECLAT Security. Todos los derechos reservados.