Los préstamos flash (flash loans) son préstamos entre contratos inteligentes que deben devolverse en la misma transacción. Este artículo describe la especificación de préstamos flash ERC 3156, así como las formas en que los prestamistas (lenders) y prestatarios (borrowers) de préstamos flash pueden ser hackeados. Al final se proporcionan ejercicios de seguridad sugeridos.
A continuación se muestra un ejemplo extremadamente simple de un préstamo flash.

Si el prestatario no devuelve el préstamo, la declaración require con el mensaje “flash not paid back” hará que toda la transacción se revierta.
Solo los contratos pueden trabajar con préstamos flash
Una billetera EOA no puede llamar a una función para obtener el préstamo flash y luego transferir los tokens de vuelta en una sola transacción. La integración con un préstamo flash requiere un contrato inteligente separado.
Los préstamos flash no necesitan garantía
Si un préstamo flash se implementa correctamente (¡un gran si!), entonces no hay riesgo de que el préstamo no sea devuelto, porque un revert o una declaración require fallida hará que la transacción falle, y el Ether no se transferirá.
¿Para qué se utilizan los préstamos flash?
Arbitraje
El caso de uso más común para un préstamo flash es realizar una operación de arbitraje. Por ejemplo, si el Ether se cotiza a $1,200 en un pool y a $1,300 en otra aplicación DeFi, sería deseable comprar el Ether en el primer pool y venderlo en el segundo para obtener una ganancia de $100. Sin embargo, en primer lugar necesitas dinero para comprar el Ether. Un préstamo flash es la solución ideal para esto, ya que no necesitas tener $1,200 a la mano. Puedes pedir prestados $1,200 en Ether, venderlos por $1,300 y devolver los $1,200, quedándote con una ganancia de $100 (menos las comisiones).
Refinanciación de préstamos
Para los préstamos regulares de DeFi, por lo general se requiere algún tipo de garantía. Por ejemplo, si pidieras prestados $10,000 en stablecoins, necesitarías depositar $15,000 en Ether como garantía.
Si tu préstamo en stablecoins tuviera un interés del 5% y quisieras refinanciarlo con otro contrato inteligente de préstamos al 4%, necesitarías:
- pagar los $10,000 en stablecoins
- retirar la garantía de $15,000 en Ether
- depositar la garantía de $15,000 en Ether en el otro protocolo
- pedir prestados $10,000 en stablecoins nuevamente a la tasa de interés más baja
Esto sería problemático si tuvieras los $10,000 inmovilizados en alguna otra aplicación. Con un préstamo flash, puedes realizar los pasos del 1 al 4 sin usar ninguna de tus propias stablecoins.
Intercambio de garantías
En el ejemplo anterior, el prestatario estaba usando $15,000 en Ether como garantía. Pero, ¿supongamos que el protocolo ofrece un ratio de colateralización más bajo usando wBTC (wrapped bitcoin)? El prestatario podría usar un préstamo flash y una serie de pasos similares a los descritos anteriormente para intercambiar la garantía en lugar del capital principal.
Liquidación de prestatarios
En el contexto de los préstamos DeFi, si la garantía cae por debajo de un cierto umbral, entonces dicha garantía puede ser liquidada: vendida forzosamente para cubrir el costo del préstamo. En el ejemplo anterior, si el valor del Ether cayera a $12,000, entonces el protocolo podría permitir a alguien comprar el Ether por $11,500 si primero pagan el préstamo de $10,000.
Un liquidador podría usar un préstamo flash para saldar el préstamo de $10,000 en stablecoins y recibir $11,500. Luego venderían esto en otro exchange por stablecoins, y después devolverían el préstamo flash.
Aumento del rendimiento para otras aplicaciones DeFi
Uniswap y AAVE generan dinero para los depositantes a través de comisiones de trading o intereses de préstamos. Pero dado que tienen una cantidad tan grande de capital en un solo lugar, pueden ganar dinero adicional ofreciendo también préstamos flash. Esto aumenta la eficiencia del capital, ya que el mismo capital ahora tiene más usos.
Creación de un bucle de apalancamiento en una sola transacción
Se pueden lograr posiciones largas y cortas apalancadas (longs y shorts) utilizando protocolos de préstamos. Por ejemplo, para ir en largo apalancado con ETH, un usuario puede depositar ETH como garantía en un pool de préstamos, pedir prestada una stablecoin, intercambiar la stablecoin por ETH, y luego depositar el ETH en el pool de préstamos y seguir repitiendo el proceso. El tamaño combinado de la garantía y el ETH prestado será mayor que la cantidad original, otorgando así al prestatario una mayor exposición al precio de ETH.
Para ir en corto apalancado con ETH, un usuario puede depositar stablecoins en un pool de préstamos, pedir prestado ETH, intercambiar el ETH por stablecoins, y luego depositar las stablecoins en el pool de préstamos y seguir repitiendo el proceso. Ahora el usuario tiene una gran deuda de ETH que será más fácil de pagar si el precio de ETH cae.
La cantidad total de activos que se pueden pedir prestados de esta manera es
donde es el ratio préstamo-valor (loan-to-value) máximo que aceptará el protocolo. Por ejemplo, si el protocolo requiere un depósito de $1000 en stablecoins para pedir prestados $800 en ETH, entonces el LTV es . Así, el usuario podría tener hasta veces la exposición al precio de ETH respecto al valor de su depósito. Es decir, podrían estar expuestos a $5,000 en ETH con un depósito de $1000.
En lugar de hacer todas estas transacciones en un bucle que podría costar bastante gas, se puede:
- Usar un préstamo flash para pedir prestados $5,000 en stablecoins
- Intercambiar las stablecoins por $5,000 en ETH
- Poner el ETH en el pool de préstamos como garantía
- Pedir prestados $4,000 en stablecoins del pool de préstamos
- Añadir $1,000 de sus propias stablecoins a los $4,000 en stablecoins que pidió prestados del pool de préstamos y devolver el préstamo flash.
Ahora el usuario tiene $5,000 en ETH como garantía y $4,000 en stablecoins prestadas del pool de préstamos.
Hackeo de Smart Contracts
Los préstamos flash son probablemente más famosos por su uso por parte de hackers de sombrero negro para explotar protocolos. Los principales vectores de ataque para los préstamos flash son la manipulación de precios y la manipulación de la gobernanza (votos). Utilizados en aplicaciones DeFi con defensas inadecuadas, los préstamos flash permiten a los atacantes comprar masivamente un activo aumentando su precio, o adquirir un montón de tokens de votación para forzar la aprobación de una propuesta de gobernanza.
La siguiente es una lista de hackeos de préstamos flash para los curiosos. Sin embargo, la vulnerabilidad es de doble filo. Un contrato prestamista y un contrato prestatario de préstamos flash también pueden ser vulnerables a perder dinero si no se implementan correctamente.
Ejemplos de hackeos con préstamos flash
Los ataques con préstamos flash son uno de los exploits más comunes, presumiblemente porque los desarrolladores que provienen de un entorno Web2 no están acostumbrados a ellos. Aquí hay algunos de los ejemplos más notorios.
rekt.news/deus-dao-rekt/
rekt.news/jimbo-rekt/
rekt.news/platypus-finance-rekt/
rekt.news/beanstalk-rekt/
rekt.news/inverse-rekt2/
El uso de préstamos flash para hackear protocolos es un tema aparte, este artículo se centra en las implementaciones inseguras de contratos de prestamistas y prestatarios de préstamos flash.
Protocolo ERC3156
ERC 3156 busca estandarizar la interfaz para obtener préstamos flash. Aunque el flujo de trabajo es directo, los detalles exactos de implementación necesitan definirse, por ejemplo, ¿deberíamos llamar a la función getFlashLoan, onFlashLoan, o de otra manera? ¿Y luego qué parámetros debería aceptar?
Especificación del Receptor ERC3156
El primer aspecto del estándar es la interfaz que el prestatario necesita implementar, la cual se muestra a continuación. El prestatario solo necesita implementar una función.

