Summary
The Staking::claimRewards function icalculates rewards based on the number of weeks since the last claim. However, when a soulmate deposits multiple times within the weeks, the rewards are calculated incorrectly because the deposit timestamp is not tracked.
Vulnerability Details
function claimRewards() public {
uint256 soulmateId = soulmateContract.ownerToId(msg.sender);
if (lastClaim[msg.sender] == 0) {
lastClaim[msg.sender] = soulmateContract.idToCreationTimestamp(
soulmateId
);
}
uint256 timeInWeeksSinceLastClaim = ((block.timestamp -
lastClaim[msg.sender]) / 1 weeks);
if (timeInWeeksSinceLastClaim < 1)
revert Staking__StakingPeriodTooShort();
lastClaim[msg.sender] = block.timestamp;
@> uint256 amountToClaim = userStakes[msg.sender] *
timeInWeeksSinceLastClaim;
loveToken.transferFrom(
address(stakingVault),
msg.sender,
amountToClaim
);
emit RewardsClaimed(msg.sender, amountToClaim);
}
Impact
Run the first test:
function test_WrongClaimRewardsOnMultipleDeposit() public {
uint256 balancePerSoulmates = 5e18;
uint256 weekOfStaking = 5;
_depositTokenToStake(balancePerSoulmates);
console2.log("loveToken balance of soulmate1", loveToken.balanceOf(soulmate1));
vm.expectRevert();
stakingContract.claimRewards();
vm.warp(block.timestamp + weekOfStaking * 1 weeks + 1 seconds);
vm.startPrank(soulmate1);
stakingContract.claimRewards();
assertTrue(loveToken.balanceOf(soulmate1) == weekOfStaking * balancePerSoulmates);
console2.log("loveToken balance of soulmate1", loveToken.balanceOf(soulmate1));
vm.warp(block.timestamp + weekOfStaking * 1 weeks + 1 days);
stakingContract.claimRewards();
console2.log("loveToken balance of soulmate1", loveToken.balanceOf(soulmate1));
}
[PASS] test_WrongClaimRewardsOnMultipleDeposit() (gas: 505355)
Logs:
loveToken balance of soulmate1 0
loveToken balance of soulmate1 25000000000000000000
loveToken balance of soulmate1 50000000000000000000
The soulmate1 rewards after 5 weeks for 5 deposited loveToken is 25e18 and after other 5 weeks + 1 day is again 25e18. Rewards Total amount = 50e18.
Run the second test, where the solmates1 deposit other 5 token after the first claim.
function test_WrongClaimRewardsOnMultipleDeposit() public {
uint256 balancePerSoulmates = 5e18;
uint256 weekOfStaking = 5;
_depositTokenToStake(balancePerSoulmates);
console2.log("loveToken balance of soulmate1", loveToken.balanceOf(soulmate1));
vm.expectRevert();
stakingContract.claimRewards();
vm.warp(block.timestamp + weekOfStaking * 1 weeks + 1 seconds);
vm.startPrank(soulmate1);
stakingContract.claimRewards();
assertTrue(loveToken.balanceOf(soulmate1) == weekOfStaking * balancePerSoulmates);
console2.log("loveToken balance of soulmate1", loveToken.balanceOf(soulmate1));
vm.warp(block.timestamp + weekOfStaking * 1 weeks + 1 days);
loveToken.approve(address(stakingContract), 5e18);
stakingContract.deposit(5e18);
stakingContract.claimRewards();
console2.log("loveToken balance of soulmate1", loveToken.balanceOf(soulmate1));
}
[PASS] test_WrongClaimRewardsOnMultipleDeposit() (gas: 516234)
Logs:
loveToken balance of soulmate1 0
loveToken balance of soulmate1 25000000000000000000
loveToken balance of soulmate1 70000000000000000000
The soulmate1 rewards after 5 weeks for 5 deposited loveToken is 25e18 and after other 5 weeks + 1 day now is 50e18. This is wrong because the second claim is done subsquently to the deposit. No time is passed so the second deposit amount hasn't accrued rewards. He/she should have received only 25e18 also on the second claim.
Tools Used
Manual review
Recommendations
The rewards calculation logic should be adjusted considering also if a new deposit has occurred and on what timestamp.