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