Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: high
Valid

The `OperatorStakingPool::_withdraw()` function lacks the implementation of token transfer, which prevents tokens from being correctly withdrawn when invoked.

Summary

The OperatorStakingPool::_withdraw() function lacks the implementation of token transfer, which prevents tokens from being correctly withdrawn when invoked.

Vulnerability Details

The OperatorStakingPool contract is responsible for handling withdrawals through its withdraw() method. However, as illustrated in the code snippet below, the internal _withdraw() function only updates the relevant share balance without executing the actual token transfer. This omission results in the inability to withdraw tokens, despite the withdrawal process appearing to complete successfully.

// OperatorStakingPool::withdraw()
function withdraw(uint256 _amount) external {
if (!isOperator(msg.sender)) revert SenderNotAuthorized();
_withdraw(msg.sender, _amount);
}
// OperatorStakingPool::_withdraw()
function _withdraw(address _operator, uint256 _amount) private {
uint256 sharesAmount = lst.getSharesByStake(_amount);
shareBalances[_operator] -= sharesAmount;
totalShares -= sharesAmount;
emit Withdraw(_operator, _amount, sharesAmount);
}

Code Snippet

https://github.com/Cyfrin/2024-09-stakelink/blob/f5824f9ad67058b24a2c08494e51ddd7efdbb90b/contracts/linkStaking/OperatorStakingPool.sol#L199-L205

Impact

The function currently updates the operator's share balance but does not execute the corresponding token transfer. As a result, operators are unable to withdraw their staked tokens, potentially causing financial losses and operational disruption.

Tools Used

Manual Review

Recommendations

To address this issue, the _withdraw() function must include logic to transfer tokens from the contract back to the operator. Below is the recommended modification:

function _withdraw(address _operator, uint256 _amount) private {
uint256 sharesAmount = lst.getSharesByStake(_amount);
+ // Check if the operator has enough shares to withdraw
+ if (shareBalances[_operator] < sharesAmount) revert InsufficientShares();
shareBalances[_operator] -= sharesAmount;
totalShares -= sharesAmount;
+ // Transfer the token back to the operator
+ // Ensure the contract has enough balance to transfer
+ if (lst.balanceOf(address(this)) < _amount) revert InsufficientContractBalance();
+ lst.transfer(_operator, _amount); // Actual token transfer
emit Withdraw(_operator, _amount, sharesAmount);
}

By adding these checks and the token transfer, the withdraw function will behave as expected, ensuring both balances and token ownership are properly updated.

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`OperatorStakingPool::_withdraw()` function doesn't transfer the tokens

Support

FAQs

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