Skip to content

Latest commit

 

History

History
93 lines (63 loc) · 2.88 KB

File metadata and controls

93 lines (63 loc) · 2.88 KB

Fast Khaki Raccoon

Medium

_distributeReward can fail, leaving some rewards stuck

Summary

When we distribute rewards with _distributeReward, we send all of the at once in a single for loop.

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L240-L255

        for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
            address _token = _allRewardsTokens[_i];

            if (REWARDS_WHITELISTER.paused(_token)) {
                continue;
            }

            // shares * rewardsPerShare[token] / 1e27 
            uint256 _amount = getUnpaid(_token, _wallet);
            rewards[_token][_wallet].realized += _amount;

            // shares * rewardsPerShare[token] / 1e27
            rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);

            if (_amount > 0) {
                rewardsDistributed[_token] += _amount;
                IERC20(_token).safeTransfer(_wallet, _amount);
                emit DistributeReward(_wallet, _token, _amount);
            }
        }

This is very dangerous as if one token revers the whole TX would revert, costing the user their whole rewards, even though only one token is faulty. This can most commonly happen if or when users get blacklisted for specific tokens.

Root Cause

Claiming all rewards in one TX.

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L240-L255

        for (uint256 _i; _i < _allRewardsTokens.length; _i++) {
            address _token = _allRewardsTokens[_i];

            if (REWARDS_WHITELISTER.paused(_token)) {
                continue;
            }

            // shares * rewardsPerShare[token] / 1e27 
            uint256 _amount = getUnpaid(_token, _wallet);
            rewards[_token][_wallet].realized += _amount;

            // shares * rewardsPerShare[token] / 1e27
            rewards[_token][_wallet].excluded = _cumulativeRewards(_token, shares[_wallet], true);

            if (_amount > 0) {
                rewardsDistributed[_token] += _amount;
                IERC20(_token).safeTransfer(_wallet, _amount);
                emit DistributeReward(_wallet, _token, _amount);
            }
        }

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

  1. Rewards are 10% USDT and 90% WETH
  2. User gets blacklisted for USDT
  3. He can't claim his rewards, even thought 90% of them are in a token he is not blacklisted for
  4. All of these assets would remain stuck inside the contract

Pausing the reward token would not be sufficient given the fact that it would pause it for al users (not only this one).

Impact

Users can't claim their rewards. Loss of funds.

PoC

No response

Mitigation

Don't send rewards all at once, rather have the user chose for which tokens to claim.