# Tutorial

In this tutorial, we will explore flash loans. Flash loans allow users to take out large loans with no collateral, granted that the money is returned in the same transaction. Here's the basic idea behind flash loans:

  1. The loan receiver takes out a loan from the lender.
  2. The loan receiver does something with the money.
  3. The loan receiver repays the loan (potentially with an additional fee) to the loan lender.

If the lender is not repaid by the end of the transaction, the transaction fails. Remember that in Ethereum, transactions are atomic, so a failure in one part of the transaction nullifies the entire transaction and its effects. So, unlike classic loans, there's no risk of default - if the loan isn't paid back, then the loan never happened in the first place.

While the Ethereum community has many well-established protocols supporting flash loans (e.g., Aave (opens new window)) and standardizations (e.g., EIP-3156 (opens new window)) we will begin with a simple example of a flash loan.

To finance our flash loans, we use a new ERC20, LoanEth:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "OpenZeppelin/openzeppelin-contracts@4.4.2/contracts/token/ERC20/ERC20.sol";

contract LoanToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("LoanETH", "lETH") {
        _mint(msg.sender, initialSupply);
    }
}

And we deploy a lender contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./LoanToken.sol";

interface ReceiverInterface {
   function receiveLoan(address token_address) external returns (bool);
}

contract LenderTutorial {

    LoanToken public token;
    bool public completedLoan = false;
    bool public calledFunction = false;

    constructor() payable {
        token = new LoanToken(100);
    }

    function flashLoan(address borrower, uint256 borrowAmount) external {

        require(borrowAmount > 0, "Borrow amount must be greater than 0");

        uint256 balanceBefore = token.balanceOf(address(this));
        require(balanceBefore >= borrowAmount, "Not enough ETH in pool");

        token.transfer(borrower, borrowAmount);
        require(token.balanceOf(address(borrower)) == borrowAmount, "Borrower did not receive loan");

        ReceiverInterface borrowerInterface = ReceiverInterface(borrower);
        bool success = borrowerInterface.receiveLoan(address(token));
        require(success, "Call to receiveLoan(token_address) failed");

        require(token.balanceOf(address(this)) == balanceBefore, "Loan was not repaid!");
        completedLoan = true;
    }

    function mustHaveMoney() external returns (bool) {
        require(token.balanceOf(address(msg.sender)) == 100);
        calledFunction = true;
        return true;
    }

    function isCompleted() external view returns (bool) {
        return completedLoan && calledFunction;
    }
}

Let's dissect this. First, we create a new ERC20 and mint 100 tokens. The most interesting part is in flashLoan(address payable borrower, uint256 borrowAmount). We first ensure that the loan pool has enough LoanEth to give. Then, we transfer that over to the receiver's address and call the receiver's receive() function. After checking if the function succeeded, we ensure that the receiver transfered the LoanEth back to us.

For this lab, you should deploy the Receiver contract, take out a flash loan to call the mustHaveMoney() function successfully, then return the money.

Here's some code to get you started with implementing the contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import 'IERC20.sol';

interface LoanerInterface {
   function mustHaveMoney() external returns (bool);
}
interface ReceiverInterface {
    function receiveLoan(address token_address) external returns (bool);
}
contract Receiver is ReceiverInterface {
    ...
}