SSV.network

QuantstampFiles named in the report

Quantstamp reviewed SSV Network (SSV.network) on 21 Jul 2023: 25 findings, 0 critical; 6 marked fixed, 17 acknowledged, 2 open.

No open critical findings
Open the reportOpen SSV Network’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings25

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High000
Medium023
Low2153
MediumDelayed Liquidation Results in Insufficient SSV BalanceAcknowledged

Liquidations ensure that clusters have an adequate amount of SSV deposited to cover unclaimed network and operator fees. This is critical as operators continuously receive SSV rewards regardless of the cluster's SSV balance. In the case where a cluster's SSV balance falls below the liquidation threshold and is not liquidated, the operators risk not being able to withdraw their earnings as the SSV balance of `SSVNetwork` is insufficient. Additionally, the cluster may remain insolvent upon reactivation due to mismatch between the operators' balances and the cluster's balance.

MediumFront-Running Calls to `registerOperator()` and `registerValidator()`Acknowledged

When an operator calls `registerOperator()`, the operator's `publicKey` and `fee` amounts are passed as arguments. Once `registerOperator()` is called, the operator is registered with its `publicKey`, and the operator owner is set as the caller. Since the operator's `publicKey` refers to the operator's SSV node and differs from the caller's public key, there is no way of verifying that the caller is the actual owner of the operator's `publicKey`. Therefore, a malicious user could see a `registerOperator()` call in the pending transaction pool and front-run the transaction with the operator's `publicKey`. The same vulnerability is present with validators when calling `registerValidator()`. While the caller of `registerOperator()` or `registerValidator()` needs prior authorization from the `owner` via the `SSVNetwork.setRegisterAuth()` function, any registered operator or validator can front-run calls to `registerOperator()` or `registerValidator()` that are from other authorized users.

LowApplication Monitoring Can Be Improved by Emitting More EventsAcknowledged

In order to validate the proper deployment and initialization of the contracts, it is a good practice to emit events. Also, any important state transition can be logged, which is beneficial for monitoring the contract, and also tracking eventual bugs, or hacks. Below we present a non-exhaustive list of events that could be emitted to improve application management: 1. `OperatorLib.updateSnapshotSt()`: `operator.snapshot.*`. 1. `OperatorLib.updateOperators()`: `operator.validatorCount`. 1. `ProtocolLib.updateNetworkFee()`: `StorageProtocol.networkFeeIndex`, `StorageProtocol.networkFeeIndexBlockNumber` and `StorageProtocol.networkFee`. 1. `ProtocolLib.updateDAOEarnings()`: `StorageProtocol.daoBalance` and `StorageProtocol.daoIndexBlockNumber`. 1. `ProtocolLib.updateDAO()`: `StorageProtocol.daoValidatorCount`. 1. `ClusterLib.updateBalance()`: `cluster.balance`. 1. `ClusterLib.updateClusterData()`: `cluster.index` and `cluster.networkFeeIndex`.

LowBlock Timestamp ManipulationAcknowledged

At `SSVNetwork.sol#770`, comparisons are made against `block.timestamp`. Projects may rely on block timestamps for various purposes. Given that this project may, in principle, be deployed on any EVM chain, it is important to note that different networks may impose different requirements for their validators when it comes to setting the timestamp of a block, and attackers may be able to manipulate timestamps for their own purposes. If a smart contract relies on a timestamp, it must take this into account.

LowFees Charged for Invalid SetupAcknowledged

If there are enough operators removed for a validator, the consensus between operators may be affected for the particular validator. For example, if there are four operators, and two operators are removed, consensus cannot be reached among the operators. The validator will become idle and lose out on block proposal and attestation rewards. Based on the current implementation, the validator still owes payments to any active operators even though the setup is invalid.

LowInactive Operators Can Lead to Insufficient ConsensusAcknowledged

The `SSVNetwork.removeOperator()` function allows operators to remove themselves from the clusters they were previously operating for. Although this action causes the operators to stop receiving SSV rewards, the clusters are not updated to reflect the operator's removal. As a result, if sufficiently many operators are removed, consensus may not be reached. In addition, if an operator stops running an operator node without calling `removeOperator()`, they will no longer contribute to the consensus threshold, but they will still receive SSV rewards from the cluster owner. Based on the current implementation, the validator still owes payments to any active operators even though the setup is invalid. Therefore, validators must track the performance of the operators they use to ensure the success of their validator and maximize their rewards.

