Skip to content

Mammoth Ocean Donkey - Users will loose rewards if they increase their stake before claiming reward in the UsualSP contract #151

@sherlock-admin2

Description

@sherlock-admin2

Mammoth Ocean Donkey

High

Users will loose rewards if they increase their stake before claiming reward in the UsualSP contract

Summary

UsualS staker reward index is updated whenever they interact with staking and unstaking

File: pegasus/packages/solidity/src/token/UsualSP.sol
282:     function stake(uint256 amount) public nonReentrant whenNotPaused {
//////
286: 
287: @>      _updateReward(msg.sender);

https://github.com/sherlock-audit/2025-02-usual-labs/blob/main/pegasus/packages/solidity/src/token/UsualSP.sol#L158

This is done in the _updateReward() function

Root Cause

The problem is that for every time the _updateReward() is called on the msg.sender the

  • rewardPerTokenStored is updated
  • lastRewardPerTokenUsed of the staker is updated to the current rewardPerTokenStored
  • rewards earned are overwritten
    function _updateReward(address account) internal virtual {
        RewardAccrualBaseStorageV0 storage $ = _getRewardAccrualBaseDataStorage();
        if (block.timestamp > $.lastUpdateTime) {
  @>        $.rewardPerTokenStored = _rewardPerToken();
            $.lastUpdateTime = block.timestamp;
        }
@>      $.rewards[account] = _earned(account);
        $.lastRewardPerTokenUsed[account] = $.rewardPerTokenStored;
    }

Problem arises when the user increases their stake and _updateReward() is called, the _earned() function will calculate the rewards based on the differenc between the rewardPerTokenStored and the last lastRewardPerTokenUsed and then overwrite the rewards[account] previously updated

Internal Pre-conditions

NIL

External Pre-conditions

NIL

Attack Path

See root cause

Impact

Loss of rewards

PoC

No response

Mitigation

Modify as shown below

    function _updateReward(address account) internal virtual {
        RewardAccrualBaseStorageV0 storage $ = _getRewardAccrualBaseDataStorage();
        if (block.timestamp > $.lastUpdateTime) {
  @>        $.rewardPerTokenStored = _rewardPerToken();
            $.lastUpdateTime = block.timestamp;
        }
-       $.rewards[account] = _earned(account);
+       $.rewards[account] += _earned(account);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions