Skip to content

Conversation

@naddison36
Copy link
Collaborator

The stakeEth(...) function is used to perform the initial 1 ETH deposit for a new validator that is in the REGISTERED state. The contract defines a constant MAX_VERIFIED_VALIDATORS set to 48, which is intended to be the maximum number of validators the system can manage.

However, the check to enforce this limit contains an off-by-one error. The require statement uses a strict less-than (<) operator, which prevents the number of validators from reaching the intended maximum.

function stakeEth(...) external onlyRegistrator whenNotPaused {
    // ...
    bytes32 pubKeyHash = _hashPubKey(validatorStakeData.pubkey);
    ValidatorState currentState = validator[pubKeyHash].state;
    // ...
    if (currentState == ValidatorState.REGISTERED) {
        // ...
        // Limits the number of validator balance proofs to verifyBalances
        // @audit-issue This check should use `<=` to allow exactly 48 validators.
        require(
            verifiedValidators.length + 1 < MAX_VERIFIED_VALIDATORS,
            "Max validators"
        );

        // ...
        firstDeposit = true;
        validator[pubKeyHash].state = ValidatorState.STAKED;
    }
    // ...
}

When there are 47 validators in the verifiedValidators array, verifiedValidators.length is 47. The check evaluates to 47 + 1 < 48, which is 48 < 48. This condition is false, causing the transaction to revert. As a result, it is impossible to stake for the 48th validator, effectively limiting the system to a maximum of 47 validators instead of the intended 48.

Recommendation(s): Consider changing the strict inequality operator (<) to a less-than-or-equal-to operator (<=). This will ensure that the number of verified validators can reach the MAX_VERIFIED_VALIDATORS limit.

Code Change Checklist

To be completed before internal review begins:

  • The contract code is complete
  • Executable deployment file
  • Fork tests that test after the deployment file runs
  • Unit tests *if needed
  • The owner has done a full checklist review of the code + tests

Internal review:

  • Two approvals by internal reviewers

@github-actions
Copy link

github-actions bot commented Oct 9, 2025

Warnings
⚠️ 👀 This PR needs at least 2 reviewers

Generated by 🚫 dangerJS against 4228f4a

@codecov
Copy link

codecov bot commented Oct 9, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 40.76%. Comparing base (a241689) to head (4228f4a).
⚠️ Report is 243 commits behind head on nicka/pectra.

Additional details and impacted files
@@               Coverage Diff                @@
##           nicka/pectra    #2683      +/-   ##
================================================
- Coverage         42.66%   40.76%   -1.91%     
================================================
  Files               121      122       +1     
  Lines              5670     5748      +78     
  Branches           1502     1524      +22     
================================================
- Hits               2419     2343      -76     
- Misses             3248     3402     +154     
  Partials              3        3              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@sparrowDom sparrowDom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@naddison36 naddison36 merged commit 8c3cf41 into nicka/pectra Oct 9, 2025
14 of 19 checks passed
@naddison36 naddison36 deleted the nicka/pectra-max-validators branch October 9, 2025 07:33
naddison36 added a commit that referenced this pull request Nov 3, 2025
* add proof for validator

* add deposit proofs

* add balance proof for all validators

