Summary
Users amount is locked for a period of time against their choice.
Vulnerability Details
User can initiate unlock by calling the function initiateUnlock.
here,
check half of the lock time is passed.
updates the expiry time.
boost amount is set to zero.
Later, user can call the withdraw function and withdraw their shares.
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolPrimary.sol#L107-L121
function initiateUnlock(uint256 _lockId) external onlyLockOwner(_lockId, msg.sender) updateRewards(msg.sender) {
if (locks[_lockId].expiry != 0) revert UnlockAlreadyInitiated();
uint64 halfDuration = locks[_lockId].duration / 2;
if (locks[_lockId].startTime + halfDuration > block.timestamp) revert HalfDurationNotElapsed();
uint64 expiry = uint64(block.timestamp) + halfDuration; ------------------->> this set more time.
locks[_lockId].expiry = expiry;
uint256 boostAmount = locks[_lockId].boostAmount;
locks[_lockId].boostAmount = 0;
effectiveBalances[msg.sender] -= boostAmount;
totalEffectiveBalance -= boostAmount;
emit InitiateUnlock(msg.sender, _lockId, expiry);
}
lets see some cases,
user locked their sdl tokens.
90% of lock duration is passed.
they call initiate unlock function.
Now, their expiry time is set from current time + half duration of lock.
This is sets the expire more than total lock duration.
refer below place for wait time check
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolPrimary.sol#L134-L143
function withdraw(uint256 _lockId, uint256 _amount)
external
onlyLockOwner(_lockId, msg.sender)
updateRewards(msg.sender)
{
if (locks[_lockId].startTime != 0) {
uint64 expiry = locks[_lockId].expiry;
if (expiry == 0) revert UnlockNotInitiated();
if (expiry > block.timestamp) revert TotalDurationNotElapsed(); --------->> here.
}
Impact
users are forced to wait more than the lock duration to withdraw their amount.
Tools Used
Manual review.
Recommendations
If the expiry time exceeds the total lock duration, set the expiry as total lock duration.
https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolPrimary.sol#L107-L121
function initiateUnlock(uint256 _lockId) external onlyLockOwner(_lockId, msg.sender) updateRewards(msg.sender) {
if (locks[_lockId].expiry != 0) revert UnlockAlreadyInitiated();
uint64 halfDuration = locks[_lockId].duration / 2;
if (locks[_lockId].startTime + halfDuration > block.timestamp) revert HalfDurationNotElapsed();
uint64 expiry = uint64(block.timestamp) + halfDuration;
if(expiry > locks[_lockId].duration) --------------->>>updated
expiry = locks[_lockId].duration;------------->> updated
locks[_lockId].expiry = expiry;
uint256 boostAmount = locks[_lockId].boostAmount;
locks[_lockId].boostAmount = 0;
effectiveBalances[msg.sender] -= boostAmount;
totalEffectiveBalance -= boostAmount;
emit InitiateUnlock(msg.sender, _lockId, expiry);
}