Skip to content

WIP - Native Staking post Pectra upgrade #2559

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 227 commits into
base: master
Choose a base branch
from
Draft

Conversation

naddison36
Copy link
Collaborator

@naddison36 naddison36 commented Jun 30, 2025

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

Contracts

The scope of the contracts of this build is

  • CompoundingStakingSSVStrategy is the new staking contract.
  • CompoundingValidatorManager is inherited by CompoundingStakingSSVStrategy
  • BeaconOracle called from CompoundingValidatorManager
  • BeaconProofs called from CompoundingValidatorManager

The following libraries have also be built

  • BeaconRoots
  • BeaconProofsLib
  • PartialWithdrawal
  • BeaconConsolidation
  • Merkle
  • Endian

Build

Keeping the contract under 24Kb has been a problem during development. The following will show the contract size of the compile contracts including CompoundingStakingSSVStrategy.

export CONTRACT_SIZE=true
npx hardhat compile

Testing

Unit Tests

Are main strategy unit tests in contracts/test/strategies/compoundingSSVStaking.js

There are also unit tests of the beacon merkle proofs in contracts/test/beacon/beaconProofs.js

yarn test

Fork Tests

There are no fork tests of the strategy as its hard to mock the beacon chain. But there are fork tests of various beacon chain contracts and libraries. These includes:

  • contracts/test/beacon/beaconConsolidation.mainnet.fork-test.js
  • contracts/test/beacon/beaconOracle.mainnet.fork-test.js
  • contracts/test/beacon/beaconProofs.mainnet.fork-test.js
  • contracts/test/beacon/beaconRoots.mainnet.fork-test.js
  • contracts/test/beacon/partialWithdrawal.mainnet.fork-test.js

Hoodi Testnet

Contracts have been deployed to Hoodi with deployment script contracts/deploy/hoodi/001_core.js

Local testing

Testing against a local fork node using real beacon chain data

yarn run node

# Replace the BeaconRoot contract used by EIP-4788
npx hardhat mockBeaconRoot --network localhost

# impersonate 2/8 guardian with SSV tokens
export IMPERSONATE=0x4FF1b9D9ba8558F5EAfCec096318eA0d8b541971
# trasnfer SSV from guardian to new staking strategy
FORK=true npx hardhat transfer --symbol SSV --amount 10 --to 0xaF04828Ed923216c77dC22a2fc8E077FDaDAA87d --network localhost

# Deposit WETH from Vault to the new staking strategy
FORK=true npx hardhat depositToStrategy --symbol OETH --strategy CompoundingStakingSSVStrategyProxy --assets WETH --amounts 32 --network localhost

# impersonate validatorRegistrator  
export IMPERSONATE=0x4b91827516f79d6F6a1F292eD99671663b09169a

