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

Incorrect Lock and Claim Cycle calculation causes users to wait more during unstaking and claiming rewards.

Vulnerability Details

Unstaking

  • lockCycle is defined as 6 epochs. where by each epoch is 7 days thus 42 days

  • The unstake function checks if currentEpoch - _epoch <= lockCycle

  • This results in users being unable to unstake until the 8th epoch (after 49 days) instead of the intended (42 days only)

Completing claim request for reward

  • claimCycle is set to 3 epochs (21 days)

  • The completeClaimRequest function checks if currentEpoch - cr.requestEpoch <= claimCycle.

  • This results in users being unable to complete their claim until the 5th epoch after the request, instead of the 4th epoch

Poc

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity =0.8.21;
import "../src/FjordStaking.sol";
import {FjordPoints} from "../src/FjordPoints.sol";
import {Test, console} from "forge-std/Test.sol";
import {FjordToken} from "../src/FjordToken.sol";
contract CycleTest is Test {
FjordStaking fjordStaking;
FjordToken fjordToken;
address rewardAdmin = makeAddr("rewardAdmin");
address alice = makeAddr("alice"); // starts with 100 ether fjord tokens
address bob = makeAddr("bob");
FjordPoints fjordPoints;
function setUp() public {
fjordToken = new FjordToken();
fjordPoints = new FjordPoints();
fjordStaking = new FjordStaking(
address(fjordToken),
rewardAdmin,
makeAddr("dummy_sablier"),
address(0),
address(fjordPoints)
);
fjordPoints.setStakingContract(address(fjordStaking));
deal(address(fjordToken), alice, 100 ether);
deal(address(fjordToken), bob, 100 ether);
deal(address(fjordToken), rewardAdmin, 100 ether);
vm.prank(alice);
fjordToken.approve(address(fjordStaking), type(uint256).max);
vm.prank(rewardAdmin);
fjordToken.approve(address(fjordStaking), type(uint256).max);
vm.prank(bob);
fjordToken.approve(address(fjordStaking), type(uint256).max);
}
function test_lock_cycle() public {
vm.startPrank(alice);
// alice stakes in 1st epoch
fjordStaking.stake(50 ether);
// 6 epochs go by
vm.warp(block.timestamp + 7 days * fjordStaking.lockCycle());
// alice still can't unstake even though the lock cycle has clearly passed
vm.expectRevert(FjordStaking.UnstakeEarly.selector);
fjordStaking.unstake(1, 50 ether);
vm.stopPrank();
}
function test_claim_cycle() public {
vm.prank(rewardAdmin);
// Reward admin adds rewards
fjordStaking.addReward(4 ether);
vm.prank(alice);
// alice stakes in 1st epoch
fjordStaking.stake(50 ether);
vm.warp(block.timestamp + 7 days);
vm.prank(bob);
// // bob stakes in 2nd epoch
fjordStaking.stake(50 ether);
// very many days pass by
vm.warp(block.timestamp + 100 days);
vm.startPrank(alice);
fjordStaking.claimReward(false);
// 3 epochs go by thus completing the claim cycle
vm.warp(block.timestamp + 7 days * fjordStaking.claimCycle());
// alice still can't completer her claim request even though claim cycle has passed
vm.expectRevert(FjordStaking.CompleteRequestTooEarly.selector);
fjordStaking.completeClaimRequest();
vm.stopPrank();
}
}

Impact

Users are forced to keep their tokens staked for one extra epoch (7 days) beyond the intended lock period reducing reduces liquidity and flexibility for users.

In addition to that, delay in accessing earned rewards could discourage participation in the staking program.

Tools Used

Manual Review, Foundry

Recommendations

In the unstake, unstakeVested change

- if (currentEpoch - _epoch <= lockCycle) revert UnstakeEarly();
+ if (currentEpoch - _epoch < lockCycle) revert UnstakeEarly();

Also in unstakeAll

- if (dr.epoch == 0 || currentEpoch - epoch <= lockCycle) continue;
+ if (dr.epoch == 0 || currentEpoch - epoch < lockCycle) continue;

Similarly in completeClaimRequest;

- if (currentEpoch - cr.requestEpoch <= claimCycle) revert CompleteRequestTooEarly()
+ if (currentEpoch - cr.requestEpoch < claimCycle) revert CompleteRequestTooEarly()
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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