Skip to content

Commit d834a4a

Browse files
committed
feat: add StakeVault and implements StakeManager interface into RewardsStreamerMP
This commit adds the following changes: - Add the contract `StakeVault` so funds can be stored safely by the user. #14 - Added the interface `ITrustedCodehashAccess` and contract `TrustedCodehashAccess` in the `src/access` directory, which implements the `ITrustedCodehashAccess` interface and provides functionality to set or update the trust status for a contract's codehash and implemented it on `RewardStreamerMP`. #15 - added the interface `IStakeManager` and implemented it on `RewardStreamerMP` #13 These changes are necessary to enforce security measures and restrict access based on the codehash of the caller, and allow for better reuse of code between StakeManager and RewardStreamerMP.
1 parent 24c3469 commit d834a4a

File tree

5 files changed

+430
-52
lines changed

5 files changed

+430
-52
lines changed

src/IStakeManager.sol

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.18;
3+
4+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import { ITrustedCodehashAccess } from "./access/ITrustedCodehashAccess.sol";
6+
7+
interface IStakeManager is ITrustedCodehashAccess {
8+
error StakeManager__FundsLocked();
9+
error StakeManager__InvalidLockTime();
10+
error StakeManager__InsufficientFunds();
11+
error StakeManager__StakeIsTooLow();
12+
13+
function stake(uint256 _amount, uint256 _seconds) external;
14+
function unstake(uint256 _amount) external;
15+
function lock(uint256 _secondsIncrease) external;
16+
function leave() external returns (bool _leaveAccepted);
17+
function acceptUpdate() external returns (address _migrated);
18+
19+
function potentialMP() external view returns (uint256);
20+
function totalMP() external view returns (uint256);
21+
function totalStaked() external view returns (uint256);
22+
function totalSupply() external view returns (uint256 _totalSupply);
23+
function totalSupplyMinted() external view returns (uint256 _totalSupply);
24+
function pendingReward() external view returns (uint256);
25+
function getStakedBalance(address _vault) external view returns (uint256 _balance);
26+
27+
function STAKE_TOKEN() external view returns (IERC20);
28+
function REWARD_TOKEN() external view returns (IERC20);
29+
function MIN_LOCKUP_PERIOD() external view returns (uint256);
30+
function MAX_LOCKUP_PERIOD() external view returns (uint256);
31+
function MP_APY() external view returns (uint256);
32+
function MAX_BOOST() external view returns (uint256);
33+
34+
function calculateMP(uint256 _balance, uint256 _deltaTime) public pure returns (uint256);
35+
}

src/RewardsStreamerMP.sol

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@ pragma solidity ^0.8.26;
44
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
55
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
66

