Are you building a decentralized application (dapp) where users are able to store funds in your smart contract? Perhaps a staking dapp or a yield farming dapp? Well, why not utilize those funds stored in your contract to further generate passive income yield. Since those funds are just sitting there, might as well generate more money from them.
There are a few protocols that allow you to automatically lend / supply liquidity to them and they offer you a decent yield in return. The two most noticeable protocols in that area are Aave and Compound.
For this specific tutorial, we are going to look at Aave lending pool contract that will receive ETH funds from your contract and enable you to receive a stable or variable yield (APY) for suppyling those funds.
We are specifically going to perform the whole process in a Solidity smart contract. This can of course be done using Javascript by creating and running a script. However, to deeply understand the internal working of the of the procedure and Aave interfaces, it is better to integrate it in your Solidity contract.
WETH Gateway
Since, we are interested in generating passive income yield with ETH funds, we need to consider a very important detail here:
Aave protocol works with ERC-20 based assets. But we know that ETH is not based on the ERC-20 standard. First we will need to convert ETH to Wrapped Ether (WETH) and supply WETH tokens to the Aave Lending Pool.
When we want to withdraw ETH funds back to our contract, WETH will be converted back to ETH and then sent to our contract address.
Fortunately, Aave provides a specific interface for this purpose called WETH Gateway. WETH gateway interface offers the following functions out of the box:
- depositETH()
function depositETH(address lendingPool, address onBehalfOf, uint16 referralCode) - withdrawETH()
function withdrawETH(address lendingPool, uint256 amount, address to)
Both of these functions will automatically convert ETH to WETH and WETH to ETH.
Another important thing to note is when you deposit ETH funds using the depositETH() function, the lending pool will issue aWeth (Aave Interest Bearing Tokens) to your contract address to keep track of the funds you have supplied to the Aave lending pool.
When you call the withdrawETH() function from your contract, the WETH gateway first burns the aWeth tokens and then sends the ETH funds back to your contract along with any interest earned.
Important Aave (v2) Addresses and Interfaces for Goerli testnet
We are going to deploy our smart contract on the Goerli testnet, therefore, we need to know these important addresses and interfaces needed to perform the process.
- Aave (v2) Lending Pool address: 0x4bd5643ac6f66a5237E18bfA7d47cF22f1c9F210
- WETH Gateway contract address: 0x3bd3a20Ac9Ff1dda1D99C0dFCE6D65C4960B3627
- aWETH contract address: 0x3bd3a20Ac9Ff1dda1D99C0dFCE6D65C4960B3627
And we need the following Interfaces to make this work:
Important Note:
The withdrawETH() function won’t do the job alone. Before you call this function, you will need to set the relevant ERC20 allowance of aWETH, so that the WETHGateway can burn the associated aWeth tokens.
Solidity Smart Contract
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/interfaces/IERC20.sol"; interface IWETHGateway { function depositETH( address lendingPool, address onBehalfOf, uint16 referralCode ) external payable; function withdrawETH( address lendingPool, uint256 amount, address onBehalfOf ) external; } interface ILendingPool { function getUserAccountData(address user) external view returns ( uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor ); } contract SampleContract { //IWETHGateway interface for the Goerli testnet IWETHGateway public iWethGateway = IWETHGateway(0x3bd3a20Ac9Ff1dda1D99C0dFCE6D65C4960B3627); //ILendingPool interface ILendingPool public iLendingPool = ILendingPool(0x4bd5643ac6f66a5237E18bfA7d47cF22f1c9F210); //Lending Pool address for the Aave (v2) lending pool on Goerli testnet address public constant lendingPoolAddress = 0x4bd5643ac6f66a5237E18bfA7d47cF22f1c9F210; //Contract Address for the aWeth tokens generated after depositing ETH to keep track of the amount deposited in lending pool address public constant aWethAddress = 0x22404B0e2a7067068AcdaDd8f9D586F834cCe2c5; address public owner; constructor() payable { owner = msg.sender; } function stakeEther() external payable { //Deposit ETH via WETHGateway //It will convert ETH to WETH and also send funds to the lending pool iWethGateway.depositETH{value: msg.value}(lendingPoolAddress, address(this), 0); } function withdrawEther(uint _amount) external { //Withdraw lended funds via the Weth Gateway //It will convert back the WETH to ETH and send it to the contract //Ensure you set the relevant ERC20 allowance of aWETH, before calling this function, so the WETHGateway contract can burn the associated aWETH IERC20(aWethAddress).approve(address(iWethGateway), type(uint256).max); iWethGateway.withdrawETH(lendingPoolAddress, _amount, address(this)); } //To check the balance of aWeth tokens for our contract address function getContractAWETHBalance() external view returns(uint) { return IERC20(aWethAddress).balanceOf(address(this)); } //Receive function is needed because withdrawETH is sending funds to the contract without call data receive() external payable { } }
It is also important to note the importance of the receive() function in this contract.When ether is sent to a contract with no calldata, then receive() function is used as a fallback function to receive the ether. If there is no fallback function then the transaction will revert.