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 andtoken.allowance
returned correct value
- Logged
- 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 toaAAVE
contract - Bunch of logged events for Transfer/Approval/Mint/DelegatedPowerChanged/…
- Transferred
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.