sherlock-audit / 2023-01-ajna-judging

1 stars 0 forks source link

Blockian - Permanent freezing of unclaimed yield #151

Closed github-actions[bot] closed 1 year ago

github-actions[bot] commented 1 year ago

Blockian

high

Permanent freezing of unclaimed yield

Summary

A user can accidentally freeze potential rewards from the RewardsManager.sol

Vulnerability Detail

On claimRewards in RewardsManager contract the epochToClaim_ is not limited to the currentBurnEpoch(), thus allowing a user to send epochToClaim_ > currentBurnEpoch, rendering isEpochClaimed[tokenId_][epochToClaim_] true from all epochs from 0 - epochToClaim_ and disallowing the user rewards he may have received in future epochs

epochToClaim_ no limit check

Impact

By accidentally sending the wrong epoch a user may freeze unclaimed yield with no way of getting it back

Code Snippet

    function claimRewards(
        uint256 tokenId_,
        uint256 epochToClaim_ // not limited
    ) external override {
        if (msg.sender != stakes[tokenId_].owner) revert NotOwnerOfDeposit();

        if (isEpochClaimed[tokenId_][epochToClaim_]) revert AlreadyClaimed(); // will revert for epochs the user didn't receive the rewards from yet

        _claimRewards(tokenId_, epochToClaim_);
    }

POC

Add this test to the RewardsManager.t.sol

    function testClaimRewardsFroozeUnclaimedYield() external {
        skip(10);

        uint256[] memory depositIndexes = new uint256[](5);
        depositIndexes[0] = 9;
        depositIndexes[1] = 1;
        depositIndexes[2] = 2;
        depositIndexes[3] = 3;
        depositIndexes[4] = 4;
        MintAndMemorializeParams memory mintMemorializeParams = MintAndMemorializeParams({
            indexes: depositIndexes,
            minter: _minterOne,
            mintAmount: 1000 * 1e18,
            pool: _poolOne
        });

        uint256 tokenIdOne = _mintAndMemorializePositionNFT(mintMemorializeParams);
        _stakeToken(address(_poolOne), _minterOne, tokenIdOne);

        uint256 currentBurnEpoch = _poolOne.currentBurnEpoch();

        changePrank(_minterOne);
        _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + 10);

        for (uint i = 1; i <= 10; i++) {
            vm.expectRevert(IRewardsManagerErrors.AlreadyClaimed.selector);
            _rewardsManager.claimRewards(tokenIdOne, currentBurnEpoch + i); // the user got all 10 next burn epochs rewards frozen as he receives AlreadyClaimed for all of them 
        }
    }

Tool used

Manual Review

Recommendation

There are 2 main ways to fix this issue. Either limit the for loops that depend on epochToClaim_ for example instead of

for (uint256 epoch = lastBurnEpoch; epoch < epochToClaim_; )

use

uint256 curBurnEpoch = IPool(ajnaPool).currentBurnEpoch();
uint256 maxEpoch = epochToClaim_ > curBurnEpoch ? curBurnEpoch : epochToClaim_;
for (uint256 epoch = lastBurnEpoch; epoch < maxEpoch; )

Which is a BAD solution (explanation in detail in the next issue)

Or simply add a require statement when calling claimRewards

    function claimRewards(
        uint256 tokenId_,
        uint256 epochToClaim_
    ) external override {
        if (epochToClaim_ > IPool(stakes[tokenId_].ajnaPool).currentBurnEpoch()) revert EpochNotAvailableYet();

        // rest of the function
    }

Which is a better solution

Duplicate of #122

dmitriia commented 1 year ago

Escalate for 50 USDC Should be Medium as having user mistake of calling claimRewards() with epochToClaim_ > currentBurnEpoch as a prerequisite. It is a dup of #122

sherlock-admin commented 1 year ago

Escalate for 50 USDC Should be Medium as having user mistake of calling claimRewards() with epochToClaim_ > currentBurnEpoch as a prerequisite. It is a dup of #122

You've created a valid escalation for 50 USDC!

To remove the escalation from consideration: Delete your comment. To change the amount you've staked on this escalation: Edit your comment (do not create a new comment).

You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final.

hrishibhat commented 1 year ago

Escalation accepted

Valid dupe of #122

sherlock-admin commented 1 year ago

Escalation accepted

Valid dupe of #122

This issue's escalations have been accepted!

Contestants' payouts and scores will be updated according to the changes made on this issue.