16 min read
ยท3 years ago
๐ต Borrow millions for free from Aave using Flash Loans

Have you ever wanted to become a millionaire without having to give up anything? Well, that's what flash loans are for. ๐ค๐ค
In this lesson we will learn how to take a Flash Loan from Aave and utilize this new concept in DeFi which doesn't exist in the web2 world. There is no good analogy for this from the traditional finance world since this is simply impossible outside blockchains.
Are you excited? Well, I surely am ๐ฅณ๐ฅณ
Which one of the following is the company behind flash loans?
Uniswap
Sushiswap
Aave
Synthetix
๐ฆ Traditional Banking Systems?
How do traditional banking systems work? If you want a loan you have to put forward a collateral against which you can take the loan. This is typically how lending/borrowing in DeFi also works.
However, you may need just a shit ton of money at times to execute some sort of attack that you cannot possibly provide collateral for, perhaps to execute a huge arbitrage trade or attack some contracts.
๐ธ What are Flash Loans?
You might be thinking: Is it some kind of loan? Well, yes, it is. It's a special type of loan where a borrower can borrow an asset as long as they return the borrowed amount and some interest before the end of the transaction. Since the borrowed amount is returned back, with interest, in the same transaction, there is no possibility for anyone to run away with the borrowed money. If the loan is not repaid in the same transaction, the transaction fails overall and is reverted.
What happens if the borrower is not able to repay the borrowed money at the end in a flash loan transaction?
The borrower is penalized
The borrower is banned from taking a flash loan again
The transaction fails and is reverted as if the borrower never took a loan in the first place
This simple but fascinating detail is what allows you to borrow billions with no upfront capital or collateral because you need to pay it back in the same transaction itself. However, you can go wild with that money between borrowing and paying it back.
What are flash loans?
It's a loan where a borrower can borrow assets without requiring a collateral
It's a loan where a borrower can borrow assets by providing a collateral
It is not a loan
Remember that all of this happens in one transaction ๐
How many transactions does it take to execute a flash loan?
2-10
1
10+
๐ธ Applications of a Flash Loan
They help in arbitrage between assets, causing liquidations in DeFi lending protocols, often play part in DeFi hacks, and other use cases. You can essentially use your own creativity to create something new ๐
In this tutorial we will only focus on how a Simple Flash Loan
works which includes being able to borrow one asset. There are alternatives where you can borrow multiple assets as well. To read about other kinds of flash loans, read the documentation from Aave
Which one of the following is not an application of a flash loan?
Arbitrage
Liquidations
All are valid examples
Let us try to go a little deep on one use case: arbitrage.
What is arbitrage? Imagine there are two crypto exchanges - A and B. Now A is selling a token LW3
for a lower price than B. You can profit if you buy LW3
from A in exchange for DAI and then sell it on B gaining more DAI than the amount you initially started with.
Trading off price differences across exchanges is called arbitrage. Arbitrageurs are a necessary evil that help keep prices consistent across exchanges.
๐ค How do Flash Loans work?
There are 4 basic steps to any flash loan. To execute a flash loan, you first need to write a smart contract that has a transaction that uses a flash loan. Assume the function is called createFlashLoan()
. The following 4 steps happen when you call that function, in order:
Your contract calls a function on a Flash Loan provider, like Aave, indicating which asset you want and how much of it
The Flash Loan provider sends the assets to your contract, and calls back into your contract for a different function,
executeOperation
executeOperation
is all custom code you must have written - you go wild with the money here. At the end, you approve the Flash Loan provider to withdraw back the borrowed assets, along with a premiumThe Flash Loan provider takes back the assets it gave you, along with the premium.