7+
import { TrustedCodehashAccess } from "./access/TrustedCodehashAccess.sol";
8+
import { IStakeManager } from "./IStakeManager.sol";
9+
710
// Rewards Streamer with Multiplier Points
8-
contract RewardsStreamerMP is ReentrancyGuard {
9-
error StakingManager__AmountCannotBeZero();
10-
error StakingManager__TransferFailed();
11-
error StakingManager__InsufficientBalance();
12-
error StakingManager__InvalidLockingPeriod();
13-
error StakingManager__CannotRestakeWithLockedFunds();
14-
error StakingManager__TokensAreLocked();
15-
16-
IERC20 public immutable STAKING_TOKEN;
11+
contract RewardsStreamerMP is IStakeManager, TrustedCodehashAccess, ReentrancyGuard {
12+
error StakeManager__TransferFailed();
13+
error StakeManager__CannotRestakeWithLockedFunds();
14+
15+
IERC20 public immutable STAKE_TOKEN;
1716
IERC20 public immutable REWARD_TOKEN;
1817

1918
uint256 public constant SCALE_FACTOR = 1e18;
20-
uint256 public constant MP_RATE_PER_YEAR = 1e18;
19+
uint256 public constant MP_APY = 1e18;
2120

22-
uint256 public constant MIN_LOCKING_PERIOD = 90 days;
23-
uint256 public constant MAX_LOCKING_PERIOD = 4 * 365 days;
21+
uint256 public constant MIN_LOCKUP_PERIOD = 90 days;
22+
uint256 public constant MAX_LOCKUP_PERIOD = 4 * 365 days;
2423
uint256 public constant MAX_MULTIPLIER = 4;
2524

2625
uint256 public totalStaked;
@@ -42,50 +41,50 @@ contract RewardsStreamerMP is ReentrancyGuard {
4241
mapping(address account => UserInfo data) public users;
4342

4443
constructor(address _stakingToken, address _rewardToken) {
45-
STAKING_TOKEN = IERC20(_stakingToken);
44+
STAKE_TOKEN = IERC20(_stakingToken);
4645
REWARD_TOKEN = IERC20(_rewardToken);
4746
lastMPUpdatedTime = block.timestamp;
4847
}
4948

50-
function stake(uint256 amount, uint256 lockPeriod) external nonReentrant {
51-
if (amount == 0) {
52-
revert StakingManager__AmountCannotBeZero();
49+
function stake(uint256 _amount, uint256 _seconds) external onlyTrustedCodehash nonReentrant {
50+
if (_amount == 0) {
51+
revert StakeManager__StakeIsTooLow();
5352
}
5453

55-
if (lockPeriod != 0 && (lockPeriod < MIN_LOCKING_PERIOD || lockPeriod > MAX_LOCKING_PERIOD)) {
56-
revert StakingManager__InvalidLockingPeriod();
54+
if (_seconds != 0 && (_seconds < MIN_LOCKUP_PERIOD || _seconds > MAX_LOCKUP_PERIOD)) {
55+
revert StakeManager__InvalidLockTime();
5756
}
5857

5958
_updateGlobalState();
6059
updateUserMP(msg.sender);
6160

6261
UserInfo storage user = users[msg.sender];
6362
if (user.lockUntil != 0 && user.lockUntil > block.timestamp) {
64-
revert StakingManager__CannotRestakeWithLockedFunds();
63+
revert StakeManager__CannotRestakeWithLockedFunds();
6564
}
6665

6766
uint256 userRewards = calculateUserRewards(msg.sender);
6867
if (userRewards > 0) {
6968
distributeRewards(msg.sender, userRewards);
7069
}
7170

72-
bool success = STAKING_TOKEN.transferFrom(msg.sender, address(this), amount);
71+
bool success = STAKE_TOKEN.transferFrom(msg.sender, address(this), _amount);
7372
if (!success) {
74-
revert StakingManager__TransferFailed();
73+
revert StakeManager__TransferFailed();
7574
}
7675

77-
user.stakedBalance += amount;
78-
totalStaked += amount;
76+
user.stakedBalance += _amount;
77+
totalStaked += _amount;
7978

80-
uint256 initialMP = amount;
81-
uint256 userPotentialMP = amount * MAX_MULTIPLIER;
79+
uint256 initialMP = _amount;
80+
uint256 userPotentialMP = _amount * MAX_MULTIPLIER;
8281

83-
if (lockPeriod != 0) {
84-
uint256 lockMultiplier = (lockPeriod * MAX_MULTIPLIER * SCALE_FACTOR) / MAX_LOCKING_PERIOD;
82+
if (_seconds != 0) {
83+
uint256 lockMultiplier = (_seconds * MAX_MULTIPLIER * SCALE_FACTOR) / MAX_LOCKUP_PERIOD;
8584
lockMultiplier = lockMultiplier / SCALE_FACTOR;
86-
initialMP += (amount * lockMultiplier);
87-
userPotentialMP += (amount * lockMultiplier);
88-
user.lockUntil = block.timestamp + lockPeriod;
85+
initialMP += (_amount * lockMultiplier);
86+
userPotentialMP += (_amount * lockMultiplier);
87+
user.lockUntil = block.timestamp + _seconds;
8988
} else {
9089
user.lockUntil = 0;
9190
}
@@ -100,14 +99,14 @@ contract RewardsStreamerMP is ReentrancyGuard {
10099
user.lastMPUpdateTime = block.timestamp;
101100
}
102101

103-
function unstake(uint256 amount) external nonReentrant {
102+
function unstake(uint256 _amount) external onlyTrustedCodehash nonReentrant {
104103
UserInfo storage user = users[msg.sender];
105-
if (amount > user.stakedBalance) {
106-
revert StakingManager__InsufficientBalance();
104+
if (_amount > user.stakedBalance) {
105+
revert StakeManager__InsufficientFunds();
107106
}
108107

109108
if (block.timestamp < user.lockUntil) {
110-
revert StakingManager__TokensAreLocked();
109+
revert StakeManager__FundsLocked();
111110
}
112111

113112
_updateGlobalState();
@@ -119,10 +118,10 @@ contract RewardsStreamerMP is ReentrancyGuard {
119118
}
120119

121120
uint256 previousStakedBalance = user.stakedBalance;
122-
user.stakedBalance -= amount;
123-
totalStaked -= amount;
121+
user.stakedBalance -= _amount;
122+
totalStaked -= _amount;
124123

125-
uint256 amountRatio = (amount * SCALE_FACTOR) / previousStakedBalance;
124+
uint256 amountRatio = (_amount * SCALE_FACTOR) / previousStakedBalance;
126125
uint256 mpToReduce = (user.userMP * amountRatio) / SCALE_FACTOR;
127126
uint256 potentialMPToReduce = (user.userPotentialMP * amountRatio) / SCALE_FACTOR;
128127

@@ -131,14 +130,37 @@ contract RewardsStreamerMP is ReentrancyGuard {
131130
totalMP -= mpToReduce;
132131
potentialMP -= potentialMPToReduce;
133132

134-
bool success = STAKING_TOKEN.transfer(msg.sender, amount);
133+
bool success = STAKE_TOKEN.transfer(msg.sender, _amount);
135134
if (!success) {
136-
revert StakingManager__TransferFailed();
135+
revert StakeManager__TransferFailed();
137136
}
138137

139138
user.userRewardIndex = rewardIndex;
140139
}
141140

141+
function lock(uint256 _secondsIncrease) external onlyTrustedCodehash {
142+
//TODO: increase lock time
143+
revert("Not implemented");
144+
}
145+
146+
function exit() external returns (bool _leaveAccepted) {
147+
if (!isTrustedCodehash(msg.sender.codehash)) {
148+
//case owner removed access from a class of StakeVault,. they might exit
149+
delete user[msg.sender];
150+
return true;
151+
} else {
152+
//TODO: handle other cases
153+
//TODO: handle update/migration case
154+
//TODO: handle emergency exit
155+
revert("Not implemented");
156+
}
157+
}
158+
159+
function acceptUpdate() external onlyTrustedCodehash returns (address _migrated) {
160+
//TODO: handle update/migration
161+
revert("Not implemented");
162+
}
163+
142164
function _updateGlobalState() internal {
143165
updateGlobalMP();
144166
updateRewardIndex();
@@ -160,7 +182,7 @@ contract RewardsStreamerMP is ReentrancyGuard {
160182
return;
161183
}
162184

163-
uint256 accruedMP = (timeDiff * totalStaked * MP_RATE_PER_YEAR) / (365 days * SCALE_FACTOR);
185+
uint256 accruedMP = calculateMP(totalStaked, timeDiff);
164186
if (accruedMP > potentialMP) {
165187
accruedMP = potentialMP;
166188
}
@@ -193,8 +215,8 @@ contract RewardsStreamerMP is ReentrancyGuard {
193215
}
194216
}
195217

196-
function updateUserMP(address userAddress) internal {
197-
UserInfo storage user = users[userAddress];
218+
function updateUserMP(address _vault) internal {
219+
UserInfo storage user = users[_vault];
198220

199221
if (user.userPotentialMP == 0 || user.stakedBalance == 0) {
200222
user.lastMPUpdateTime = block.timestamp;
@@ -206,7 +228,7 @@ contract RewardsStreamerMP is ReentrancyGuard {
206228
return;
207229
}
208230

209-
uint256 accruedMP = (timeDiff * user.stakedBalance * MP_RATE_PER_YEAR) / (365 days * SCALE_FACTOR);
231+
uint256 accruedMP = calculateMP(user.stakedBalance, timeDiff);
210232

211233
if (accruedMP > user.userPotentialMP) {
212234
accruedMP = user.userPotentialMP;
@@ -218,8 +240,8 @@ contract RewardsStreamerMP is ReentrancyGuard {
218240
user.lastMPUpdateTime = block.timestamp;
219241
}
220242

221-
function calculateUserRewards(address userAddress) public view returns (uint256) {
222-
UserInfo storage user = users[userAddress];
243+
function calculateUserRewards(address _vault) public view returns (uint256) {
244+
UserInfo storage user = users[_vault];
223245
uint256 userWeight = user.stakedBalance + user.userMP;
224246
uint256 deltaRewardIndex = rewardIndex - user.userRewardIndex;
225247
return (userWeight * deltaRewardIndex) / SCALE_FACTOR;
@@ -236,19 +258,35 @@ contract RewardsStreamerMP is ReentrancyGuard {
236258

237259
bool success = REWARD_TOKEN.transfer(to, amount);
238260
if (!success) {
239-
revert StakingManager__TransferFailed();
261+
revert StakeManager__TransferFailed();
240262
}
241263
}
242264

243-
function getStakedBalance(address userAddress) external view returns (uint256) {
244-
return users[userAddress].stakedBalance;
265+
function calculateMP(uint256 _balance, uint256 _deltaTime) public pure returns (uint256) {
266+
return (_deltaTime * _balance * MP_APY) / (365 days * SCALE_FACTOR);
267+
}
268+
269+
function getStakedBalance(address _vault) external view returns (uint256 _balance) {
270+
return users[_vault].stakedBalance;
271+
}
272+
273+
function getPendingRewards(address _vault) external view returns (uint256) {
274+
return calculateUserRewards(_vault);
275+
}
276+
277+
function getUserInfo(address _vault) external view returns (UserInfo memory) {
278+
return users[_vault];
279+
}
280+
281+
function totalSupplyMinted() external view returns (uint256 _totalSupply) {
282+
return totalStaked + totalMP;
245283
}
246284

247-
function getPendingRewards(address userAddress) external view returns (uint256) {
248-
return calculateUserRewards(userAddress);
285+
function totalSupply() external view returns (uint256 _totalSupply) {
286+
return totalStaked + totalMP + potentialMP;
249287
}
250288

251-
function getUserInfo(address userAddress) external view returns (UserInfo memory) {
252-
return users[userAddress];
289+
function pendingReward() external view returns (uint256 _pendingReward) {
290+
return STAKE_TOKEN().balanceOf(address(this)) - accountedRewards;
253291
}
254292
}

0 commit comments

Comments
 (0)