Back
Simple Lending
Understand the inner workings of Aave by completing the Lending challenge.
Simple Lending
If you haven't completed the Token Swap challenge yet, check it out here!
Let's take a step further and dive into another core DeFi primitive, lending. Aave is one of the top protocols, securing billions in assets. Let's learn how to earn yield by lending through smart contracts.
In this challenge, you'll implement a contract that deposits into Aave pools on your behalf. You'll also be able to withdraw your supplied tokens along with the earned rewards.
Objective
Your task is to:
- Develop your own contract with
stake
andunstake
functionalities based on the provided base contract. - Deploy it to Scroll Sepolia testnet.
- And finally verify it.
If you need help with using a smart contract framework for completing this challenge, the Level Up: Build with Foundry guide might be a helpful start!
If you get stuck, feel free to ask for help in Level Up Telegram group.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// In this example, the DataTypes library is used to query the AToken address that corresponds DAI
// Later, we will use the getReserveData that will return a ReserveData object.
// Aave docs: https://docs.aave.com/developers/core-contracts/pool#getreservedata
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;
}
}
// IPool is the main AAVE interface exposed to users, the most notable functions are borrow, supply and withdraw
// AAVE docs: https://docs.aave.com/developers/core-contracts/pool
interface IPool {
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);
}
// ERC20 interface used to interact with the staking token, which is DAI on this tutorial
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);
}
// This contract acts as a proxy to earn yield on AAVE. It can be used seamlessly on the background on
// a variety of contexts such as auctions, DAO treasuries, lotteries, etc...
contract AaveLender {
// AAVE Pool Address, deployed on Scroll Sepolia at 0x48914C788295b5db23aF2b5F0B3BE775C4eA9440
address public immutable AAVE_POOL_ADDRESS = 0x48914C788295b5db23aF2b5F0B3BE775C4eA9440;
// In this example we will stake DAI, but any ERC20 supported by AAVE can be also used
address public immutable STAKED_TOKEN_ADDRESS = 0x7984E363c38b590bB4CA35aEd5133Ef2c6619C40;
// Function that stakes DAI and lends it on the background
function stake(uint amount) public {
...
// 1. Transfer the DAI tokens to be deposited into this contract
// 2. Allow (or approve) the Aave Pool contract so it can manage the deposited DAI tokens
// 3. Call the supply function in the Aave Pool on behalf of the transaction sender
}
// Every user is able to unstake the exact amount it has staked, all the yield generated by AAVE will go the owner
function unstake(uint amount) public {
...
// 1. Transfer the aDAI token (The AToken) to be withdrawn (notice you’ll need to retrieve the aDAI token address first)
// 2. Allow (or approve) the Aave Pool contract so it can manage the deposited aDAI tokens
// 3. Call the withdraw function so the sender receives the DAI back
}
}
What You'll Need to Know
Core Functions in Aave: Supply, Withdraw, Borrow, and Repay
These are the four core functions in the Aave protocol:
- Supply: Lend tokens by depositing them into the Aave Pool.
- Withdraw: Retrieve your supplied tokens along with any earned rewards.
- Borrow: Borrow tokens from the pool.
- Repay: Pay back the borrowed tokens plus interest.
In this challenge, we will focus on the supply
and withdraw
functions.
ATokens: Aave's Auto-Yield Tokens
When you supply
tokens to Aave, you receive ATokens as a receipt. For example, if you deposit 10 DAI
into the Aave Pool, you will get 10 aDAI
in return. This token tracks how much you supplied and the yield you've earned, so when you withdraw
, you receive your tokens along with any accrued rewards.
For instance, if you deposit 10 DAI
today, you immediately receive 10 aDAI
. After several blocks, your 10 aDAI
might become 11 aDAI
. When you withdraw
, you return your 11 aDAI
and receive 11 DAI
, earning a 1 DAI
profit.
ATokens use rebase functionality, making it easy for users to track their earnings without manual interaction.
What is a Rebase Token?
Rebase tokens, like ATokens, fully comply with the ERC20 standard but with a twist. They include a mechanism that adjusts all holders' balances proportionally based on a variable called RATE
.
Typically, the balanceOf()
function in ERC20 looks like this:
balanceOf(x) = balances[x]
In rebase tokens, the RATE
variable modifies this:
balanceOf(x) = sharesOf(x) * RATE
Here:
sharesOf(x)
also called "fragments", represents the amount of tokens a holder has in proportion to the rest.RATE
is a variable that adjusts to distribute rewards evenly across all holders.
This allows the protocol to efficiently distribute rewards, and users can simply check their AToken balance to see how much they've earned without needing to interact further.
Understanding Aave Functions in This Challenge
The supply()
Function
In this challenge, the deposit
function in your smart contract will call the supply
function to deposit DAI
into the Aave pool. In return, the user will receive an equivalent amount of aDAI
.
function supply(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
- asset: The token being deposited into the pool (we’ll use DAI in this challenge).
- amount: The amount of the
asset
being deposited, in wei. - onBehalfOf: The address that will receive the ATokens.
- referralCode: A code for third-party integrations. For this challenge, set it to 0.
The withdraw()
Function
The withdraw
function will transfer the user's aDAI
back to the Aave pool, allowing them to receive their DAI
along with any earned interest.
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
- asset: The underlying asset being withdrawn from the pool (in this case, DAI, not the AToken).
- amount: The amount of the
asset
being withdrawn, in wei. - to: The address that will receive the tokens.
Retrieving the AToken Address with getReserveData()
Each ERC20 token supported by Aave has a corresponding AToken. You can find the address of the AToken by calling the getReserveData()
function, which returns the aTokenAddress along with other details about the token pool. You'll need this address to manage funds in the withdraw function.
ISwapRouter.ExactInputSingleParamsIPool(AAVE_POOL_ADDRESS)
.getReserveData(STAKED_TOKEN_ADDRESS)
.aTokenAddress;
This will provide the AToken address, which is required for interacting with the Aave Pool.
A Cool Tip
- Did you know that the Official Aave testnet UI supports Scroll Sepolia? After supplying tokens you will be able to see your position in the UI. Also, you can mint testnet DAI on The Aave faucet in order to test your code.
Step by step: How to complete this challenge
-
Implement the stake function:
- Transfer the DAI tokens to this contract.
- Approve the Aave Pool contract to manage the deposited DAI.
- Call the
supply()
function on behalf of the transaction sender.
-
Implement the unstake function:
- Transfer the aDAI tokens (the AToken) for withdrawal.
- Approve the Aave Pool contract to manage the aDAI.
- Call the
withdraw()
function to receive DAI back.
Test it yourself
To ensure your implementation works, follow these steps:
- Clone the repo and navigate to the directory:
git clone https://github.com/LevelUpWeb3/SimpleLending-Challenge
cd LendingChallengeLevelUp
-
Complete the functions in
src/AaveLender.sol
-
Run the tests on Scroll Sepolia:
forge test --fork-url https://scroll-testnet-public.unifra.io
Further Reading
Submit Challenge
0x Swap API ChallengeEnd of challenges