MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: medium
Invalid

Stakers of public pools will lose their staked assets if the pool is permanently locked (expired)

Summary

Distribution._withdraw function will revert if it's called after the pool is permanently locked (expired), resulting in stakers of public pools losing their staked assets.

Vulnerability Details

  • The protocol allows stakers to withdraw their staked asset via withdraw function if the withdrawal window is open; which is after some locking time from the latest deposit or when the payout hasn't started:

    require(block.timestamp < pool.payoutStart ||
    (block.timestamp > pool.payoutStart + pool.withdrawLockPeriod &&
    block.timestamp >
    userData.lastStake +
    pool.withdrawLockPeriodAfterStake), "DS: pool withdraw is locked");
  • So the user will be able to withdraw if the following a || (b && c) condition returns true, where:

    • a is block.timestamp < pool.payoutStart, and it depends on the pool configuration only.

    • b is block.timestamp > pool.payoutStart + pool.withdrawLockPeriod, and it depends on the pool configuration only.

    • c is userData.lastStake + pool.withdrawLockPeriodAfterStake, and it depends on the pool configuration and the time of the last staking made by the user.

    • a and b conditions are static as they are not affected by the user staking time, while c is dynamic and will change whenever the user stakes.

    • And to get the a || (b && c) check permanently returns false:

      • a should be always false, and this is met when block.timestamp >= pool.payoutStart.

      • b && c check should be always false, and this is met when b is false where block.timestamp <= pool.payoutStart + pool.withdrawLockPeriod

      • false || (false && true) will return false.

  • As can be noticed from the above condition; the pool will be permanently locked (expired) if these two conditions are met:
    block.timestamp >= pool.payoutStart and block.timestamp <= pool.payoutStart + pool.withdrawLockPeriod
    where:

    • pool.payoutStart represents the timestamp when the pool starts to pay out rewards.

    • pool.withdrawLockPeriod represents the period in seconds when the user can't withdraw his staked assets.

  • So when the pool becomes permanently locked (expired); stakers of this pool will not be able to withdraw their staked assets as the withdraw transaction will always revert (withdraw will be DoS'd).

Impact

  • Since there is no mechanism to enable stakers of expired pools to withdraw their assets; these staked assets will be locked in the Distribution contract as they can't be withdrawn from the pool.

  • Note that this loss of funds is limited for stakers in public pools , as private pools don't require the staker to actually deposit any assets (staking in private pools is accessible by the contract owner only, and it's meant by it to reward these participants with MOR tokens on Arbitrum).

Proof of Concept

Distribution._withdraw function/L243-L248

require(block.timestamp < pool.payoutStart ||
(block.timestamp > pool.payoutStart + pool.withdrawLockPeriod &&
block.timestamp >
userData.lastStake +
pool.withdrawLockPeriodAfterStake), "DS: pool withdraw is locked");

Tools Used

Manual Review.

Recommendations

Add a mechanism that enables users from withdrawing their staked assets from expired pools.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
0xhals Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
0xhals Submitter
over 1 year ago
inallhonesty Lead Judge over 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.