FORK=true npx hardhat registerValidator --ssv 10 --pubkey 0xb3aad1f5a7b6bfbcd81b75f8a60e6e54cc64fbf09cb05a46f92bab8c6c017106d643d1de70027b5d30fa943b9207c543 --operatorids 424,425,426,427 --shares 0xa819d1092f8c335cf85d318e9b6a4d82934190294c4a687a92ddb6cb9cd8ce3eee64676b899854535e34af10bd3575291077bb8617ed84424f80608a76664771129aea2f7a7cfcd390a6db04e87dfaefb170f057a2404210b04d1e1c0e1e9b4f886cef456a3dad1da1ac67a2976c59fe8cdb8442de60d70164ca8816dc69037a76a0ba7c06db9e954c67eadab7a4b5bdade0f92bc67ce24ef3e2a81141282124b8f00687fbf322604687b6eab9f62bdd23886c0418eb76878ffd49c26101b931a9dc12231d601a80aec054c382977168109863c1dfb477de7f32836a1d3ef53f76fe8c7c4c5ca79d8bd0194c30807f35b62fa199237b1ec2ad9f73a26a8dd561a6c9fd88b90a64a6a6e9e7c2a0401def70dd3858300366cbe1bcaec5fa8c009e57fe9150a98733ecc843d2c92f839ab31f9b73ee489ea059aff2c1576a8ae81a4db45ef417b07d640dea3fd1f70c279433a78044664e96d36c1fb7851166e601c42af2e9d7a8b7adeffd62a6e7cea8fb8de1610991b63609f833d5c7e2272c7caf07cd49645bf0d059a1f8b7b749b51b044de99df6511d378af6a72503ddb141344bb608c56965060d7d5d6bc6acb642a8b629f7997a5ebc1e6173acc538299acbd500686a0898ba6e33474fcef7f563dec872a5147b6cf13a0e86b4f8e3232698f24f429e9dfd6541bdd8be4e73d216740481ea08a77619fbc6cfc22bda7c43283d8b1057cb1cd66024735e739b875e55d5fcb5dd988dbfe9b2b2196f93d586643ba5642e2d486acb8a841e3901c53676e59ed6562ac0ea23d2e0f395bfbc12f75500352252d20178428df1799cda8c58b423a6c301549cbf75bffe97d1dd8d4ca9ef217e9f16ec2d6bb7fa5d04dc729bfafb7c262e33aa2b13bd4ff52e1050b7c9fe4768c63a8d82a5cb6c959a8e5d9170e82afb4f47b6055f246c883716a97299ee76eacb11b0d1e4beeaf5efd3ecd15f6395b40e9e29b06c308e22d833460b363c8e8ec5497f53866b1655ecca4fe5c34860a2f7d88fee2c3f98685af8729829c971fc1a16d6affb816d559e2440999e8db741148fa33db51a218ec2abdd6bdf190c4b7721b7dd36c1a1788bfb3bc14aeb979ce0e059b46bda1d182180fe46d7c56de8956f6ce64b85b2cca6e31e8c8ea30c3090bbe7454b217c80979bcdb0c802b5a4a0795edd4bcb11fdb7114bc1e59653274689530fcd6f5e84a5e7ad23e1f26129e48bfe450566791126dba7a3da69ad5e6730f498c267e3ca89760a9b6a7cb8dca4c6980fd58433193f78df0562429fb4bbe4e1484adb443e5dd50f3f4a91af0d3d37b987c623945cc5c6fb2db010fee3992c9a16d026410af8d608969da3367628feb29106497c6ef529dc7e48de81e1036c2bf0068d33e7f69ab65c3c13930b3aba111495c80e906542f6047fb7dcc3e770a7b43d87f310700d87a15ff138965bfc78f9d16e875825535d3aca4328aa725939e4a4544cd1fe8e772258485c41b6444b620200b3b2c5172a9ea13b79747157f1417fb8cb5eaf457571913696c779c7300991eeb51b7d61e99735aedb6e7aa9c24ec90f669706bafc28faa585e71d76db262d425d7882c2d7a00013ec4274c01d71564fae5e00f01f8c122728315fef1b80c4e8c1180a82565e82576e1018da9aaae9b1d3879350fd46f7cf3d93366236ea253d9dc4395237c2a06b27fcd19896294a320049773c3d9ac2001f75d3d0c34879f6ec31f4b43bde164147311d020bf5458deab4e5c804f00878d5938e228ce76034c34fff012051cd5a31cf7979cf41e6bc0c53a23b3ee4e8f0a9c20741a6167d0b15d8fbbc78adaaa687bb9c916aee900ebbeb7d75af --network localhost

FORK=true npx hardhat stakeValidator --amount 1 --pubkey 0xb3aad1f5a7b6bfbcd81b75f8a60e6e54cc64fbf09cb05a46f92bab8c6c017106d643d1de70027b5d30fa943b9207c543 --sig 0x8754ee1ac90130cfb828172ed9e81fbbdf86f35298f05b3c152db673f2a6a82873e7acbdb5e2d0d035e253c396266254160e627e945a228a4c034157c7a4418efd08173e780ecbc30cedaae05f8239a453df924d8e8e11460f5e10b1c2024012 --network localhost

