Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: medium
Invalid

On `OparetorVCS.sol` `removeVault` function is not calling `updateDeposits` before `IOperatorVault(vault).exitVault()`

Summary

on OparetorVault.sol the function exitVault() says that @dev updateDeposits must be called before calling this function

on OparetorVSC.sol the removeVault function is calling exitVault() but not calling updateDeposits function.

This led to loss of funds.

Vulnerability Details

on OparetorVault.sol the function exitVault() says that @dev updateDeposits must be called before calling this function

I pointed out that comment.

/**
* @notice Withdraws tokens from the Chainlink staking contract and sends them to the vault controller
👉 * @dev updateDeposits must be called before calling this function
* @dev used to withdraw remaining principal and rewards after operator has been removed
* @dev will also send any unclaimed operator rewards to rewards receiver
* @return total principal withdrawn
* @return total rewards withdrawn
*/
function exitVault() external onlyVaultController returns (uint256, uint256) {
if (!isRemoved()) revert OperatorNotRemoved();
uint256 opRewards = getUnclaimedRewards();
if (opRewards != 0) _withdrawRewards();
uint256 rewards = getRewards();
if (rewards != 0) rewardsController.claimReward();
uint256 principal = getPrincipalDeposits();
stakeController.unstakeRemovedPrincipal();
uint256 balance = token.balanceOf(address(this));
token.safeTransfer(vaultController, balance);
return (principal, rewards);
}

on OparetorVSC.sol the removeVault function is calling exitVault() but not calling updateDeposits function.

on the pointing line, it is calling the exitVault function. But before calling this function it must call updateDeposits function.

function removeVault(uint256 _queueIndex) public {
address vault = vaultsToRemove[_queueIndex];
vaultsToRemove[_queueIndex] = vaultsToRemove[vaultsToRemove.length - 1];
vaultsToRemove.pop();
_updateStrategyRewards();
👉 (uint256 principalWithdrawn, uint256 rewardsWithdrawn) = IOperatorVault(vault).exitVault();
totalDeposits -= principalWithdrawn + rewardsWithdrawn;
totalPrincipalDeposits -= principalWithdrawn;
uint256 numVaults = vaults.length;
uint256 index;
for (uint256 i = 0; i < numVaults; ++i) {
if (address(vaults[i]) == vault) {
index = i;
break;
}
}
for (uint256 i = index; i < numVaults - 1; ++i) {
vaults[i] = vaults[i + 1];
}
vaults.pop();
token.safeTransfer(address(stakingPool), token.balanceOf(address(this)));
}

If we call the updateDeposits function with _minRewards of 1, then the test/linkStaking/operator-vcs.test.ts test file fails.

function removeVault(uint256 _queueIndex) public {
address vault = vaultsToRemove[_queueIndex];
vaultsToRemove[_queueIndex] = vaultsToRemove[vaultsToRemove.length - 1];
vaultsToRemove.pop();
_updateStrategyRewards();
👉✅ IOperatorVault(vault).updateDeposits(1, address(stakingPool));
(uint256 principalWithdrawn, uint256 rewardsWithdrawn) = IOperatorVault(vault).exitVault();
totalDeposits -= principalWithdrawn + rewardsWithdrawn;
totalPrincipalDeposits -= principalWithdrawn;
uint256 numVaults = vaults.length;
uint256 index;
for (uint256 i = 0; i < numVaults; ++i) {
if (address(vaults[i]) == vault) {
index = i;
break;
}
}
for (uint256 i = index; i < numVaults - 1; ++i) {
vaults[i] = vaults[i + 1];
}
vaults.pop();
token.safeTransfer(address(stakingPool), token.balanceOf(address(this)));
}

The failed message of (test\linkStaking\operator-vcs.test.ts:445:12)

❗❗❗❗❗

OperatorVCS
removeVault should work correctly:
AssertionError: expected 950 to equal 900
+ expected - actual
-950
+900

But if you set the updateDeposits _minRewards to 0 then the test pass. which means the stacking pool does not want any rewards that's why it is passing.

Impact

we must call the updateDeposits function otherwise the strategy loses funds.

Tools Used

Manually Reviewed

Recommendations

Call the updateDeposits function as mentioned on the impact.

Also, update the test file of (test\linkStaking\operator-vcs.test.ts:445:12)

--- assert.equal(fromEther(await strategy.getTotalDeposits()), 900)
+++ assert.equal(fromEther(await strategy.getTotalDeposits()), 950)
Updates

Lead Judging Commences

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

[INVALID] `exitVault` doesn't call `updateDeposits` before calling `_withdrawRewards` in the vault removal process

Appeal created

angrymustacheman Auditor
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[INVALID] `exitVault` doesn't call `updateDeposits` before calling `_withdrawRewards` in the vault removal process

Support

FAQs

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