From ea7cdfe0ca7a1a3930e82ae78fb9a6c3e5709784 Mon Sep 17 00:00:00 2001 From: Liron Achdut Date: Mon, 5 May 2025 19:08:34 +0300 Subject: [PATCH 1/2] feat: add claimAndUnstake function --- src/Hyperstaker.sol | 55 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/Hyperstaker.sol b/src/Hyperstaker.sol index 017d3e2..8ab014d 100644 --- a/src/Hyperstaker.sol +++ b/src/Hyperstaker.sol @@ -143,15 +143,13 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra } /// @notice Unstake a Hypercert, this will transfer the Hypercert from the contract to the user and delete all - ///stake information + /// stake information /// @param _hypercertId id of the Hypercert to unstake - function unstake(uint256 _hypercertId) external whenNotPaused { + function unstake(uint256 _hypercertId) public whenNotPaused { require(stakes[_hypercertId].stakingStartTime != 0, NotStaked()); address staker = stakes[_hypercertId].staker; require(staker == msg.sender, NotStakerOfHypercert(staker)); - delete stakes[_hypercertId]; - emit Unstaked(_hypercertId); - hypercertMinter.safeTransferFrom(address(this), msg.sender, _hypercertId, 1, ""); + _unstake(_hypercertId); } /// @notice Claim a reward eligable by a staked Hypercert for a given round @@ -165,16 +163,21 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra uint256 reward = _calculateReward(_hypercertId, _roundId); require(reward != 0, NoRewardAvailable()); - _setRoundClaimed(_hypercertId, _roundId); - emit RewardClaimed(_hypercertId, reward); + _claimReward(_hypercertId, _roundId, reward); + } - address rewardToken = rounds[_roundId].rewardToken; - if (rewardToken != address(0)) { - require(IERC20(rewardToken).transfer(msg.sender, reward), RewardTransferFailed()); - } else { - (bool success,) = payable(msg.sender).call{value: reward}(""); - require(success, NativeTokenTransferFailed()); + /// @notice Claim all rewards available for a staked Hypercert and unstake it + /// @param _hypercertId id of the Hypercert to claim all rewards and unstake + function claimAndUnstake(uint256 _hypercertId) external whenNotPaused { + address staker = stakes[_hypercertId].staker; + require(staker == msg.sender, NotStakerOfHypercert(staker)); + for (uint256 i = 0; i < rounds.length - 1; i++) { + uint256 reward = calculateReward(_hypercertId, i); + if (reward != 0) { + _claimReward(_hypercertId, i, reward); + } } + unstake(_hypercertId); } // VIEW FUNCTIONS @@ -229,6 +232,32 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra round.totalRewards * hypercertMinter.unitsOf(_hypercertId) * stakeDuration / (totalUnits * round.duration); } + /// @notice Unstake a Hypercert, this will transfer the Hypercert from the contract to the user and delete all + /// stake information + /// @param _hypercertId id of the Hypercert to unstake + function _unstake(uint256 _hypercertId) internal { + delete stakes[_hypercertId]; + emit Unstaked(_hypercertId); + hypercertMinter.safeTransferFrom(address(this), msg.sender, _hypercertId, 1, ""); + } + + /// @notice Set a round as claimed for a staked Hypercert and transfer the reward to the user + /// @param _hypercertId id of the Hypercert to claim the reward for + /// @param _roundId id of the round to claim the reward for + /// @param _reward amount of the reward to claim + function _claimReward(uint256 _hypercertId, uint256 _roundId, uint256 _reward) internal { + _setRoundClaimed(_hypercertId, _roundId); + emit RewardClaimed(_hypercertId, _reward); + + address rewardToken = rounds[_roundId].rewardToken; + if (rewardToken != address(0)) { + require(IERC20(rewardToken).transfer(msg.sender, _reward), RewardTransferFailed()); + } else { + (bool success,) = payable(msg.sender).call{value: _reward}(""); + require(success, NativeTokenTransferFailed()); + } + } + /// @notice Get the hypercert type id for a given hypercert id /// @param _hypercertId id of the Hypercert to get the type id for /// @return hypercert type id for the given hypercert id From 9a52b24b32a70746abb5f85738bafd0cd69d59bd Mon Sep 17 00:00:00 2001 From: Liron Achdut Date: Mon, 5 May 2025 20:08:44 +0300 Subject: [PATCH 2/2] feat: add onlyStaker modifier --- src/Hyperstaker.sol | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Hyperstaker.sol b/src/Hyperstaker.sol index 8ab014d..02024ff 100644 --- a/src/Hyperstaker.sol +++ b/src/Hyperstaker.sol @@ -54,6 +54,13 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra event RewardClaimed(uint256 indexed hypercertId, uint256 reward); event RewardSet(address indexed token, uint256 amount); + modifier onlyStaker(uint256 _hypercertId) { + require(stakes[_hypercertId].stakingStartTime != 0, NotStaked()); + address staker = stakes[_hypercertId].staker; + require(staker == msg.sender, NotStakerOfHypercert(staker)); + _; + } + constructor() { _disableInitializers(); } @@ -96,7 +103,8 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra // ADMIN FUNCTIONS - /// @notice Set the reward for the current round, this ends the current round and starts a new one. Only callable by a manager + /// @notice Set the reward for the current round, this ends the current round and starts a new one. Only callable + /// by a manager /// @param _rewardToken address of the reward token /// @param _rewardAmount amount of the reward for the current round function setReward(address _rewardToken, uint256 _rewardAmount) external payable onlyRole(MANAGER_ROLE) { @@ -129,7 +137,7 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra // USER FUNCTIONS - /// @notice Stake a Hypercert, this will transfer the Hypercert from the user to the contract + /// @notice Stake a Hypercert, this will transfer the Hypercert from the owner to the contract /// @param _hypercertId id of the Hypercert to stake function stake(uint256 _hypercertId) external whenNotPaused { require(hypercertMinter.unitsOf(_hypercertId) != 0, NoUnitsInHypercert()); @@ -145,20 +153,14 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra /// @notice Unstake a Hypercert, this will transfer the Hypercert from the contract to the user and delete all /// stake information /// @param _hypercertId id of the Hypercert to unstake - function unstake(uint256 _hypercertId) public whenNotPaused { - require(stakes[_hypercertId].stakingStartTime != 0, NotStaked()); - address staker = stakes[_hypercertId].staker; - require(staker == msg.sender, NotStakerOfHypercert(staker)); + function unstake(uint256 _hypercertId) public whenNotPaused onlyStaker(_hypercertId) { _unstake(_hypercertId); } /// @notice Claim a reward eligable by a staked Hypercert for a given round /// @param _hypercertId id of the Hypercert to claim the reward for /// @param _roundId id of the round to claim the reward for - function claimReward(uint256 _hypercertId, uint256 _roundId) external whenNotPaused { - require(stakes[_hypercertId].stakingStartTime != 0, NotStaked()); - address staker = stakes[_hypercertId].staker; - require(staker == msg.sender, NotStakerOfHypercert(staker)); + function claimReward(uint256 _hypercertId, uint256 _roundId) external whenNotPaused onlyStaker(_hypercertId) { require(!isRoundClaimed(_hypercertId, _roundId), AlreadyClaimed()); uint256 reward = _calculateReward(_hypercertId, _roundId); require(reward != 0, NoRewardAvailable()); @@ -168,9 +170,7 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra /// @notice Claim all rewards available for a staked Hypercert and unstake it /// @param _hypercertId id of the Hypercert to claim all rewards and unstake - function claimAndUnstake(uint256 _hypercertId) external whenNotPaused { - address staker = stakes[_hypercertId].staker; - require(staker == msg.sender, NotStakerOfHypercert(staker)); + function claimAndUnstake(uint256 _hypercertId) external whenNotPaused onlyStaker(_hypercertId) { for (uint256 i = 0; i < rounds.length - 1; i++) { uint256 reward = calculateReward(_hypercertId, i); if (reward != 0) { @@ -187,6 +187,7 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra /// @param _roundId id of the round to calculate the reward for /// @return amount of the reward eligable for the staked Hypercert for the given round function calculateReward(uint256 _hypercertId, uint256 _roundId) public view returns (uint256) { + require(stakes[_hypercertId].stakingStartTime != 0, NotStaked()); if (isRoundClaimed(_hypercertId, _roundId)) { return 0; } @@ -225,7 +226,6 @@ contract Hyperstaker is AccessControlUpgradeable, PausableUpgradeable, UUPSUpgra Round memory round = rounds[_roundId]; require(round.endTime != 0, RoundNotSet()); uint256 stakeStartTime = stakes[_hypercertId].stakingStartTime; - require(stakeStartTime != 0, NotStaked()); stakeStartTime = stakeStartTime < round.startTime ? round.startTime : stakeStartTime; uint256 stakeDuration = stakeStartTime > round.endTime ? 0 : round.endTime - stakeStartTime; return