Branch data Line data Source code
1 : : // SPDX-License-Identifier: BUSL-1.1
2 : : pragma solidity ^0.8.10;
3 : :
4 : : import {VersionedInitializable} from '../misc/aave-upgradeability/VersionedInitializable.sol';
5 : : import {SafeCast} from '../dependencies/openzeppelin/contracts/SafeCast.sol';
6 : : import {IScaledBalanceToken} from '../interfaces/IScaledBalanceToken.sol';
7 : : import {RewardsDistributor} from './RewardsDistributor.sol';
8 : : import {IRewardsController} from './interfaces/IRewardsController.sol';
9 : : import {ITransferStrategyBase} from './interfaces/ITransferStrategyBase.sol';
10 : : import {RewardsDataTypes} from './libraries/RewardsDataTypes.sol';
11 : : import {IEACAggregatorProxy} from '../helpers/interfaces/IEACAggregatorProxy.sol';
12 : :
13 : : /**
14 : : * @title RewardsController
15 : : * @notice Abstract contract template to build Distributors contracts for ERC20 rewards to protocol participants
16 : : * @author Aave
17 : : **/
18 : : contract RewardsController is RewardsDistributor, VersionedInitializable, IRewardsController {
19 : : using SafeCast for uint256;
20 : :
21 : : uint256 public constant REVISION = 1;
22 : :
23 : : // This mapping allows whitelisted addresses to claim on behalf of others
24 : : // useful for contracts that hold tokens to be rewarded but don't have any native logic to claim Liquidity Mining rewards
25 : : mapping(address => address) internal _authorizedClaimers;
26 : :
27 : : // reward => transfer strategy implementation contract
28 : : // The TransferStrategy contract abstracts the logic regarding
29 : : // the source of the reward and how to transfer it to the user.
30 : : mapping(address => ITransferStrategyBase) internal _transferStrategy;
31 : :
32 : : // This mapping contains the price oracle per reward.
33 : : // A price oracle is enforced for integrators to be able to show incentives at
34 : : // the current Aave UI without the need to setup an external price registry
35 : : // At the moment of reward configuration, the Incentives Controller performs
36 : : // a check to see if the provided reward oracle contains `latestAnswer`.
37 : : mapping(address => IEACAggregatorProxy) internal _rewardOracle;
38 : :
39 : : modifier onlyAuthorizedClaimers(address claimer, address user) {
40 : 1 : require(_authorizedClaimers[user] == claimer, 'CLAIMER_UNAUTHORIZED');
41 : : _;
42 : : }
43 : :
44 : : constructor(address emissionManager) RewardsDistributor(emissionManager) {}
45 : :
46 : : /**
47 : : * @dev Initialize for RewardsController
48 : : * @dev It expects an address as argument since its initialized via PoolAddressesProvider._updateImpl()
49 : : **/
50 : : function initialize(address) external initializer {}
51 : :
52 : : /// @inheritdoc IRewardsController
53 : : function getClaimer(address user) external view override returns (address) {
54 : 2002 : return _authorizedClaimers[user];
55 : : }
56 : :
57 : : /**
58 : : * @dev Returns the revision of the implementation contract
59 : : * @return uint256, current revision version
60 : : */
61 : : function getRevision() internal pure override returns (uint256) {
62 : 1387 : return REVISION;
63 : : }
64 : :
65 : : /// @inheritdoc IRewardsController
66 : : function getRewardOracle(address reward) external view override returns (address) {
67 : 1 : return address(_rewardOracle[reward]);
68 : : }
69 : :
70 : : /// @inheritdoc IRewardsController
71 : : function getTransferStrategy(address reward) external view override returns (address) {
72 : 1 : return address(_transferStrategy[reward]);
73 : : }
74 : :
75 : : /// @inheritdoc IRewardsController
76 : : function configureAssets(
77 : : RewardsDataTypes.RewardsConfigInput[] memory config
78 : : ) external override onlyEmissionManager {
79 : 9023 : for (uint256 i = 0; i < config.length; i++) {
80 : : // Get the current Scaled Total Supply of AToken or Debt token
81 : 9023 : config[i].totalSupply = IScaledBalanceToken(config[i].asset).scaledTotalSupply();
82 : :
83 : : // Install TransferStrategy logic at IncentivesController
84 : 9023 : _installTransferStrategy(config[i].reward, config[i].transferStrategy);
85 : :
86 : : // Set reward oracle, enforces input oracle to have latestPrice function
87 : 9023 : _setRewardOracle(config[i].reward, config[i].rewardOracle);
88 : : }
89 : 9023 : _configureAssets(config);
90 : : }
91 : :
92 : : /// @inheritdoc IRewardsController
93 : : function setTransferStrategy(
94 : : address reward,
95 : : ITransferStrategyBase transferStrategy
96 : : ) external onlyEmissionManager {
97 : 2 : _installTransferStrategy(reward, transferStrategy);
98 : : }
99 : :
100 : : /// @inheritdoc IRewardsController
101 : : function setRewardOracle(
102 : : address reward,
103 : : IEACAggregatorProxy rewardOracle
104 : : ) external onlyEmissionManager {
105 : 2 : _setRewardOracle(reward, rewardOracle);
106 : : }
107 : :
108 : : /// @inheritdoc IRewardsController
109 : : function handleAction(address user, uint256 totalSupply, uint256 userBalance) external override {
110 : 138815 : _updateData(msg.sender, user, userBalance, totalSupply);
111 : : }
112 : :
113 : : /// @inheritdoc IRewardsController
114 : : function claimRewards(
115 : : address[] calldata assets,
116 : : uint256 amount,
117 : : address to,
118 : : address reward
119 : : ) external override returns (uint256) {
120 : 2521 : require(to != address(0), 'INVALID_TO_ADDRESS');
121 : 2521 : return _claimRewards(assets, amount, msg.sender, msg.sender, to, reward);
122 : : }
123 : :
124 : : /// @inheritdoc IRewardsController
125 : : function claimRewardsOnBehalf(
126 : : address[] calldata assets,
127 : : uint256 amount,
128 : : address user,
129 : : address to,
130 : : address reward
131 : : ) external override onlyAuthorizedClaimers(msg.sender, user) returns (uint256) {
132 : 1 : require(user != address(0), 'INVALID_USER_ADDRESS');
133 : 1 : require(to != address(0), 'INVALID_TO_ADDRESS');
134 : 1 : return _claimRewards(assets, amount, msg.sender, user, to, reward);
135 : : }
136 : :
137 : : /// @inheritdoc IRewardsController
138 : : function claimRewardsToSelf(
139 : : address[] calldata assets,
140 : : uint256 amount,
141 : : address reward
142 : : ) external override returns (uint256) {
143 : 1 : return _claimRewards(assets, amount, msg.sender, msg.sender, msg.sender, reward);
144 : : }
145 : :
146 : : /// @inheritdoc IRewardsController
147 : : function claimAllRewards(
148 : : address[] calldata assets,
149 : : address to
150 : : ) external override returns (address[] memory rewardsList, uint256[] memory claimedAmounts) {
151 : 1 : require(to != address(0), 'INVALID_TO_ADDRESS');
152 : 1 : return _claimAllRewards(assets, msg.sender, msg.sender, to);
153 : : }
154 : :
155 : : /// @inheritdoc IRewardsController
156 : : function claimAllRewardsOnBehalf(
157 : : address[] calldata assets,
158 : : address user,
159 : : address to
160 : : )
161 : : external
162 : : override
163 : : onlyAuthorizedClaimers(msg.sender, user)
164 : : returns (address[] memory rewardsList, uint256[] memory claimedAmounts)
165 : : {
166 : 1 : require(user != address(0), 'INVALID_USER_ADDRESS');
167 : 1 : require(to != address(0), 'INVALID_TO_ADDRESS');
168 : 1 : return _claimAllRewards(assets, msg.sender, user, to);
169 : : }
170 : :
171 : : /// @inheritdoc IRewardsController
172 : : function claimAllRewardsToSelf(
173 : : address[] calldata assets
174 : : ) external override returns (address[] memory rewardsList, uint256[] memory claimedAmounts) {
175 : 1 : return _claimAllRewards(assets, msg.sender, msg.sender, msg.sender);
176 : : }
177 : :
178 : : /// @inheritdoc IRewardsController
179 : : function setClaimer(address user, address caller) external override onlyEmissionManager {
180 : 1004 : _authorizedClaimers[user] = caller;
181 : 1004 : emit ClaimerSet(user, caller);
182 : : }
183 : :
184 : : /**
185 : : * @dev Get user balances and total supply of all the assets specified by the assets parameter
186 : : * @param assets List of assets to retrieve user balance and total supply
187 : : * @param user Address of the user
188 : : * @return userAssetBalances contains a list of structs with user balance and total supply of the given assets
189 : : */
190 : : function _getUserAssetBalances(
191 : : address[] calldata assets,
192 : : address user
193 : : ) internal view override returns (RewardsDataTypes.UserAssetBalance[] memory userAssetBalances) {
194 : 3537 : userAssetBalances = new RewardsDataTypes.UserAssetBalance[](assets.length);
195 : 3537 : for (uint256 i = 0; i < assets.length; i++) {
196 : 3537 : userAssetBalances[i].asset = assets[i];
197 : 3537 : (userAssetBalances[i].userBalance, userAssetBalances[i].totalSupply) = IScaledBalanceToken(
198 : : assets[i]
199 : : ).getScaledUserBalanceAndSupply(user);
200 : : }
201 : 0 : return userAssetBalances;
202 : : }
203 : :
204 : : /**
205 : : * @dev Claims one type of reward for a user on behalf, on all the assets of the pool, accumulating the pending rewards.
206 : : * @param assets List of assets to check eligible distributions before claiming rewards
207 : : * @param amount Amount of rewards to claim
208 : : * @param claimer Address of the claimer who claims rewards on behalf of user
209 : : * @param user Address to check and claim rewards
210 : : * @param to Address that will be receiving the rewards
211 : : * @param reward Address of the reward token
212 : : * @return Rewards claimed
213 : : **/
214 : : function _claimRewards(
215 : : address[] calldata assets,
216 : : uint256 amount,
217 : : address claimer,
218 : : address user,
219 : : address to,
220 : : address reward
221 : : ) internal returns (uint256) {
222 : 2523 : if (amount == 0) {
223 : 1 : return 0;
224 : : }
225 : 2522 : uint256 totalRewards;
226 : :
227 : 2522 : _updateDataMultiple(user, _getUserAssetBalances(assets, user));
228 : 2522 : for (uint256 i = 0; i < assets.length; i++) {
229 : 2522 : address asset = assets[i];
230 : 2522 : totalRewards += _assets[asset].rewards[reward].usersData[user].accrued;
231 : :
232 : 2522 : if (totalRewards <= amount) {
233 : 2521 : _assets[asset].rewards[reward].usersData[user].accrued = 0;
234 : : } else {
235 : 1 : uint256 difference = totalRewards - amount;
236 : 1 : totalRewards -= difference;
237 : 1 : _assets[asset].rewards[reward].usersData[user].accrued = difference.toUint128();
238 : 1 : break;
239 : : }
240 : : }
241 : :
242 : 2522 : if (totalRewards == 0) {
243 : 623 : return 0;
244 : : }
245 : :
246 : 1899 : _transferRewards(to, reward, totalRewards);
247 : 1899 : emit RewardsClaimed(user, reward, to, claimer, totalRewards);
248 : :
249 : 1899 : return totalRewards;
250 : : }
251 : :
252 : : /**
253 : : * @dev Claims one type of reward for a user on behalf, on all the assets of the pool, accumulating the pending rewards.
254 : : * @param assets List of assets to check eligible distributions before claiming rewards
255 : : * @param claimer Address of the claimer on behalf of user
256 : : * @param user Address to check and claim rewards
257 : : * @param to Address that will be receiving the rewards
258 : : * @return
259 : : * rewardsList List of reward addresses
260 : : * claimedAmount List of claimed amounts, follows "rewardsList" items order
261 : : **/
262 : : function _claimAllRewards(
263 : : address[] calldata assets,
264 : : address claimer,
265 : : address user,
266 : : address to
267 : : ) internal returns (address[] memory rewardsList, uint256[] memory claimedAmounts) {
268 : 3 : uint256 rewardsListLength = _rewardsList.length;
269 : 3 : rewardsList = new address[](rewardsListLength);
270 : 3 : claimedAmounts = new uint256[](rewardsListLength);
271 : :
272 : 3 : _updateDataMultiple(user, _getUserAssetBalances(assets, user));
273 : :
274 : 3 : for (uint256 i = 0; i < assets.length; i++) {
275 : 3 : address asset = assets[i];
276 : 3 : for (uint256 j = 0; j < rewardsListLength; j++) {
277 : 3 : if (rewardsList[j] == address(0)) {
278 : 3 : rewardsList[j] = _rewardsList[j];
279 : : }
280 : 3 : uint256 rewardAmount = _assets[asset].rewards[rewardsList[j]].usersData[user].accrued;
281 : 3 : if (rewardAmount != 0) {
282 : 3 : claimedAmounts[j] += rewardAmount;
283 : 3 : _assets[asset].rewards[rewardsList[j]].usersData[user].accrued = 0;
284 : : }
285 : : }
286 : : }
287 : 3 : for (uint256 i = 0; i < rewardsListLength; i++) {
288 : 3 : _transferRewards(to, rewardsList[i], claimedAmounts[i]);
289 : 3 : emit RewardsClaimed(user, rewardsList[i], to, claimer, claimedAmounts[i]);
290 : : }
291 : 3 : return (rewardsList, claimedAmounts);
292 : : }
293 : :
294 : : /**
295 : : * @dev Function to transfer rewards to the desired account using delegatecall and
296 : : * @param to Account address to send the rewards
297 : : * @param reward Address of the reward token
298 : : * @param amount Amount of rewards to transfer
299 : : */
300 : : function _transferRewards(address to, address reward, uint256 amount) internal {
301 : 1902 : ITransferStrategyBase transferStrategy = _transferStrategy[reward];
302 : :
303 : 1902 : bool success = transferStrategy.performTransfer(to, reward, amount);
304 : :
305 : 1902 : require(success == true, 'TRANSFER_ERROR');
306 : : }
307 : :
308 : : /**
309 : : * @dev Returns true if `account` is a contract.
310 : : * @param account The address of the account
311 : : * @return bool, true if contract, false otherwise
312 : : */
313 : : function _isContract(address account) internal view returns (bool) {
314 : : // This method relies on extcodesize, which returns 0 for contracts in
315 : : // construction, since the code is only stored at the end of the
316 : : // constructor execution.
317 : :
318 : 9025 : uint256 size;
319 : : // solhint-disable-next-line no-inline-assembly
320 : : assembly {
321 : 9025 : size := extcodesize(account)
322 : : }
323 : 9025 : return size > 0;
324 : : }
325 : :
326 : : /**
327 : : * @dev Internal function to call the optional install hook at the TransferStrategy
328 : : * @param reward The address of the reward token
329 : : * @param transferStrategy The address of the reward TransferStrategy
330 : : */
331 : : function _installTransferStrategy(
332 : : address reward,
333 : : ITransferStrategyBase transferStrategy
334 : : ) internal {
335 : 9025 : require(address(transferStrategy) != address(0), 'STRATEGY_CAN_NOT_BE_ZERO');
336 : 9025 : require(_isContract(address(transferStrategy)) == true, 'STRATEGY_MUST_BE_CONTRACT');
337 : :
338 : 9025 : _transferStrategy[reward] = transferStrategy;
339 : :
340 : 9025 : emit TransferStrategyInstalled(reward, address(transferStrategy));
341 : : }
342 : :
343 : : /**
344 : : * @dev Update the Price Oracle of a reward token. The Price Oracle must follow Chainlink IEACAggregatorProxy interface.
345 : : * @notice The Price Oracle of a reward is used for displaying correct data about the incentives at the UI frontend.
346 : : * @param reward The address of the reward token
347 : : * @param rewardOracle The address of the price oracle
348 : : */
349 : :
350 : : function _setRewardOracle(address reward, IEACAggregatorProxy rewardOracle) internal {
351 : 9025 : require(rewardOracle.latestAnswer() > 0, 'ORACLE_MUST_RETURN_PRICE');
352 : 9025 : _rewardOracle[reward] = rewardOracle;
353 : 9025 : emit RewardOracleUpdated(reward, address(rewardOracle));
354 : : }
355 : : }
|