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();
->
-> if (currentEpoch != _epoch) {
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.