Skip to content

Conversation

@lirona
Copy link
Collaborator

@lirona lirona commented May 5, 2025

Summary by CodeRabbit

  • New Features

    • Added a function to claim all available rewards and unstake in a single transaction, streamlining the process for stakers.
  • Improvements

    • Enhanced security and consistency by ensuring only the rightful staker can claim rewards or unstake.
    • Improved reward calculation by requiring the hypercert to be staked before rewards can be calculated.
  • Bug Fixes

    • Refined error handling when claiming rewards and unstaking.

@lirona lirona linked an issue May 5, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented May 5, 2025

Walkthrough

This update to the Hyperstaker.sol contract introduces a new onlyStaker modifier to centralize and simplify checks ensuring that a hypercert is staked and the caller is the staker. The unstake and claimReward functions now use this modifier and delegate their logic to new internal functions. A new claimAndUnstake function is added, enabling users to claim all available rewards and then unstake in a single transaction. The calculateReward function now requires the hypercert to be staked before calculation. Internal reward and unstake logic has been refactored for clarity and reuse.

Changes

File(s) Change Summary
src/Hyperstaker.sol Added onlyStaker modifier; refactored unstake and claimReward to use modifier and internal logic; added claimAndUnstake function; updated calculateReward to check for staked status; introduced internal _unstake and _claimReward functions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Hyperstaker

    User->>Hyperstaker: claimAndUnstake(hypercertId)
    loop For each round except last
        Hyperstaker->>Hyperstaker: calculateReward(hypercertId, roundId)
        alt reward > 0 and not claimed
            Hyperstaker->>Hyperstaker: _claimReward(hypercertId, roundId, reward)
        end
    end
    Hyperstaker->>Hyperstaker: _unstake(hypercertId)
    Hyperstaker-->>User: Hypercert and rewards transferred
Loading

Possibly related issues

Possibly related PRs

Poem

A modifier hops in, so checks are less dense,
With internal helpers, the logic makes sense.
Now claim and unstake, all in one go—
Rewards in your pocket, your cert you’ll tow!
The contract’s more tidy, its functions more sleek,
Hyperstaker’s new features are truly unique!
🥕


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@lirona lirona merged commit 03027ac into main May 5, 2025
1 of 5 checks passed
@lirona lirona deleted the 63-create-a-claimandunstake-function branch May 5, 2025 17:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/Hyperstaker.sol (1)

174-181: Gas & redundancy concerns in claimAndUnstake loop

  1. Using rounds.length - 1 recalculates the bound on every iteration.
    Cache it once to save gas.

  2. unstake(_hypercertId) triggers the onlyStaker modifier a second time, even
    though the caller was already validated. Calling the internal _unstake
    directly avoids an extra SLOAD/gas and keeps the error surface smaller.

function claimAndUnstake(uint256 _hypercertId) external whenNotPaused onlyStaker(_hypercertId) {
-    for (uint256 i = 0; i < rounds.length - 1; i++) {
+    uint256 lastCompletedRound = rounds.length - 1;
+    for (uint256 i = 0; i < lastCompletedRound; i++) {
         uint256 reward = calculateReward(_hypercertId, i);
         if (reward != 0) {
             _claimReward(_hypercertId, i, reward);
         }
     }
-    unstake(_hypercertId);
+    _unstake(_hypercertId);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cca39a and 9a52b24.

📒 Files selected for processing (1)
  • src/Hyperstaker.sol (6 hunks)
🔇 Additional comments (1)
src/Hyperstaker.sol (1)

239-242: State-before-interaction looks good

Stake data is deleted and the Unstaked event emitted before the external
safeTransferFrom call, reducing re-entrancy risk. ✔️

Comment on lines +190 to 191
require(stakes[_hypercertId].stakingStartTime != 0, NotStaked());
if (isRoundClaimed(_hypercertId, _roundId)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Same compilation issue in calculateReward

The new check mirrors the pattern above and needs the same fix.

-        require(stakes[_hypercertId].stakingStartTime != 0, NotStaked());
+        if (stakes[_hypercertId].stakingStartTime == 0) revert NotStaked();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require(stakes[_hypercertId].stakingStartTime != 0, NotStaked());
if (isRoundClaimed(_hypercertId, _roundId)) {
if (stakes[_hypercertId].stakingStartTime == 0) revert NotStaked();
if (isRoundClaimed(_hypercertId, _roundId)) {

Comment on lines +57 to +62
modifier onlyStaker(uint256 _hypercertId) {
require(stakes[_hypercertId].stakingStartTime != 0, NotStaked());
address staker = stakes[_hypercertId].staker;
require(staker == msg.sender, NotStakerOfHypercert(staker));
_;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

❌ Custom-error syntax breaks compilation

require only accepts a boolean condition and an optional string message.
Passing a custom error identifier as the second argument (require(condition, NotStaked());) is invalid Solidity syntax and will not compile.

modifier onlyStaker(uint256 _hypercertId) {
-    require(stakes[_hypercertId].stakingStartTime != 0, NotStaked());
-    address staker = stakes[_hypercertId].staker;
-    require(staker == msg.sender, NotStakerOfHypercert(staker));
+    if (stakes[_hypercertId].stakingStartTime == 0) revert NotStaked();
+    address staker = stakes[_hypercertId].staker;
+    if (staker != msg.sender) revert NotStakerOfHypercert(staker);
     _;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modifier onlyStaker(uint256 _hypercertId) {
require(stakes[_hypercertId].stakingStartTime != 0, NotStaked());
address staker = stakes[_hypercertId].staker;
require(staker == msg.sender, NotStakerOfHypercert(staker));
_;
}
modifier onlyStaker(uint256 _hypercertId) {
if (stakes[_hypercertId].stakingStartTime == 0) revert NotStaked();
address staker = stakes[_hypercertId].staker;
if (staker != msg.sender) revert NotStakerOfHypercert(staker);
_;
}

Comment on lines +253 to +255
if (rewardToken != address(0)) {
require(IERC20(rewardToken).transfer(msg.sender, _reward), RewardTransferFailed());
} else {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prefer SafeERC20 for wide-token compatibility

Some ERC-20 tokens (e.g. USDT, certain roll-ups) don’t return a boolean or
otherwise deviate from the ERC-20 spec, causing IERC20.transfer(...) to revert
or return misleading values.
Using OpenZeppelin’s SafeERC20 handles these edge-cases and reduces the chance
of silent transfer failures.

-import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";-            require(IERC20(rewardToken).transfer(msg.sender, _reward), RewardTransferFailed());
+            SafeERC20.safeTransfer(IERC20(rewardToken), msg.sender, _reward);

(Remember to add using SafeERC20 for IERC20;.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (rewardToken != address(0)) {
require(IERC20(rewardToken).transfer(msg.sender, _reward), RewardTransferFailed());
} else {
// At the top of src/Hyperstaker.sol, update your imports and add the `using` directive:
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
using SafeERC20 for IERC20;
// … later in your function …
if (rewardToken != address(0)) {
// SafeERC20 will handle non-standard ERC-20s and revert on failure
SafeERC20.safeTransfer(IERC20(rewardToken), msg.sender, _reward);
} else {
// …

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create a claimAndUnstake function

2 participants