stake.link

stake.link
DeFiHardhatBridge
27,500 USDC
View results
Submission Details
Severity: low
Valid

When `lockingDuration=1`, `boostAmount` is received even though there is no locking period.

Summary

Users will receive a boost based on the lock duration. The documentation also says no locking - multiplier 0.(https://docs.stake.link/faq).
However, if _lockingDuration=1 is entered, the boost is received even though the lock duration is 0.
This is contrary to the basic design of the protocol. The amount of reSDL in possession is critical to the benefit received from the protocol and must be calculated accurately.

Vulnerability Details

In the _createLock function, boostAmount is calculated.

    uint256 boostAmount = boostController.getBoostAmount(_amount, _lockingDuration);

https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/base/SDLPool.sol#L392

The calculation formula in getBoostAmount is as follows, which is 0 if _lockingDuration=0, but returns the calculated value if _lockingDuration=1.Therefore, users can receive a boost.

    return (_amount * uint256(maxBoost) * uint256(_lockingDuration)) / uint256(maxLockingDuration);

https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/LinearBoostController.sol#L38

For locks with lockingDuration set, an initiateUnlock process is required.
However, if you look at the description in this section, if duration=0, halfDuration=0 and the current block.timestamp is set to expiry.

    uint64 halfDuration = locks[_lockId].duration / 2;
    if (locks[_lockId].startTime + halfDuration > block.timestamp) revert HalfDurationNotElapsed();
    uint64 expiry = uint64(block.timestamp) + halfDuration;
    locks[_lockId].expiry = expiry;

https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolPrimary.sol#L109-L113

Looking at the withdraw function, it does not revert when expiry == block.timestamp.

        if (expiry > block.timestamp) revert TotalDurationNotElapsed();

https://github.com/Cyfrin/2023-12-stake-link/blob/549b2b8c4a5b841686fceb9c311dca9ac58225df/contracts/core/sdlPool/SDLPoolPrimary.sol#L142

In other words, if _lockingDuration==1, there is no locking period and the asset can be withdrawn at any time while still receiving boost.
This is contrary to the principle of no locking - multiplier 0.
Users will not benefit from choosing _lockingDuration==0 and will all enter _lockingDuration==1 in order to receive the boost and benefit from the protocol.
This is also true for the secondary chain.

Impact

Contrary to the basic principles stated in the document, the user will receive a boost.
Also, users who are unaware of this and enter _lockingDuration==0 as documented will effectively lose benefit.

Tools Used

Manual

Recommendations

A minimum lock duration would be a good idea.

Updates

Lead Judging Commences

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

min-duration

Support

FAQs

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