LowIncompatible with Deflationary TokensAcknowledged

The contracts are not designed to accommodate deflationary tokens when accepting token transfers. If a user transfers funds that incur a fee on transfer during `deposit()` or `reactivate()`, the contract will report that the user has deposited more funds than they actually sent. This can cause issues during withdrawal. This issue is provided as informational if payments are updated to accept other tokens.

LowInconsistent Use of `shrink()` and `expand()`Acknowledged

The current implementation of the `updateNetworkFee()` function in the code snippet is emitting the entire network `fee` in the `NetworkFeeUpdated` event, despite the shrunken `fee` being saved in the `network` struct. This could lead to confusion and misinterpretation of the actual network fee. ``` Network memory network_ = network; DAO memory dao_ = dao; dao_.updateDAOEarnings(network.networkFee); dao = dao_; network_.networkFeeIndex = NetworkLib.currentNetworkFeeIndex(network_); network_.networkFeeIndexBlockNumber = uint64(block.number); emit NetworkFeeUpdated(network_.networkFee.expand(), fee); network_.networkFee = fee.shrink(); network = network_; ``` Inconsistent uses of shrunken fees were found in the following functions: 1. `fee` in `SSVNetwork.declareOperatorFee()` 2. `fee` in `SSVNetwork.reduceOperatorFee()` 3. `fee` in `SSVNetwork.registerOperator()` 4. `fee` in `SSVNetwork.updateNetworkFee()` 5. `amount` in `SSVNetwork.updateMinimumLiquidationCollateral()` 6. `fee` in `SSVNetwork.declareOperatorFee()` 7. `fee` in `SSVNetwork.reduceOperatorFee()`

LowKeyshare Reuse Could Lead to Compromising Validator Private KeyAcknowledged

The project uses Shamir Secret Sharing to split validator private keys into encrypted key shares for distribution to operators. However, suppose an operator is removed from a cluster and later added back in. The case where the private key would be split into the same key shares as in the initial configuration and a different encrypted key share would be sent to an operator should be avoided, as this may result in an operator potentially accumulating the quorum number of key shares, compromising the validator private key.

LowMissing Input ValidationAcknowledged

It is crucial to validate inputs, even if the inputs come from trusted addresses, to avoid human error. A lack of robust input validation can only increase the likelihood and impact in the event of mistakes. The following is the list of places that can potentially benefit from a stricter input validation: 1. `SSVNetwork.sol#L110`: the `minimumBlocksBeforeLiquidation_` of the `__SSVNetwork_init_unchained()` can be less than `MINIMAL_LIQUIDATION_THRESHOLD`. 1. `SSVNetwork.sol#L173`: the `recipientAddress` of the `setFeeRecipientAddress()` should not be zero address. 1. `SSVNetwork.sol#L180`: the `keccak256` of `publicKey` of the `registerValidator()` should not be zero bytes. 1. `SSVNetwork.sol#L180`: the length of `shares` of the `registerValidator()` should match the length of `operatorIds`. 1. `SSVNetwork.sol#L604`: the `amount` of `withdrawNetworkEarnings()` should be greater than zero. 1. **(Fix Review)** `ProtocolLib.sol#18`: the `fee` of the `updateNetworkFee()` function not checked to be non-zero or otherwise reasonably bound. 1. **(Fix Review)** `ProtocolLib.sol#L38`: the `deltaValidatorCount` of the `updateDAO()` function is not checked to be non-zero or otherwise reasonably bound. 1. **(Fix Review)** `SSVNetworkViews.sol#L58:` the `operatorId` of the `getOperatorById()` function is not checked to be an existing operator. 1. **(Fix Review)** `SSVDAO.sol#L45`: the `percentage` of the `updateOperatorFeeIncreaseLimit()` function is not checked to be non-zero or smaller than `PRECISION_FACTOR`.

Show 7 more
LowOperator Fee Cannot Increase Once Set to ZeroAcknowledged

The `_declareOperatorFee()` function calculates the maximum allowed fee increase based on the current `operatorFee` and the maximum allowed fee increase percentage `operatorMaxFeeIncrease`. If the new fee is greater than the maximum allowed fee increase, it reverts with a `FeeExceedsIncreaseLimit` error. The operator fee can be set to zero in `declareOperatorFee()` or reduced to zero with `reduceOperatorFee()` and due to `operatorMaxFeeIncrease`, increasing the operator fee will become impossible once the operator fee is set to zero.

