Skip to content
This repository has been archived by the owner on Mar 3, 2024. It is now read-only.

berndartmueller - Users are unable to withdraw from LMPVault if the accumulated TOKE rewards are below the MIN_STAKE_AMOUNT threshold #642

Closed
sherlock-admin opened this issue Aug 30, 2023 · 0 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue

Comments

@sherlock-admin
Copy link
Contributor

sherlock-admin commented Aug 30, 2023

berndartmueller

high

Users are unable to withdraw from LMPVault if the accumulated TOKE rewards are below the MIN_STAKE_AMOUNT threshold

Summary

Staking TOKE rewards in the GPToke staking contract potentially reverts if the token rewards for a given user are below the MIN_STAKE_AMOUNT threshold. This prevents affected users from withdrawing funds from the LMPVault vault.

Vulnerability Detail

Whenever LMPVault shares are transferred (e.g., burned when withdrawing), the LMPVault.beforeTokenTransfer function is called, which internally withdraws and unstakes from the associated rewarder contract in line 844:

834: function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override whenNotPaused {
835:     // Nothing to do really do here
836:     if (from == to) {
837:         return;
838:     }
839:
840:     // If this isn't a mint of new tokens, then they are being transferred
841:     // from someone who is "staked" in the rewarder. Make sure they stop earning
842:     // When they transfer those funds
843:     if (from != address(0)) {
844:         rewarder.withdraw(from, amount, true);
845:     }
846:
847:     // Make sure the destination wallet total share balance doesn't go above the
848:     // current perWalletLimit
849:     if (balanceOf(to) + amount > perWalletLimit) {
850:         revert OverWalletLimit(to);
851:     }
852: }

The MainRewarder.withdraw function calls the AbstractRewarder._getReward function and attempts to claim the accumulated rewards for the user. In the case of the LMPVault, the rewardToken is most probably the TOKE token, thus the accumulated TOKE rewards will be intended to be staked in the GPToke contract by calling the GPToke.stake function in line 375.

However, the GPToke.stake function, specifically, the _stake function enforces a minimum stake amount of MIN_STAKE_AMOUNT and reverts if the amount is below this threshold in line 103:

src/staking/GPToke.sol#L103

098: function _stake(uint256 amount, uint256 duration, address to) internal whenNotPaused {
099:     //
100:     // validation checks
101:     //
102:     if (to == address(0)) revert ZeroAddress();
103:     if (amount < MIN_STAKE_AMOUNT) revert StakingAmountInsufficient();
104:     if (amount > MAX_STAKE_AMOUNT) revert StakingAmountExceeded();
...      // [...]
126: }

Affected users are the ones that have a small position in the LMPVault vault and thus have little accumulated rewards, or users wanting to withdraw from the LMPVault vault before having sufficient accumulated TOKE reward. In either case, the withdrawal attempt will revert, leaving the user unable to withdraw funds. The user would have to wait until the accumulated rewards are sufficient to meet the MIN_STAKE_AMOUNT threshold to be able to finally withdraw.

Impact

Users with either little or no TOKE rewards accumulated so far are unable to withdraw from the LMPVault vault.

Code Snippet

src/rewarders/AbstractRewarder.sol#L375

354: function _getReward(address account) internal {
355:     Errors.verifyNotZero(account, "account");
356:
357:     uint256 reward = earned(account);
358:     (IGPToke gpToke, address tokeAddress) = (systemRegistry.gpToke(), address(systemRegistry.toke()));
359:
360:     // slither-disable-next-line incorrect-equality
361:     if (reward == 0) return;
362:
363:     rewards[account] = 0;
364:     emit RewardPaid(account, reward);
365:
366:     // if NOT toke, or staking is turned off (by duration = 0), just send reward back
367:     if (rewardToken != tokeAddress || tokeLockDuration == 0) {
368:         IERC20(rewardToken).safeTransfer(account, reward);
369:     } else {
370:         // authorize gpToke to get our reward Toke
371:         // slither-disable-next-line unused-return
372:         IERC20(address(tokeAddress)).approve(address(gpToke), reward);
373:
374:         // stake Toke
375:         gpToke.stake(reward, tokeLockDuration, account);
376:     }
377: }

Tool used

Manual Review

Recommendation

Consider checking in the AbstractRewarder._getReward function if the accumulated amount of TOKE rewards is sufficient to meet the MIN_STAKE_AMOUNT threshold before attempting to stake the rewards in the GPToke contract. If the amount is lower, the rewards should be transferred to the user's address instead of staking them in the GPToke contract.

Duplicate of #565

@github-actions github-actions bot added Medium A valid Medium severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels Sep 11, 2023
@sherlock-admin2 sherlock-admin2 changed the title Nice Maroon Frog - Users are unable to withdraw from LMPVault if the accumulated TOKE rewards are below the MIN_STAKE_AMOUNT threshold berndartmueller - Users are unable to withdraw from LMPVault if the accumulated TOKE rewards are below the MIN_STAKE_AMOUNT threshold Oct 3, 2023
@sherlock-admin2 sherlock-admin2 added the Reward A payout will be made for this issue label Oct 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Medium A valid Medium severity issue Reward A payout will be made for this issue
Projects
None yet
Development

No branches or pull requests

2 participants