Liquid Staking

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

Potential misuse of `setOperator` function in `OperatorVault.sol` can lead to a situation where the wrong operator is set or an operator that later becomes compromised cannot be replaced in `OperatorVault.sol`

Summary

The OperatorVault contract has a function setOperator that allows the owner of the contract to set the operator address. However, this function can only be called once, and once the operator is set, it cannot be changed. This can lead to a situation where the wrong operator is set or an operator that later becomes compromised cannot be replaced, rendering the vault unusable or vulnerable. This centralization risk can have significant consequences for the proper functioning of the vault.

Vulnerability Details

The setOperator function allows the contract owner to set the address of the operator for the vault. However, once the operator is set, the contract does not allow for any modifications. If the wrong operator address is set initially, there is no way to change it, even if it’s necessary to do so later due to misconfiguration, compromise, or other issues.

function setOperator(address _operator) public onlyOwner {
if (operator != address(0)) revert OperatorAlreadySet();
if (_operator == address(0)) revert ZeroAddress();
operator = _operator;
}
  1. If the owner accidentally sets the wrong operator, they cannot change it afterward. This would require redeployment or migration of the contract.

  2. If the operator address becomes compromised or behaves maliciously, there is no mechanism to replace them, potentially putting all funds at risk.

  3. If the operator becomes inactive or inaccessible (e.g., loss of private keys), the vault operations could come to a halt.

PoC:

  1. Scenario: Imagine a scenario where the contract owner mistakenly sets an incorrect address as the operator (either an inactive address or an attacker-controlled one). This address is now responsible for all operator-related functions, such as managing staking and rewards. Since the operator cannot be changed, the vault becomes either locked or vulnerable.
    2.Realistic PoC:
    Deploy the OperatorVault contract.
    Call the setOperator function with a wrong or compromised address.
    Try to interact with the vault using the wrong operator.
    Observe that the contract has no mechanism to change the operator address after it has been set.
    Example (testing the inflexibility):

// Step 1: Deploy contract
OperatorVault operatorVault = new OperatorVault();
// Step 2: Set a wrong operator address (e.g., a compromised address)
operatorVault.setOperator(0x1234567890123456789012345678901234567890);
// Step 3: Attempt to reset the operator (this should fail)
operatorVault.setOperator(0x0987654321098765432109876543210987654321);

Expected output:
The second call to setOperator will fail with the error OperatorAlreadySet. This proves the inflexibility of the current design, where the operator cannot be updated once set.

Here’s a sample test script using Hardhat to demonstrate the potential misuse of setOperator:

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("OperatorVault", function () {
let operatorVault, owner, addr1, addr2;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const OperatorVault = await ethers.getContractFactory("OperatorVault");
operatorVault = await OperatorVault.deploy();
await operatorVault.initialize(
addr1.address, // token
owner.address, // vault controller
addr1.address, // staking controller
addr1.address, // rewards controller
addr1.address, // pfAlertsController
ethers.constants.AddressZero, // operator initially set to zero
addr1.address // rewards receiver
);
});
it("Should set operator once and fail to set it again", async function () {
// Set the operator for the first time
await operatorVault.setOperator(addr1.address);
expect(await operatorVault.operator()).to.equal(addr1.address);
// Attempt to set the operator again (should fail)
await expect(
operatorVault.setOperator(addr2.address)
).to.be.revertedWith("OperatorAlreadySet");
});
it("Should fail if trying to set zero address as operator", async function () {
await expect(
operatorVault.setOperator(ethers.constants.AddressZero)
).to.be.revertedWith("ZeroAddress");
});
});

Impact

  1. If the wrong operator address is set or the operator becomes compromised, the vault becomes vulnerable or inaccessible.

  2. Once the operator is set, even if the contract owner wants to change it due to operational or security concerns, it’s impossible. The only option would be to redeploy or migrate the vault, which can be costly and risky.

  3. If the operator becomes inactive (e.g., loss of private keys), the vault operations could be entirely halted, affecting the staking process and the reward distribution mechanism.

Tools Used

Manual review.

Recommendations

  1. Implement an upgrade mechanism or governance system that allows the operator to be replaced if needed. This would give flexibility in case the initially set operator is wrong or becomes compromised.

function changeOperator(address _newOperator) public onlyOwner {
if (_newOperator == address(0)) revert ZeroAddress();
operator = _newOperator;
}

Alternatively, using a multi-signature or governance-based approach to managing the operator role could ensure decentralization and prevent a single point of failure.
2. To prevent misuse or centralization issues, a time delay or voting mechanism can be introduced before changing the operator. This ensures that stakeholders can raise concerns or block a malicious change.

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.