# Copy beacon root for the block after the the execution layer deposit
npx hardhat copyBeaconRoot --block 22923667 --network localhost
# Map block to slot of when the deposit on the execution layer was made
npx hardhat verifySlot --block 22923666 --network localhost

# Copy beacon root for a recent block on mainnet to verify the validator against. This has beacon chain slot 12145631
npx hardhat copyBeaconRoot --block 22923678 --network localhost
# Check the root was copied to the local forked node
npx hardhat beaconRoot --block 22923678 --network localhost

# Verify the validator to the previous slot of the block copied before
npx hardhat verifyValidator --index 1930685 --slot 12145630 --network localhost

# A recent slot is 12144903 which is for block number 22922951
npx hardhat copyBeaconRoot --block 22922952 --network localhost

# Verify deposit using execution layer deposit block
# and recent slot - the same used to verify the validator
npx hardhat verifyDeposit --block 22922652 --slot 12144903 --root 0x9de115e290009d56d89d8d72d7ed1528aa2c2586609f879cbb4ba215f92f5d27 --network localhost

Deployment

Added native staking value transitions diagram
Started beacon proofs unit tests
proveBalances does old doAccounting first
Upgraded @nomiclabs/hardhat-ethers
Copy link

github-actions bot commented Jun 30, 2025

Warnings
⚠️ 👀 This PR needs at least 2 reviewers

Generated by 🚫 dangerJS against e6e0679

Copy link

codecov bot commented Jun 30, 2025

Codecov Report

❌ Patch coverage is 80.33241% with 71 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.07%. Comparing base (244da21) to head (e6e0679).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
.../strategies/NativeStaking/ValidatorRegistrator.sol 0.00% 33 Missing ⚠️
contracts/contracts/beacon/BeaconOracle.sol 40.90% 13 Missing ⚠️
...gies/NativeStaking/CompoundingValidatorManager.sol 93.18% 12 Missing ⚠️
contracts/contracts/beacon/BeaconConsolidation.sol 0.00% 10 Missing ⚠️
...es/NativeStaking/CompoundingStakingSSVStrategy.sol 93.61% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2559      +/-   ##
==========================================
+ Coverage   35.13%   40.07%   +4.94%     
==========================================
  Files         111      121      +10     
  Lines        5303     5664     +361     
  Branches     1405     1501      +96     
==========================================
+ Hits         1863     2270     +407     
+ Misses       3438     3393      -45     
+ Partials        2        1       -1     

☔ 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.

consolidationTargetStrategy = address(0);

// Unpause the strategy to allow further operations
_unpause();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we can add a separate consolidation strategy to manage the accounting during the consolidation. That will make changes to the existing strategies minimal and move most of the consolidation verification logic out of the new strategy.

I'll experiment on a branch