LowPrivileged RolesAcknowledged

All privileged permissions should be clearly documented for users. The privileged permissions of each contract are documented below. `SSVNetwork` and `SSVNetworkViews` are upgradeable contracts that can have their implementation contracts swapped out at any time through `upgradeTo()` function by the contract owner. - `SSVNetwork.removeValidator()` can only be called by `_validatorPKs[hashedValidator].owner` - `SSVNetwork.liquidate()` can be called by a cluster's owner when `isLiquidatable()` returns false The following are all cases of owner privileges: - `SSVNetwork.updateNetworkFee()` - `SSVNetwork.withdrawNetworkEarnings()` - `SSVNetwork.updateOperatorFeeIncreaseLimit()` - `SSVNetwork.updateDeclareOperatorFeePeriod()` - `SSVNetwork.updateExecuteOperatorFeePeriod()` - `SSVNetwork.updateLiquidationThresholdPeriod()` - `SSVNetwork.updateMinimumLiquidationCollateral()` - **(Fix Review)** `SSVNetwork.setRegisterAuth()`: If a validator is authorized, calls `SSVNetwork.registerValidator()`, then has their authorization revoked, the validator is still active and can call `SSVNetwork.reactivate()` if the cluster becomes inactive. - **(Fix Review)** `SSVNetwork.upgradeModule()`: This privileged function has a high exploit risk if the `owner` is compromised. If a module is upgraded to a malicious contract, the malicious contract could drain the `SSVNetwork` contract of all funds.

LowRenouncable OwnershipAcknowledged

If the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function protected by the `onlyOwner` modifier will no longer be able to be executed.

LowRisk of Killing UpgradesAcknowledged

The UUPS pattern was chosen for the upgradeable contracts. One of the drawbacks of using such a pattern is that if a future implementation does not implement the `upgradeTo()` function, then upgrades for the contracts have effectively been killed.

LowSame Validator Public Key Registered by Different CallersAcknowledged

When registering a validator, the validator's `publicKey` and the caller's address are hashed to create a validator ID, which is considered the unique identifier for the validator and is used as the key in the `validatorPKs` mapping. Since the `registerValidator()` function does not verify that a validator's `publicKey` is unique when registering, multiple different users can call `registerValidator()` with the same `publicKey` but have unique validator identifiers.

LowUncapped State VariablesAcknowledged

There are numerous state variables within `SSVNetwork` that do not have a lower or upper bound limit. While the functions that update these state variables are only accessible to the owner, an improper configuration could lead to a negative user experience or loss of funds. 1. `operatorMaxFeeIncrease`: No upper bound limit. If set to a large value, operators could increase their fee substantially after `declareOperatorFeePeriod` has passed. Unaware cluster owners are then at risk of paying high operator fees or liquidation. 2. `declareOperatorFeePeriod`: No lower bound limit. If set to a small value, operators could update their fee before cluster owners have enough time to respond. 3. `executeOperatorFeePeriod`: No lower bound limit. If set to a small value, operators would not have an adequate amount of time to execute their fee update. 4. `minimumBlocksBeforeLiquidation`: No upper bound limit. If set to a large value, all clusters are at risk for liquidation. 5. `minimumLiquidationCollateral`: No upper bound limit. If set to a large value, all clusters are at risk for liquidation.

LowUse of Unsafe Transfer FunctionsAcknowledged

Functions `CoreLib.transferBalance()` and `CoreLib.deposit()` use the ERC20 functions `transfer()` and `transferFrom()` to transfer funds from the underlying token (`SSVStorage.token`). However, there [exist tokens that do not return a value](https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca) and would be handled wrongly in the given context.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (9)

  • contracts/libraries/OperatorLib.sol
  • contracts/libraries/ClusterLib.sol
  • contracts/ISSVNetworkCore.sol
  • contracts/ISSVNetwork.sol
  • contracts/SSVNetworkViews.sol
  • contracts/libraries/Types.sol
  • contracts/libraries/NetworkLib.sol
  • contracts/SSVNetwork.sol
  • contracts/ISSVNetworkViews.sol

Part of SSV Network’s security record

SSV Network
Audits2 audits by 2 firmsLast audit Nov 2024, by ChainSecurity.
Critical findingsNo open critical findingsAcross 2 reports.
Open SSV Network’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us