Polygon Flash Loans – Troubleshooting Aave Flashloan Script Errors

flash-loansjavascriptpolygonremixsolidity

I have successfully deployed and executed a flashloan on the Polygon network in testnet and have since adapted it for mainnet.

On Mainnet it compiles and gets deployed with no problem, but when I deploy in Remix and execute the requestFlashLoan function with any of the following addresses, followed by 1 USDC (1000000) I get these error messages:

Aave: aUSDC Token V3 – requestFlashLoan(0x625e7708f30ca75bfd92586e17077590c60eb4cd,1000000) -- Fail with error '27'

Token USD Coin (PoS) – requestFlashLoan(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174,1000000) -- Fail with error 'ERC20: transfer amount exceeds balance

Token Aave Matic Market USDC – requestFlashLoan(0x1a13F4Ca1d028320A707D99520AbFefca3998b7F,1000000) -- Fail with error '27'

I am not sure if I simply haven't found the right token address or if there's something else I have overlooked.

I used the addresses pool provider as recommended by Aave:
0xa97684ead0e402dc232d5a977953df7ecbab3cdb

I have my .env, hardhat.config.js and package.js files all in working order, as I said compiling and deployment is a breeze, no errors or warnings.

So here is my Solidiy script:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import {FlashLoanSimpleReceiverBase} from "https://github.com/aave/aave-v3-core/blob/master/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import {IPoolAddressesProvider} from "https://github.com/aave/aave-v3-core/blob/master/contracts/interfaces/IPoolAddressesProvider.sol";
import {IERC20} from "https://github.com/aave/aave-v3-core/blob/master/contracts/dependencies/openzeppelin/contracts/IERC20.sol";


contract FlashLoan is FlashLoanSimpleReceiverBase {
    address payable owner;

    constructor(address _addressProvider) FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))
    {
        owner = payable(msg.sender);
    }

    function executeOperation(
    address asset,
    uint256 amount,
    uint256 premium,
    address initiator,
    bytes calldata params
  ) external override returns (bool) {
    // We have the funds at this point
    // custom logic
    uint256 amountOwed = amount + premium;
    IERC20(asset).approve(address(POOL), amountOwed);

    return true;
  }

  function requestFlashLoan(address _token, uint256 _amount) public{
    address receiverAddress = address(this);
    address asset = _token;
    uint256 amount = _amount; 
    bytes memory params = "";
    uint16 referralCode = 0;

    POOL.flashLoanSimple(
        receiverAddress,
        asset,
        amount,
        params,
        referralCode
    );
  }

 function getBalance(address _tokenAddress) external view returns (uint256) {
        return IERC20(_tokenAddress).balanceOf(address(this));
 }

 function withdraw(address _tokenAddress) external onlyOwner{
    IERC20 token = IERC20(_tokenAddress);
    token.transfer(msg.sender, token.balanceOf(address(this)));
 }

    modifier onlyOwner(){

        require(msg.sender == owner, "Only the contract owner can call this function" );
        _;
    }

    receive() external payable {}

}

And here is my deployment script:

    const hre = require("hardhat");
    
    async function main() {
     const FlashLoan = await hre.ethers.getContractFactory("FlashLoan");
     const flashLoan = await FlashLoan.deploy(
      "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb"
      );
    
     await flashLoan.deployed();
     console.log("Flash loan contract deployed: ", flashLoan.address)
    }
    
    main().catch((error) => {
      console.error(error);
      process.exitCode = 1;
    });

With the contract deployed I then go to remix, compile the Solidity script then interact with my contract address to pull up the functions for the flashloan in remix and add the address and the amount and the amount and click on getFlashLoan.

enter image description here

But it never succeeds and I always get a warning pop-up regarding gas fees saying it will probably fail as soon as I click on the getFlashLoan function, so I max out the gas fees.

enter image description here

It always gets mined and appears as a failed transaction on Polygonscan with the one of the two error messages mentioned above.

enter image description here

enter image description here

