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