* Mock Beacon Roots (#2596)

* add MockBeaconRoots on hoodi to reflect the state on meinnet

* add support to verify Slot on hoodi

* Added requestValidatorWithdraw Hardhat task

* Updated validator proofs

* add more validators data

* add a last validator

* Add balance proof for 20 validators

* add a new validators

* add nex balance proof for 21 validators

* add support for hoodie when calling registerValidator

* add check that validator ids are in ascending order

* add a task for the initial 1 ETH deposit

* Fix bug in verifyBalances when an active validator is exited

* Prettier

* sort the operator ids instead of throwing an error

* prettier

* Changed requestConsolidation on the old staking strategy to only be called by governor

* Added getValidator Hardhat task

* Remove log output

* snapBalances changed to be external from public

* Added onlyRegistrator to snapBalances to prevent denial of calling verifyBalances

* Removed onlyRegistrator from verifyBalances as its on snapBalances

* Added extra checks on the number of balance leaves and proofs in verifyBalances

* Made Endian library MIT as its used by BeaconOracle which is also MIT

* Changed the initial validator deposit to be 32 ETH

* Formatted the output of getValidator Hardhat task

* Added more data to getValidator hardhat task

* Updated validatorWithdrawal Natspec with new understanding of how full withdrawals work

* Added Hoodi upgrade script for the staking strategy

* Removed reverts from BeaconRoots contract as the underlying beacon roots contract will revert
Removed the old deploy of MockBeaconRoots to mainnet

* Deployed new MockBeaconRoots

* Upgraded the CompoundingStakingSSVStrategyProxy on Hoodi

* Fixes to Hardhat tasks

* add deployment files

* Changed the WETH contract for Hoodi

* Changed the Hoodi strategist and governor to be the Relayer

* Governor spelling

* Upgraded the OETH Vault on Hoodi

* Upgrade the compounding staking strategy to use new WETH token

* Added tenderlyUpload to verify a single contract

* beacon proof generators to handle Hoodi

* Deployed new BeaconProofs and CompoundingStakingSSVStrategy

* Add 1 wei of value to validatorWithdrawal call

* verifySlot can now be done from a block
Added snapStakingStrat HH task

* Renamed validatorContainerProof to balancesContainerProof

* Upgraded the native staking contract on Hoodi

* Renamed the ETH/WETH conversion functions

* P2P API support for Hoodi
Iterate over validator balances in snapStakingStrat

* Updated verifySlot options

* Prettier

* Fix storage checker

* Fix old Native Staking fork tests

* bulk exit and remove validator (#2185)

* Added setFeeRecipient to the Native Staking Strategy
Added SSV approve and setFeeRecipientAddress to the Native Staking Strategy's initialize function
Correct the Natspec on how tx fees are handled.

* Updated OETH process diagrams

* Added deploy script to upgrade Native Staking Strategies
Added fork tests to set MEV fee recipient
Removed old native staking fork test file that is no longer being used

* Deployed new Native Staking Strategy to Holesky

* Updated value flows

* Generated new NativeStakingStrategy diagram

* Changed exit and remove validators to be bulk functions

* Prettier

* Fix storage checker

* Prettier

* Changed verifyBalances so a slot before the first pending deposit can be mapped in BeaconOracle
Deployed new staking contracts to Hoodi

* Fixed check in verifyBalances

* Preparing HH tasks to be run as Actions

* Defender Action to verify slots

* Deployed latest compounding staking strategy

* Added getPendingDeposits view function
Added pending deposits to snapStakingStrategy

* Updated snapStakingStrat HH task

* Improved error message when generating verifyBalances proof

* Remove BeaconOracle from new staking strategy (#2608)

* Improved error message when generating verifyBalances proof

* WIP remove BeaconOracle

* Prettier

* Fixed unit tests

* Updated the process diagrams

* Updated verifyBalancesContainer param names

* add comment

* Nicka/pectra remove consolidation (#2609)

* Improved error message when generating verifyBalances proof

* Removed the consolidation logic

* Removed TargetStrategyAdded

* Simplified verifyValidatorBalance

* Cleaned up defender actions

* Deployed to Hoodi

* Add setRegistrator HH task

* add an in depth explanation of the accounting behaviour (#2610)

* Updates to Hardhat tasks

* Natspec cleanup

* Prettier

* Changed validatorWithdrawal to be payable so the 1 wei fee can be paid

* Added deposit root to the getPendingDeposits output
Deployed new version to Hoodi
Improve snapStakingStrategy HH task

* Fix verifyDeposit when the deposit queue is empty

* Updated OETH contracts diagram and staking transitions diagram

* improved getValidator

* Added more logging

* Validator task upgrades (#2614)

* add the ability to register a validator using UUID

* add the ability to stake to the validator using uuid

* add support for getting validator info by validator public key

* Changed verifyFirstPendingDepositSlot to return of the deposit queue is empty or not
Updated Natspec

* Prettier js

* Check if slot of the pending deposit is zero

* Deployed new contracts to Hoodi

* Added off-chain checks for zero value slots

* Added more logging to HH tasks
Fixed full exit logic in validatorWithdrawal

* Fix linter

* Fix full exit logic in HH task

* Fixed partial withdrawal in HH task

* Improved snapStakingStrat HH task

* Added more checks to proof verifications

* DEployed the latest contracts to Hoodi

* Make sure the beacon chain data matches the block used in snapStakingStrat

* adjust comment

* Add pending beacon chain deposits info to snapStakingStrat

* added more validator info to snapStakingStrat

* Updated Natspec

* Call safeApproveAllTokens from initialize function

* Added comment on use of min in _transferWeth

* Added check of validator public key length to registerSsvValidator

* Fixed Natspec on validatorWithdrawal

* Fixed removeSsvValidator Natspec

* Read depositsRoots.length into memory in getPendingDeposits

* Fixed Slither

* Upgraded the contracts on Hoodi

* Minor changes to request validator HH tasks

* Add test coverage of getPendingDeposits

* Add test that collectRewardTokens is unsupported

* Fix off-chain verifyBalances when deposit queue is empty

* Moved check of public key length into `_hashPubKey`

* Update HOODI API key

* Removed beaconOracle ABI as its no longer used

* Removed .out file

* No longer need the optional provider in getSigner factory

* Prettier

* Removed _asset from internal _withdraw function

* Simplified setting of the lastVerifiedEthBalance in _convertEthToWeth
Also updated the comments when lastSnapTimestamp is reset to zero in _convertEthToWeth and _convertWethToEth

* Override setPTokenAddress and removePToken so they revert with "Unsupported function"

* add comment

* OZ C-01  - Postponed Pending Deposit Breaks verifyDeposit and verifyBalances (#2622)

* Changed verifyBalances so it reverts if pending deposits
Added verifyBalancesWithDeposits that can only be called by the staking monitor

* Renamed verifyValidatorPubkey to verifyValidator

* Renamed verifyFirstPendingDepositSlot to verifyFirstPendingDeposit

* Added verifyValidatorWithdrawableEpoch and verifyValidatorPubKeySubTree proofs
verifyDeposit now checks the first pending deposit is not to an exiting validator

* Added verification of deposits back to the verifyBalances
Updated Natspec with maths on calculating the gen indexes
Removed the staking monitor

* Linter

* More Natspec updates

* More Natspec changes

* Remove deposit after validator has exited

* Tidy up the removal of a deposit

* Added DepositValidatorExited event
Added amountWei to the DepositValidatorExiting event

* Changed DepositValidatorExiting to DepositToValidatorExiting

* Fixed stack too deep

* Refactor names in verifyDeposit and verifyBalances

* Renamed param structs in verifyDeposit and verifyBalances

* Updated comment on why a validator with a pending deposit can be exiting

* Fixed logic in verifyBalances checking is an exiting validator is not withdrawable

* verifyValidator can now handle front-run deposits

* Renamed params in verifyDeposit

* Refactored validator storage data

* Removed wethBalance from BalancesVerified event

* Fix verification of slot in verifyFirstPendingDeposit
Added more beacon proof unit tests

* Changed some of the storage variables so they are public

* Fix and improved the beacon proofs fork tests

* WIP fixing strategy unit tests

* More fixing the unit tests

* Renamed block root in verifyBalances to make it clear its for the snapBalances root

* Generated new unit test data

* Fixed verifyBalance Hardhat task

* Add more unit testing data

* Got unit tests working again

* Updated comment on exiting validator

* Added nextDepositID to be a unique identifier of a deposit to a validator (#2624)

* Break from iterating over the deposits once the deposit has been found in verifyValidator for an invalid validator

* Added Compounding Validator State Diagram

* Improved Beacon Proof constants for readability (#2625)

* Removed the unused SourceStrategyAdded event (#2626)

* Added EXITING validator state to help prevent deposits to exiting validators (#2627)

* set validator status to exiting if such transition detected (#2629)

* M-02 registrator protections (#2630)

* Split out the view functions from CompoundingValidatorManager
Added MAX_DEPOSITS and MAX_VALIDATORS

* Limit to only one deposit to an unverified validator at a time

* Removed onlyRegistrator from snapBalances and used a 2 epoch time delay instead to prevent DoS of verifying balances

* Moved MAX_VERIFIED_VALIDATORS check into stakeEth

* Moved MAX_VERIFIED_VALIDATORS check into when REGISTERED block
Renamed setFirstDeposit to resetFirstDeposit

* Updated Natspec

* Fixed unit tests

* Updated validator state diagram after merge

* Fixed check of deposit being processed in verifyBalances

* N-07 Code Simplifications (#2631)

* move index division outside of if/else

* Refactor _snapBalances function to optimize timestamp handling

* Simplify totalValidatorBalance calculation by removing redundant type conversion

* Rename VALIDATOR_STATE enum to ValidatorState for consistency and clarity

* Refactor import statements and update VALIDATOR_STATE to ValidatorState for consistency

* Prettier

* Moved div 2 out of assembly block in Merkle contract

* Removed timestamp from BalancesSnapped event

* Removed timestamp from snapBalances log

---------

Co-authored-by: Nicholas Addison <nick@addisonbrown.com.au>

* N-08 Missing, Incorrect, or Misleading Documentation (#2632)

* Fix parameter description in withdrawSSV function to clarify SSV token withdrawal

* Add description

* Enhance documentation for request and fee functions in PartialWithdrawal library

* Changed SNAP_BALANCES_DELAY to 1 epoch

* Set zero governor on implementation contract

* Deployed latest contracts on Hoodi

* Moved new deposit storage variables for backward compatibility with Hoodi

* Redeployed CompoundingStakingSSVStrategy to Hoodi

* Moved new deposit storage variables back up as now deploying a new staking contract

* Deployed a new compounding staking contract to Hoodi

* Fixed snapStakingStrat

* Fixed verifyBalances when a deposit is done to an exiting validator

* Skip failing unit test for now

* Deployed latest staking contract to Hoodi

* FIxed verifyBalances when there are no pending deposits

* Updated OETH contracts diagram

* Updated verifyDeposit process flow

* simplify snapping balances (#2633)

* simplify snapping balances

* type simplification

* OZ N-03 Checks Effects Interactions (CEI) Pattern (#2634)

* Updated Natspec of verifyDeposit

* Moved 3rd party contract interactions to after storage variables are updated

* various minor changes to SSV staking (#2635)

* improve comments

* add CEI pattern

* add comment

* 70b contract size optimisation

* add comment

* prettier

* Redeployed latest staking contract to Hoodi

* Fixed snapStakingStrat Hardhat task

* Removed root from VerifyBalances Hardhat task

* Added removeValidator Hardhat task

* Fixed registration of new compounding validator

* Fixed verifyBalances Hardhat task

* verifyBalance to default validator slot for verification to the same as the first pending deposit if the deposit queue is empty

* Fixed verifyDeposit Hardhat task

* Improve verifyBalances hardhat task when the deposit queue is empty

* Fixed validator status in snapStakingStrat HH task

* change snap balances delay to 35 slots (#2637)

* add comment (#2638)

* Added dryrun to stakeValidator HH task

* Fixed logging of the deposit ID from stakeValidator HH task

* add the support to verify BLS signature before depositing to validator (#2639)

* add the support to verify BLS signature before depositing to validator

* prettier

* Removed withdrawable check from verifyDeposit (#2640)

* verifyDeposit removed check if the validator of the first deposit was exiting

* Fixed the verifyDeposit unit test data

* allow for multiple full/partial validator exits even if full exit request has already been made (#2641)

(cherry picked from commit 4febfad)

* change validator index from uint64 to uint40 (#2642)

* account as early as possible for the loss because of the front run deposit (#2643)

* [SP - 4] Verify validator type (#2644)

* verify that validator type is the consolidating one

* add test

* Deployed latest to Hoodi

* Fix verifyDeposit Hardhat task

* Redeployed both BeaconProofs and CompoundingStakingSSVStrategy to Hoodi

* Deployed new strategy to Hoodi

* Restrict initial deposit to 1 ETH (#2646)

* Changed DEPOSIT_AMOUNT_WEI to 1 ETH

* Adjusted unit tests for 1 ETH deposits

* Deployed strategy contract with initial 1 ETH deposit

* Changed stakeValidator so sig, withdrawalCredentials and forkVersion are optional
Only verify the sig for 1 ETH deposits

* Fixed validator public key proof unit test

* verifyDeposit slot default to 33 slots ago

* fix slither

* Validator deposit and withdrawal amounts can have decimals

* Format snapStakingStrat

* Do not allow exiting a validator with a pending deposit (#2647)

* Validator deposit and withdrawal amounts can have decimals

* Format snapStakingStrat

* Revert exiting a validator if there is a pending deposit

* prettier

* add check for zero padding in withdrawal credentials (#2645)

* add check for zero padding in withdrawal credentials

* more efficient withdrawal credentials check

* ORGN-01 Array Element Skipping During Deposit Removal in verifyBalances (#2650)

* Fix for: Deposit Balance Undercount Due To Missing Timestamp Validation In verifyDeposit() (#2651)

* add verifyDeposit check to confirm there is no conflicting timestamp

* shorten error

* Fixed unit test of verifyValidator proof

* Deposits to withdrawn validator (#2652)

* add basic files for mocking beacon roots

* add tests for multiple deposits to exiting validator. Also fix bug not storing the deposit data

* add a test where deposit is removed in verifyBalances

* more test checks

* Post merge fix

* Disable linter in MockBeaconProofs

* Fixed unit tests

* Added unit test for a front-run initial deposit

* Added more unit tests for front-run deposit

* Alternative fix to setting the withdrawable epoch (#2654)

* fix tests

* More unit tests (#2655)

* Alternative fix to setting the withdrawable epoch

* Added more unit tests

* Fix inconsistent test failure

* add check that the deposits have been removed

* add comment

---------

Co-authored-by: Nicholas Addison <nick@addisonbrown.com.au>

* SP-12 Miscellaneous General Comments (#2657)

* Changed CompoundingValidatorManager storage gap to 40

* Fixed Natspec of BALANCES_CONTAINER_GENERALIZED_INDEX

* verifyDeposit has early exit if slot of first pending deposit is zero
Default first pending deposit slot to 1 if the beacon deposit queue is empty

* Fixed verificationEpoch calc to use the slot of the parent root (#2656)

* Added more unit tests for verifyBalances

* Fixed the unit tests

* Added unit test for removing an invalid validator

* Deploy to latest to Hoodi

* remove deposit count subtraction as it is no longer needed (#2660)

* simplify withdrawal credential comparison (#2659)

* correct the comment (#2663)

* Refactor verifyBalances to use inclusion proofs of pending deposits (#2658)

* Refactor verifyBalances to use inclusion proofs of pending deposits

* Refactor stakeEth to use the pending deposit root as the deposit identifier.
Ensure the pending deposit root is unique.

* WIP fixing unit tests

* Logical state diagram for the staking strategy

* Fix verifyBalances to not verify pending deposits of exited validators

* More unit test fixes

* Fixes the last of the failing unit tests

* verify deposits to exiting validators (#2664)

* add the support to handle verifying deposits to exited validators

* verifyBalances no longer removes deposits

* remove event

* remove deposit when done to an exiting validator

* Removed setting the validator as EXISTING from verifyDeposit

* Updated staking state diagram

* Can now use a normal for loop of the deposits in verifyBalances

* Moved firstPendingDepositEpoch to just before it is used

* change error message

* add pausable ability to the validator

---------

Co-authored-by: Nicholas Addison <nick@addisonbrown.com.au>

* Exiting validator (#2665)

* do not exit a validator as long as it has pending deposits

* Fixed verifyBalances - deposit loop inside the validator loop using different iterator

* Prettier

---------

Co-authored-by: Nicholas Addison <nick@addisonbrown.com.au>

---------

Co-authored-by: Domen Grabec <grabec@gmail.com>

* Restrict full withdrawals for non activateable validators (#2662)

* make it mandatory for validator to have active balance before allowing full withdrawals

* minor update

* fix bugs

* add test

* prettier

* Updated the validator state diagram

* make some variables internal to save contract space

* simplification

* Added ACTIVE validator state (#2666)

* Added ACTIVE validator state

* Fixed ACTIVE change
Updated unit tests

* Updated state diagram

* Deploy new strategy to Hoodi

---------

Co-authored-by: Nicholas Addison <nick@addisonbrown.com.au>

* Logging and linter

* Fix to allow multiple full validator exits (#2667)

* Allow a validator full exit when it is already exiting

* Updated validator state diagram

* Increased the min balance to be active to over 32.25 ETH (#2668)

Co-authored-by: Domen Grabec <grabec@gmail.com>

* Gas optimization in verifyBalances to only iterate over the deposits if the validator has a zero balance (#2671)

* restrict pending deposit index to uint32 (#2670)

* Restricted the pending deposit index to uint32 as the pending deposits container height is only 28

* Added check that pendingDepositIndex is < 2**27 in verifyPendingDeposit

* Can verify a deposit if the validator is exiting and the pending deposit queue is empty (#2669)

* Can verify a deposit if the validator is exiting and the pending deposit queue is empty

* add comment

---------

Co-authored-by: Domen Grabec <grabec@gmail.com>

* Deployed the latest staking strategy to Hoodi

* Fixed verifyDeposit Hardhat task

* Fixed verifyBalances Hardhat task when there is a pending deposit

* Added negative unit tests

* Added more unit tests

* ORGN-02 can make a validator INVALID with an incorrect validator type (#2673)

* Allow verification of a non 0x02 validator so it can be marked as INVALID

* Added unit test for verifying an invalid validator type

* pass withdrawal credentials when verifying validator (#2674)

* Fixed verifyValidator Hardhat task

* Fixed beacon proof fork test

---------

Co-authored-by: Domen Grabec <grabec@gmail.com>

* Deployed the latest contracts to Hoodi

* Fixed the validator states in Hardhat task snapStakingStrat

* typo

* typo

* Improve getValidator Hardhat task output

* remove comment

* remove StakingMonitorChanged event from CompoundingValidatorManager (#2682)

* MAX_VERIFIED_VALIDATORS off by one (#2683)

* Fix comments (#2684)

* automated withdrawals from the staking strategy and validators (#2685)

* Allow withdraws from the staking strategy by the Registrator for automation

* Added signMessage HH task for Etherscan ownership verification

* Deployed latest strategy contract to Hoodi

* Added autoValidatorWithdrawals Hardhat task

* Fixed unit tests

* Changed execute option to dryrun

* Created reusable totalPartialWithdrawals function
Fixed summing of partial withdrawal amount as its in Gwei

* Remove upgrade of old native staking strategies (#2686)

* Revert upgrading the old native staking strategies

* Restored old Hardhat tasks to bulk exit and remove validators from the old native staking strategies

* Added lodestar packages to lock file

* Bumped deploy script numbers

* auto deposits to validators (#2688)

* Added autoValidatorDeposits Hardhat task

* Set the new staking strategy as the default for the OETH Vault

* Refactor auto deposit

* Fix auto deposits (#2689)

* Fix auto deposits to include pending deposits

* Increased MAX_DEPOSITS from 12 to 32
Deployed latest strategy contract to Hoodi

* fix the problem where prettier isn't able to load the plugin automatically using pnpm

* fix small bug

* Added queue position to pending deposits in snapStakingStrat (#2691)

* Improve formatting of snapStakingStrat

* Added unit tests address for CompoundingStakingStrategyProxy

* Deployed the new staking strategy contracts to mainnet (#2692)

* Deployed the new staking strategy contracts to mainnet

* Added 154_upgrade_native_staking to .migrations file

* Add governance proposal id

* Fix spelling in README

* Added BEACON_PROVIDER_URL to CI for mainnet fork tests

---------

Co-authored-by: Clément <clemmoller@gmail.com>
Co-authored-by: Domen Grabec <grabec@gmail.com>
Co-authored-by: Clément <55331875+clement-ux@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants