Skip to content
Advertisement

Supply ETH to Aave through solidity

I’m trying deposit into Aave V2 Contract Aave’s Code Examples

// SPDX-License-Identifier: MIT
pragma solidity >= 0.4.22 < 0.8.7;

import { IERC20, ILendingPool, IProtocolDataProvider, IStableDebtToken } from './Interfaces.sol';
import { SafeERC20 } from './Libraries.sol';

/**
* This is a proof of concept starter contract, showing how uncollaterised loans are possible
* using Aave v2 credit delegation.
* This example supports stable interest rate borrows.
* It is not production ready (!). User permissions and user accounting of loans should be implemented.
* See @dev comments
*/

contract MyV2CreditDelegation {
    using SafeERC20 for IERC20;
    
    ILendingPool constant lendingPool = ILendingPool(address(0x9FE532197ad76c5a68961439604C037EB79681F0)); // Kovan
    IProtocolDataProvider constant dataProvider = IProtocolDataProvider(address(0x744C1aaA95232EeF8A9994C4E0b3a89659D9AB79)); // Kovan
    
    address owner;

    constructor () public {
        owner = msg.sender;
    }

    /**
    * Deposits collateral into the Aave, to enable credit delegation
    * This would be called by the delegator.
    * @param asset The asset to be deposited as collateral
    * @param amount The amount to be deposited as collateral
    * @param isPull Whether to pull the funds from the caller, or use funds sent to this contract
    *  User must have approved this contract to pull funds if `isPull` = true
    * 
    */
    function depositCollateral(address asset, uint256 amount, bool isPull) public {
        if (isPull) {
            IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
        }
        IERC20(asset).safeApprove(address(lendingPool), amount);
        lendingPool.deposit(asset, amount, address(this), 0);
    }

    /**
    * Approves the borrower to take an uncollaterised loan
    * @param borrower The borrower of the funds (i.e. delgatee)
    * @param amount The amount the borrower is allowed to borrow (i.e. their line of credit)
    * @param asset The asset they are allowed to borrow
    * 
    * Add permissions to this call, e.g. only the owner should be able to approve borrowers!
    */
    function approveBorrower(address borrower, uint256 amount, address asset) public {
        (, address stableDebtTokenAddress,) = dataProvider.getReserveTokensAddresses(asset);
        IStableDebtToken(stableDebtTokenAddress).approveDelegation(borrower, amount);
    }
    
    /**
    * Repay an uncollaterised loan
    * @param amount The amount to repay
    * @param asset The asset to be repaid
    * 
    * User calling this function must have approved this contract with an allowance to transfer the tokens
    * 
    * You should keep internal accounting of borrowers, if your contract will have multiple borrowers
    */
    function repayBorrower(uint256 amount, address asset) public {
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
        IERC20(asset).safeApprove(address(lendingPool), amount);
        lendingPool.repay(asset, amount, 1, address(this));
    }
    
    /**
    * Withdraw all of a collateral as the underlying asset, if no outstanding loans delegated
    * @param asset The underlying asset to withdraw
    * 
    * Add permissions to this call, e.g. only the owner should be able to withdraw the collateral!
    */
    function withdrawCollateral(address asset) public {
        (address aTokenAddress,,) = dataProvider.getReserveTokensAddresses(asset);
        uint256 assetBalance = IERC20(aTokenAddress).balanceOf(address(this));
        lendingPool.withdraw(asset, assetBalance, owner);
    }
}

I have code which consumes this like so:

App = {
  web3Provider: null,
  contracts: {},

  init: async function() {
    return await App.initWeb3();
  },

  initWeb3: async function() {
    // Modern dapp browsers...
    if (window.ethereum) {
      App.web3Provider = window.ethereum;
      try {
        // Request account access
        await window.ethereum.enable();
      } catch (error) {
        // User denied account access...
        console.error("User denied account access")
      }
    }
    // Legacy dapp browsers...
    else if (window.web3) {
      App.web3Provider = window.web3.currentProvider;
    }
    // If no injected web3 instance is detected, fall back to Ganache
    else {
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
    }
    web3 = new Web3(App.web3Provider);

    return App.initContract();
  },

  initContract: function() {
    $.getJSON('MyV2CreditDelegation.json', function(data) {
      // Get the necessary contract artifact file and instantiate it with @truffle/contract
      var safeYieldArtifact = data;
      App.contracts.MyV2CreditDelegation = TruffleContract(safeYieldArtifact);
    
      // Set the provider for our contract
      App.contracts.MyV2CreditDelegation.setProvider(App.web3Provider);
    });
    

    

    return App.bindEvents();
  },

  bindEvents: function() {
    $(document).on('click', '.btn-deposit', App.handleDeposit);
    $(document).on('click', '.btn-withdrawl', App.handleWithdrawl);
  },

  handleDeposit: function(event) {
    event.preventDefault();
    web3.eth.getAccounts(function(error, accounts) {
      if (error) {
        console.log(error);
      }
    
      var account = accounts[0];

      App.contracts.MyV2CreditDelegation.deployed().then(function(instance) {
        creditDelegatorInstance = instance;
        const mockETHAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
        // Execute adopt as a transaction by sending account
        return creditDelegatorInstance.depositCollateral(mockETHAddress, 1, true);
      }).then(function(result) {
        //return App.markAdopted();
      }).catch(function(err) {
        console.log(err.message);
      });
    });
  },

  handleWithdrawl: function(event) {
    event.preventDefault();
  },
};

$(function() {
  $(window).load(function() {
    App.init();
  });
});

When attempting to supply, Metamask displays an error:

ALERT: Transaction Error. Exception thrown in contract code.

And just a simple button to call it in html:

<button class="btn btn-default btn-withdrawl" 
  type="button"> Withdrawl
</button>

I’m running ganache

ganache-cli --fork https://mainnet.infura.io/v3/{{MyProjectId}}

The only error I see in the console is:

Transaction: 0x9961f8a187c09fd7c9ebf803771fa161c9939268bb01552a1598807bcfdc13ff Gas usage: 24813 Block Number: 12905002 Block Time: Mon Jul 26 2021 20:38:30 GMT-0400 (Eastern Daylight Time) Runtime Error: revert

My guess is that I’m not calling the contract from Web3 appropriately

How can programatically supply Eth (Or any other token) to aave?

Advertisement

Answer

I used Hardhat to send transactions instead of Web3 in the browser, but I succeeded:

  • I used the Kovan test network.
  • I used the MyCrypto faucet to give my test address some ETH.
  • I used the Aave faucet to give myself some AAVE.
  • I imported the contracts from Aave’s Code Examples.
  • I ran the script below:

Create a new HardHat project

npm init --yes
npm install --save-dev hardhat 
npm install @nomiclabs/hardhat-waffle 
npm install --save-dev "@nomiclabs/hardhat-ethers@^2.0.0" "ethers@^5.0.0" "ethereum-waffle@^3.2.0"
npx hardhat 
(follow the prompt)
import '@nomiclabs/hardhat-ethers';
import * as dotenv from 'dotenv';
import { LogDescription } from 'ethers/lib/utils';
import hre from 'hardhat';
import { IERC20__factory, MyV2CreditDelegation__factory } from '../typechain';

dotenv.config();

// Infura, Alchemy, ... however you can get access to the Kovan test network
// E.g. https://kovan.infura.io/v3/<project-id>
const KOVAN_JSON_RPC = process.env.KOVAN_JSON_RPC || '';
if (!KOVAN_JSON_RPC) {
    console.error('Forgot to set KOVAN_JSON_RPC in aave.ts or .env');
    process.exit(1);
}

// Test account that has Kovan ETH and an AAVE token balance
const AAVE_HOLDER = '';