Does anyone have any idea what it might be?

This is the original code for the flashloan on testnet (Mumbai):

// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import {FlashLoanSimpleReceiverBase} from "@aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";
import {IPoolAddressesProvider} from "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
import {IERC20} from "@aave/core-v3/contracts/dependencies/openzeppelin/contracts/IERC20.sol";

contract FlashLoan is FlashLoanSimpleReceiverBase {
    address payable owner;

    constructor(address _addressProvider)
        FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))
    {
        owner = payable(msg.sender);
    }

    /**
        This function is called after your contract has received the flash loaned amount
     */
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        //
        // This contract now has the funds requested.
        // Your logic goes here.
        //

        // At the end of your logic above, this contract owes
        // the flashloaned amount + premiums.
        // Therefore ensure your contract has enough to repay
        // these amounts.

        // Approve the Pool contract allowance to *pull* the owed amount
        uint256 amountOwed = amount + premium;
        IERC20(asset).approve(address(POOL), amountOwed);

        return true;
    }

    function requestFlashLoan(address _token, uint256 _amount) public {
        address receiverAddress = address(this);
        address asset = _token;
        uint256 amount = _amount;
        bytes memory params = "";
        uint16 referralCode = 0;

        POOL.flashLoanSimple(
            receiverAddress,
            asset,
            amount,
            params,
            referralCode
        );
    }

    function getBalance(address _tokenAddress) external view returns (uint256) {
        return IERC20(_tokenAddress).balanceOf(address(this));
    }

    function withdraw(address _tokenAddress) external onlyOwner {
        IERC20 token = IERC20(_tokenAddress);
        token.transfer(msg.sender, token.balanceOf(address(this)));
    }

    modifier onlyOwner() {
        require(
            msg.sender == owner,
            "Only the contract owner can call this function"
        );
        _;
    }

    receive() external payable {}
}

And this is the deployment script:

const hre = require("hardhat");

async function main() {
  console.log("deploying...");
  const FlashLoan = await hre.ethers.getContractFactory("FlashLoan");
  const flashLoan = await FlashLoan.deploy(
    "0xc4dCB5126a3AfEd129BC3668Ea19285A9f56D15D"
  );

  await flashLoan.deployed();

  console.log("Flash loan contract deployed: ", flashLoan.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Thanks in Advance.

Best Answer

The ERC20: transfer amount exceeds balance error means that you are trying to transfer or transferFrom more tokens than you currently hold.

The USDC token address you provided in your answer seems to be the correct one, so I don't think that is the issue.

I think the issue is being caused by the flashloan needing to be repaid with a premium. For example, you are borrowing $1 USDC, but when Aave tries to repay the loan on your behalf, they want to transferFrom the $1 USDC plus a little extra. So when the transferFrom call is made with $1.05~ USDC, it reverts because your flashloan receiver contract only has the initial $1 USDC.

You could add a conditional that views your USDC balanceOf before you attempt to repay the flashloan:

error InsufficientBalance(uint bal, uint amount);



function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        //
        // This contract now has the funds requested.
        // Your logic goes here.
        //

        // At the end of your logic above, this contract owes
        // the flashloaned amount + premiums.
        // Therefore ensure your contract has enough to repay
        // these amounts.

        // Check balance and revert if flashloan can't be repaid
        uint256 bal = getBalance(asset);
        uint256 amountOwed = amount + premium;
        if(bal < amountOwed) { 
        revert InsufficientBalance(bal, amountOwed);
        }

       // Approve the Pool contract allowance to *pull* the owed amount
        
        IERC20(asset).approve(address(POOL), amountOwed);
        return true;
    }

This way if your contract doesn't have enough USDC, the function call will revert with message that displays your balance compared to the amount you are trying to transfer.

I see you have a withdraw function already written, but for future reference to anyone reading this, if you do plan on sending real funds you a contract you've deployed, make sure you fully understand the code you've written and ensure that there is a way for you to withdraw your tokens, otherwise they will be lost forever.

Related Topic