Flash Loan bug

OK, I am trying to use a simplified version of a flash loan contract I am working on. The main version has two contracts and allows for variations on whether you start with a token loan or an eth loan and then which exchange you trade on first or second (UNI or KYBER).

But I was having a hard time getting it to run, so I made this leaner, streamlined, one-use-case contract. that is designed to pull an amount of eth from aave as a loan, and then trade it for a token on UNI and then trade that token back to ETH on KYBER. But still having issues trying to run this through remix on Ropsten.

pragma solidity ^0.5.16;

import "https://github.com/CryptoEatsTheWorld/flashloan-arbitrage/blob/master/contracts/FlashLoanReceiverBase.sol";
import "https://github.com/CryptoEatsTheWorld/flashloan-arbitrage/blob/master/contracts/ILendingPool.sol";
import "https://github.com/CryptoEatsTheWorld/flashloan-arbitrage/blob/master/contracts/ILendingPoolAddressesProvider.sol";
import "https://github.com/CryptoEatsTheWorld/flashloan-arbitrage/blob/master/contracts/ERC20.sol";
import "https://github.com/CryptoEatsTheWorld/flashloan-arbitrage/blob/master/contracts/IUniswap.sol";

interface IKyberNetworkProxy {
    function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) external view returns (uint expectedRate, uint slippageRate);
}

interface ISimpleKyberProxy {
    function swapTokenToEther(IERC20 token, uint256 srcAmount, uint256 minConversionRate) external returns (uint256 destAmount);
    function swapEtherToToken(IERC20 token, uint256 minConversionRate) external payable returns (uint256 destAmount);
    function swapTokenToToken(IERC20 src, uint256 srcAmount, IERC20 dest, uint256 minConversionRate) external returns (uint256 destAmount);
}

interface IWETH {
    function deposit() external payable;
    function withdraw(uint wad) external;
    function totalSupply() external view returns (uint);
    function approve(address guy, uint wad) external returns (bool);
    function transfer(address dst, uint wad) external returns (bool);
    function transferFrom(address src, address dst, uint wad) external returns (bool);
}

// MAINNET ADDRESS: 0x24a42fD28C976A61Df5D00D0599C34c4f90748c8
// KOVAN ADDRESS: 0x506B0B2CF20FAA8f38a4E2B524EE43e1f4458Cc5 // V2: 0x652B2937Efd0B5beA1c8d54293FC1289672AFC6b
// ROPSTEN ADDRESS: 0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728