If you look at this diagram, you can see how a flash loan helped the user make a profit in an arbitrage trade. Initially the user started a transaction by calling lets say a method createFlashLoan
in your contract which is named as FlashLoan Contract
. When the user calls this function, your contract calls the Pool Contract
which exposes the liquidity management methods for a given pool of assets and has already been deployed by Aave. When the Pool Contract
receives a request to create a flash loan, it calls the executeOperation
method on your contract with the DAI in the amount user has requested. Note that the user didn't have to provide any collateral to get the DAI, he just had to call the transaction and that Pool Contract
requires you to have the executeOperation
method for it to send you the DAI
Which contract sends your contract the asset/assets you requested to borrow using the flash loan?
Pool Contract
Liquidity Contract
Asset Contract
Now in the executeOperation
method after receiving the DAI, you can call the contract for Exchange A
and buy some LW3
tokens from all the DAI that the Pool Contract
sent you. After receiving the LW3 Tokens
you can again swap them for DAI by calling the Exchange B
contract.
Which method is required in your contract for the contract to be able to receive a flash loan?
executeTask
execute
executeOperation
By this time now your contract has made a profit, so it can allow the Pool Contract
to withdraw the amount which it sent our contract along with some interest and return from the executeOperation
method.
Once our contract returns from the executeOperation
method, the Pool Contract
has allowance to withdraw the DAI it originally sent along with interest from our FlashLoan Contract
, so it withdraws it.
All this happens in one transaction, if anything is not satisfied during the transaction like for example our contract fails in doing the arbitrage, remember everything will get reverted and it will be as if our contract never got the DAI in the first place. All you would have lost is the gas fees for executing all this.
What do you need to payback for borrowing the asset/assets using the flash loans ?
the assets/asset which you borrowed
premiums
the assets/asset which you borrowed and their respective premiums
Nothing
User can now withdraw profits from the contract after the transaction is completed.
It has been suggested by Aave to withdraw the funds after a successful arbitrage and not keep them long in your contract because it can cause a griefing attack
.
An example has been provided here.
Why is it suggested to take out funds from your contract after a successful arbitrage as early as you can?
because of re entrancy
because of griefing attack
because of signature replay
๐จBuild
Let's build an example where you can experience how we can start a flash loan. Note we won't be actually doing an arbitrage here, because finding profitable arbitrage opportunities is the hardest part and not related to the code, but will essentially learn how to execute a flash loan.
NOTE: Just like hedge funds protect their trading strategies, protecting arbitrage opportunities is important for arbitrageurs. If those opportunities are made public, they really aren't opportunities anymore as someone is always willing to 'do it cheaper' driving the profit margin to essentially zero.
To start the project, open up your terminal and create a new project directory.
mkdir flash-loans
Let's start by setting up a Foundry project inside the flash-loans
directory.
cd flash-loans
forge init .
Now, let's install the required dependencies for the project.
forge install openzeppelin/openzeppelin-contracts aave/aave-v3-core
This installs the OpenZeppelin contracts library and the Aave contracts library.
Finally, run the following command to configure remappings in your project
forge remappings > remappings.txt
Writing the Smart Contract
Now let's gets started with writing our smart contract, create your first smart contract inside the flash-loans/src
directory and name it FlashLoanExample.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import "aave-v3-core/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import "aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract FlashLoanExample is FlashLoanSimpleReceiverBase {
event Log(address asset, uint256 val);
constructor(IPoolAddressesProvider provider)
FlashLoanSimpleReceiverBase(provider)
{}
function createFlashLoan(address asset, uint256 amount) external {
address receiver = address(this);
bytes memory params = ""; // use this to pass arbitrary data to executeOperation
uint16 referralCode = 0;
POOL.flashLoanSimple(receiver, asset, amount, params, referralCode);
}
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool) {
// do things like arbitrage here
// abi.decode(params) to decode params
uint256 amountOwing = amount + premium;
IERC20(asset).approve(address(POOL), amountOwing);
emit Log(asset, amountOwing);
return true;
}
}
Now let's try to decompose this contract and understand it a little better.
When we declared the contract we did it like this contract FlashLoanExample is FlashLoanSimpleReceiverBase {...}
, our contract is named FlashLoanExample
and it is inheriting a contract named FlashLoanSimpleReceiverBase
which is a contract from Aave which you use to setup your contract as the receiver for the flash loan.
Now after declaring the contract, if we look at the constructor, it takes in a provider of type IPoolAddressesProvider
which is essentially the address of the Pool Contract
we talked about in the example above wrapped around an interface of type IPoolAddressesProvider
. This interface is also provided to us by Aave and can be found here. FlashLoanSimpleReceiverBase
requires this provider in its constructor.
constructor(IPoolAddressesProvider provider)
FlashLoanSimpleReceiverBase(provider)
{}
The first function we implemented was createFlashLoan
which takes in the asset and amount from the user for which he wants to start the flash loan. Now for the receiver address, you can specify the address of the FlashLoanExample Contract
and we have no params, so let's just keep it as empty. For referralCode
we kept it as 0 because this transaction was executed by user directly without any middle man. To read more about these parameters you can go here. After declaring these variables, you can call the flashLoanSimple
method inside the instance of the Pool Contract
which is initialized within the FlashLoanSimpleReceiverBase
which our contract had inherited, you can look at the code here.
Which function do you use in your contract to create a flash loan to borrow only one asset?
flashLoanSimple
flashLoan
flashLoans
function createFlashLoan(address asset, uint amount) external {
address receiver = address(this);
bytes memory params = "";
uint16 referralCode = 0;
POOL.flashLoanSimple(
receiver,
asset,
amount,
params,
referralCode
);
}
After making a flashLoanSimple
call, Pool Contract
will perform some checks and will send the asset in the amount that was requested to the FlashLoanExample Contract
and will call the executeOperation
method. Now inside this method you can do anything with this asset but in this contract we just give approval to the Pool Contract
to withdraw the amount that we owe along with some premium. Then we emit a log and return from the function.
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool){
// do things like arbitrage, liquidation, etc
// abi.decode(params) to decode params
uint amountOwing = amount + premium;
IERC20(asset).approve(address(POOL), amountOwing);
emit Log(asset, amountOwing);
return true;
}
}
Now we will try to create a test to actually see this flash loan in action.
Now because Pool Contract
is deployed on Ethereum Mainnet, we need some way to interact with it in our tests.
We use a feature of Foundry known as Forking
. It creates a local fork of the Ethereum blockchain from the block number when we run our tests - so our local fork will have all the data up until that point (i.e. liquidity in Aave pools) and then we can locally create new txns/blocks for our local fork. That way you can interact with deployed protocols and test complex interactions locally. Note this has been referenced from the official Foundry Book.
To configure this, let's add the env variable for QUICKNODE_RPC_URL
. Create a new file called .env
inside flash-loans
and add the following:
QUICKNODE_RPC_URL="<QUICKNODE-RPC-URL-FOR-ETHEREUM-MAINNET>"
Replace QUICKNODE-RPC-URL-FOR-ETHEREUM-MAINNET
with the URL of the node for Ethereum Mainnet. To get this URL go to Quicknode and login. After that click on Create an Endpoint
and select chain as Ethereum
and network as Mainnet
. Copy the HTTP Provider
link from your dashboard, and add it to the environment file.
After creating the .env
file, create a new file in test
and name it FlashLoanExample.t.sol
. This is where we'll write our test. Paste the following code there:
Create a new file named config.js
as well inside flash-loans
and add the following lines of code to it
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {FlashLoanExample} from "../src/FlashLoanExample.sol";
import "aave-v3-core/contracts/interfaces/IPoolAddressesProvider.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
contract CounterTest is Test {
//Ethereum Mainnet DAI Contract Address
address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
// Mainnet Pool contract address
address constant POOL_ADDRESS_PROVIDER =
0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e;
//vm.envString is provided by Foundry so that we can read our .env file and get values from there
string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");
...
//remaining code
...
If you look at this snippet, we have two variables - DAI
, and POOL_ADDRESS_PROVIDER
. DAI
is the address of the DAI
contract on Ethereum mainnet. POOL_ADDRESS_PROVIDER
is the address of the PoolAddressesProvider
on Etheruem mainnet that our contract is expecting in the constructor. The address can be found here.
Since we are not actually executing any arbitrage, and therefore will not be able to pay the premium if we run the contract as-is, we use Foundry's deal
cheatcode, that lets us arbitrarily set the balance of any address, even without their private key and send transactions on their behalf. However, of course, this only works on the local development network and not on real networks. Using this feature, we will "magically" get our account funded with some DAI so we have enough DAI to pay back the loan with premium.
Now add the following lines of code to your file:
uint256 mainnetFork;
IERC20 public token;
FlashLoanExample public flashLoanExample;
function setUp() public {
// vm is a variable included in the forge standard library that is used to manipulate the execution environment of our tests
// create a fork of Ethereum mainnet using the specified RPC URL and store its id in mainnetFork
mainnetFork = vm.createFork(MAINNET_RPC_URL);
//select the fork thus obtained, using its id
vm.selectFork(mainnetFork);
//deploy FlashLoanExample to the created fork with POOL_ADDRESS_PROVIDER as its constructor argument
flashLoanExample = new FlashLoanExample(IPoolAddressesProvider(POOL_ADDRESS_PROVIDER));
//fetch the DAI contract
token = IERC20(DAI);
}
function testTakeAndReturnLoan() public {
// Get 2000 DAI in our contract by using deal
// deal is a cheatcode that lets us arbitrarily set the balance of any address and works with most ERC-20 tokens
uint BALANCE_AMOUNT_DAI = 2000 ether;
deal(DAI, address(flashLoanExample), BALANCE_AMOUNT_DAI);
// Request and execute a flash loan of 10,000 DAI from Aave
flashLoanExample.createFlashLoan(DAI, 10000);
// By this point, we should have executed the flash loan and paid back (10,000 + premium) DAI to Aave
// Let's check our contract's remaining DAI balance to see how much it has left
uint remainingBalance = token.balanceOf(address(flashLoanExample));
// Our remaining balance should be <2000 DAI we originally had, because we had to pay the premium
//asserLt => assert strictly less than
assertLt(remainingBalance, BALANCE_AMOUNT_DAI);
}
}
Now let's try to understand whats happening in these lines of code.
setUp
is a special function in Forge's testing library that runs before each test. First, we declare a variable called mainnetFork
. This stores the id of the forked chain that we create a few lines later with vm.createFork
. vm.selectFork
then tells our execution environment to use the chain id in mainnetFork
. We use IERC20(DAI)
to be able to interact with the DAI contract. Remember Foundry will simulate Ethereum Mainnet, so when you get the contract at the address of DAI
which you had specified, Foundry will actually create an instance of the DAI contract which matches that of Ethereum Mainnet.
Tests in Foundry start with the string "test". We create a test called testTakeAndReturnLoan
that will take a loan from aave and return it with the required premium. We use the deal
cheatcode provided to us by Forge's standard library to arbitrarily change the balance of our flashLoanExample
contract to 2000 ether. We need to do this so we can pay off the loan with premium, as we will otherwise not be able to pay the premium. In real world applications, the premium would be paid off the profits made from arbitrage or attacking a smart contract.
After this we start a flash loan and checking that the remaining balance of FlashLoanExampleContract
is less than the amount it initially started with, the amount will be less because the contract had to pay a premium on the loaned amount.
flashLoanExample.createFlashLoan(DAI, 10000);
// By this point, we should have executed the flash loan and paid back (10,000 + premium) DAI to Aave
// Let's check our contract's remaining DAI balance to see how much it has left
uint remainingBalance = token.balanceOf(address(flashLoanExample));
// Our remaining balance should be <2000 DAI we originally had, because we had to pay the premium
//asserLt => assert strictly less than
assertLt(remainingBalance, BALANCE_AMOUNT_DAI);
To run the test you can simply execute on your terminal:
forge test
If all your tests pass, then that means you successfully were able to borrow a loan of 10,000 DAI from Aave with no required collateral and then paid back the 10k DAI + a premium.
Hope you learnt something from this tutorial. As always, if you feel stuck somewhere, reach out on the Discord and we'd be happy to help you out!
Babak Fa
ยท7 months ago
๐
Bobi Dada
ยท8 months ago
Thank you
F.ch
ยท9 months ago
Ok
HopelessGramophone
ยท9 months ago
Useful
Soft-spokenGallop
ยท9 months ago
Great