-
Notifications
You must be signed in to change notification settings - Fork 47
/
StakedTokenIncentivesController.sol
291 lines (253 loc) · 10.1 KB
/
StakedTokenIncentivesController.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
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
pragma abicoder v2;
import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {DistributionTypes} from '../lib/DistributionTypes.sol';
import {VersionedInitializable} from '../protocol/libraries/sturdy-upgradeability/VersionedInitializable.sol';
import {DistributionManager} from './DistributionManager.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
import {IScaledBalanceToken} from '../interfaces/IScaledBalanceToken.sol';
import {ISturdyIncentivesController} from '../interfaces/ISturdyIncentivesController.sol';
import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol';
import {IAToken} from '../interfaces/IAToken.sol';
import {ILendingPool} from '../interfaces/ILendingPool.sol';
import {IReserveInterestRateStrategy} from '../interfaces/IReserveInterestRateStrategy.sol';
import {IYieldDistribution} from '../interfaces/IYieldDistribution.sol';
import {IYieldDistributorAdapter} from '../interfaces/IYieldDistributorAdapter.sol';
import {DataTypes} from '../protocol/libraries/types/DataTypes.sol';
import {Errors} from '../protocol/libraries/helpers/Errors.sol';
/**
* @title StakedTokenIncentivesController
* @notice Distributor contract for rewards to the Sturdy protocol, using a staked token as rewards asset.
* The contract stakes the rewards before redistributing them to the Sturdy protocol participants.
* @author Sturdy
**/
contract StakedTokenIncentivesController is
ISturdyIncentivesController,
VersionedInitializable,
DistributionManager
{
using SafeERC20 for IERC20;
uint256 private constant REVISION = 1;
address private constant STURDY_TOKEN = 0x59276455177429ae2af1cc62B77AE31B34EC3890;
mapping(address => uint256) internal _usersUnclaimedRewards;
ILendingPoolAddressesProvider internal _addressProvider;
// 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;
modifier onlyAuthorizedClaimers(address claimer, address user) {
require(_authorizedClaimers[user] == claimer, Errors.CLAIMER_UNAUTHORIZED);
_;
}
constructor(address emissionManager) DistributionManager(emissionManager) {}
/**
* @dev Initialize IStakedTokenIncentivesController
* @param _provider the address of the corresponding addresses provider
**/
function initialize(ILendingPoolAddressesProvider _provider) external initializer {
_addressProvider = _provider;
}
/// @inheritdoc ISturdyIncentivesController
function configureAssets(
address[] calldata assets,
uint256[] calldata emissionsPerSecond
) external payable override onlyEmissionManager {
uint256 length = assets.length;
require(length == emissionsPerSecond.length, Errors.YD_INVALID_CONFIGURATION);
DistributionTypes.AssetConfigInput[]
memory assetsConfig = new DistributionTypes.AssetConfigInput[](assets.length);
for (uint256 i; i < length; ++i) {
assetsConfig[i].underlyingAsset = assets[i];
assetsConfig[i].emissionPerSecond = uint104(emissionsPerSecond[i]);
require(
assetsConfig[i].emissionPerSecond == emissionsPerSecond[i],
Errors.YD_INVALID_CONFIGURATION
);
assetsConfig[i].totalStaked = IScaledBalanceToken(assets[i]).scaledTotalSupply();
}
_configureAssets(assetsConfig);
}
/// @inheritdoc ISturdyIncentivesController
function handleAction(address user, uint256 totalSupply, uint256 userBalance) external override {
ILendingPoolAddressesProvider provider = _addressProvider;
address reserveAsset = IAToken(msg.sender).UNDERLYING_ASSET_ADDRESS();
require(reserveAsset != address(0), Errors.YD_INVALID_CONFIGURATION);
IYieldDistributorAdapter distributorAdapter = IYieldDistributorAdapter(
_addressProvider.getAddress('YIELD_DISTRIBUTOR_ADAPTER')
);
address[] memory sYieldDistributors = distributorAdapter.getStableYieldDistributors(
reserveAsset
);
uint256 length = sYieldDistributors.length;
if (length != 0) {
for (uint256 i = 0; i < length; ++i) {
IYieldDistribution(sYieldDistributors[i]).handleAction(
user,
msg.sender,
totalSupply,
userBalance
);
}
}
address vYieldDistributor = distributorAdapter.getVariableYieldDistributor(reserveAsset);
if (vYieldDistributor != address(0)) {
IYieldDistribution(vYieldDistributor).handleAction(
user,
msg.sender,
totalSupply,
userBalance
);
}
if (assets[msg.sender].emissionPerSecond == 0) return;
uint256 accruedRewards = _updateUserAssetInternal(user, msg.sender, userBalance, totalSupply);
if (accruedRewards != 0) {
_usersUnclaimedRewards[user] += accruedRewards;
emit RewardsAccrued(user, accruedRewards);
}
}
/// @inheritdoc ISturdyIncentivesController
function getRewardsBalance(
address[] calldata assets,
address user
) external view override returns (uint256) {
uint256 unclaimedRewards = _usersUnclaimedRewards[user];
uint256 length = assets.length;
DistributionTypes.UserStakeInput[] memory userState = new DistributionTypes.UserStakeInput[](
length
);
for (uint256 i; i < length; ++i) {
userState[i].underlyingAsset = assets[i];
(userState[i].stakedByUser, userState[i].totalStaked) = IScaledBalanceToken(assets[i])
.getScaledUserBalanceAndSupply(user);
}
unclaimedRewards += _getUnclaimedRewards(user, userState);
return unclaimedRewards;
}
/// @inheritdoc ISturdyIncentivesController
function claimRewards(
address[] calldata assets,
uint256 amount,
address to
) external override returns (uint256) {
require(to != address(0), Errors.YD_INVALID_CONFIGURATION);
return _claimRewards(assets, amount, msg.sender, msg.sender, to);
}
/// @inheritdoc ISturdyIncentivesController
function claimRewardsOnBehalf(
address[] calldata assets,
uint256 amount,
address user,
address to
) external override onlyAuthorizedClaimers(msg.sender, user) returns (uint256) {
require(user != address(0), Errors.YD_INVALID_CONFIGURATION);
require(to != address(0), Errors.YD_INVALID_CONFIGURATION);
return _claimRewards(assets, amount, msg.sender, user, to);
}
/**
* @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards.
* @param amount Amount of rewards to claim
* @param user Address to check and claim rewards
* @param to Address that will be receiving the rewards
* @return Rewards claimed
**/
/// @inheritdoc ISturdyIncentivesController
function setClaimer(address user, address caller) external payable override onlyEmissionManager {
_authorizedClaimers[user] = caller;
emit ClaimerSet(user, caller);
}
/// @inheritdoc ISturdyIncentivesController
function getClaimer(address user) external view override returns (address) {
return _authorizedClaimers[user];
}
/// @inheritdoc ISturdyIncentivesController
function getUserUnclaimedRewards(address _user) external view override returns (uint256) {
return _usersUnclaimedRewards[_user];
}
/// @inheritdoc ISturdyIncentivesController
function REWARD_TOKEN() external view override returns (address) {
return STURDY_TOKEN;
}
/// @inheritdoc ISturdyIncentivesController
function DISTRIBUTION_END()
external
view
override(DistributionManager, ISturdyIncentivesController)
returns (uint256)
{
return _distributionEnd;
}
/// @inheritdoc ISturdyIncentivesController
function getAssetData(
address asset
)
public
view
override(DistributionManager, ISturdyIncentivesController)
returns (uint256, uint256, uint256)
{
return (
assets[asset].index,
assets[asset].emissionPerSecond,
assets[asset].lastUpdateTimestamp
);
}
/// @inheritdoc ISturdyIncentivesController
function getUserAssetData(
address user,
address asset
) public view override(DistributionManager, ISturdyIncentivesController) returns (uint256) {
return assets[asset].users[user];
}
/**
* @dev returns the revision of the implementation contract
*/
function getRevision() internal pure override returns (uint256) {
return REVISION;
}
function PRECISION() external pure override returns (uint8) {
return _PRECISION;
}
/**
* @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards.
* @param amount Amount of rewards to claim
* @param user Address to check and claim rewards
* @param to Address that will be receiving the rewards
* @return Rewards claimed
**/
function _claimRewards(
address[] calldata assets,
uint256 amount,
address claimer,
address user,
address to
) internal returns (uint256) {
if (amount == 0) {
return 0;
}
uint256 unclaimedRewards = _usersUnclaimedRewards[user];
uint256 length = assets.length;
DistributionTypes.UserStakeInput[] memory userState = new DistributionTypes.UserStakeInput[](
length
);
for (uint256 i; i < length; ++i) {
userState[i].underlyingAsset = assets[i];
(userState[i].stakedByUser, userState[i].totalStaked) = IScaledBalanceToken(assets[i])
.getScaledUserBalanceAndSupply(user);
}
uint256 accruedRewards = _claimRewards(user, userState);
if (accruedRewards != 0) {
unclaimedRewards += accruedRewards;
emit RewardsAccrued(user, accruedRewards);
}
if (unclaimedRewards == 0) {
return 0;
}
uint256 amountToClaim = amount > unclaimedRewards ? unclaimedRewards : amount;
_usersUnclaimedRewards[user] = unclaimedRewards - amountToClaim; // Safe due to the previous line
if (IERC20(STURDY_TOKEN).balanceOf(address(this)) >= amountToClaim) {
IERC20(STURDY_TOKEN).safeTransfer(to, amountToClaim);
}
emit RewardsClaimed(user, to, claimer, amountToClaim);
return amountToClaim;
}
}