contract OneFlashLoan is FlashLoanReceiverBase(address(0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728)){

  using SafeMath for uint;
  
  address erc20AddressOfAsset;
  uint8 firstSwapDEX;
  address firstSwapCoin;
  uint8 secondSwapDEX;
  address secondSwapCoin;
  address owner;
  address constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; // MAINNET, KOVAN & ROPSTEN ADDRESS ARE THE SAME
  IUniswapFactory public uniswapFactory = IUniswapFactory(UNISWAP_FACTORY);
  // MAINNET ADDRESS: 0x9AAb3f75489902f3a48495025729a0AF77d4b11e
  // KOVAN ADDRES: 0xc153eeAD19e0DBbDb3462Dcc2B703cC6D738A37c
  // ROPSTEN ADDRESS: 0x818E6FECD516Ecc3849DAf6845e3EC868087B755
  address constant KYBER_PROXY = 0x818E6FECD516Ecc3849DAf6845e3EC868087B755;
  IKyberNetworkProxy public kyberProxy = IKyberNetworkProxy(KYBER_PROXY);
  ISimpleKyberProxy public simpleKyberProxy = ISimpleKyberProxy(KYBER_PROXY);
  
  constructor() public { owner = msg.sender; }
  
  function() external payable {}
  
  function startArbUsingEthReserve(uint _amount, address _firstSwapCoin) external onlyOwner {

    firstSwapDEX = 1;
    firstSwapCoin = _firstSwapCoin;
    secondSwapDEX = 2;
    secondSwapCoin = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    bytes memory data = "";
    
    // THEN WE INITIATE THE LOAN, WHICH ON CALLBALL WILL TRIGGER executeoperation()
    ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool());
    lendingPool.flashLoan(address(this), address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE), _amount, data);

  }
  
  function executeOperation(address _reserve, uint256 _amount, uint256 _fee, bytes calldata) external {
    
    require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance, was the flashLoan successful?");
    
    address tempHeldToken;
    uint256 tempHeldTokenAmount;
    uint256 finalAmount;
    uint256 deadline;
    
    deadline = now + 3000;
    (tempHeldToken, tempHeldTokenAmount) = swapEthToTokenFirstStep(firstSwapCoin, deadline);
    finalAmount = swapTokenToEth(tempHeldToken, tempHeldTokenAmount);
    
    erc20AddressOfAsset = address(0);
    firstSwapDEX = 0;
    firstSwapCoin = address(0);
    secondSwapDEX = 0;
    secondSwapCoin = address(0);
    
    uint256 totalDebt = _amount.add(_fee);
    require(finalAmount > totalDebt, "Did not profit");
    transferFundsBackToPoolInternal(_reserve, totalDebt);

  }

  function swapEthToTokenFirstStep(address _destinationCoin, uint256 _deadline) internal returns (address, uint256) {

    uint256 boughtCoinAmount;

    address uniswap_token_Address = uniswapFactory.getExchange(_destinationCoin);
    IUniswapExchange specificUniswapExchange = IUniswapExchange(uniswap_token_Address);

    // SWAPPING AT EXCHANGE
    boughtCoinAmount = specificUniswapExchange.ethToTokenSwapInput.value(address(this).balance)(1, _deadline);

    return (_destinationCoin, boughtCoinAmount);
  }
  
  function swapTokenToEth(address _tempHeldToken, uint256 _tempHeldTokenAmount) internal returns (uint256) {

    uint256 boughtCoinAmount;
    ERC20 startingCoin = ERC20(_tempHeldToken);

    ERC20 eth = ERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
    (uint256 expectedRate, ) = kyberProxy.getExpectedRate(startingCoin, eth, _tempHeldTokenAmount);

    // SWAPPING AT EXCHANGE
    require(startingCoin.approve(address(kyberProxy), _tempHeldTokenAmount), "Could not approve token-to-token purchase (KYBER).");
    boughtCoinAmount = simpleKyberProxy.swapTokenToEther(startingCoin, _tempHeldTokenAmount, expectedRate);

    return boughtCoinAmount;
  }  
  
  
}

All you need to input is an amount of the loan (in WEI) and the Ropsten contract address of the token you want to swap to on the first swap and input those into the startArbUsinEthReserve function.

When I try on Remix it gives me that “Transaction likely to fail…” error and in the log it shows

transact to OneFlashLoan.startArbUsingEthReserve pending ...

transact to OneFlashLoan.startArbUsingEthReserve errored: [object Object]

It appears that it isn’t even getting past the first function where the loan is requested.

Any help would be awesome.

1 Like

I am not sure, but it seems like the error is in the executeOperation(),

(tempHeldToken, tempHeldTokenAmount) = swapEthToTokenFirstStep(firstSwapCoin, deadline);

And I do not know what are your parameters for startArbUsingEthReserve, so I really do not know wether you can swap the firstSwapCoin in the uniswap.

BTW, a kind reminder, you can use the package of the OpenZeppelin: npm install @openzeppelin/contracts, so you do not rewrite some contracts, such as SafeMath.sol.
And at the same time, you can have a check about this tool: Better Solidity debugging: console.log, and now they rename this tool to Hardhat.

1 Like

Thanks but not sure how you determined that the issue may be with that function. That function is a callback that is triggered after the loan is obtained. I think the loan doesn’t ever get completed to begin with.

