Branch data Line data Source code
1 : : // SPDX-License-Identifier: BUSL-1.1
2 : : pragma solidity ^0.8.17;
3 : :
4 : : import {ERC4626Upgradeable, Math, IERC4626} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol';
5 : : import {SafeERC20, IERC20} from 'openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol';
6 : : import {IERC20Permit} from 'openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol';
7 : :
8 : : import {IPool, IPoolAddressesProvider} from '../../interfaces/IPool.sol';
9 : : import {IAaveOracle} from '../../interfaces/IAaveOracle.sol';
10 : : import {DataTypes, ReserveConfiguration} from '../../protocol/libraries/configuration/ReserveConfiguration.sol';
11 : :
12 : : import {IAToken} from './interfaces/IAToken.sol';
13 : : import {IERC4626StataToken} from './interfaces/IERC4626StataToken.sol';
14 : :
15 : : /**
16 : : * @title ERC4626StataTokenUpgradeable
17 : : * @notice Wrapper smart contract that allows to deposit tokens on the Aave protocol and receive
18 : : * a token which balance doesn't increase automatically, but uses an ever-increasing exchange rate.
19 : : * @dev ERC20 extension, so ERC20 initialization should be done by the children contract/s
20 : : * @author BGD labs
21 : : */
22 : : abstract contract ERC4626StataTokenUpgradeable is ERC4626Upgradeable, IERC4626StataToken {
23 : : using Math for uint256;
24 : :
25 : : /// @custom:storage-location erc7201:aave-dao.storage.ERC4626StataToken
26 : : struct ERC4626StataTokenStorage {
27 : : IERC20 _aToken;
28 : : }
29 : :
30 : : // keccak256(abi.encode(uint256(keccak256("aave-dao.storage.ERC4626StataToken")) - 1)) & ~bytes32(uint256(0xff))
31 : : bytes32 private constant ERC4626StataTokenStorageLocation =
32 : : 0x55029d3f54709e547ed74b2fc842d93107ab1490ab7555dd9dd0bf6451101900;
33 : :
34 : : function _getERC4626StataTokenStorage()
35 : : private
36 : : pure
37 : : returns (ERC4626StataTokenStorage storage $)
38 : : {
39 : : assembly {
40 : 21104 : $.slot := ERC4626StataTokenStorageLocation
41 : : }
42 : : }
43 : :
44 : : uint256 public constant RAY = 1e27;
45 : :
46 : : IPool public immutable POOL;
47 : : IPoolAddressesProvider public immutable POOL_ADDRESSES_PROVIDER;
48 : :
49 : : constructor(IPool pool) {
50 : 0 : POOL = pool;
51 : 0 : POOL_ADDRESSES_PROVIDER = pool.ADDRESSES_PROVIDER();
52 : : }
53 : :
54 : : function __ERC4626StataToken_init(address newAToken) internal onlyInitializing {
55 : 97 : IERC20 aTokenUnderlying = __ERC4626StataToken_init_unchained(newAToken);
56 : 97 : __ERC4626_init_unchained(aTokenUnderlying);
57 : : }
58 : :
59 : : function __ERC4626StataToken_init_unchained(
60 : : address newAToken
61 : : ) internal onlyInitializing returns (IERC20) {
62 : : // sanity check, to be sure that we support that version of the aToken
63 : 97 : address poolOfAToken = IAToken(newAToken).POOL();
64 : 97 : if (poolOfAToken != address(POOL)) revert PoolAddressMismatch(poolOfAToken);
65 : :
66 : 97 : IERC20 aTokenUnderlying = IERC20(IAToken(newAToken).UNDERLYING_ASSET_ADDRESS());
67 : :
68 : 97 : ERC4626StataTokenStorage storage $ = _getERC4626StataTokenStorage();
69 : 97 : $._aToken = IERC20(newAToken);
70 : :
71 : 97 : SafeERC20.forceApprove(aTokenUnderlying, address(POOL), type(uint256).max);
72 : :
73 : 97 : return aTokenUnderlying;
74 : : }
75 : :
76 : : ///@inheritdoc IERC4626StataToken
77 : : function depositATokens(uint256 assets, address receiver) external returns (uint256) {
78 : 12005 : uint256 shares = previewDeposit(assets);
79 : 12005 : _deposit(_msgSender(), receiver, assets, shares, false);
80 : :
81 : 11005 : return shares;
82 : : }
83 : :
84 : : ///@inheritdoc IERC4626StataToken
85 : : function depositWithPermit(
86 : : uint256 assets,
87 : : address receiver,
88 : : uint256 deadline,
89 : : SignatureParams memory sig,
90 : : bool depositToAave
91 : : ) external returns (uint256) {
92 : 5000 : IERC20Permit assetToDeposit = IERC20Permit(
93 : : depositToAave ? asset() : address(_getERC4626StataTokenStorage()._aToken)
94 : : );
95 : :
96 : : try
97 : 5000 : assetToDeposit.permit(_msgSender(), address(this), assets, deadline, sig.v, sig.r, sig.s)
98 : 0 : {} catch {}
99 : :
100 : 5000 : uint256 shares = previewDeposit(assets);
101 : 5000 : _deposit(_msgSender(), receiver, assets, shares, depositToAave);
102 : 4000 : return shares;
103 : : }
104 : :
105 : : ///@inheritdoc IERC4626StataToken
106 : : function redeemATokens(
107 : : uint256 shares,
108 : : address receiver,
109 : : address owner
110 : : ) external returns (uint256) {
111 : 3000 : uint256 assets = previewRedeem(shares);
112 : 3000 : _withdraw(_msgSender(), receiver, owner, assets, shares, false);
113 : :
114 : 2000 : return assets;
115 : : }
116 : :
117 : : ///@inheritdoc IERC4626StataToken
118 : : function aToken() public view returns (IERC20) {
119 : 1002 : ERC4626StataTokenStorage storage $ = _getERC4626StataTokenStorage();
120 : 1002 : return $._aToken;
121 : : }
122 : :
123 : : ///@inheritdoc IERC4626
124 : : function maxMint(address) public view override returns (uint256) {
125 : 2002 : uint256 assets = maxDeposit(address(0));
126 : 2002 : if (assets == type(uint256).max) return type(uint256).max;
127 : 0 : return convertToShares(assets);
128 : : }
129 : :
130 : : ///@inheritdoc IERC4626
131 : : function maxWithdraw(address owner) public view override returns (uint256) {
132 : 2002 : return convertToAssets(maxRedeem(owner));
133 : : }
134 : :
135 : : ///@inheritdoc IERC4626
136 : : function totalAssets() public view override returns (uint256) {
137 : 1002 : return _convertToAssets(totalSupply(), Math.Rounding.Floor);
138 : : }
139 : :
140 : : ///@inheritdoc IERC4626
141 : : function maxRedeem(address owner) public view override returns (uint256) {
142 : 6005 : DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(asset());
143 : :
144 : : // if paused or inactive users cannot withdraw underlying
145 : : if (
146 : 6005 : !ReserveConfiguration.getActive(reserveData.configuration) ||
147 : 6005 : ReserveConfiguration.getPaused(reserveData.configuration)
148 : : ) {
149 : 1000 : return 0;
150 : : }
151 : :
152 : : // otherwise users can withdraw up to the available amount
153 : 5005 : uint256 underlyingTokenBalanceInShares = convertToShares(reserveData.virtualUnderlyingBalance);
154 : 5005 : uint256 cachedUserBalance = balanceOf(owner);
155 : 5005 : return
156 : 5005 : underlyingTokenBalanceInShares >= cachedUserBalance
157 : : ? cachedUserBalance
158 : : : underlyingTokenBalanceInShares;
159 : : }
160 : :
161 : : ///@inheritdoc IERC4626
162 : : function maxDeposit(address) public view override returns (uint256) {
163 : 3006 : DataTypes.ReserveDataLegacy memory reserveData = POOL.getReserveData(asset());
164 : :
165 : : // if inactive, paused or frozen users cannot deposit underlying
166 : : if (
167 : 3006 : !ReserveConfiguration.getActive(reserveData.configuration) ||
168 : 3006 : ReserveConfiguration.getPaused(reserveData.configuration) ||
169 : 3005 : ReserveConfiguration.getFrozen(reserveData.configuration)
170 : : ) {
171 : 2 : return 0;
172 : : }
173 : :
174 : 3004 : uint256 supplyCap = ReserveConfiguration.getSupplyCap(reserveData.configuration) *
175 : : (10 ** ReserveConfiguration.getDecimals(reserveData.configuration));
176 : : // if no supply cap deposit is unlimited
177 : 3004 : if (supplyCap == 0) return type(uint256).max;
178 : :
179 : : // return remaining supply cap margin
180 : 1000 : uint256 currentSupply = (IAToken(reserveData.aTokenAddress).scaledTotalSupply() +
181 : : reserveData.accruedToTreasury).mulDiv(_rate(), RAY, Math.Rounding.Ceil);
182 : 1000 : return currentSupply > supplyCap ? 0 : supplyCap - currentSupply;
183 : : }
184 : :
185 : : ///@inheritdoc IERC4626StataToken
186 : : function latestAnswer() external view returns (int256) {
187 : 1001 : uint256 aTokenUnderlyingAssetPrice = IAaveOracle(POOL_ADDRESSES_PROVIDER.getPriceOracle())
188 : : .getAssetPrice(asset());
189 : : // @notice aTokenUnderlyingAssetPrice * rate / RAY
190 : 1001 : return int256(aTokenUnderlyingAssetPrice.mulDiv(_rate(), RAY, Math.Rounding.Floor));
191 : : }
192 : :
193 : : function _deposit(
194 : : address caller,
195 : : address receiver,
196 : : uint256 assets,
197 : : uint256 shares,
198 : : bool depositToAave
199 : : ) internal virtual {
200 : 19007 : if (shares == 0) {
201 : 0 : revert StaticATokenInvalidZeroShares();
202 : : }
203 : : // If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
204 : : // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
205 : : // calls the vault, which is assumed not malicious.
206 : : //
207 : : // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
208 : : // assets are transferred and before the shares are minted, which is a valid state.
209 : : // slither-disable-next-line reentrancy-no-eth
210 : :
211 : 19007 : if (depositToAave) {
212 : 4002 : address cachedAsset = asset();
213 : 4002 : SafeERC20.safeTransferFrom(IERC20(cachedAsset), caller, address(this), assets);
214 : 3002 : POOL.deposit(cachedAsset, assets, address(this), 0);
215 : : } else {
216 : 15005 : ERC4626StataTokenStorage storage $ = _getERC4626StataTokenStorage();
217 : 15005 : SafeERC20.safeTransferFrom($._aToken, caller, address(this), assets);
218 : : }
219 : 16007 : _mint(receiver, shares);
220 : :
221 : 16005 : emit Deposit(caller, receiver, assets, shares);
222 : : }
223 : :
224 : : function _deposit(
225 : : address caller,
226 : : address receiver,
227 : : uint256 assets,
228 : : uint256 shares
229 : : ) internal virtual override {
230 : 2002 : _deposit(caller, receiver, assets, shares, true);
231 : : }
232 : :
233 : : function _withdraw(
234 : : address caller,
235 : : address receiver,
236 : : address owner,
237 : : uint256 assets,
238 : : uint256 shares,
239 : : bool withdrawFromAave
240 : : ) internal virtual {
241 : 5002 : if (caller != owner) {
242 : 2000 : _spendAllowance(owner, caller, shares);
243 : : }
244 : :
245 : : // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
246 : : // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
247 : : // calls the vault, which is assumed not malicious.
248 : : //
249 : : // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
250 : : // shares are burned and after the assets are transferred, which is a valid state.
251 : 4002 : _burn(owner, shares);
252 : 4000 : if (withdrawFromAave) {
253 : 2000 : POOL.withdraw(asset(), assets, receiver);
254 : : } else {
255 : 2000 : ERC4626StataTokenStorage storage $ = _getERC4626StataTokenStorage();
256 : 2000 : SafeERC20.safeTransfer($._aToken, receiver, assets);
257 : : }
258 : :
259 : 4000 : emit Withdraw(caller, receiver, owner, assets, shares);
260 : : }
261 : :
262 : : function _withdraw(
263 : : address caller,
264 : : address receiver,
265 : : address owner,
266 : : uint256 assets,
267 : : uint256 shares
268 : : ) internal virtual override {
269 : 2002 : _withdraw(caller, receiver, owner, assets, shares, true);
270 : : }
271 : :
272 : : function _convertToShares(
273 : : uint256 assets,
274 : : Math.Rounding rounding
275 : : ) internal view virtual override returns (uint256) {
276 : : // * @notice assets * RAY / exchangeRate
277 : 28013 : return assets.mulDiv(RAY, _rate(), rounding);
278 : : }
279 : :
280 : : function _convertToAssets(
281 : : uint256 shares,
282 : : Math.Rounding rounding
283 : : ) internal view virtual override returns (uint256) {
284 : : // * @notice share * exchangeRate / RAY
285 : 14007 : return shares.mulDiv(_rate(), RAY, rounding);
286 : : }
287 : :
288 : : function _rate() internal view returns (uint256) {
289 : 44021 : return POOL.getReserveNormalizedIncome(asset());
290 : : }
291 : : }
|