generated from ZeframLou/foundry-template
-
Notifications
You must be signed in to change notification settings - Fork 29
/
AaveV3ERC4626.sol
255 lines (199 loc) · 10.1 KB
/
AaveV3ERC4626.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.13;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {ERC4626} from "solmate/mixins/ERC4626.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {IPool} from "./external/IPool.sol";
import {IRewardsController} from "./external/IRewardsController.sol";
/// @title AaveV3ERC4626
/// @author zefram.eth
/// @notice ERC4626 wrapper for Aave V3
/// @dev Important security note: due to Aave using a rebasing model for aTokens,
/// this contract cannot independently keep track of the deposited funds, so it is possible
/// for an attacker to directly transfer aTokens to this contract, increase the vault share
/// price atomically, and then exploit an external lending market that uses this contract
/// as collateral.
contract AaveV3ERC4626 is ERC4626 {
/// -----------------------------------------------------------------------
/// Libraries usage
/// -----------------------------------------------------------------------
using SafeTransferLib for ERC20;
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
event ClaimRewards(uint256 amount);
/// -----------------------------------------------------------------------
/// Constants
/// -----------------------------------------------------------------------
uint256 internal constant DECIMALS_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFF;
uint256 internal constant ACTIVE_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFF;
uint256 internal constant FROZEN_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFF;
uint256 internal constant PAUSED_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFF;
uint256 internal constant SUPPLY_CAP_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFF000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
uint256 internal constant SUPPLY_CAP_START_BIT_POSITION = 116;
uint256 internal constant RESERVE_DECIMALS_START_BIT_POSITION = 48;
/// -----------------------------------------------------------------------
/// Immutable params
/// -----------------------------------------------------------------------
/// @notice The Aave aToken contract
ERC20 public immutable aToken;
/// @notice The Aave Pool contract
IPool public immutable lendingPool;
/// @notice The address that will receive the liquidity mining rewards (if any)
address public immutable rewardRecipient;
/// @notice The Aave RewardsController contract
IRewardsController public immutable rewardsController;
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(
ERC20 asset_,
ERC20 aToken_,
IPool lendingPool_,
address rewardRecipient_,
IRewardsController rewardsController_
) ERC4626(asset_, _vaultName(asset_), _vaultSymbol(asset_)) {
aToken = aToken_;
lendingPool = lendingPool_;
rewardRecipient = rewardRecipient_;
rewardsController = rewardsController_;
}
/// -----------------------------------------------------------------------
/// Aave liquidity mining
/// -----------------------------------------------------------------------
/// @notice Claims liquidity mining rewards from Aave and sends it to rewardRecipient
function claimRewards() external {
address[] memory assets = new address[](1);
assets[0] = address(aToken);
(, uint256[] memory claimedAmounts) = rewardsController.claimAllRewards(assets, rewardRecipient);
emit ClaimRewards(claimedAmounts[0]);
}
/// -----------------------------------------------------------------------
/// ERC4626 overrides
/// -----------------------------------------------------------------------
function withdraw(uint256 assets, address receiver, address owner)
public
virtual
override
returns (uint256 shares)
{
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) {
allowance[owner][msg.sender] = allowed - shares;
}
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
// withdraw assets directly from Aave
lendingPool.withdraw(address(asset), assets, receiver);
}
function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) {
allowance[owner][msg.sender] = allowed - shares;
}
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
// withdraw assets directly from Aave
lendingPool.withdraw(address(asset), assets, receiver);
}
function totalAssets() public view virtual override returns (uint256) {
// aTokens use rebasing to accrue interest, so the total assets is just the aToken balance
return aToken.balanceOf(address(this));
}
function afterDeposit(uint256 assets, uint256 /*shares*/ ) internal virtual override {
/// -----------------------------------------------------------------------
/// Deposit assets into Aave
/// -----------------------------------------------------------------------
// approve to lendingPool
asset.safeApprove(address(lendingPool), assets);
// deposit into lendingPool
lendingPool.supply(address(asset), assets, address(this), 0);
}
function maxDeposit(address) public view virtual override returns (uint256) {
// check if asset is paused
uint256 configData = lendingPool.getReserveData(address(asset)).configuration.data;
if (!(_getActive(configData) && !_getFrozen(configData) && !_getPaused(configData))) {
return 0;
}
// handle supply cap
uint256 supplyCapInWholeTokens = _getSupplyCap(configData);
if (supplyCapInWholeTokens == 0) {
return type(uint256).max;
}
uint8 tokenDecimals = _getDecimals(configData);
uint256 supplyCap = supplyCapInWholeTokens * 10 ** tokenDecimals;
return supplyCap - aToken.totalSupply();
}
function maxMint(address) public view virtual override returns (uint256) {
// check if asset is paused
uint256 configData = lendingPool.getReserveData(address(asset)).configuration.data;
if (!(_getActive(configData) && !_getFrozen(configData) && !_getPaused(configData))) {
return 0;
}
// handle supply cap
uint256 supplyCapInWholeTokens = _getSupplyCap(configData);
if (supplyCapInWholeTokens == 0) {
return type(uint256).max;
}
uint8 tokenDecimals = _getDecimals(configData);
uint256 supplyCap = supplyCapInWholeTokens * 10 ** tokenDecimals;
return convertToShares(supplyCap - aToken.totalSupply());
}
function maxWithdraw(address owner) public view virtual override returns (uint256) {
// check if asset is paused
uint256 configData = lendingPool.getReserveData(address(asset)).configuration.data;
if (!(_getActive(configData) && !_getPaused(configData))) {
return 0;
}
uint256 cash = asset.balanceOf(address(aToken));
uint256 assetsBalance = convertToAssets(balanceOf[owner]);
return cash < assetsBalance ? cash : assetsBalance;
}
function maxRedeem(address owner) public view virtual override returns (uint256) {
// check if asset is paused
uint256 configData = lendingPool.getReserveData(address(asset)).configuration.data;
if (!(_getActive(configData) && !_getPaused(configData))) {
return 0;
}
uint256 cash = asset.balanceOf(address(aToken));
uint256 cashInShares = convertToShares(cash);
uint256 shareBalance = balanceOf[owner];
return cashInShares < shareBalance ? cashInShares : shareBalance;
}
/// -----------------------------------------------------------------------
/// ERC20 metadata generation
/// -----------------------------------------------------------------------
function _vaultName(ERC20 asset_) internal view virtual returns (string memory vaultName) {
vaultName = string.concat("ERC4626-Wrapped Aave v3 ", asset_.symbol());
}
function _vaultSymbol(ERC20 asset_) internal view virtual returns (string memory vaultSymbol) {
vaultSymbol = string.concat("wa", asset_.symbol());
}
/// -----------------------------------------------------------------------
/// Internal functions
/// -----------------------------------------------------------------------
function _getDecimals(uint256 configData) internal pure returns (uint8) {
return uint8((configData & ~DECIMALS_MASK) >> RESERVE_DECIMALS_START_BIT_POSITION);
}
function _getActive(uint256 configData) internal pure returns (bool) {
return configData & ~ACTIVE_MASK != 0;
}
function _getFrozen(uint256 configData) internal pure returns (bool) {
return configData & ~FROZEN_MASK != 0;
}
function _getPaused(uint256 configData) internal pure returns (bool) {
return configData & ~PAUSED_MASK != 0;
}
function _getSupplyCap(uint256 configData) internal pure returns (uint256) {
return (configData & ~SUPPLY_CAP_MASK) >> SUPPLY_CAP_START_BIT_POSITION;
}
}