-
Notifications
You must be signed in to change notification settings - Fork 2
/
StakerVault.sol
403 lines (342 loc) · 14 KB
/
StakerVault.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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../libraries/ScaledMath.sol";
import "../libraries/Errors.sol";
import "../libraries/AddressProviderHelpers.sol";
import "../libraries/UncheckedMath.sol";
import "../interfaces/IStakerVault.sol";
import "../interfaces/IAddressProvider.sol";
import "../interfaces/IVault.sol";
import "../interfaces/tokenomics/IRewardsGauge.sol";
import "../interfaces/tokenomics/IInflationManager.sol";
import "../interfaces/pool/ILiquidityPool.sol";
import "../interfaces/tokenomics/ILpGauge.sol";
import "../interfaces/IERC20Full.sol";
import "./utils/Preparable.sol";
import "./Controller.sol";
import "./pool/LiquidityPool.sol";
import "./access/Authorization.sol";
import "./utils/Pausable.sol";
/**
* @notice This contract handles staked tokens from Backd pools
* However, note that this is NOT an ERC-20 compliant contract and these
* tokens should never be integrated with any protocol assuming ERC-20 compliant
* tokens
* @dev When paused, allows only withdraw/unstake
*/
contract StakerVault is IStakerVault, Authorization, Pausable, Initializable, Preparable {
using AddressProviderHelpers for IAddressProvider;
using SafeERC20 for IERC20;
using ScaledMath for uint256;
using UncheckedMath for uint256;
bytes32 internal constant _LP_GAUGE = "lpGauge";
IController public immutable controller;
IInflationManager public immutable inflationManager;
IAddressProvider public immutable addressProvider;
address public token;
mapping(address => uint256) public balances;
mapping(address => uint256) public actionLockedBalances;
mapping(address => mapping(address => uint256)) internal _allowances;
// All the data fields required for the staking tracking
uint256 private _poolTotalStaked;
mapping(address => bool) public strategies;
uint256 public strategiesTotalStaked;
constructor(IController _controller)
Authorization(_controller.addressProvider().getRoleManager())
{
controller = _controller;
IInflationManager inflationManager_ = controller.inflationManager();
require(address(inflationManager_) != address(0), Error.ZERO_ADDRESS_NOT_ALLOWED);
inflationManager = inflationManager_;
addressProvider = _controller.addressProvider();
}
function initialize(address _token) external override initializer {
token = _token;
}
function initializeLpGauge(address _lpGauge) external override onlyGovernance returns (bool) {
require(currentAddresses[_LP_GAUGE] == address(0), Error.ROLE_EXISTS);
_setConfig(_LP_GAUGE, _lpGauge);
inflationManager.addGaugeForVault(token);
return true;
}
function prepareLpGauge(address _lpGauge) external override onlyGovernance returns (bool) {
_prepare(_LP_GAUGE, _lpGauge);
return true;
}
function executeLpGauge() external override onlyGovernance returns (bool) {
_executeAddress(_LP_GAUGE);
inflationManager.addGaugeForVault(token);
return true;
}
/**
* @notice Registers an address as a strategy to be excluded from token accumulation.
* @dev This should be used if a strategy deposits into a stakerVault and should not get gov. tokens.
* @return `true` if success.
*/
function addStrategy(address strategy) external override returns (bool) {
require(msg.sender == address(inflationManager), Error.UNAUTHORIZED_ACCESS);
strategies[strategy] = true;
return true;
}
/**
* @notice Transfer staked tokens to an account.
* @dev This is not an ERC20 transfer, as tokens are still owned by this contract, but fees get updated in the LP pool.
* @param account Address to transfer to.
* @param amount Amount to transfer.
* @return `true` if success.
*/
function transfer(address account, uint256 amount) external override notPaused returns (bool) {
require(msg.sender != account, Error.SELF_TRANSFER_NOT_ALLOWED);
require(balances[msg.sender] >= amount, Error.INSUFFICIENT_BALANCE);
ILiquidityPool pool = addressProvider.getPoolForToken(token);
pool.handleLpTokenTransfer(msg.sender, account, amount);
address lpGauge = currentAddresses[_LP_GAUGE];
if (lpGauge != address(0)) {
ILpGauge(lpGauge).userCheckpoint(msg.sender);
ILpGauge(lpGauge).userCheckpoint(account);
}
balances[msg.sender] -= amount;
balances[account] += amount;
emit Transfer(msg.sender, account, amount);
return true;
}
/**
* @notice Transfer staked tokens from src to dst.
* @dev This is not an ERC20 transfer, as tokens are still owned by this contract, but fees get updated in the LP pool.
* @param src Address to transfer from.
* @param dst Address to transfer to.
* @param amount Amount to transfer.
* @return `true` if success.
*/
function transferFrom(
address src,
address dst,
uint256 amount
) external override notPaused returns (bool) {
/* Do not allow self transfers */
require(src != dst, Error.SAME_ADDRESS_NOT_ALLOWED);
/* Get the allowance, infinite for the account owner */
uint256 startingAllowance;
if (msg.sender == src) {
startingAllowance = type(uint256).max;
} else {
startingAllowance = _allowances[src][msg.sender];
}
require(startingAllowance >= amount, Error.INSUFFICIENT_ALLOWANCE);
uint256 srcTokens = balances[src];
require(srcTokens >= amount, Error.INSUFFICIENT_BALANCE);
address lpGauge = currentAddresses[_LP_GAUGE];
if (lpGauge != address(0)) {
ILpGauge(lpGauge).userCheckpoint(src);
ILpGauge(lpGauge).userCheckpoint(dst);
}
ILiquidityPool pool = addressProvider.getPoolForToken(token);
pool.handleLpTokenTransfer(src, dst, amount);
/* Update token balances */
balances[src] = srcTokens.uncheckedSub(amount);
balances[dst] = balances[dst] + amount;
/* Update allowance if necessary */
if (startingAllowance != type(uint256).max) {
_allowances[src][msg.sender] = startingAllowance.uncheckedSub(amount);
}
emit Transfer(src, dst, amount);
return true;
}
/**
* @notice Approve staked tokens for spender.
* @param spender Address to approve tokens for.
* @param amount Amount to approve.
* @return `true` if success.
*/
function approve(address spender, uint256 amount) external override notPaused returns (bool) {
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
/**
* @notice If an action is registered and stakes funds, this updates the actionLockedBalances for the user.
* @param account Address that registered the action.
* @param amount Amount staked by the action.
* @return `true` if success.
*/
function increaseActionLockedBalance(address account, uint256 amount)
external
override
returns (bool)
{
require(addressProvider.isAction(msg.sender), Error.UNAUTHORIZED_ACCESS);
address lpGauge = currentAddresses[_LP_GAUGE];
if (lpGauge != address(0)) {
ILpGauge(lpGauge).userCheckpoint(account);
}
actionLockedBalances[account] += amount;
return true;
}
/**
* @notice If an action is executed/reset, this updates the actionLockedBalances for the user.
* @param account Address that registered the action.
* @param amount Amount executed/reset by the action.
* @return `true` if success.
*/
function decreaseActionLockedBalance(address account, uint256 amount)
external
override
returns (bool)
{
require(addressProvider.isAction(msg.sender), Error.UNAUTHORIZED_ACCESS);
address lpGauge = currentAddresses[_LP_GAUGE];
if (lpGauge != address(0)) {
ILpGauge(lpGauge).userCheckpoint(account);
}
if (actionLockedBalances[account] > amount) {
actionLockedBalances[account] = actionLockedBalances[account].uncheckedSub(amount);
} else {
actionLockedBalances[account] = 0;
}
return true;
}
function poolCheckpoint() external override returns (bool) {
if (currentAddresses[_LP_GAUGE] != address(0)) {
return ILpGauge(currentAddresses[_LP_GAUGE]).poolCheckpoint();
}
return false;
}
function getLpGauge() external view override returns (address) {
return currentAddresses[_LP_GAUGE];
}
function isStrategy(address user) external view override returns (bool) {
return strategies[user];
}
/**
* @notice Get the total amount of tokens that are staked by actions
* @return Total amount staked by actions
*/
function getStakedByActions() external view override returns (uint256) {
address[] memory actions = addressProvider.allActions();
uint256 total;
for (uint256 i; i < actions.length; i = i.uncheckedInc()) {
total += balances[actions[i]];
}
return total;
}
function allowance(address owner, address spender) external view override returns (uint256) {
return _allowances[owner][spender];
}
function balanceOf(address account) external view override returns (uint256) {
return balances[account];
}
function getPoolTotalStaked() external view override returns (uint256) {
return _poolTotalStaked;
}
/**
* @notice Returns the total balance in the staker vault, including that locked in positions.
* @param account Account to query balance for.
* @return Total balance in staker vault for account.
*/
function stakedAndActionLockedBalanceOf(address account)
external
view
override
returns (uint256)
{
return balances[account] + actionLockedBalances[account];
}
function actionLockedBalanceOf(address account) external view override returns (uint256) {
return actionLockedBalances[account];
}
function decimals() external view override returns (uint8) {
return IERC20Full(token).decimals();
}
function getToken() external view override returns (address) {
return token;
}
function unstake(uint256 amount) public override returns (bool) {
return unstakeFor(msg.sender, msg.sender, amount);
}
/**
* @notice Stake an amount of vault tokens.
* @param amount Amount of token to stake.
* @return `true` if success.
*/
function stake(uint256 amount) public override returns (bool) {
return stakeFor(msg.sender, amount);
}
/**
* @notice Stake amount of vault token on behalf of another account.
* @param account Account for which tokens will be staked.
* @param amount Amount of token to stake.
* @return `true` if success.
*/
function stakeFor(address account, uint256 amount) public override notPaused returns (bool) {
require(IERC20(token).balanceOf(msg.sender) >= amount, Error.INSUFFICIENT_BALANCE);
address lpGauge = currentAddresses[_LP_GAUGE];
if (lpGauge != address(0)) {
ILpGauge(lpGauge).userCheckpoint(account);
}
uint256 oldBal = IERC20(token).balanceOf(address(this));
if (msg.sender != account) {
ILiquidityPool pool = addressProvider.getPoolForToken(token);
pool.handleLpTokenTransfer(msg.sender, account, amount);
}
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
uint256 staked = IERC20(token).balanceOf(address(this)) - oldBal;
require(staked == amount, Error.INVALID_AMOUNT);
balances[account] += staked;
if (strategies[account]) {
strategiesTotalStaked += staked;
} else {
_poolTotalStaked += staked;
}
emit Staked(account, amount);
return true;
}
/**
* @notice Unstake tokens on behalf of another account.
* @dev Needs to be approved.
* @param src Account for which tokens will be unstaked.
* @param dst Account receiving the tokens.
* @param amount Amount of token to unstake/receive.
* @return `true` if success.
*/
function unstakeFor(
address src,
address dst,
uint256 amount
) public override returns (bool) {
ILiquidityPool pool = addressProvider.getPoolForToken(token);
uint256 allowance_ = _allowances[src][msg.sender];
require(
src == msg.sender || allowance_ >= amount || address(pool) == msg.sender,
Error.UNAUTHORIZED_ACCESS
);
require(balances[src] >= amount, Error.INSUFFICIENT_BALANCE);
address lpGauge = currentAddresses[_LP_GAUGE];
if (lpGauge != address(0)) {
ILpGauge(lpGauge).userCheckpoint(src);
}
uint256 oldBal = IERC20(token).balanceOf(address(this));
if (src != dst) {
pool.handleLpTokenTransfer(src, dst, amount);
}
IERC20(token).safeTransfer(dst, amount);
uint256 unstaked = oldBal.uncheckedSub(IERC20(token).balanceOf(address(this)));
if (src != msg.sender && allowance_ != type(uint256).max && address(pool) != msg.sender) {
// update allowance
_allowances[src][msg.sender] -= unstaked;
}
balances[src] -= unstaked;
if (strategies[src]) {
strategiesTotalStaked -= unstaked;
} else {
_poolTotalStaked -= unstaked;
}
emit Unstaked(src, amount);
return true;
}
function _isAuthorizedToPause(address account) internal view override returns (bool) {
return _roleManager().hasRole(Roles.GOVERNANCE, account);
}
}