forked from polyroll/polyroll-farm
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStakingPool.sol
More file actions
272 lines (235 loc) · 11.2 KB
/
Copy pathStakingPool.sol
File metadata and controls
272 lines (235 loc) · 11.2 KB
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
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
/*
* StakingPool is a fair contract that gives tokens at a fixed reward per block to stakers in proportion to amount staked.
* StakingPool is a secure contract where only stakers can withdraw their own stake.
* Credit: Code is forked from @apeswapfinance/apeswap-banana-farm/contracts/BEP20RewardApeV2.sol with ReentrancyGuard added.
*/
import '@pancakeswap/pancake-swap-lib/contracts/math/SafeMath.sol';
import '@pancakeswap/pancake-swap-lib/contracts/token/BEP20/IBEP20.sol';
import '@pancakeswap/pancake-swap-lib/contracts/token/BEP20/SafeBEP20.sol';
import '@pancakeswap/pancake-swap-lib/contracts/access/Ownable.sol';
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.1.0/contracts/utils/ReentrancyGuard.sol";
contract StakingPool is Ownable, ReentrancyGuard {
using SafeMath for uint256;
using SafeBEP20 for IBEP20;
// Info of each user.
struct UserInfo {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
}
// Info of each pool.
struct PoolInfo {
IBEP20 lpToken; // Address of LP token contract.
uint256 allocPoint; // How many allocation points assigned to this pool. Rewards to distribute per block.
uint256 lastRewardBlock; // Last block number that Rewards distribution occurs.
uint256 accRewardTokenPerShare; // Accumulated Rewards per share, times 1e30. See below.
}
// The stake token
IBEP20 public stakeToken;
// The reward token
IBEP20 public rewardToken;
// Reward tokens created per block.
uint256 public rewardPerBlock;
// Keep track of number of tokens staked in case the contract earns reflect fees
uint256 public totalStaked = 0;
// Info of each pool.
PoolInfo[] public poolInfo;
// Info of each user that stakes LP tokens.
mapping (address => UserInfo) public userInfo;
// Total allocation poitns. Must be the sum of all allocation points in all pools.
uint256 private totalAllocPoint = 0;
// The block number when Reward mining starts.
uint256 public startBlock;
// The block number when mining ends.
uint256 public bonusEndBlock;
event Deposit(address indexed user, uint256 amount);
event DepositRewards(uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event EmergencyWithdraw(address indexed user, uint256 amount);
event EmergencyRewardWithdraw(address indexed user, uint256 amount);
event SkimStakeTokenFees(address indexed user, uint256 amount);
constructor(
IBEP20 _stakeToken,
IBEP20 _rewardToken,
uint256 _rewardPerBlock,
uint256 _startBlock,
uint256 _bonusEndBlock
) public {
stakeToken = _stakeToken;
rewardToken = _rewardToken;
rewardPerBlock = _rewardPerBlock;
startBlock = _startBlock;
bonusEndBlock = _bonusEndBlock;
// staking pool
poolInfo.push(PoolInfo({
lpToken: _stakeToken,
allocPoint: 1000,
lastRewardBlock: startBlock,
accRewardTokenPerShare: 0
}));
totalAllocPoint = 1000;
}
// Return reward multiplier over the given _from to _to block.
function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
if (_to <= bonusEndBlock) {
return _to.sub(_from);
} else if (_from >= bonusEndBlock) {
return 0;
} else {
return bonusEndBlock.sub(_from);
}
}
// View function to see pending Reward on frontend.
function pendingReward(address _user) external view returns (uint256) {
PoolInfo storage pool = poolInfo[0];
UserInfo storage user = userInfo[_user];
uint256 accRewardTokenPerShare = pool.accRewardTokenPerShare;
if (block.number > pool.lastRewardBlock && totalStaked != 0) {
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 tokenReward = multiplier.mul(rewardPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
accRewardTokenPerShare = accRewardTokenPerShare.add(tokenReward.mul(1e30).div(totalStaked));
}
return user.amount.mul(accRewardTokenPerShare).div(1e30).sub(user.rewardDebt);
}
// Update reward variables of the given pool to be up-to-date.
function updatePool(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
if (block.number <= pool.lastRewardBlock) {
return;
}
if (totalStaked == 0) {
pool.lastRewardBlock = block.number;
return;
}
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 tokenReward = multiplier.mul(rewardPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
pool.accRewardTokenPerShare = pool.accRewardTokenPerShare.add(tokenReward.mul(1e30).div(totalStaked));
pool.lastRewardBlock = block.number;
}
// Update reward variables for all pools. Be careful of gas spending!
function massUpdatePools() public {
uint256 length = poolInfo.length;
for (uint256 pid = 0; pid < length; ++pid) {
updatePool(pid);
}
}
/// Deposit staking token into the contract to earn rewards.
/// @dev Since this contract needs to be supplied with rewards we are
/// sending the balance of the contract if the pending rewards are higher
/// @param _amount The amount of staking tokens to deposit
function deposit(uint256 _amount) public nonReentrant {
PoolInfo storage pool = poolInfo[0];
UserInfo storage user = userInfo[msg.sender];
uint256 finalDepositAmount = 0;
updatePool(0);
if (user.amount > 0) {
uint256 pending = user.amount.mul(pool.accRewardTokenPerShare).div(1e30).sub(user.rewardDebt);
if(pending > 0) {
uint256 currentRewardBalance = rewardBalance();
if(currentRewardBalance > 0) {
if(pending > currentRewardBalance) {
safeTransferReward(address(msg.sender), currentRewardBalance);
} else {
safeTransferReward(address(msg.sender), pending);
}
}
}
}
if (_amount > 0) {
uint256 preStakeBalance = totalStakeTokenBalance();
pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
finalDepositAmount = totalStakeTokenBalance().sub(preStakeBalance);
user.amount = user.amount.add(finalDepositAmount);
totalStaked = totalStaked.add(finalDepositAmount);
}
user.rewardDebt = user.amount.mul(pool.accRewardTokenPerShare).div(1e30);
emit Deposit(msg.sender, finalDepositAmount);
}
/// Withdraw rewards and/or staked tokens. Pass a 0 amount to withdraw only rewards
/// @param _amount The amount of staking tokens to withdraw
function withdraw(uint256 _amount) public nonReentrant {
PoolInfo storage pool = poolInfo[0];
UserInfo storage user = userInfo[msg.sender];
require(user.amount >= _amount, "withdraw: exceed stake");
updatePool(0);
uint256 pending = user.amount.mul(pool.accRewardTokenPerShare).div(1e30).sub(user.rewardDebt);
if(pending > 0) {
uint256 currentRewardBalance = rewardBalance();
if(currentRewardBalance > 0) {
if(pending > currentRewardBalance) {
safeTransferReward(address(msg.sender), currentRewardBalance);
} else {
safeTransferReward(address(msg.sender), pending);
}
}
}
if(_amount > 0) {
user.amount = user.amount.sub(_amount);
pool.lpToken.safeTransfer(address(msg.sender), _amount);
totalStaked = totalStaked.sub(_amount);
}
user.rewardDebt = user.amount.mul(pool.accRewardTokenPerShare).div(1e30);
emit Withdraw(msg.sender, _amount);
}
/// Obtain the reward balance of this contract
/// @return wei balance of conract
function rewardBalance() public view returns (uint256) {
return rewardToken.balanceOf(address(this));
}
// Deposit Rewards into contract
function depositRewards(uint256 _amount) external {
require(_amount > 0, 'Deposit value must be greater than 0.');
rewardToken.safeTransferFrom(address(msg.sender), address(this), _amount);
emit DepositRewards(_amount);
}
/// @param _to address to send reward token to
/// @param _amount value of reward token to transfer
function safeTransferReward(address _to, uint256 _amount) internal {
rewardToken.safeTransfer(_to, _amount);
}
/* Admin Functions */
/// @param _rewardPerBlock The amount of reward tokens to be given per block
function setRewardPerBlock(uint256 _rewardPerBlock) external onlyOwner {
rewardPerBlock = _rewardPerBlock;
}
/// @param _bonusEndBlock The block when rewards will end
function setBonusEndBlock(uint256 _bonusEndBlock) external onlyOwner {
require(_bonusEndBlock > bonusEndBlock, 'new bonus end block must be greater than current');
bonusEndBlock = _bonusEndBlock;
}
/// @dev Obtain the stake token fees (if any) earned by reflect token
function getStakeTokenFeeBalance() public view returns (uint256) {
return totalStakeTokenBalance().sub(totalStaked);
}
/// @dev Obtain the stake balance of this contract
/// @return wei balace of contract
function totalStakeTokenBalance() public view returns (uint256) {
// Return BEO20 balance
return stakeToken.balanceOf(address(this));
}
/// @dev Remove excess stake tokens earned by reflect fees
function skimStakeTokenFees() external onlyOwner {
uint256 stakeTokenFeeBalance = getStakeTokenFeeBalance();
stakeToken.safeTransfer(msg.sender, stakeTokenFeeBalance);
emit SkimStakeTokenFees(msg.sender, stakeTokenFeeBalance);
}
/* Emergency Functions */
// Withdraw without caring about rewards. EMERGENCY ONLY.
function emergencyWithdraw() external nonReentrant {
PoolInfo storage pool = poolInfo[0];
UserInfo storage user = userInfo[msg.sender];
pool.lpToken.safeTransfer(address(msg.sender), user.amount);
totalStaked = totalStaked.sub(user.amount);
user.amount = 0;
user.rewardDebt = 0;
emit EmergencyWithdraw(msg.sender, user.amount);
}
// Withdraw reward. EMERGENCY ONLY.
function emergencyRewardWithdraw(uint256 _amount) external onlyOwner nonReentrant {
require(_amount <= rewardBalance(), 'not enough rewards');
// Withdraw rewards
safeTransferReward(address(msg.sender), _amount);
emit EmergencyRewardWithdraw(msg.sender, _amount);
}
}