One thought I had is this. I import “flashloanreceiverbase” and the associated contracts with it. Then I have to create an instance of that contract in my “contract” statement, as you can see in the code.

But there were different pragam versions of all these files, which is why I copied them all to my personal repo and adjusted the pragma versions to match. Nothing too drastic - I changed them from 0.5.0 to ^0.5.16. Everything compiles fine, but could this be breaking my contract? Like if the live contract instantiated in this line

...is FlashLoanReceiverBase(address(0x1c8756FD2B28e9426CDBDcC7E3c4d64fa9A54728))

is not the exact same prama version number of the imported version of that contract I am using - even though all the data is the same in both?

1 Like

If do not make mistake, when you call

lendingPool.flashLoan(address(this), asset, amount, data);

if this asset is supported by the aave protocol and there is enough cash, you can borrow out immediately, so your arbitrage logic is in the function

executeOperation()

So wether this token can be swapped in the uniswap I am not sure, and what are your parameters when call startArbUsingEthReserve ()

1 Like

Well after deploying on Ropsten (this is the deployment address: 0x241B5B6227DC54B46BD603c7f6b0b01c1f22Ca0A) I then entered these for my flash loan:

I tried entering DIA(Ropsten): 0xaD6D458402F60fD3Bd25163575031ACDce07538D as the coin to swap to. And then if I try an amount of 10 Ether (in wei) I get:

Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
execution reverted: There is not enough liquidity available to borrow

So I figure OK, the ropsten pool doesn’t have much in it I guess. So then I try 1 ether and it says:

Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
execution reverted

It would make sense if it failed and reverted AFTER the transaction went through, because maybe the Arb was a bad trade that wasn’t profitable, but it isn’t even getting to that step yet. It’s giving this error before I even try to pay to go ahead with the transaction.

1 Like

Anyone else have any feedback? I’m still stuck on this.

1 Like

A little more progress here. I realized after some course work today that one flaw in my contract is I was trying to send return variables back from the functions that were doing the exchange trades. I didn’t realize that functions that change state cannot return values. So I changed those functions to use state variables instead of the return values.

Anyway, not saying everything is sorted out yet, but it was just another thing to fix as I move forward on this.

1 Like

Hi @CryptoEatsTheWorld,

My first thoughts are:

  • It looks like you have cloned OpenZeppelin Contracts into your repository. I would import from an OpenZeppelin Contracts release tag, e.g. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/token/ERC20/ERC20.sol

  • I would look at Debugging using Hardhat to help work out what is going on: https://hardhat.org/hardhat-network/#console-log

(These are both what @Skyge suggested :smile:).

Does your contract have enough funds to pay the fee for your flash loan?

If you execute your contract at this stage, the transaction will fail. This is because your MyFlashloanContract.sol contract does not have enough Dai to pay back the Flash Loan (as the executeOperation() function does not produce any extra funds to payback the loan). To solve this, ensure your contract has some Dai to pay back the Flash Loan .

From: https://docs.aave.com/developers/tutorials/performing-a-flash-loan/…-with-remix#call-your-contract

Well the whole point of arbitrage is that it generates enough funds to pay back the loan and the fee. If not, then it SHOULD revert. Which is why it would not make sense to just provide DAI to my contract and waste it on failed loans that make no money.

In flash loan tutorials they always provide money for the fee because they typically have no arbitrage in their example and are only showing how the loan process works.

And as for the contracts, I needed to edit the pragma on them slightly from for example 0.5.0 to 0.5.16 to everything matched properly. I couldn’t do that with a straight reference from the openzepplin repo.

1 Like

Hi @CryptoEatsTheWorld,

If pragmas are ^0.5.x then as long as the compiler version is 0.5.x or higher it should be fine and you shouldn’t need to change pragmas. It is only when they specify a specific version and you have other contracts requiring a later version that you run into problems.

OK, good to know. Thanks.

1 Like