Direct loss of funds for users who withdraw their NFTs
_unstakeVested deletes a user's stake (not a vested one) when they unstake their NFT.
When calling stake or stakeVested, these functions always set userData.unredeemedEpoch to currentEpoch.
This is necessary because the next time the user interacts with the contract, they trigger _redeem (through the redeemPendingRewards modifier). If userData.lastClaimedEpoch is set, the system will account for the user's stake, reward it, increase userData.totalStaked by it. Note that this only occurs in the next epoch because ud.unredeemedEpoch < currentEpoch.
If userData[msg.sender].unredeemedEpoch is accidentally set to 0 before _redeem is triggered in the next epoch, userData.totalStaked will not increase, and the user's stake will be esentially be deleted.
This is exactly what happens inside _unstakeVested. The function incorrectly assumes that the NFT is being withdrawn in the same epoch it was staked, causing any normal or vested stakes in this epoch to be deleted.
Example:
A user stakes an NFT at epoch X.
At epoch X+10, the user stakes 1000 tokens using stake.
stake sets userData.unredeemedEpoch = X+10 and deposits[msg.sender][X+10] = 1000.
The user withdraws their NFT, and userData.unredeemedEpoch is set to 0.
In the next epoch, when the user interacts with the contract to trigger _redeem, their totalStaked balance is not increased.
As a result, the user's funds are stuck inside the contract without even generating rewards. The user can call unstake after the lock period, but since totalStaked is still 0, and unstake decreases it, the TX would revert:
The second if condition inside _unstakeVested incorrectly assumes that the user staked during the current epoch and sets his unredeemedEpoch to 0.
Manual review.
Do not reset unredeemedEpoch inside _unstakeVested. If the user has 0 vestedStaked and 0 staked, then _redeem would not perform any action.
Users that try to unstake tokens from an earlier epoch after they staked in the current epoch will have their ` unredeemedEpoch` set to 0, leading to them being unable to access the newly staked tokens. Impact: High – Tokens are lost Likelihood: Medium – It happens every time a user performs the respective sequence of calls. It’s not always but it’s also not a low likelihood scenario. It’s normal usage of the protocol, that doesn’t necessarily require special conditions.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.