-
Notifications
You must be signed in to change notification settings - Fork 14
/
VaultBase.sol
309 lines (254 loc) · 11.1 KB
/
VaultBase.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
// Based on https://github.com/iearn-finance/vaults/blob/master/contracts/vaults/yVault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../interfaces/governance/IBoostHandler.sol";
import "../interfaces/IStrategy.sol";
import "../interfaces/IMinter.sol";
import "../interfaces/IVault.sol";
abstract contract VaultBase is IVault, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
using Address for address;
using SafeMath for uint256;
// Info of each user
struct UserInfo {
uint256 shares; // User shares
uint256 rewardDebt; // Reward debt (in terms of WMATIC)
uint256 lastDepositTime;
uint256 tokensStaked; // Number of tokens staked, only used to calculate profit on the frontend (different than shares)
}
uint256 public constant keepMax = 10000;
/* ========== STATE VARIABLES ========== */
// Info of each user
mapping (address => UserInfo) public userInfo;
// The total amount of pending rewards available for stakers to claim
uint256 public override totalPendingReward;
// Accumulated rewards per share, times 1e12.
uint256 public accRewardPerShare;
// The total # of shares issued
uint256 public override totalShares;
// Withdrawing before this much time has passed will have a withdrawal penalty
uint256 public override withdrawPenaltyTime = 3 days;
// Withdrawal penalty, 100 = 1%
uint256 public override withdrawPenalty = 50;
// For vaults that are farming pools with a deposit fee
uint256 public depositFee = 0;
//Allowed amount of the token sent to the fee dist each vault can mint ADDY rewards for, default 1000 (1000 WMATIC = roughly 0.65 ETH = 312 ADDY)
uint256 public override rewardAllocation = 1e18 * 1000;
// Certain vaults will give up to 10x ADDY rewards
// Additional usecase for ADDY: lock it to boost the yield of a certain vault
uint256 private rewardMultiplier = 1000;
uint256 private constant MULTIPLIER_BASE = 1000;
uint256 private constant MULTIPLIER_MAX = 10000;
uint256 private constant BOOST_BASE = 10000;
IERC20 public override token;
address public override strategy;
IMinter internal minter;
address public ercFund;
address public boostHandler;
constructor(IStrategy _strategy, address _minter, address _ercFund)
public
{
require(address(_strategy) != address(0));
token = IERC20(_strategy.want());
strategy = address(_strategy);
minter = IMinter(_minter);
ercFund = _ercFund;
}
/* ========== VIEW FUNCTIONS ========== */
// 1000 = 1x multiplier
function getRewardMultiplier() public override view returns (uint256) {
return rewardMultiplier;
}
function applyRewardMultiplier(uint256 _amount) internal view returns (uint256) {
return _amount.mul(rewardMultiplier).div(MULTIPLIER_BASE);
}
function getBoost(address _user) public view returns (uint256) {
if (boostHandler != address(0)) {
return IBoostHandler(boostHandler).getBoost(_user, address(this));
}
return 0;
}
//Returns base amount + amount from boost
function applyBoost(address _user, uint256 _amount) internal view returns (uint256) {
if (boostHandler != address(0)) {
return _amount.add(_amount.mul(getBoost(_user)).div(BOOST_BASE));
}
return _amount;
}
function getRatio() public override view returns (uint256) {
return balance().mul(1e18).div(totalShares);
}
function balance() public override view returns (uint256) {
return
token.balanceOf(address(this)).add(
IStrategy(strategy).balanceOf()
);
}
function balanceOf(address _user) public override view returns (uint256) {
return userInfo[_user].shares;
}
function getPendingReward(address _user) public override view returns (uint256) {
return userInfo[_user].shares.mul(accRewardPerShare).div(1e12).sub(userInfo[_user].rewardDebt);
}
function getLastDepositTime(address _user) public override view returns (uint256) {
return userInfo[_user].lastDepositTime;
}
function getTokensStaked(address _user) public override view returns (uint256) {
return userInfo[_user].tokensStaked;
}
/* ========== MUTATIVE FUNCTIONS ========== */
function depositAll() external override {
deposit(token.balanceOf(msg.sender));
}
function deposit(uint256 _amount) public override nonReentrant {
require(msg.sender == tx.origin, "no contracts");
_claimReward(msg.sender);
uint256 _pool = balance();
uint256 _before = token.balanceOf(address(this));
token.safeTransferFrom(msg.sender, address(this), _amount);
uint256 _after = token.balanceOf(address(this));
_amount = _after.sub(_before); // Additional check for deflationary tokens
uint256 shares = 0;
if (totalShares == 0) {
shares = _amount;
} else {
shares = (_amount.mul(totalShares)).div(_pool);
}
//when farming pools with a deposit fee
if(depositFee > 0) {
uint256 fee = shares.mul(depositFee).div(keepMax);
shares = shares.sub(fee);
}
totalShares = totalShares.add(shares);
UserInfo storage user = userInfo[msg.sender];
user.shares = user.shares.add(shares);
user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
user.lastDepositTime = now;
user.tokensStaked = user.tokensStaked.add(_amount);
earn();
emit Deposited(msg.sender, _amount);
}
function earn() internal {
uint256 _bal = token.balanceOf(address(this));
token.safeTransfer(strategy, _bal);
IStrategy(strategy).deposit();
}
// Withdraw all tokens and claim rewards.
function withdrawAll() external override nonReentrant {
UserInfo storage user = userInfo[msg.sender];
uint256 _shares = user.shares;
uint256 r = (balance().mul(_shares)).div(totalShares);
_claimReward(msg.sender);
// Check balance
uint256 b = token.balanceOf(address(this));
if (b < r) {
uint256 _withdraw = r.sub(b);
IStrategy(strategy).withdraw(_withdraw);
uint256 _after = token.balanceOf(address(this));
uint256 _diff = _after.sub(b);
if (_diff < _withdraw) {
r = b.add(_diff);
}
}
totalShares = totalShares.sub(_shares);
user.shares = user.shares.sub(_shares);
user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
user.tokensStaked = 0;
// Deduct early withdrawal fee if applicable
if(user.lastDepositTime.add(withdrawPenaltyTime) >= now) {
uint256 earlyWithdrawalFee = r.mul(withdrawPenalty).div(keepMax);
r = r.sub(earlyWithdrawalFee);
token.safeTransfer(ercFund, earlyWithdrawalFee);
}
token.safeTransfer(msg.sender, r);
emit Withdrawn(msg.sender, r);
}
// Withdraw all tokens without caring about rewards in the event that the reward mechanism breaks.
// Normal early withdrawal penalties will apply.
function emergencyWithdraw() public nonReentrant {
UserInfo storage user = userInfo[msg.sender];
uint256 _shares = user.shares;
uint256 r = (balance().mul(_shares)).div(totalShares);
// Check balance
uint256 b = token.balanceOf(address(this));
if (b < r) {
uint256 _withdraw = r.sub(b);
IStrategy(strategy).withdraw(_withdraw);
uint256 _after = token.balanceOf(address(this));
uint256 _diff = _after.sub(b);
if (_diff < _withdraw) {
r = b.add(_diff);
}
}
if(_shares <= totalShares) {
totalShares = totalShares.sub(_shares);
}
else {
totalShares = 0;
}
user.shares = 0;
user.rewardDebt = 0;
user.tokensStaked = 0;
// Deduct early withdrawal fee if applicable
if(user.lastDepositTime.add(withdrawPenaltyTime) >= now) {
uint256 earlyWithdrawalFee = r.mul(withdrawPenalty).div(keepMax);
r = r.sub(earlyWithdrawalFee);
token.safeTransfer(ercFund, earlyWithdrawalFee);
}
token.safeTransfer(msg.sender, r);
emit Withdrawn(msg.sender, r);
}
function claim() public nonReentrant {
UserInfo storage user = userInfo[msg.sender];
require(user.shares > 0, "no stake");
_claimReward(msg.sender);
user.rewardDebt = user.shares.mul(accRewardPerShare).div(1e12);
}
/* ========== INTERNAL FUNCTIONS ========== */
// Handles claiming the user's pending rewards
function _claimReward(address _user) internal virtual;
/* ========== RESTRICTED FUNCTIONS ========== */
//Vault deployer also needs to register the vault with the new minter
function setMinter(address newMinter) public onlyOwner {
require(newMinter != address(0));
minter = IMinter(newMinter);
}
//Sets a new boost handler
//Set boost handler to the zero address in order to disable it
function setBoostHandler(address _handler) public onlyOwner {
boostHandler = _handler;
}
function setWithdrawPenaltyTime(uint256 _withdrawPenaltyTime) public override onlyOwner {
require(_withdrawPenaltyTime <= 30 days, "delay too high");
withdrawPenaltyTime = _withdrawPenaltyTime;
}
function setWithdrawPenalty(uint256 _withdrawPenalty) public override onlyOwner {
require(_withdrawPenalty <= 50, "penalty too high");
withdrawPenalty = _withdrawPenalty;
}
function setRewardMultiplier(uint256 _rewardMultiplier) public override onlyOwner {
require(_rewardMultiplier <= MULTIPLIER_MAX, "multiplier too high");
rewardMultiplier = _rewardMultiplier;
}
//shouldn't be farming things with a high deposit fee in the first place
function setPoolDepositFee(uint256 _depositFee) public onlyOwner {
require(_depositFee <= 1000, "?");
depositFee = _depositFee;
}
//Increase the amount of the token sent to the fee dist the vault is allowed to mint ADDY for
function increaseRewardAllocation(uint256 _newReward) public override onlyOwner {
rewardAllocation = rewardAllocation.add(_newReward);
emit RewardAllocated(_newReward, rewardAllocation);
}
/* ========== EVENTS ========== */
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardAdded(address reward, uint256 amount);
event Claimed(address indexed user, uint256 amount);
event RewardAllocated(uint256 newReward, uint256 totalAllocation);
}