async function main() {
    // Fork Kovan
    await hre.network.provider.request({
        method: 'hardhat_reset',
        params: [{ forking: { jsonRpcUrl: KOVAN_JSON_RPC } }],
    });

    // Act like AAVE_HOLDER
    await hre.network.provider.request({
        method: 'hardhat_impersonateAccount',
        params: [AAVE_HOLDER],
    });
    const signer = await hre.ethers.getSigner(AAVE_HOLDER);
    console.log('signer:', signer.address);

    // AAVE token on Kovan network
    const token = IERC20__factory.connect('0xb597cd8d3217ea6477232f9217fa70837ff667af', signer);
    console.log('token balance:', (await token.balanceOf(signer.address)).toString());

    const MyV2CreditDelegation = new MyV2CreditDelegation__factory(signer);
    const delegation = await MyV2CreditDelegation.deploy({ gasLimit: 1e7 });
    console.log('delegation:', delegation.address);

    await token.approve(delegation.address, 1000000000000);
    console.log('allowance:', (await token.allowance(signer.address, delegation.address, { gasLimit: 1e6 })).toString());

    const depositTrans = await delegation.depositCollateral(token.address, 1000000000000, true, { gasLimit: 1e6 });
    console.log('depositTrans:', depositTrans.hash);
    const receipt = await depositTrans.wait();
    for (const log of receipt.logs) {
        const [name, desc] = parseLog(log) || [];
        if (desc) {
            const args = desc.eventFragment.inputs.map(({ name, type, indexed }, index) =>
                `n    ${type}${indexed ? ' indexed' : ''} ${name}: ${desc.args[name]}`);
            args.unshift(`n    contract ${name} ${log.address}`);
            console.log('Event', log.logIndex, `${desc.name}(${args ? args.join(',') : ''})`);
        } else {
            console.log('Log', log.logIndex, JSON.stringify(log.topics, null, 4), JSON.stringify(log.data));
        }
    }

    function parseLog(log: { address: string, topics: Array<string>, data: string }): [string, LogDescription] | undefined {
        try { return ['', delegation.interface.parseLog(log)]; } catch (e) { }
        try {
            const desc = token.interface.parseLog(log);
            return [log.address.toLowerCase() === token.address.toLowerCase() ? 'AAVE' : 'IERC20', desc];
        } catch (e) { }
    }
}

main().then(() => process.exit(0), error => {
    console.error(JSON.stringify(error));
    console.error(error);
});

With as result:

$ hardhat run --no-compile --network kovan .scriptsAave.ts
token balance: 999999000000000000
delegation: 0x2863E2a95Dc84C227B11CF1997e295E59ab15670
allowance: 1000000000000
depositTrans: 0x0a3d1a8bfbdfc0f403371f9936122d19bdc9f3539c34e3fb1b0a7896a398f923
Done in 57.11s.

Which you can verify on Etherscan for Kovan (blocks 26666177, 26666180 and 26666183):

  • Deploying MyV2CreditDelegation (transaction, contract)
  • Approving contract for AAVE (transaction)
    • Logged Approval event and token.allowance returned correct value
  • Depositing collateral (transaction)
    • Transferred 0.000001 AAVE from my address to deployed contract
    • Transferred 0.000001 aAAVE from my address to deployed contract
    • Transferred 0.000001 AAVE from deployed contract to aAAVE contract
    • Bunch of logged events for Transfer/Approval/Mint/DelegatedPowerChanged/…

Originally my script would first deploy a test token (and mint me some balance on it) which I would try to deposit as collateral, but the provider somehow reverted it without a transaction even showing up on Kovan Etherscan. The issue in your code might thus be the mockETHAddress, as well as it seems like you’re not actually approving your delegation contract to withdraw the specified amount.

A more complex but ready-to-run setup is available in this repository. It impersonates an account that has an ETH and AAVE balance on Kovan.

Advertisement