LCOV - code coverage report
Current view: top level - extensions/static-a-token - ERC4626StataTokenUpgradeable.sol (source / functions) Coverage Total Hit
Test: lcov.info.p Lines: 93.2 % 73 68
Test Date: 2024-09-24 09:34:24 Functions: 89.5 % 19 17
Branches: - 0 0

             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                 :             : }
        

Generated by: LCOV version 2.1-1