LearnWeb3 Logo

16 min read

ยท

3 years ago

+15,000 XP
368
41
12

๐Ÿ’ต 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 premium

  • The 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!

*You must be signed in to submit quiz
You must be signed in to post comments
User avatar

Babak Fa

ยท

7 months ago

๐Ÿ‘

0
User avatar

Bobi Dada

ยท

8 months ago

Thank you

0
User avatar

F.ch

ยท

9 months ago

Ok

0
User avatar

HopelessGramophone

ยท

9 months ago

Useful

0
User avatar

Soft-spokenGallop

ยท

9 months ago

Great

0
BUGG Logo