DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: high
Invalid

Users can earn unfair `FjordPoints` by instant staking/unstaking action

Github link

Summary

Users can earn points without actual staking by executing stake() and unstake() within the same transaction.

Vulnerability Details

When a user takes staking operation (staking/unstaking), they call FjordPoints::onStaked and FjordPoints::onUnstaked functions:

FjordPoints.sol#L192-L227

function onStaked(address user, uint256 amount)
external
onlyStaking
checkDistribution
updatePendingPoints(user)
{
UserInfo storage userInfo = users[user];
userInfo.stakedAmount = userInfo.stakedAmount.add(amount);
totalStaked = totalStaked.add(amount);
emit Staked(user, amount);
}
...
function onUnstaked(address user, uint256 amount)
external
onlyStaking
checkDistribution
updatePendingPoints(user)
{
UserInfo storage userInfo = users[user];
if (amount > userInfo.stakedAmount) {
revert UnstakingAmountExceedsStakedAmount();
}
userInfo.stakedAmount = userInfo.stakedAmount.sub(amount);
totalStaked = totalStaked.sub(amount);
emit Unstaked(user, amount);
}

Both functions call updatePendingPoints(msg.sender) to update points for the user:

modifier updatePendingPoints(address user) {
UserInfo storage userInfo = users[user];
uint256 owed = userInfo.stakedAmount.mul(pointsPerToken.sub(userInfo.lastPointsPerToken))
.div(PRECISION_18);
userInfo.pendingPoints = userInfo.pendingPoints.add(owed);
userInfo.lastPointsPerToken = pointsPerToken;
_;
}

Due to the current implementation of unstake function, it will bypass if user unstakes from currentEpoch. This results in earning unfair points for the user without having actually staked.

function unstake(uint16 _epoch, uint256 _amount)
external
checkEpochRollover
redeemPendingRewards
returns (uint256 total)
{
if (_amount == 0) revert InvalidAmount();
DepositReceipt storage dr = deposits[msg.sender][_epoch];
if (dr.epoch == 0) revert DepositNotFound();
if (dr.staked < _amount) revert UnstakeMoreThanDeposit();
-> // _epoch is same as current epoch then user can unstake immediately
-> if (currentEpoch != _epoch) {
// _epoch less than current epoch then user can unstake after at complete lockCycle
if (currentEpoch - _epoch <= lockCycle) revert UnstakeEarly();
}

Impact

Users can earn unfair FjordPoints by instant staking/unstaking action, which disrupts points distribution for honest users.

Tools Used

Manual Review

Recommendations

Make sure that points do not accumulate if users unstake within the same epoch they staked.

Updates

Lead Judging Commences

inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.