MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: low
Valid

Users are unable to withdraw immediately, even if they stake after reaching maxEndTime

Summary

Users can still stake stETH in the pool after the pool's maxEndTime, but they won't receive any rewards. Withdrawal of these stETH funds necessitates users to wait for the withdrawLockPeriodAfterStake duration. In essence, users lock up their stETH for a specific period but do not receive any returns.

Vulnerability Details

Users could still stake even if the pool's maxEndTime has reached, this will update the lastStake to the current block.timestamp.

function _stake(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private {
require(amount_ > 0, "DS: nothing to stake");
Pool storage pool = pools[poolId_];
PoolData storage poolData = poolsData[poolId_];
UserData storage userData = usersData[user_][poolId_];
if (pool.isPublic) {
// https://docs.lido.fi/guides/lido-tokens-integration-guide/#steth-internals-share-mechanics
uint256 balanceBefore_ = IERC20(depositToken).balanceOf(address(this));
IERC20(depositToken).safeTransferFrom(_msgSender(), address(this), amount_);
uint256 balanceAfter_ = IERC20(depositToken).balanceOf(address(this));
amount_ = balanceAfter_ - balanceBefore_;
require(userData.deposited + amount_ >= pool.minimalStake, "DS: amount too low");
totalDepositedInPublicPools += amount_;
}
userData.pendingRewards = _getCurrentUserReward(currentPoolRate_, userData);
// Update pool data
poolData.lastUpdate = uint128(block.timestamp);
poolData.rate = currentPoolRate_;
poolData.totalDeposited += amount_;
// Update user data
userData.lastStake = uint128(block.timestamp);
userData.rate = currentPoolRate_;
userData.deposited += amount_;
emit UserStaked(poolId_, user_, amount_);
}

Users are unable to receive any rewards as the reward becomes zero after the maxEndTime. Additionally, users must wait an extra withdrawLockPeriodAfterStake duration to withdraw their stETH.

function _withdraw(address user_, uint256 poolId_, uint256 amount_, uint256 currentPoolRate_) private {
Pool storage pool = pools[poolId_];
PoolData storage poolData = poolsData[poolId_];
UserData storage userData = usersData[user_][poolId_];
uint256 deposited_ = userData.deposited;
require(deposited_ > 0, "DS: user isn't staked");
if (amount_ > deposited_) {
amount_ = deposited_;
}
uint256 newDeposited_;
if (pool.isPublic) {
require(
//@audit users must wait an extra `withdrawLockPeriodAfterStake`
block.timestamp < pool.payoutStart ||
(block.timestamp > pool.payoutStart + pool.withdrawLockPeriod &&
block.timestamp > userData.lastStake + pool.withdrawLockPeriodAfterStake),
"DS: pool withdraw is locked"
);
[...]
}

POC:
Add the test to test/Distribution.test.ts and run it with npx hardhat test.

diff --git a/test/Distribution.test.ts b/test/Distribution.test.ts
index a08b6f6..af3e00c 100644
--- a/test/Distribution.test.ts
+++ b/test/Distribution.test.ts
@@ -1387,6 +1387,14 @@ describe('Distribution', () => {
await expect(distribution.withdraw(poolId, wei(0.1))).to.be.revertedWith('DS: pool withdraw is locked');
});
+
+ it.only("revert when withdrawing, although stake after endTime", async () => {
+ await setNextTime(oneDay * 51);
+
+ await distribution.stake(poolId, wei(1));
+
+ await expect(distribution.withdraw(poolId, wei(0.1))).to.be.revertedWith('DS: pool withdraw is locked');
+ });
});
describe('#removeUpgradeability', () => {

Impact

Users can still stake stETH in the pool after the pool's maxEndTime, but they won't receive any rewards. Withdrawal of these stETH funds necessitates users to wait for the withdrawLockPeriodAfterStake duration. In essence, users lock up their stETH for a specific period but do not receive any returns.

Tools Used

hardhat

Recommendations

Disallow users from staking in pools that have reached maxEndTime.

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Users can stake their stETH in a finished pool and their funds will be locked for withdrawLockPeriodAfterStake, making them miss stETH rewards and earning no MOR

Support

FAQs

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