A continuación describimos los argumentos de la función:
initiator
Esta es la dirección que inició el préstamo flash. Probablemente quieras algún tipo de validación aquí para que direcciones no confiables no inicien préstamos flash en tu contrato. Por lo general, la dirección serías tú, ¡pero no deberías asumir eso!
Se espera que la función onFlashLoan sea llamada por el contrato del préstamo flash, no por el initiator. Deberías comprobar que msg.sender sea el contrato del préstamo flash dentro de la función onFlashLoan() porque esta función es externa y cualquiera puede llamarla.
El initiator no es msg.sender ni el contrato del préstamo flash. Es la dirección que desencadenó que el contrato prestamista del flash loan llamara a la función onFlashLoan del receptor.
token
Esta es la dirección del token ERC20 que estás pidiendo prestado. Los contratos que ofrecen préstamos flash generalmente tendrán varios tokens que pueden prestar. El estándar de préstamo flash ERC 3156 no admite el préstamo flash de Ether nativo, pero esto puede implementarse prestando WETH y haciendo que el prestatario desenvuelva (unwrap) el WETH. Debido a que el contrato prestatario no es necesariamente el contrato que llamó al prestamista, es posible que se deba indicar al contrato prestatario qué token se está prestando.
fee
La tarifa (fee) es la cantidad del token que debe pagarse como comisión por el préstamo. Se denomina en una cantidad absoluta, no en porcentajes.
data
Si tu contrato receptor de préstamos flash no está programado (hard coded) para tomar una acción particular al recibir un préstamo flash, puedes parametrizar su comportamiento con el parámetro data. Por ejemplo, si tu contrato está haciendo arbitraje entre pools de trading, entonces especificarías con qué pools operar.
valor de retorno
El contrato debe devolver keccak256("ERC3156FlashBorrower.onFlashLoan") por razones que discutiremos más adelante.
Implementación de referencia del prestatario
Esto ha sido modificado a partir del código en la especificación ERC 3156 para hacer el fragmento más pequeño. Ten en cuenta que este contrato todavía deposita una confianza perfecta en el prestamista del flash loan. Si el prestamista se viera comprometido de alguna manera, el contrato a continuación podría ser explotado al alimentarlo con datos falsos de amount, fee e initiator. Si el prestamista es inmutable, esto no es una preocupación, pero podría ser un vector de ataque si el prestamista es actualizable.

