Skip to content

Resolve issues with verifyDeposit and verifyBalances #2622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: nicka/pectra
Choose a base branch
from

Conversation

naddison36
Copy link
Collaborator

@naddison36 naddison36 commented Aug 15, 2025

Changes

  • verifyDeposit:
    • checks that the validator of the first pending deposit is not exiting. It does this using a second proof to the validator's withdrawable epoch. This proof can be after the first pending deposit proof so deposits to new validators have had time to be registered on the beacon chain.
    • checks if the strategy's validator is exiting. If it is, it records the withdrawable epoch in the deposit data.
  • verifyBalances:
    • Verifies validators before deposits so exited validators can have their state set to EXITED
    • If a deposit is to an exiting validator, it checks the withdrawable epoch has not been reached
    • will revert if there is a deposit to an exiting validator that is on or after the withdrawable epoch and the validator has not yet withdrawn

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

Deploy checklist

Two reviewers complete the following checklist:

- [ ] All deployed contracts are listed in the deploy PR's description
- [ ] Deployed contract's verified code (and all dependencies) match the code in master
- [ ] Contract constructors have correct arguments
- [ ] The transactions that interacted with the newly deployed contract match the deploy script.
- [ ] Governance proposal matches the deploy script
- [ ] Smoke tests pass after fork test execution of the governance proposal

Added verifyBalancesWithDeposits that can only be called by the staking monitor
…ee proofs

verifyDeposit now checks the first pending deposit is not to an exiting validator
Copy link

github-actions bot commented Aug 15, 2025

Warnings
⚠️ 👀 This PR needs at least 2 reviewers

Generated by 🚫 dangerJS against 92beddd

// Verify the withdrawableEpoch on the validator of the strategy's deposit
IBeaconProofs(BEACON_PROOFS).verifyValidatorWithdrawable(
depositBlockRoot,
validatorData.index,
Copy link
Member

Choose a reason for hiding this comment

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

if we verify Index here do we have a need for a separate verifyValidator function?

Copy link
Member

Choose a reason for hiding this comment

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

This function could promote a validator from STAKED to VERIFIED and add an entry to verifiedValidators

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.

Left a few comments. Great job on these changes aside from 2 issues I think they are solid

bytes validatorPubKeyProof;
}

struct DepositValidatorData {
Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe name this DepositValidatorProofData

@@ -479,6 +493,22 @@ abstract contract CompoundingValidatorManager is Governable {
emit ValidatorVerified(pubKeyHash, validatorIndex);
}

struct FirstPendingDepositData {
Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe name this FirstPendingDepositProofData


// Verify the withdrawableEpoch on the validator of the strategy's deposit
IBeaconProofs(BEACON_PROOFS).verifyValidatorWithdrawable(
depositBlockRoot,
Copy link
Member

Choose a reason for hiding this comment

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

🔴 I think this should be validatorBlockRoot instead of depositBlockRoot

// Store the exit epoch in the deposit data
deposit.withdrawableEpoch = validatorData.withdrawableEpoch;

emit DepositValidatorExiting(
Copy link
Member

Choose a reason for hiding this comment

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

super nit: maybe DepositToValidatorExiting

validatorData.pubKeyProof
);

// If the validator is exiting
Copy link
Member

Choose a reason for hiding this comment

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

comment nit: maybe add this is most likely to validator being slashed

// If there are no deposits then we can skip the deposit verification
// This section is after the validator balance verifications so an exited validator will be marked
// as EXITED before the deposits are verified. If there was a deposit to an exited validator
// then the deposit can only be removed once the validator is fully exited.
Copy link
Member

Choose a reason for hiding this comment

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

nit comment addition:
... validator is fully exited. The ETH deposited to an exiting validator remains in the beaconState.pendingDeposits as part of the beacon chain's deposits_to_postpone array inside which the deposit remains as long as the validator has not fully exited from the beacon chain. Once the validator has fully exited the postponed deposit is credited back to the strategy contract.

// The verification of the validator the first pending deposit is for must be on or after when
// `snapBalances` was called.
require(
balancesMem.timestamp <= validatorVerificationBlockTimestamp,
Copy link
Member

Choose a reason for hiding this comment

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

A thought: what if we remove the validatorVerificationBlockTimestamp and just calculate it as: balancesMem.timestamp + SLOT_DURATION. This would impose an extra limitation to verifyBalances where 3 slots in succession are required to produce a block instead of 2.

// now has to wait until the validator's balance is verified to be zero.
require(
firstPendingDeposit.slot < depositData.slot ||
verificationEpoch < depositData.withdrawableEpoch ||
Copy link
Member

Choose a reason for hiding this comment

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

🔴 in StakeEth the depositData.withdrawableEpoch is set to FAR_FUTURE_EPOCH. This means if verify deposit has not been called yet on a withdrawable validator the verificationEpoch < depositData.withdrawableEpoch check will always be true and make this whole require pass.

I think this require should look like this:

require(
  firstPendingDeposit.slot < depositData.slot ||
      (
         verificationEpoch < depositData.withdrawableEpoch &&
         depositData.withdrawableEpoch != FAR_FUTURE_EPOCH
       ) ||
      validatorState[depositData.pubKeyHash] ==
          VALIDATOR_STATE.EXITED,
      "Deposit likely processed"
  );

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.

2 participants