LCOV - code coverage report
Current view: top level - extensions/static-a-token - ERC20AaveLMUpgradeable.sol (source / functions) Coverage Total Hit
Test: lcov.info.p Lines: 92.5 % 93 86
Test Date: 2024-09-24 09:34:24 Functions: 90.9 % 22 20
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 {ERC20Upgradeable} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol';
       5                 :             : import {IERC20} from 'openzeppelin-contracts/contracts/interfaces/IERC20.sol';
       6                 :             : import {SafeERC20} from 'openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol';
       7                 :             : import {SafeCast} from 'solidity-utils/contracts/oz-common/SafeCast.sol';
       8                 :             : 
       9                 :             : import {IRewardsController} from '../../rewards/interfaces/IRewardsController.sol';
      10                 :             : import {IERC20AaveLM} from './interfaces/IERC20AaveLM.sol';
      11                 :             : 
      12                 :             : /**
      13                 :             :  * @title ERC20AaveLMUpgradeable.sol
      14                 :             :  * @notice Wrapper smart contract that supports tracking and claiming liquidity mining rewards from the Aave system
      15                 :             :  * @dev ERC20 extension, so ERC20 initialization should be done by the children contract/s
      16                 :             :  * @author BGD labs
      17                 :             :  */
      18                 :             : abstract contract ERC20AaveLMUpgradeable is ERC20Upgradeable, IERC20AaveLM {
      19                 :             :   using SafeCast for uint256;
      20                 :             : 
      21                 :             :   /// @custom:storage-location erc7201:aave-dao.storage.ERC20AaveLM
      22                 :             :   struct ERC20AaveLMStorage {
      23                 :             :     address _referenceAsset; // a/v token to track rewards on INCENTIVES_CONTROLLER
      24                 :             :     address[] _rewardTokens;
      25                 :             :     mapping(address user => RewardIndexCache cache) _startIndex;
      26                 :             :     mapping(address user => mapping(address reward => UserRewardsData cache)) _userRewardsData;
      27                 :             :   }
      28                 :             : 
      29                 :             :   // keccak256(abi.encode(uint256(keccak256("aave-dao.storage.ERC20AaveLM")) - 1)) & ~bytes32(uint256(0xff))
      30                 :             :   bytes32 private constant ERC20AaveLMStorageLocation =
      31                 :             :     0x4fad66563f105be0bff96185c9058c4934b504d3ba15ca31e86294f0b01fd200;
      32                 :             : 
      33                 :             :   function _getERC20AaveLMStorage() private pure returns (ERC20AaveLMStorage storage $) {
      34                 :             :     assembly {
      35                 :      119662 :       $.slot := ERC20AaveLMStorageLocation
      36                 :             :     }
      37                 :             :   }
      38                 :             : 
      39                 :             :   IRewardsController public immutable INCENTIVES_CONTROLLER;
      40                 :             : 
      41                 :             :   constructor(IRewardsController rewardsController) {
      42                 :           0 :     INCENTIVES_CONTROLLER = rewardsController;
      43                 :             :   }
      44                 :             : 
      45                 :             :   function __ERC20AaveLM_init(address referenceAsset_) internal onlyInitializing {
      46                 :          85 :     __ERC20AaveLM_init_unchained(referenceAsset_);
      47                 :             :   }
      48                 :             : 
      49                 :             :   function __ERC20AaveLM_init_unchained(address referenceAsset_) internal onlyInitializing {
      50                 :          85 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
      51                 :          85 :     $._referenceAsset = referenceAsset_;
      52                 :             : 
      53                 :          85 :     if (INCENTIVES_CONTROLLER != IRewardsController(address(0))) {
      54                 :          85 :       refreshRewardTokens();
      55                 :             :     }
      56                 :             :   }
      57                 :             : 
      58                 :             :   ///@inheritdoc IERC20AaveLM
      59                 :             :   function claimRewardsOnBehalf(
      60                 :             :     address onBehalfOf,
      61                 :             :     address receiver,
      62                 :             :     address[] memory rewards
      63                 :             :   ) external {
      64                 :        3000 :     address msgSender = _msgSender();
      65                 :        3000 :     if (msgSender != onBehalfOf && msgSender != INCENTIVES_CONTROLLER.getClaimer(onBehalfOf)) {
      66                 :        1000 :       revert InvalidClaimer(msgSender);
      67                 :             :     }
      68                 :             : 
      69                 :        2000 :     _claimRewardsOnBehalf(onBehalfOf, receiver, rewards);
      70                 :             :   }
      71                 :             : 
      72                 :             :   ///@inheritdoc IERC20AaveLM
      73                 :             :   function claimRewards(address receiver, address[] memory rewards) external {
      74                 :        1000 :     _claimRewardsOnBehalf(_msgSender(), receiver, rewards);
      75                 :             :   }
      76                 :             : 
      77                 :             :   ///@inheritdoc IERC20AaveLM
      78                 :             :   function claimRewardsToSelf(address[] memory rewards) external {
      79                 :        1001 :     _claimRewardsOnBehalf(_msgSender(), _msgSender(), rewards);
      80                 :             :   }
      81                 :             : 
      82                 :             :   ///@inheritdoc IERC20AaveLM
      83                 :             :   function refreshRewardTokens() public override {
      84                 :        9088 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
      85                 :        9088 :     address[] memory rewards = INCENTIVES_CONTROLLER.getRewardsByAsset($._referenceAsset);
      86                 :        9088 :     for (uint256 i = 0; i < rewards.length; i++) {
      87                 :        9003 :       _registerRewardToken(rewards[i]);
      88                 :             :     }
      89                 :             :   }
      90                 :             : 
      91                 :             :   ///@inheritdoc IERC20AaveLM
      92                 :             :   function collectAndUpdateRewards(address reward) public returns (uint256) {
      93                 :        2517 :     if (reward == address(0)) {
      94                 :           0 :       return 0;
      95                 :             :     }
      96                 :             : 
      97                 :        2517 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
      98                 :        2517 :     address[] memory assets = new address[](1);
      99                 :        2517 :     assets[0] = address($._referenceAsset);
     100                 :             : 
     101                 :        2517 :     return INCENTIVES_CONTROLLER.claimRewards(assets, type(uint256).max, address(this), reward);
     102                 :             :   }
     103                 :             : 
     104                 :             :   ///@inheritdoc IERC20AaveLM
     105                 :             :   function isRegisteredRewardToken(address reward) public view override returns (bool) {
     106                 :        9006 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     107                 :        9006 :     return $._startIndex[reward].isRegistered;
     108                 :             :   }
     109                 :             : 
     110                 :             :   ///@inheritdoc IERC20AaveLM
     111                 :             :   function getCurrentRewardsIndex(address reward) public view returns (uint256) {
     112                 :       37953 :     if (address(reward) == address(0)) {
     113                 :           0 :       return 0;
     114                 :             :     }
     115                 :       37953 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     116                 :       37953 :     (, uint256 nextIndex) = INCENTIVES_CONTROLLER.getAssetIndex($._referenceAsset, reward);
     117                 :       37953 :     return nextIndex;
     118                 :             :   }
     119                 :             : 
     120                 :             :   ///@inheritdoc IERC20AaveLM
     121                 :             :   function getTotalClaimableRewards(address reward) external view returns (uint256) {
     122                 :        1001 :     if (reward == address(0)) {
     123                 :           0 :       return 0;
     124                 :             :     }
     125                 :             : 
     126                 :        1001 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     127                 :        1001 :     address[] memory assets = new address[](1);
     128                 :        1001 :     assets[0] = $._referenceAsset;
     129                 :        1001 :     uint256 freshRewards = INCENTIVES_CONTROLLER.getUserRewards(assets, address(this), reward);
     130                 :        1001 :     return IERC20(reward).balanceOf(address(this)) + freshRewards;
     131                 :             :   }
     132                 :             : 
     133                 :             :   ///@inheritdoc IERC20AaveLM
     134                 :             :   function getClaimableRewards(address user, address reward) external view returns (uint256) {
     135                 :       13361 :     return _getClaimableRewards(user, reward, balanceOf(user), getCurrentRewardsIndex(reward));
     136                 :             :   }
     137                 :             : 
     138                 :             :   ///@inheritdoc IERC20AaveLM
     139                 :             :   function getUnclaimedRewards(address user, address reward) external view returns (uint256) {
     140                 :        4356 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     141                 :        4356 :     return $._userRewardsData[user][reward].unclaimedRewards;
     142                 :             :   }
     143                 :             : 
     144                 :             :   ///@inheritdoc IERC20AaveLM
     145                 :             :   function getReferenceAsset() external view returns (address) {
     146                 :           2 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     147                 :           2 :     return $._referenceAsset;
     148                 :             :   }
     149                 :             : 
     150                 :             :   ///@inheritdoc IERC20AaveLM
     151                 :             :   function rewardTokens() external view returns (address[] memory) {
     152                 :           1 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     153                 :           1 :     return $._rewardTokens;
     154                 :             :   }
     155                 :             : 
     156                 :             :   /**
     157                 :             :    * @notice Updates rewards for senders and receiver in a transfer (not updating rewards for address(0))
     158                 :             :    * @param from The address of the sender of tokens
     159                 :             :    * @param to The address of the receiver of tokens
     160                 :             :    */
     161                 :             :   function _update(address from, address to, uint256 amount) internal virtual override {
     162                 :       12595 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     163                 :       12595 :     for (uint256 i = 0; i < $._rewardTokens.length; i++) {
     164                 :       11589 :       address rewardToken = address($._rewardTokens[i]);
     165                 :       11589 :       uint256 rewardsIndex = getCurrentRewardsIndex(rewardToken);
     166                 :       11589 :       if (from != address(0)) {
     167                 :        2000 :         _updateUser(from, rewardsIndex, rewardToken);
     168                 :             :       }
     169                 :       11589 :       if (to != address(0) && from != to) {
     170                 :       10589 :         _updateUser(to, rewardsIndex, rewardToken);
     171                 :             :       }
     172                 :             :     }
     173                 :       12595 :     super._update(from, to, amount);
     174                 :             :   }
     175                 :             : 
     176                 :             :   /**
     177                 :             :    * @notice Adding the pending rewards to the unclaimed for specific user and updating user index
     178                 :             :    * @param user The address of the user to update
     179                 :             :    * @param currentRewardsIndex The current rewardIndex
     180                 :             :    * @param rewardToken The address of the reward token
     181                 :             :    */
     182                 :             :   function _updateUser(address user, uint256 currentRewardsIndex, address rewardToken) internal {
     183                 :       12589 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     184                 :       12589 :     uint256 balance = balanceOf(user);
     185                 :       12589 :     if (balance > 0) {
     186                 :        2589 :       $._userRewardsData[user][rewardToken].unclaimedRewards = _getClaimableRewards(
     187                 :             :         user,
     188                 :             :         rewardToken,
     189                 :             :         balance,
     190                 :             :         currentRewardsIndex
     191                 :             :       ).toUint128();
     192                 :             :     }
     193                 :       12589 :     $._userRewardsData[user][rewardToken].rewardsIndexOnLastInteraction = currentRewardsIndex
     194                 :             :       .toUint128();
     195                 :             :   }
     196                 :             : 
     197                 :             :   /**
     198                 :             :    * @notice Compute the pending in WAD. Pending is the amount to add (not yet unclaimed) rewards in WAD.
     199                 :             :    * @param balance The balance of the user
     200                 :             :    * @param rewardsIndexOnLastInteraction The index which was on the last interaction of the user
     201                 :             :    * @param currentRewardsIndex The current rewards index in the system
     202                 :             :    * @return The amount of pending rewards in WAD
     203                 :             :    */
     204                 :             :   function _getPendingRewards(
     205                 :             :     uint256 balance,
     206                 :             :     uint256 rewardsIndexOnLastInteraction,
     207                 :             :     uint256 currentRewardsIndex
     208                 :             :   ) internal view returns (uint256) {
     209                 :       19949 :     if (balance == 0) {
     210                 :           0 :       return 0;
     211                 :             :     }
     212                 :       19949 :     return (balance * (currentRewardsIndex - rewardsIndexOnLastInteraction)) / 10 ** decimals();
     213                 :             :   }
     214                 :             : 
     215                 :             :   /**
     216                 :             :    * @notice Compute the claimable rewards for a user
     217                 :             :    * @param user The address of the user
     218                 :             :    * @param reward The address of the reward
     219                 :             :    * @param balance The balance of the user in WAD
     220                 :             :    * @param currentRewardsIndex The current rewards index
     221                 :             :    * @return The total rewards that can be claimed by the user (if `fresh` flag true, after updating rewards)
     222                 :             :    */
     223                 :             :   function _getClaimableRewards(
     224                 :             :     address user,
     225                 :             :     address reward,
     226                 :             :     uint256 balance,
     227                 :             :     uint256 currentRewardsIndex
     228                 :             :   ) internal view returns (uint256) {
     229                 :       19950 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     230                 :       19950 :     RewardIndexCache memory rewardsIndexCache = $._startIndex[reward];
     231                 :       19950 :     if (!rewardsIndexCache.isRegistered) {
     232                 :           1 :       revert RewardNotInitialized(reward);
     233                 :             :     }
     234                 :             : 
     235                 :       19949 :     UserRewardsData memory currentUserRewardsData = $._userRewardsData[user][reward];
     236                 :       19949 :     return
     237                 :       19949 :       currentUserRewardsData.unclaimedRewards +
     238                 :       19949 :       _getPendingRewards(
     239                 :             :         balance,
     240                 :             :         currentUserRewardsData.rewardsIndexOnLastInteraction == 0
     241                 :             :           ? rewardsIndexCache.lastUpdatedIndex
     242                 :             :           : currentUserRewardsData.rewardsIndexOnLastInteraction,
     243                 :             :         currentRewardsIndex
     244                 :             :       );
     245                 :             :   }
     246                 :             : 
     247                 :             :   /**
     248                 :             :    * @notice Claim rewards on behalf of a user and send them to a receiver
     249                 :             :    * @param onBehalfOf The address to claim on behalf of
     250                 :             :    * @param rewards The addresses of the rewards
     251                 :             :    * @param receiver The address to receive the rewards
     252                 :             :    */
     253                 :             :   function _claimRewardsOnBehalf(
     254                 :             :     address onBehalfOf,
     255                 :             :     address receiver,
     256                 :             :     address[] memory rewards
     257                 :             :   ) internal virtual {
     258                 :        4000 :     for (uint256 i = 0; i < rewards.length; i++) {
     259                 :        4000 :       if (address(rewards[i]) == address(0)) {
     260                 :        4000 :         continue;
     261                 :             :       }
     262                 :        4000 :       uint256 currentRewardsIndex = getCurrentRewardsIndex(rewards[i]);
     263                 :        4000 :       uint256 balance = balanceOf(onBehalfOf);
     264                 :        4000 :       uint256 userReward = _getClaimableRewards(
     265                 :             :         onBehalfOf,
     266                 :             :         rewards[i],
     267                 :             :         balance,
     268                 :             :         currentRewardsIndex
     269                 :             :       );
     270                 :        4000 :       uint256 totalRewardTokenBalance = IERC20(rewards[i]).balanceOf(address(this));
     271                 :        4000 :       uint256 unclaimedReward = 0;
     272                 :             : 
     273                 :        4000 :       if (userReward > totalRewardTokenBalance) {
     274                 :        1516 :         totalRewardTokenBalance += collectAndUpdateRewards(address(rewards[i]));
     275                 :             :       }
     276                 :             : 
     277                 :        4000 :       if (userReward > totalRewardTokenBalance) {
     278                 :           0 :         unclaimedReward = userReward - totalRewardTokenBalance;
     279                 :           0 :         userReward = totalRewardTokenBalance;
     280                 :             :       }
     281                 :        4000 :       if (userReward > 0) {
     282                 :        1516 :         ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     283                 :        1516 :         $._userRewardsData[onBehalfOf][rewards[i]].unclaimedRewards = unclaimedReward.toUint128();
     284                 :        1516 :         $
     285                 :             :         ._userRewardsData[onBehalfOf][rewards[i]]
     286                 :             :           .rewardsIndexOnLastInteraction = currentRewardsIndex.toUint128();
     287                 :        1516 :         SafeERC20.safeTransfer(IERC20(rewards[i]), receiver, userReward);
     288                 :             :       }
     289                 :             :     }
     290                 :             :   }
     291                 :             : 
     292                 :             :   /**
     293                 :             :    * @notice Initializes a new rewardToken
     294                 :             :    * @param reward The reward token to be registered
     295                 :             :    */
     296                 :             :   function _registerRewardToken(address reward) internal {
     297                 :        9003 :     if (isRegisteredRewardToken(reward)) return;
     298                 :        9003 :     uint256 startIndex = getCurrentRewardsIndex(reward);
     299                 :             : 
     300                 :        9003 :     ERC20AaveLMStorage storage $ = _getERC20AaveLMStorage();
     301                 :        9003 :     $._rewardTokens.push(reward);
     302                 :        9003 :     $._startIndex[reward] = RewardIndexCache(true, startIndex.toUint240());
     303                 :             : 
     304                 :        9003 :     emit RewardTokenRegistered(reward, startIndex);
     305                 :             :   }
     306                 :             : }
        

Generated by: LCOV version 2.1-1