Skip to content
This repository has been archived by the owner on Jan 12, 2025. It is now read-only.

PUSH0 - New staking positions still gets the full reward amount as with old stakings, diluting rewards for old stakers #74

Open
sherlock-admin4 opened this issue Jul 15, 2024 · 2 comments
Labels
Has Duplicates A valid issue with 1+ other issues describing the same vulnerability Medium A Medium severity issue. Reward A payout will be made for this issue Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed

Comments

@sherlock-admin4
Copy link

sherlock-admin4 commented Jul 15, 2024

PUSH0

High

New staking positions still gets the full reward amount as with old stakings, diluting rewards for old stakers

Summary

New staking positions still gets the full reward amount as with old stakings, diluting rewards for old stakers. Furthermore, due to the way multipliers are calculated, extremely short stakings are still very effective in stealing long-term stakers' rewards.

Vulnerability Detail

In the Magic LUM Staking system, users can lock-stake MLUM in exchange for voting power, as well as a share of the protocol revenue. As per the docs:

You can stake and lock your Magic LUM token in the Magic LUM staking pools to benefit from protocol profits. All protocol returns like a part of the trading fees, Fairlaunch listing fees, and NFT trading fees flow into the staking pool in form of USDC.

Rewards are distributed every few days, and you can Claim at any time.

However, when rewards are distributed, new and old staking positions are treated alike, and immediately receive the same rewards as it is distributed.

Thus, anyone can stake MLUM for a short duration as rewards are distributed, and siphon away rewards from long-term stakers. Staking for a few days still gets almost the same multiplier as staking for a year, and the profitability can be calculated and timed by calculating the protocol's revenue using various offchain methods (e.g. watching the total trade volume in each time intervals).

Consider the following scenario:

  • Alice stakes 50 MLUM for a year.
  • Bob has 50 MLUM but hasn't staked.
  • Bob notices that there is a spike in trading activity, and the protocol is gaining a lot of trading volume in a short time (thereby gaining a lot of revenue).
  • Bob stakes 50 MLUM for 1 second.
  • As soon as the rewards are distributed, Bob can harvest his part immediately.

Note that expired positions, while should not be able to vote, still accrue rewards. Thus Bob can just leave the position there and withdraw whenever he wants to without watching the admin actions. A more sophisticated attack involves front-running the admin reward distribution to siphon rewards, then unstake right away.

PoC

Due to the way multipliers are calculated, 1-year lock positions are only at most 3 times stronger than a 1-second lock for the same amount of MLUM.

The following coded PoC shows the given scenario, where Bob is able to siphon 25% of the rewards away by staking for a duration of a single second and leave it there.

Paste the following test into MlumStaking.t.sol, and run it by forge test --match-test testPoCHarvestDilute -vv:

function testPoCHarvestDilute() public {
    _stakingToken.mint(ALICE, 100 ether);
    _stakingToken.mint(BOB, 100 ether);

    vm.startPrank(ALICE);
    _stakingToken.approve(address(_pool), 50 ether);
    _pool.createPosition(50 ether, 365 days);
    vm.stopPrank();

    vm.startPrank(BOB);
    _stakingToken.approve(address(_pool), 50 ether);
    _pool.createPosition(50 ether, 1);
    vm.stopPrank();

    _rewardToken.mint(address(_pool), 100 ether);

    skip(100); // Bob can stake anytime and then just wait

    vm.prank(ALICE);
    _pool.harvestPosition(1);
    vm.prank(BOB);
    _pool.harvestPosition(2);

    console.logUint(_rewardToken.balanceOf(ALICE));
    console.logUint(_rewardToken.balanceOf(BOB));
}

The test logs will be

Ran 1 test for test/MlumStaking.t.sol:MlumStakingTest
[PASS] testPoCHarvestDilute() (gas: 940195)
Logs:
  75000000000000000000
  25000000000000000000

i.e. Bob was able to siphon 25% of the rewards.

Impact

Staking rewards can be stolen from honest stakers.

Code Snippet

https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/MlumStaking.sol#L354

Tool used

Manual Review

Recommendation

When new positions are created, their position should be recorded, but their amount with multipliers should be summed up and queued until the next reward distribution.

When the admin distributes rewards, there should be a function that first updates the pool, then add the queued amounts into staking. That way, newly created positions can still vote, but they do not accrue rewards for the immediate following distribution (only the next one onwards).

  • One must note down the timestamp that the position was created (as well as the timestamp the rewards were last distributed), so that when the position unstakes, the contract knows whether to burn the unstaked shares from the queued shares pool or the active shares pool.
@github-actions github-actions bot added the Excluded Excluded by the judge without consulting the protocol or the senior label Jul 21, 2024
@0xSmartContract 0xSmartContract added Medium A Medium severity issue. Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label Has Duplicates A valid issue with 1+ other issues describing the same vulnerability and removed Excluded Excluded by the judge without consulting the protocol or the senior Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels Jul 22, 2024
@sherlock-admin2
Copy link

The protocol team fixed this issue in the following PRs/commits:
https://github.com/metropolis-exchange/magicsea-staking/pull/7

@sherlock-admin3 sherlock-admin3 added Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed labels Jul 29, 2024
@sherlock-admin4 sherlock-admin4 changed the title Icy Basil Seal - New staking positions still gets the full reward amount as with old stakings, diluting rewards for old stakers PUSH0 - New staking positions still gets the full reward amount as with old stakings, diluting rewards for old stakers Jul 29, 2024
@sherlock-admin4 sherlock-admin4 added the Reward A payout will be made for this issue label Jul 29, 2024
@sherlock-admin2
Copy link

The Lead Senior Watson signed off on the fix.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Has Duplicates A valid issue with 1+ other issues describing the same vulnerability Medium A Medium severity issue. Reward A payout will be made for this issue Sponsor Confirmed The sponsor acknowledged this issue is valid Will Fix The sponsor confirmed this issue will be fixed
Projects
None yet
Development

No branches or pull requests

4 participants