Summary
According to natspec of OperatorVault#exitVault()
:
updateDeposits must be called before calling this function
However, it's not enforced in OperatorVCS#removeVault()
, which is a public function that anyone can call, making vault to have its funds transferred to wrong address.
Vulnerability Details
Let's start with the natspec:
* @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) {
However, in function OperatorVCS#removeVault()
, OperatorVault#updateDeposits()
is not called before exiting vault:
* @notice Removes a vault that has been queued for removal
* @param _queueIndex index of vault in removal queue
*/
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();
Impact
Sending removed vault funds to wrong address.
Tools Used
Manual review
Recommendations
Create internal function _updateDeposits()
having updateDeposits()
function body, make both updateDeposits()
and exitVault()
call it:
Contract: OperatorVault.sol
- function exitVault() external onlyVaultController returns (uint256, uint256) {
+ function exitVault(uint256 _minRewards, address _rewardsReceiver) external onlyVaultController returns (uint256, uint256) {
+ _updateDeposits(_minRewards, _rewardsReceiver);
if (!isRemoved()) revert OperatorNotRemoved();
uint256 opRewards = getUnclaimedRewards();
if (opRewards != 0) _withdrawRewards();