Closed c4-bot-5 closed 6 months ago
Picodes marked the issue as primary issue
othernet-global (sponsor) disputed
Regarding virtualRewards: the idea is that the ratio of totalRewards/totalShare before the user increases share needs to equal (totalRewards+virtualRewards)/(totalShare+shareIncrease). Namely that the ratio of rewards to shares before and after needs to remain the same. It's akin to a liquidity pool where the two tokens are "rewards" and "shares". When a user want add shares they borrow the rewards needed to create the correct proportion of virtualRewards / addedShares.
Unless I am missing something the code seems correct. These rewards are virtual and are an accountability trick to compute the actual rewards accumulated by the staker later on.
Picodes marked the issue as unsatisfactory: Insufficient proof
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L78-L85
Vulnerability details
Issue Description
The
totalRewards
represent the total pending SALT rewards for each poolID. When theStakingRewards.addSALTRewards
function is called during the global upkeep call, it updates thetotalRewards
value for each pool according to the SALT reward received.The issue is that the
totalRewards
is incorrectly increased in theStakingRewards._increaseUserShare
function when a user tries to increase their shares:This is incorrect because virtual rewards
virtualRewardsToAdd
are only used to deduct a portion of the user rewards that they are not allowed to claim as they increase their shares at the giventotalRewards/totalShares
ratio.By increasing the
totalRewards
value byvirtualRewardsToAdd
, the function is adding virtual rewards that don't exist in the contract yet in the form of SALT tokens. This will make the reward calculation incorrect and compromise the whole shares/rewards system.Users who stake afterward will be promised rewards that don't exist and may not be able to claim them later due to insufficient SALT funds in the contract.
Impact
The incorrect update of
totalRewards
in theStakingRewards._increaseUserShare
function will lead to bad shares/rewards accounting and result in some users being unable to claim rewards due to insufficient SALT funds in the contract.Proof of Concept
Let's take a simple scenario of a user Bob who will deposit 1000 shares in the pool
poolID == 10
.Bob shares:
user.userShare = 0
Bob virtual rewards:
user.virtualRewards = 0
Pool total shares:
totalShares[poolID] = 8000
Pool total rewards:
totalRewards[poolID] = 10000
When Bob calls the
depositLiquidityAndIncreaseShare
function, it will internally invokeStakingRewards._increaseUserShare
function to add the 1000 shares.After
StakingRewards._increaseUserShare
is called, we get to these states using the formula above:Bob shares:
user.userShare = 1000
Bob virtual rewards:
user.virtualRewards = 1250
Pool total shares:
totalShares[poolID] = 9000
Pool total rewards:
totalRewards[poolID] = 11250
totalRewards
was increased from10000
to11250
, but we know that no SALT rewards were added to the contract to reflect that change. These rewards are virtual, having no existence, yet they are recorded as normal rewards.After Bob's call, anyone who increases their shares will receive an incorrect rewards amount. If later on, all users try to claim their rewards, some won't be able to do so as they will have insufficient SALT balance in the contract to account for all the rewards. It will come down to who claims first.
The magnitude of this issue will increase with each instance of a user increasing shares.
Tools Used
Manual review, VS Code
Recommended Mitigation
To solve this issue, the
totalRewards
value should not be increased in theStakingRewards._increaseUserShare
function:Assessed type
Error