Flash loan on uniswap v2

I am building a smart contract for flash loan swap, however, whenever I call my "flash" function I get "execution reverted: Dai/insufficient-balance". I read through your discussions but nothing solved it. can someone please guide me, I am kind of learning also.

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IUniswap {
    function swapExactTokensForTokens(uint256, uint256, address[] calldata, address, uint256) external returns (uint256[] memory);
    function swapExactETHForTokens(uint256, address[] calldata, address, uint256) external payable returns (uint256[] memory);
    function WETH() external pure returns (address);
    function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts);
}

contract FlashBot is Ownable {
    using SafeMath for uint256;

    uint256 public constant MAX_SLIPPAGE_RATIO = 5; // 5%
    uint256 public constant MIN_PROFIT = 0.05 ether;
    uint256 public constant MIN_TOKEN_BALANCE = 0.1 ether;
    uint256 public constant TRADE_INTERVAL = 60; // 1 minute
    uint256 public nextTradeTime;

    address public uniswapV2Router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; 
    address public WETH = IUniswap(uniswapV2Router).WETH();

    event FlashLoanExecuted(address indexed user, address indexed token, uint256 flashLoanAmount, uint256 expectedAmount, uint256 profit);

    function flash(address token, uint256 flashLoanAmount) public onlyOwner {
   IERC20 tokenContract = IERC20(token);
    IERC20 wethContract = IERC20(WETH);

    require(tokenContract.approve(uniswapV2Router, flashLoanAmount), "Approval failed");
    require(wethContract.approve(uniswapV2Router, flashLoanAmount), "WETH approval failed");
    require(tokenContract.transferFrom(msg.sender, address(this), flashLoanAmount), "Token transfer failed");

    // Add this code to approve the contract to spend tokens from the user's account
    require(tokenContract.approve(address(this), 100000000000000), "Approval failed");

    // Calculate the gas cost of the transaction
    uint256 gasCost = gasleft() * tx.gasprice;

    // Calculate the flash loan amount needed to cover the gas cost
    uint256 flashLoanAmountWithGas = flashLoanAmount.add(gasCost);

    // Execute the flash loan trade
    address[] memory path = new address[](2);
    path[0] = token;
    path[1] = WETH;

    // Swap the tokens for WETH
    uint256[] memory amounts = IUniswap(uniswapV2Router).swapExactTokensForTokens(
        flashLoanAmountWithGas,
        0,
        path,
        address(this),
        block.timestamp + 60
    );

Trying to help: Did the EOA calling the flash function approve the FlashBot contract to spend the DAI beforehand ?

Just to clarify: When the contract calls tokenContract.approve(address(this), 100000000000000) in your code, the FlashBot contract makes an external call to the tokenContract and the msg.sender is FlashBot, not the EOA who called the flash function. So here, FlashBot is approving itself to spend its own tokens by calling tokenContract.approve(address(this), ...), which is pointless (a contract can call transfer and transferFrom from its own address without approving the tokens first).

The EOA has to call tokenContract.approve(*flashbot_contract*, 100000000000000) independently, and then call flash. The flashbot_contract will then be able to call tokenContract.transferFrom(msg.sender, address(this), flashLoanAmount) to transfer tokens to itself (on behalf of the EOA).

As I said, I am quite new to this. so after reading your answer the solution I came up with is adding a new "approve" function before calling Flash but I am getting the same error,


    function approveToken(address token, uint256 amount) public onlyOwner {
        IERC20 tokenContract = IERC20(token);
        require(tokenContract.approve(address(this), amount), "Approval failed");
    }

    function flash(address token, uint256 flashLoanAmount) public onlyOwner {
     IERC20 tokenContract = IERC20(token);
     IERC20 wethContract = IERC20(WETH);
   
      require(tokenContract.approve(uniswapV2Router, flashLoanAmount), "Approval failed");
     require(wethContract.approve(uniswapV2Router, flashLoanAmount), "WETH approval failed");
       tokenContract.transferFrom(msg.sender, address(this), flashLoanAmount);

Did I completely misunderstand you? :slight_smile:

Whoever is calling approveToken in your test needs to be the one to call the tokenContract.approve. Don't make it a function in the contract otherwise the contract will be the one approving and not the sender because the contract is making an external call to the tokenContract.

Basically, from the user's perspective:

  1. daiContract.approve(address(flashbotContract), type(uint256).max) <- Note the daiContract, not flashbotContract
  2. flashbotContract.flash(...) etc.

Alright, Thanks for sparing your time. It means a lot. If I have more questions I will be back

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

interface IUniswapV2Router02 {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function factory() external view returns (address);
    function WETH() external view returns (address);
    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function addLiquidityETH(address token,uint amountTokenDesired,uint amountTokenMin,uint amountETHMin,address to,uint deadline) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable returns (uint[] memory amounts);
}

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
}

contract UniswapV2FlashSwap {
    address public immutable factory;
    address public immutable WETH;
    address public owner;

    address public constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

    constructor(address _factory, address _WETH) {
        factory = _factory;
        WETH = _WETH;
        owner = msg.sender;
    }

    receive() external payable {}

    function uniswapV2Call(address, uint, uint, bytes calldata) external {}

    function initiateFlashSwap(address _tokenIn,  uint256 _amountIn,  address _tokenOut, address _targetToken, uint256 _amountOutMin) external payable {
        require(msg.sender == owner, "UniswapV2FlashSwap: caller is not the owner");

        uint256 profit = 0;
        while (true) {
            // Calculate the amount of tokens to borrow
            uint256 amountToBorrow = IUniswapV2Router02(UNISWAP_V2_ROUTER).getAmountsIn(_amountOutMin + profit, getPath(_tokenOut, _tokenIn))[0];

            IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, amountToBorrow);

            // Borrow tokens from Uniswap using flash loan
            address[] memory path = getPath(WETH, _tokenIn);
            IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactETHForTokens{value: amountToBorrow}(
                amountToBorrow,
                path,
                address(this),
                block.timestamp
            );

        // Swap borrowed tokens for target token
        path = getPath(_tokenIn, _targetToken);
        uint[] memory amounts = IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactTokensForTokens(
            IERC20(_tokenIn).balanceOf(address(this)),
            _amountOutMin,
            path,
            address(this),
            block.timestamp
        );


                      // Calculate profit and check if it's greater than zero
        profit = amounts[amounts.length - 1] - (_amountOutMin + profit);
        require(profit > 0, "UniswapV2FlashSwap: insufficient profit");

        // Swap target tokens for borrowed tokens
        IERC20(_targetToken).approve(UNISWAP_V2_ROUTER, amounts[amounts.length - 1]);
        IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactTokensForTokens(
            amounts[amounts.length - 1],
            0,
            getPath(_targetToken, _tokenIn),
            address(this),
            block.timestamp
        );

        // Repay flash loan
        IERC20(_tokenIn).transfer(msg.sender, amountToBorrow);

        // Check if target token balance is greater than or equal to requested amountOutMin
        if (IERC20(_targetToken).balanceOf(address(this)) >= _amountOutMin) {
            // Transfer target tokens to the caller
            IERC20(_targetToken).transfer(msg.sender, IERC20(_targetToken).balanceOf(address(this)));
            break;
        }
    }
}

function getPath(address _tokenIn, address _tokenOut) private view returns (address[] memory path) {
    if (_tokenIn == WETH || _tokenOut == WETH) {
        path = new address[](2);
        path[0] = _tokenIn;
        path[1] = _tokenOut;
    } else {
        path = new address[](3);
        path[0] = _tokenIn;
        path[1] = WETH;
        path[2] = _tokenOut;
    }
}

function transferOwnership(address newOwner) external {
    require(msg.sender == owner, "UniswapV2FlashSwap: caller is not the owner");
    owner = newOwner;
}

function withdrawToken(address token, uint256 amount) external {
    require(msg.sender == owner, "UniswapV2FlashSwap: caller is not the owner");
    IERC20(token).transfer(msg.sender, amount);
}

function withdrawETH(uint256 amount) external {
    require(msg.sender == owner, "UniswapV2FlashSwap: caller is not the owner");
    payable(msg.sender).transfer(amount);
}
}

This version of code giving an compiler warning because of unused uint256 _amountIn at initiateFlashSwap function.

So you preferred to not use this parameter?

P.S. Google leads to this code as an example of flash loans contract on Uniswap V2, so it will be nice to resolve this warning, and polish this code for the next visitors searching for it

just says that some parameter in the function is currently unused so you might want to use the parameter. Even if not other smart contracts that are arleady deployed might give you this parameter as you might want to use it. You need to know if you need that parameter and if any contract that might initiate this function needs the parameter.

1 Like