/
LiquidityMiningCampaign.sol
184 lines (142 loc) · 6.19 KB
/
LiquidityMiningCampaign.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
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
import "openzeppelin-solidity/contracts/math/Math.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/IERC20Detailed.sol";
import "./SafeERC20Detailed.sol";
import "./RewardsPoolBase.sol";
import "./LockScheme.sol";
import "./StakeTransferer.sol";
import "./StakeReceiver.sol";
import "./pool-features/OnlyExitFeature.sol";
contract LiquidityMiningCampaign is StakeTransferer, OnlyExitFeature {
using SafeMath for uint256;
using SafeERC20Detailed for IERC20Detailed;
address rewardToken;
address[] lockSchemes;
mapping(address => uint256) public userAccruedRewads;
event StakedAndLocked(address indexed _userAddress, uint256 _tokenAmount, address _lockScheme);
event ExitedAndUnlocked(address indexed _userAddress);
event BonusTransferred(address indexed _userAddress, uint256 _bonusAmount);
constructor(
IERC20Detailed _stakingToken,
uint256 _startBlock,
uint256 _endBlock,
address[] memory _rewardsTokens,
uint256[] memory _rewardPerBlock,
uint256 _stakeLimit) public RewardsPoolBase(_stakingToken, _startBlock, _endBlock, _rewardsTokens, _rewardPerBlock,_stakeLimit)
{
rewardToken = _rewardsTokens[0];
}
function stakeAndLock( uint256 _tokenAmount, address _lockScheme) external{
_stakeAndLock(msg.sender,_tokenAmount, _lockScheme);
}
/** @dev Stakes LP tokens to the campaing and lockes them to a specific lockScheme contract to earn bonuses
@param _userAddress the address of the staker
@param _tokenAmount the amount to be staked
@param _lockScheme the address of the lock scheme
*/
function _stakeAndLock(address _userAddress ,uint256 _tokenAmount, address _lockScheme) internal {
require(_userAddress != address(0x0), "_stakeAndLock::Invalid staker");
require(_tokenAmount > 0, "stakeAndLock::Cannot stake 0");
UserInfo storage user = userInfo[_userAddress];
uint256 userRewards = 0;
updateRewardMultipliers();
updateUserAccruedReward(_userAddress);
userRewards = user.tokensOwed[0];
for (uint256 i = 0; i < lockSchemes.length; i++) {
uint256 additionalRewards = calculateProportionalRewards(_userAddress, userRewards.sub(userAccruedRewads[_userAddress]), lockSchemes[i]);
LockScheme(lockSchemes[i]).updateUserAccruedRewards(_userAddress, additionalRewards);
}
userAccruedRewads[_userAddress] = userRewards;
_stake(_tokenAmount, _userAddress, true);
LockScheme(_lockScheme).lock(_userAddress, _tokenAmount);
emit StakedAndLocked( _userAddress, _tokenAmount, _lockScheme);
}
function exitAndUnlock() public nonReentrant {
_exitAndUnlock(msg.sender);
}
/** @dev Exits the current campaing and claims the bonuses
@param _userAddress the address of the staker
*/
function _exitAndUnlock(address _userAddress) internal {
UserInfo storage user = userInfo[_userAddress];
if (user.amountStaked == 0) {
return;
}
updateRewardMultipliers();
updateUserAccruedReward(_userAddress);
//todo check how to secure that 0 is the albt
uint256 finalRewards = user.tokensOwed[0].sub(userAccruedRewads[_userAddress]);
for (uint256 i = 0; i < lockSchemes.length; i++) {
uint256 additionalRewards = calculateProportionalRewards(_userAddress, finalRewards, lockSchemes[i]);
if(additionalRewards > 0) {
LockScheme(lockSchemes[i]).updateUserAccruedRewards(_userAddress, additionalRewards);
}
uint256 bonus = LockScheme(lockSchemes[i]).exit(_userAddress);
IERC20Detailed(rewardToken).safeTransfer(_userAddress, bonus);
}
_exit(_userAddress);
userAccruedRewads[_userAddress] = 0;
emit ExitedAndUnlocked(_userAddress);
}
function setReceiverWhitelisted(address receiver, bool whitelisted) override(StakeTransferer) onlyFactory public {
StakeTransferer.setReceiverWhitelisted(receiver, whitelisted);
}
function exitAndStake(address _stakePool) public {
_exitAndStake(msg.sender, _stakePool);
}
/** @dev Exits the current campaing, claims the bonus and stake all rewards to ALBT staking contract
@param _userAddress the address of the staker
@param _stakePool the address of the pool where the tokens will be staked
*/
function _exitAndStake(address _userAddress,address _stakePool) internal onlyWhitelistedReceiver(_stakePool) {
UserInfo storage user = userInfo[_userAddress];
updateRewardMultipliers();
if (user.amountStaked == 0) {
return;
}
updateUserAccruedReward(_userAddress);
//todo check how to secure that 0 is the albt
uint256 finalRewards = user.tokensOwed[0].sub(userAccruedRewads[_userAddress]);
uint256 userBonus;
uint256 amountToStake;
for (uint256 i = 0; i < lockSchemes.length; i++) {
uint256 additionalRewards = calculateProportionalRewards(_userAddress, finalRewards, lockSchemes[i]);
if(additionalRewards > 0) {
LockScheme(lockSchemes[i]).updateUserAccruedRewards(_userAddress, additionalRewards);
}
userBonus = LockScheme(lockSchemes[i]).exit(_userAddress);
amountToStake = amountToStake.add(userBonus);
}
amountToStake = amountToStake.add(user.tokensOwed[0]);
_withdraw(user.amountStaked, _userAddress);
user.tokensOwed[0] = 0;
_claim(_userAddress);
IERC20Detailed(rewardToken).safeApprove(_stakePool, amountToStake);
StakeReceiver(_stakePool).delegateStake(_userAddress, amountToStake);
userAccruedRewads[_userAddress] = 0;
}
/** @dev Function calculating the proportional rewards between all lock schemes where the user has locked tokens
@param _userAddress the address of the staker
@param _accruedRewards all unAccruedRewards that needs to be split
@param _lockScheme the address of the lock scheme
*/
function calculateProportionalRewards(address _userAddress, uint256 _accruedRewards, address _lockScheme) internal view returns (uint256) {
if(totalStaked == 0) {
return 0;
}
uint256 userLockedStake = LockScheme(_lockScheme).getUserLockedStake(_userAddress);
return _accruedRewards.mul(userLockedStake).div(totalStaked);
}
function exit() public override {
_exitAndUnlock(msg.sender);
}
/** @dev Sets all schemes that are part of the current LMC
@param _lockSchemes the address of the staker
*/
function setLockSchemes(address[] memory _lockSchemes) public {
lockSchemes = _lockSchemes;
}
}