Especificación del Prestamista ERC3156
A continuación se muestra la interfaz para el prestamista especificada por ERC3156

Los argumentos en la interfaz anterior tienen el mismo significado que se describió en la sección anterior, por lo que no se repetirán aquí.
La función flashLoan() necesita lograr algunas operaciones importantes:
- Alguien podría llamar a
flashLoan()con un token que el contrato de préstamo flash no soporta. Esto debe verificarse. - Alguien podría llamar a
flashLoan()con un amount que sea mayor quemaxFlashLoan. Esto también debe verificarse. datasimplemente se reenvía al llamador.
Aún más importante, flashLoan() debe transferir los tokens al receptor y transferirlos de vuelta. No debe depender de que el prestatario transfiera los tokens de vuelta para el reembolso. La razón de esto se discutirá en la próxima sección. Hemos copiado la implementación de referencia que se puede encontrar en la especificación de EIP 3156 aquí para enfatizar las partes importantes:

Ten en cuenta que la implementación de referencia asume que los tokens ERC20 devuelven verdadero en caso de éxito, lo que no todos hacen, así que usa la biblioteca SafeTransfer si utilizas tokens ERC20 que no cumplen con el estándar.
Consideraciones de Seguridad
Control de acceso y validación de entrada para el prestatario
El contrato inteligente prestatario debe tener los controles en su lugar para permitir solo que el contrato prestamista sea quien llame a onFlashLoan(). De lo contrario, algún actor distinto al prestamista del flash loan puede llamar a onFlashLoan() y causar comportamientos inesperados.
Además, cualquiera puede llamar a flashloan() con un prestatario arbitrario como objetivo y pasar data arbitraria. Para asegurar que la data no sea maliciosa, un contrato receptor de préstamos flash solo debería permitir un conjunto restringido de initiators.
Los bloqueos de reentrada son muy importantes
ERC 3156 por definición no puede seguir el patrón checks-effects-interactions para prevenir reentradas (reentrancy). Tiene que notificar al prestatario que ha recibido los tokens (hacer una llamada externa), luego transferir los tokens de vuelta. Como tal, se deben añadir bloqueos nonReentrant al contrato.
Es importante que el prestamista sea quien transfiera los tokens de vuelta o que existan bloqueos de reentrada.
En las implementaciones anteriores, el prestamista transfiere los tokens de vuelta desde el prestatario. El prestatario no transfiere los préstamos al prestamista. Esto es importante para evitar las “entradas laterales” (side entrances) donde el prestatario deposita dinero en el protocolo como prestamista. Ahora el pool ve que su balance ha vuelto a ser el de antes, pero el prestatario de repente se ha convertido en un prestamista con un gran depósito.
El préstamo flash de UniswapV2 no transfiere los tokens de vuelta después de que el préstamo flash finaliza. Sin embargo, utiliza un bloqueo de reentrada para asegurar que el prestatario no pueda “pagar el préstamo” depositándolo de vuelta en el protocolo como si fueran un prestamista.
Para el prestatario, asegurar que solo el contrato prestamista pueda llamar a onFlashLoan
El prestamista de flash loans está programado (hardcoded) para llamar solo a la función onFlashLoan() del receptor y a nada más. Si un prestatario tuviera una forma de especificar a qué función llamaría el prestamista, entonces el préstamo flash podría manipularse para transferir otros tokens en su posesión (llamando a ERC20.transfer) o conceder aprobación a su balance de tokens hacia una dirección maliciosa.
Debido a que tales acciones requieren una llamada explícita a un transfer o approve de ERC20, esto no puede suceder si el prestamista de flash loans solo puede llamar a onFlashLoan().
Este exploit ocurrió en el mundo real, aquí Rekt News documenta a un bot MEV siendo hackeado.
El uso de token.balanceOf(address(this)) puede ser manipulado
En la implementación anterior, no usamos balanceOf(address(this)) excepto para determinar el tamaño máximo del préstamo flash. Esto puede ser alterado por alguien más que transfiera tokens directamente al contrato, interfiriendo con la lógica. La forma en que sabemos que el préstamo flash fue pagado es porque el prestamista transfirió de vuelta el amount del préstamo + fee. Existen formas válidas de usar balanceOf(address(this)) para verificar el reembolso, pero esto debe combinarse con verificaciones de reentrada para evitar pagar el préstamo como un depósito.
Por qué el prestatario del préstamo flash necesita retornar keccak256(“ERC3156FlashBorrower.onFlashLoan”);
Esto maneja la situación donde un contrato (no el contrato del prestamista del flash loan) con una función fallback ha otorgado aprobación al contrato prestamista del flash loan. Alguien podría iniciar repetidamente un préstamo flash con ese contrato como receptor. Entonces ocurriría lo siguiente:
- El contrato víctima obtiene un préstamo flash
- El contrato víctima es llamado con
onFlashLoan()y la función fallback se activa pero no se revierte. La función fallback responde a cualquier llamada de función que no coincida con el resto de las funciones en el contrato, por lo que responderá a una llamadaonFlashLoan(). - El prestamista del flash loan retira tokens del prestatario + fee
Si esta operación sucede en un bucle, el contrato víctima con el fallback será vaciado (drained). Lo mismo podría suceder con una billetera EOA, ya que llamar a una dirección de billetera con onFlashLoan no causa un revert.
Comprobar que la función onFlashLoan no genera un revert no es suficientemente bueno. El prestamista del flash loan también comprueba que se devuelva el valor keccack256("ERC3156FlashBorrower.onFlashLoan") para saber que el prestatario tenía la intención de pedir prestados los tokens y también pagar el fee.
Problemas de práctica relacionados con préstamos flash
Los siguientes problemas de DamnVulnerableDeFi y Mr Steal Yo Crypto pueden ayudarte a practicar los vectores de ataque descritos anteriormente. Una de las mejores maneras de entender los préstamos flash es aprender qué no hacer al implementarlos.
- Naive Receiver (tu objetivo es vaciar al prestatario, no al prestamista del flash loan)
- Side Entrance
- Truster
Repasa tus conocimientos sobre ERC 4626 y luego practica estos problemas:
- Unstoppable (este es un poco más difícil, así que hazlo al final. Tu objetivo es bloquear [brick] el contrato, no robar los fondos).
- Flash Loaner (de Mr Steal Yo Crypto. Asegúrate de entender ERC 4626)
Todos estos problemas están relacionados con hackear al prestamista o al prestatario, no con usar un préstamo flash para hackear otra cosa.
Aprende más con RareSkills
Este material es parte de nuestro Solidity Bootcamp avanzado. Por favor, consulta el programa para aprender más.
Publicado originalmente el 7 de noviembre de 2023