Easy extra income from Aave in Solidity 💸

Ahmed Castro - Aug 29 '23 - - Dev Community

¿What if you could earn passive income on your DAO treasury? ¿Or during your your token presale, auction or in any project where you have idle ERC20 tokens sitting on a contract? On this guide we'll launch a staking contract that lends on AAVE on the background.

We'll launch this contract on Scroll Sepolia but I'll show what needs to be done in order to deploy it on any other chain where AAVE is deployed.

Note: We'll use Remix and Blockscout for deploying and interacting with the contracts. Alternatively you can use this repo that includes commented code, foundry unit tests, and a github action.

Step1: Launch the contract

Make sure to have a wallet such as Metamask, connect it to Scroll Sepolia, and get funds from a faucet.

Launch the following contract on Remix by passing the aavePoolAddress and stakedTokenAddress addresses as constructor params. Use the following addresses deployed on Scroll Sepolia:

  • aavePoolAddress: 0x48914C788295b5db23aF2b5F0B3BE775C4eA9440
  • stakedTokenAddress: 0x7984E363c38b590bB4CA35aEd5133Ef2c6619C40 which is the AAVE testnet DAI on Scroll Sepolia
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

library DataTypes {
  struct ReserveConfigurationMap {
    uint256 data;
  }

  struct ReserveData {
    ReserveConfigurationMap configuration;
    uint128 liquidityIndex;
    uint128 currentLiquidityRate;
    uint128 variableBorrowIndex;
    uint128 currentVariableBorrowRate;
    uint128 currentStableBorrowRate;
    uint40 lastUpdateTimestamp;
    uint16 id;
    address aTokenAddress;
    address stableDebtTokenAddress;
    address variableDebtTokenAddress;
    address interestRateStrategyAddress;
    uint128 accruedToTreasury;
    uint128 unbacked;
    uint128 isolationModeTotalDebt;
  }
}

interface IPool {
    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode, // 1 for Stable, 2 for Variable
        uint16 referralCode,
        address onBehalfOf) external;

    function supply(
        address asset,
        uint256 amount,
        address onBehalfOf,
        uint16 referralCode) external;

    function withdraw(
        address asset,
        uint256 amount,
        address to) external returns (uint256);

    function getReserveData(
        address asset) external view returns (DataTypes.ReserveData memory);
}

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

contract AaveLender {
    address public immutable AAVE_POOL_ADDRESS;
    address public immutable STAKED_TOKEN_ADDRESS;
    address public immutable ATOKEN_ADDRESS;
    address public immutable OWNER;
    mapping(address account => uint amount) public stakeByAccount;
    uint public totalStake;

    constructor(address aavePoolAddress, address stakedTokenAddress) {
        AAVE_POOL_ADDRESS = aavePoolAddress;
        STAKED_TOKEN_ADDRESS = stakedTokenAddress;
        OWNER = msg.sender;
        ATOKEN_ADDRESS = IPool(aavePoolAddress).getReserveData(stakedTokenAddress).aTokenAddress;
    }

    function stake(uint amount) public {
        totalStake += amount;
        stakeByAccount[msg.sender] += amount;
        IERC20(STAKED_TOKEN_ADDRESS).transferFrom(msg.sender, address(this), amount);
        IERC20(STAKED_TOKEN_ADDRESS).approve(AAVE_POOL_ADDRESS, amount);
        IPool(AAVE_POOL_ADDRESS).supply(
            STAKED_TOKEN_ADDRESS,
            amount,
            address(this),
            0);
    }

    function unstake(uint amount) public {
        require(amount <= stakeByAccount[msg.sender], "Not enough stake");
        totalStake -= amount;
        stakeByAccount[msg.sender] -= amount;
        IPool(AAVE_POOL_ADDRESS).withdraw(
          STAKED_TOKEN_ADDRESS,
          amount,
          msg.sender
        );
    }

    function yieldEarned() public view returns(uint){
        return IERC20(ATOKEN_ADDRESS).balanceOf(address(this)) - totalStake;
    }

    function withdraw(uint amount) public {
        require(msg.sender == OWNER, "Sender is not owner");
        require(amount <= yieldEarned(), "Maximum withdraw exceeded");
        IPool(AAVE_POOL_ADDRESS).withdraw(
          STAKED_TOKEN_ADDRESS,
          amount,
          msg.sender
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Mint testnet DAI

Now go to the Aave Faucet, connect your wallet on Scroll Sepolia, click the Faucet button and confirm the transaction.

Faucet de Aave

Once the transaction is confirmed you will be able to see your Testnet DAIs by adding the token by using the following address 0x7984E363c38b590bB4CA35aEd5133Ef2c6619C40.

Remember that you're only able to borrow and lend tokens previously added to the Aave protocol. Also notice that this DAI version is not the same as other protocols on Sepolia and Scroll Sepolia such as Uniswap v3. This is a special version deployed by Aave and it's the only one compatible with Aave on Scroll Testnet. If you're on any mainnet chain you will be able to use the token official version.

Step3: Approve the Staking contract

We need to approve the Staking contract so it can access our funds.

We'll also do this on Scroll-Etherscan.

On the DAI contract at 0x7984E363c38b590bB4CA35aEd5133Ef2c6619C40, pass the following params to the approve token function.

  • spender: The staking contract you just launched
  • amount: 100000000000000000000 which is 100 in wei format.

Approve AaveLender contract

Step 4: Earn yield

Now you can stake by calling the stake function and passing as parameter the amount of DAI you approved.

stake and lend on AAVE

On every block, the yield will be increasing bit by bit and can be displaying by calling yieldEarned. The yield can be sent to the owner by calling the withdraw method.

withdraw yield earned on aave

Next steps

This demo shows how to lend on AAVE from a smart contract with minimal code. Now is up to you to use it to improve an existing project or to create something new. Personally, I would like to add this a lottery contract I'm working on so all the accumulated ticket sales generate yield while we wait for the lottery winner.. Let me know in the comments if you have ideas or questions about this tutorial.

Learn more about Aave on the official documentation.

Thanks for watching this guide!

Follow Filosofía Código on dev.to and in Youtube for everything related to Blockchain development.

. . . . . . . . . . . . . . . . . . . . . . . . .