Liquid Staking

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

`OperatorVault::setOperator` can be set by any address that not staked in `OperatorStakingPool` contract

Summary

Function setOperator does not validate if the address set is already staked in OperatorStakingPool

Vulnerability Details

OperatorVault.sol#L248-L252

function setOperator(address _operator) public onlyOwner {
if (operator != address(0)) revert OperatorAlreadySet();
if (_operator == address(0)) revert ZeroAddress();
operator = _operator;
}

Impact

The docs stated that Node operators are required to stake their LSTs into this contract.
This would break the intended functionality of the said protocol rules because there are no such check and making the OperatorStakingPool contract obsolete.

Tools Used

Manual review

Recommendations

add a check if the address is a part of operator using OperatorStakingPool.isOperator, we keep the logic not much changed so we change initializer function :

create interface for OperatorStakingPool in contracts/linkStaking/interfaces

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.15;
interface IOperatorStakingPool {
function isOperator(address _account) external view returns (bool);
}

import above interface and change OperatorVault:

diff --git a/contracts/linkStaking/OperatorVault.sol b/contracts/linkStaking/OperatorVault.sol
index 64ccb0a..9c4036a 100644
--- a/contracts/linkStaking/OperatorVault.sol
+++ b/contracts/linkStaking/OperatorVault.sol
@@ -7,6 +7,7 @@ import "./base/Vault.sol";
import "./interfaces/IOperatorVCS.sol";
import "./interfaces/IOperatorStaking.sol";
import "./interfaces/IPFAlertsController.sol";
+import "./interfaces/IOperatorStakingPool.sol";
/**
* @title Operator Vault
@@ -19,6 +20,7 @@ contract OperatorVault is Vault {
address public operator;
address public rewardsReceiver;
IPFAlertsController public pfAlertsController;
+ IOperatorStakingPool public operatorStakingPool;
uint128 public trackedTotalDeposits;
uint128 private unclaimedRewards;
@@ -47,7 +49,8 @@ contract OperatorVault is Vault {
* @param _pfAlertsController address of Chainlink price feed alrts controller
* @param _operator address of operator represented by this vault
* @param _rewardsReceiver address authorized to claim rewards from this vault
**/
function initialize(
address _token,
address _vaultController,
@@ -55,7 +58,8 @@ contract OperatorVault is Vault {
address _rewardsController,
address _pfAlertsController,
address _operator,
- address _rewardsReceiver
+ address _rewardsReceiver,
+ address _operatorStakingPool
) public reinitializer(3) {
if (vaultController == address(0)) {
__Vault_init(_token, _vaultController, _stakeController, _rewardsController);
@@ -67,14 +71,17 @@ contract OperatorVault is Vault {
}
pfAlertsController = IPFAlertsController(_pfAlertsController);
rewardsReceiver = _rewardsReceiver;
+ operatorStakingPool = IOperatorStakingPool(_operatorStakingPool);
if (operator == address(0) && _operator != address(0)) {
+ require(operatorStakingPool.isOperator(_operator), "Operator not registered");
setOperator(_operator);
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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