-
Notifications
You must be signed in to change notification settings - Fork 0
/
AaveLender.sol
139 lines (126 loc) · 5.97 KB
/
AaveLender.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// 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 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);
}
// 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, depolyed on Scroll Sepolia at 0x48914C788295b5db23aF2b5F0B3BE775C4eA9440
address public immutable AAVE_POOL_ADDRESS;
// In this example we will stake DAI, but any ERC20 supported by AAVE can be also used
address public immutable STAKED_TOKEN_ADDRESS;
// The AToken is a representation of the tokens we have staked, in this example is used
// to check how much yield we have earned
address public immutable ATOKEN_ADDRESS;
// Only the contract owner will be able to withdraw the yield earned on AAVE
address public immutable OWNER;
// Mapping used to control many staking has done every user
mapping(address account => uint amount) public stakeByAccount;
// The totalStake is a helper variable that lets us know how much we can withdraw from the AAVE yield
uint public totalStake;
// We will need the AAVE Pool address and the token that will be staked, on this case the AAVE testnet DAI
// On Scroll Sepolia those are 0x48914C788295b5db23aF2b5F0B3BE775C4eA9440 and 0x7984E363c38b590bB4CA35aEd5133Ef2c6619C40
constructor(address aavePoolAddress, address stakedTokenAddress) {
AAVE_POOL_ADDRESS = aavePoolAddress;
STAKED_TOKEN_ADDRESS = stakedTokenAddress;
OWNER = msg.sender;
ATOKEN_ADDRESS = IPool(aavePoolAddress).getReserveData(stakedTokenAddress).aTokenAddress;
}
// Function that stakes DAI and lends it on the background
function stake(uint amount) public {
// It's always a good idea to update the state at the begining to prevent reentrancy
totalStake += amount;
stakeByAccount[msg.sender] += amount;
// We start by transfering the stake to this contract
IERC20(STAKED_TOKEN_ADDRESS).transferFrom(msg.sender, address(this), amount);
// Next, we approve and lend on AAVE
IERC20(STAKED_TOKEN_ADDRESS).approve(AAVE_POOL_ADDRESS, amount);
IPool(AAVE_POOL_ADDRESS).supply(
STAKED_TOKEN_ADDRESS,
amount,
address(this),
0);
}
// 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 {
require(amount <= stakeByAccount[msg.sender], "Not enough stake");
totalStake -= amount;
stakeByAccount[msg.sender] -= amount;
// We withdraw from aave and send the tokens to the msg.sender
IPool(AAVE_POOL_ADDRESS).withdraw(
STAKED_TOKEN_ADDRESS,
amount,
msg.sender
);
}
// The AToken let's us know how much we can withdraw in total. This is very useful to prevent the owner from withdrawing another's user stake
function yieldEarned() public view returns(uint){
return IERC20(ATOKEN_ADDRESS).balanceOf(address(this)) - totalStake;
}
// Only the owner can withdraw the yield generated by lending on AAVE
// Notice how we use the AToken to know the maximum withdrawable amount, so the Owner can't withdraw the users stake
function withdraw(uint amount) public {
// This contract only benefits the Owner so it's not an use case by itself, but the idea can be very handy when combined with in other cases
require(msg.sender == OWNER, "Sender is not owner");
// The onwner can only withdraw the yield earned so every staker is able to withdraw the exact amount deposited
require(amount <= yieldEarned(), "Maximum withdraw exceeded");
IPool(AAVE_POOL_ADDRESS).withdraw(
STAKED_TOKEN_ADDRESS,
amount,
msg.sender
);
}
}