Summary
A soulmate who withdraws their staked tokens before claiming rewards will not receive any rewards for the period during which they had their tokens staked even if is passed at least 1 week (minimum staking reward period).
Vulnerability Details
The withdraw function doesn't have any logic that checks if the soulmate has unclaimed rewards.
function withdraw(uint256 amount) public {
userStakes[msg.sender] -= amount;
loveToken.transfer(msg.sender, amount);
emit Withdrew(msg.sender, amount);
}
Impact
First scenario: The soulmate claims the rewards before to withdraw the staked amount.
function testWithdrawAmountBeforeClaim() public {
_mintOneTokenForBothSoulmates();
vm.startPrank(soulmate1);
vm.warp(block.timestamp + 14 days);
airdropContract.claim();
uint256 solmate1Amount = loveToken.balanceOf(soulmate1);
console2.log("soulmate1 loveToken amount after airdrop claim", solmate1Amount);
loveToken.approve(address(stakingContract), solmate1Amount);
stakingContract.deposit(solmate1Amount);
console2.log("soulmate1 loveToken after deposit", loveToken.balanceOf(soulmate1));
vm.warp(block.timestamp + 7 days);
stakingContract.claimRewards();
console2.log("soulmate1 loveToken amount after 7 days staking claim", loveToken.balanceOf(soulmate1));
vm.warp(block.timestamp + 14 days);
stakingContract.claimRewards();
console2.log("soulmate1 loveToken after withdraw", loveToken.balanceOf(soulmate1));
stakingContract.withdraw(solmate1Amount);
console2.log("soulmate1 loveToken after withdraw", loveToken.balanceOf(soulmate1));
vm.stopPrank();
}
[PASS] testWithdrawAmountBeforeClaim() (gas: 372034)
Logs:
soulmate1 loveToken amount after airdrop claim 14000000000000000000
soulmate1 loveToken after deposit 0
soulmate1 loveToken amount after 7 days staking claim 42000000000000000000
soulmate1 loveToken amount after 14 days staking claim 70000000000000000000
soulmate1 loveToken after withdraw 84000000000000000000
The soulmate has a final balance of loveToken of 84000000000000000000.
Second scenario: The soulmate claims the rewards after withdrawing the staked amount.
function testWithdrawAmountBeforeClaim() public {
_mintOneTokenForBothSoulmates();
vm.startPrank(soulmate1);
vm.warp(block.timestamp + 14 days);
airdropContract.claim();
uint256 solmate1Amount = loveToken.balanceOf(soulmate1);
console2.log("soulmate1 loveToken amount after airdrop claim", solmate1Amount);
loveToken.approve(address(stakingContract), solmate1Amount);
stakingContract.deposit(solmate1Amount);
console2.log("soulmate1 loveToken after deposit", loveToken.balanceOf(soulmate1));
vm.warp(block.timestamp + 7 days);
stakingContract.claimRewards();
console2.log("soulmate1 loveToken amount after 7 days staking claim", loveToken.balanceOf(soulmate1));
vm.warp(block.timestamp + 14 days);
stakingContract.withdraw(solmate1Amount);
console2.log("soulmate1 loveToken after withdraw", loveToken.balanceOf(soulmate1));
stakingContract.claimRewards();
console2.log("soulmate1 loveToken amount after 14 days staking claim", loveToken.balanceOf(soulmate1));
vm.stopPrank();
}
[PASS] testWithdrawAmountBeforeClaim() (gas: 372033)
Logs:
soulmate1 loveToken amount after airdrop claim 14000000000000000000
soulmate1 loveToken after deposit 0
soulmate1 loveToken amount after 7 days staking claim 42000000000000000000
soulmate1 loveToken after withdraw 56000000000000000000
soulmate1 loveToken amount after 14 days staking claim 56000000000000000000
The soulmate has a final balance of loveToken of 56000000000000000000. He/she loses 28000000000000000000 loveToken rewards.
Tools Used
Manual review
Recommendations
Implementing a mechanism that either automatically claims rewards before allowing withdrawals or informs users that they must claim rewards before withdrawing.