// TODO what if the last validator was exited rather than consolidated?
// slither-disable-start reentrancy-no-eth
function verifyConsolidation(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unfortunately the consolidation queue on the beacon chain does not contain the slot the request was made like the pending deposit queue. This means we can't do similar logic to verifying deposits of looking at the front of the queue and working out if our deposit must have been processed or not.

class PendingConsolidation(Container):
    source_index: ValidatorIndex
    target_index: ValidatorIndex

https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#pendingconsolidation

class PendingDeposit(Container):
    pubkey: BLSPubkey
    withdrawal_credentials: Bytes32
    amount: Gwei
    signature: BLSSignature
    slot: Slot

https://ethereum.github.io/consensus-specs/specs/electra/beacon-chain/#pendingdeposit

// for each validator
for (uint256 i = 0; i < verifiedValidatorsCount; ++i) {
// for each validator in reserve order so we can pop off exited validators at the end
for (uint256 i = verifiedValidatorsCount; i > 0; ) {
Copy link
Member

Choose a reason for hiding this comment

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

you could also put the whole for statement in the same line:

for (uint256 i = verifiedValidatorsCount - 1; i > 0; i--) {

The verifiedValidatorsCount will always be >= 1

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, yes, because of the earlier if (verifiedValidatorsCount > 0) {

Thanks

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe not. The last loop when i = 0 is skipped.

Copy link
Member

Choose a reason for hiding this comment

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

yeah true should be:

for (uint256 i = verifiedValidatorsCount - 1; i >= 0; i--) {


// Move the last validator that has already been verified to the current index.
// There's an extra SSTORE if i is the last active validator but that's fine,
// It's not a common case and the code is simpler this way.
verifiedValidators[i] = verifiedValidators[
verifiedValidatorsCount
Copy link
Member

Choose a reason for hiding this comment

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

do we need a comment explaining that this would typically be verifiedValidatorsCount - 1 but is without -1 because the verifiedValidatorsCount has been subtracted from 1 step before?

bytes calldata targetPubKey,
address targetStakingStrategy
) external nonReentrant whenNotPaused onlyRegistrator {
require(
Copy link
Member

Choose a reason for hiding this comment

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

should we check that the consolidationCount == 0. I know that the whenNotPaused will prevent 2 consolidations from happening at the same time, but as Daniel mentioned other things can pause/unpause the strategy. Would be cool to have an extra check.

view
returns (bytes32 parentRoot)
{
// Commented out the following checks as it makes unit and fork testing very difficult.
Copy link
Collaborator

@DanielVF DanielVF Jul 25, 2025

Choose a reason for hiding this comment

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

🟡 Is this a temporary comment out? If so, seems like we need a way to ensure it gets turned on for production.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I dug into this today. We can safely remove the timestamp too old checks as the BeaconRoots contract from EIP-4788 will revert if the stored timestamp does not match the timestamp being requested.

FYI, I have Hardhat task that will get the beacon block root for a particular block. For example, here's the first block from the Denub release which included EIP-4788

npx hardhat beaconRoot --block 19426587 --mainnet true
Block 19426587 has parent beacon block root 0xb35bb80bc5f4e3d8f19b62f6274add24dca334db242546c3024403027aaf6412

The hardhat task can get blocks older than 8,191 blocks as it calls with a blockTag one after the block we are interested in

async function beaconRoot({ block, mainnet }) {
  // Either use mainnet or local fork to get the block timestamp
  const provider = mainnet ? getProvider() : ethers.provider;

  // Get timestamp of the block
  const fetchedBlock = await provider.getBlock(block);
  if (fetchedBlock == null) throw Error(`Block ${block} not found`);

  const { timestamp } = fetchedBlock;
  log(`Block ${block} has timestamp ${timestamp}`);

  const data = defaultAbiCoder.encode(["uint256"], [timestamp]);
  log(`Encoded timestamp data: ${data}`);

  const root = await provider.call(
    {
      to: addresses.mainnet.beaconRoots,
      data,
    },
    block + 1 // blockTag
  );

  console.log(`Block ${block} has parent beacon block root ${root}`);

  return { root, timestamp };
}

bytes calldata slotProof,
bytes calldata blockProof
) external returns (bytes32 blockRoot) {
require(_blockToSlot[blockNumber] == 0, "Block already mapped");
Copy link
Collaborator

Choose a reason for hiding this comment

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

This code could double run for slot zero, since the require statement would not trigger.

(Don't know if that actually matters)

bytes calldata validatorPubKeyProof,
bytes32 balancesLeaf,
bytes calldata validatorBalanceProof
) external onlyRegistrator {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would seem like clean symmetrical code to have this also check the contract is paused before allowing a call to it. (The ValidatorRegistrar does do this this check)

@naddison36
Copy link
Collaborator Author

naddison36 commented Jul 28, 2025

Developer Security Review

Easy Checks

Authentication

  • Never use tx.origin
  • Every external/public function is supposed to be externally accessible
  • Every external/public function has the correct authentication
  • All initializers have onlyGovernor
  • Each method that changes access control has the correct access control

Ethereum

  • Contract does not send or receive Ethereum.
    • CompoundingStakingSSVStrategy sends ETH to
      • the Beacon deposit contract in stakeEth
      • WETH in withdraw and withdrawAll
      • 1 wei fee when requesting a withdrawal in validatorWithdrawal
    • NativeStakingSSVStrategy send 1 wei fee when requesting validator consolidation in requestConsolidation.
  • Contract has no payable methods.
    • CompoundingStakingSSVStrategy has a payable receive function to receive ETH from
      • the WETH contract
      • MEV rewards
  • Contract is not vulnerable to being sent self destruct ETH
    • Unlike NativeStakingSSVStrategy, CompoundingStakingSSVStrategy can receive ETH from anyone without impacting the accounting. Donated ETH is just added to the strategy's balance in the next snapBalances/verifyBalance cycle

Cryptographic code

  • This contract code does not roll it's own crypto.
    • BeaconProofs adds a library called Merkle to verify merkle proofs. This based off OpenZeppelin's Merkle library.
    • This is not really crypto, but BeaconProofs also adds a library called Endian that converts big and little endian number formats.
  • No signature checks without reverting on a 0x00 result.
  • No signed data could be used in a replay attack, on our contract or others.
    • CompoundingStakingSSVStrategy.stakeETH does have a signature for beacon deposits. This can only be called by the validator registrator. The initial 1 ETH deposits to a new validator can not replayed as validator's state would be in staked and only registered and verified validators can stakeETH. A compromised validator registrator can repeatedly call stakeETH if there is enough WETH/ETH in the strategy contract, but the validator's withdrawal credential has already been verified to be the staking contract so those funds are still under control of the staking contract.

Gas problems

  • Contracts with for loops must have either:
    • A way to remove items
    • Can be upgraded to get unstuck
    • Size can only controlled by admins
  • Contracts with for loops must not allow end users to add unlimited items to a loop that is used by others or admins.

Black magic

  • Does not contain selfdestruct
  • Does not use delegatecall outside of proxying. If an implementation contract were to call delegatecall under attacker control, it could call selfdestruct the implementation contract, leading to calls through the proxy silently succeeding, even though they were failing.
  • Address.isContract should be treated as if could return anything at any time, because that's reality.

Overflow

  • Code is solidity version >= 0.8.0
  • All for loops use uint256

License

  • The contract uses the appropriate limited BUSL-1.1 (Business) or the open MIT license
    • All new contracts are under BUSL-1.1 with the exception of BeaconOracle which is MIT. The Merkle and Endian libraries are also MIT
  • If the contract license changes from MIT to BUSL-1.1 any contracts importing it need to also have their license set to BUSL-1.1

Proxy

  • No storage variable initialized at definition when contract used as a proxy implementation.
  • Any added storage slots are after existing slots.
    • NativeStakingSSVStrategy adds new storage slots to an exiting contract but they are after the existing slots.
  • Any added inheritance does not affect storage slots for upgradable contracts.

Events

  • All state changing functions emit events
    • withdrawSSV doesn't emit and event but it's just a thin wrapper to SSVNetwork's withdraw which emits ClusterWithdrawn

Medium Checks

Rounding and casts

  • Contract rounds in the protocols favor
  • Contract does not have bugs from loosing rounding precision
  • Code correctly multiplies before division
  • Contract does not have bugs from zero or near zero amounts
  • Safecast is aways used when casting

Dependencies

  • Review any new contract dependencies thoroughly (e.g. OpenZeppelin imports) when new dependencies are added or version of dependencies changes.
    • OZ's Merkle library is copied. This is from OZ contracts v4.8.0 as it's simpler than the latest version.
  • If OpenZeppelin ACL roles are use review & enumerate all of them.
  • Check OpenZeppelin security vulnerabilities and see if any apply to current PR considering the version of OpenZeppelin contract used.

External calls

  • Contract addresses passed in are validated
    • This is not done in CompoundingValidatorManager.constructor, setRegistrator and addSourceStrategy. Adding will push the contract oversize.
  • No unsafe external calls.
    • beacon deposit
    • beacon withdrawal request
    • beacon consolidation request
    • beacon roots
    • SSV Network
    • WETH
  • Reentrancy guards on all state changing functions
    • Still doesn't protect against external contracts changing the state of the world if they are called.
  • No malicious behaviors
  • Low level call() must require success.
    • beacon withdrawal request
    • beacon consolidation request
  • No slippage attacks (we need to validate expected tokens received)
  • Oracles, one of:
    • No oracles
    • Oracles can't be bent
    • If oracle can be bent, it won't hurt us.
      Have added a new BeaconOracle that uses merkle proofs of the beacon chain to map blocks to slots.
  • Do not call balanceOf for external contracts to determine what they will do when they use internal accounting
    • There are multiple calls to WETH to get the strategy WETH balance

Tests

  • Each publicly callable method has a test
  • Each logical branch has a test
  • Each require() has a test
  • Edge conditions are tested
  • If tests interact with AMM make sure enough edge cases (pool tilts) are tested. Ideally with fuzzing.

Deploy

  • Deployer permissions are removed after deploy

Strategy Specific

Remove this section if the code being reviewed is not a strategy.

Strategy checks

  • Check balance cannot be manipulated up AND down by an attacker
    • an attacker can manipulated the balance up by donating ETH or WETH but that is ok. They can not manipulate the balance back down.
  • No read only reentrancy on downstream protocols during checkBalance
    • checkBalance reads the last verified ETH balance from storage and calls WETH for the strategy's balance
  • All reward tokens are collected
    • The new strategy is different to the old one in all consensus and execution rewards are compounded into the strategy's balance. They are not collected as rewards like the old native staking strategy.
    • The SSV rewards are collected the same way using the deployer of the proxy contract. This is the Defender Relayer account.
  • The harvester can sell all reward tokens
    • The Harvester is not used
  • No funds are left in the contract that should not be as a result of depositing or withdrawing
  • All funds can be recovered from the strategy by some combination of depositAll, withdraw, or withdrawAll()
    • withdrawAll will recover all WETH and ETH on the contract. It will not recover ETH pending deposits, validator withdrawals or validator balances.
  • WithdrawAll() can always withdraw an amount equal to or larger than checkBalances report, even in spite of attacker manipulation.
    • WithdrawAll can not withdraw an amount equal to or larger than checkBalances. validatorWithdrawal must be called for each of the validators with a zero amount to request a full withdrawal.
  • WithdrawAll() cannot be MEV'd
  • WithdrawAll() does not revert when strategy has 0 assets
  • Strategist cannot steal funds

Downstream

  • We have monitoring on all backend protocol's governances
  • We have monitoring on a pauses in all downstream systems

Thinking

Logic

Are there bugs in the logic?

  • Correct usage of global & local variables. -> they might differentiate only by an underscore that can be overlooked (e.g. address vs _address).

Deployment Considerations

Are there things that must be done on deploy, or in the wider ecosystem for this code to work. Are they done?

Internal State

  • What can be always said about relationships between stored state
  • What must hold true about state before a function can run correctly (preconditions)
  • What must hold true about the return or any changes to state after a function has run.
    • deposits to the strategy should increase the strategy's checkBalance by that amount
    • withdrawals from the strategy should decrease the strategy's checkBalance by that amount
    • deposits to a validator should not change the strategy's checkBalance when it is process on the execution and consensus layers
    • partial and full withdrawals from a validator should not change the strategy's checkBalance when it is process on the execution and consensus layers
    • Validators earning consensus and execution rewards should increase the strategy's checkBalance
    • Consolidating validators from the old strategies to the new strategy should not change the Vault's totalAssets as any stage in the consolidation process. The exception being any earned consensus or execution rewards.

Does this code do that?

Attack

What could the impacts of code failure in this code be.

What conditions could cause this code to fail if they were not true.

Does this code successfully block all attacks.

Flavor

Could this code be simpler?

Could this code be less vulnerable to other code behaving weirdly?

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.

5 participants