-
Notifications
You must be signed in to change notification settings - Fork 91
New Compounding Staking Strategy 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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2559 +/- ##
==========================================
+ Coverage 38.24% 40.76% +2.51%
==========================================
Files 112 122 +10
Lines 5331 5748 +417
Branches 1412 1524 +112
==========================================
+ Hits 2039 2343 +304
- Misses 3290 3402 +112
- Partials 2 3 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
contracts/contracts/strategies/NativeStaking/CompoundingValidatorManager.sol
Outdated
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingValidatorManager.sol
Outdated
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol
Outdated
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol
Outdated
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingStakingSSVStrategy.sol
Show resolved
Hide resolved
contracts/contracts/strategies/NativeStaking/CompoundingValidatorManager.sol
Outdated
Show resolved
Hide resolved
/// @notice Mapping of the root of a deposit (depositDataRoot) to its data | ||
mapping(bytes32 => DepositData) public deposits; | ||
/// @notice List of deposit roots that are still to be verified as processed on the beacon chain | ||
bytes32[] public depositsRoots; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be useful to have a function that returns the length of this list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately adding a function that returns the number of deposits will make the contract too big
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it on my invariant repo and it didn't make the contract too big, I mean, the deployment didn't revert.
Maybe foundry optimize it a bit by default, which reduce the size of the code in comparison with Hardhat or something.
// Delete the last deposit from the list | ||
depositsRoots.pop(); | ||
|
||
emit DepositVerified(depositDataRoot, deposit.amountGwei * 1 gwei); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deposit.amountGwei * 1 gwei
It will overflow when amountGwei is above 18 gwei, it could be fixed with uint256(deposit.amountGwei) * 1 gwei
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice pickup. I've fixed
require( | ||
consolidationLastPubKeyHash == bytes32(0), | ||
"Consolidation in progress" | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this require is not reachable (i.e. it will never revert).
We are looking for a situation where consolidationLastPubKeyHash != bytes32(0)
.
This can happen only in the situation where requestConsolidation()
has been called. But when requestConsolidation()
is called, it pause
the contract which prevent the snapBalance
to be called.
But still not 100% sure. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true and it takes 0.078 Kb off the contract size!
I've removed now pause is exclusively used for consolidations.
// After verifying the proof, update the contract storage | ||
deposits[depositDataRoot].status = DepositStatus.VERIFIED; | ||
// Move the last deposit to the index of the verified deposit | ||
depositsRoots[deposit.depositIndex] = depositsRoots[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟥 deposit.depositIndex
is a pointer to a row in depositsRoots
, but the elements in the depositRoots
array get moved around by this delete process, meaning the pointer in the deposit structure can point to the wrong place.
View Functions | ||
****************************************/ | ||
|
||
function getVerifiedValidators() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we plan on having a few big validators on the contract, so this is okay. But this could run out of gas, given enough validators. and usually just a length method and then querying is the most bulletproof method.
This method also might use a bit of contract size in loading these items from storage into memory in the correct format for output.
/// Only the registrator can call this function. | ||
/// @param depositAmountGwei The amount of WETH to stake to the validator in Gwei. | ||
// slither-disable-start reentrancy-eth | ||
function stakeEth( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 A must-have of ours blocking theft of user funds from the Registrator account. We cap the damage that can be done by only allowing a certain number of total outstanding deposits to unvalidated validators. If, for example, we only allowed 10 outstanding 1 ETH deposits to unverified validators, then the maximum theft amount would be ten, which would be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 I agree. I think we should keep track of staked
validators (we already keep track of verified
validators). And if the sum of those 2 surpasses some constant (say 20) the stakeEth deposit for the next validator that is REGISTERED should fail.
Otherwise a hijacked registrator can increase the number of verified validators to a point where the verifyBalances
isn't able to execute due to a too long verifiedValidators
list. Requiring pretty involved refactoring to save the bricked state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more important now we have 32 ETH initial deposits.
I need to think about how best to implement this.
/// Only the registrator can call this function. | ||
/// @param depositAmountGwei The amount of WETH to stake to the validator in Gwei. | ||
// slither-disable-start reentrancy-eth | ||
function stakeEth( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 What if we cannot validator a validator.
Perhaps the register account has been compromised or there has been a doof up on the P2P/SSV side.
We do not have a state transition path back to recovery to a good accounting state, since the validator will not validate, and there is no other transition out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can not validate a validator we would loose 32 ETH. This risk is similar to the old staking strategy. SSV network contracts do not verify the regiterValidator values aside from key/shard lengths. There would be ways to add admin functions that promote a validator to VALIDATED and assign an index to it. In unlikely case this would happen
…2592) * verifyValidatorPubkey now checks withdrawal credential is the new staking strategy * Added how to generate deposit proofs in unit tests * Linter
…gValidatorManager and BeaconProofsLib
* 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 <[email protected]>
* 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
…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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: Domen Grabec <[email protected]>
* 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 <[email protected]>
* Allow a validator full exit when it is already exiting * Updated validator state diagram
Co-authored-by: Domen Grabec <[email protected]>
…if the validator has a zero balance (#2671)
* 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
…sit 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 <[email protected]>
…#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 <[email protected]>
A new staking strategy that uses merkle proofs to verify the operations of validators running on the beacon chain.
Objectives
Links
Relevant Pectra Release changes
Contracts
The scope of the contracts of this build is
CompoundingStakingSSVStrategy
is the new staking contract.CompoundingValidatorManager
is inherited byCompoundingStakingSSVStrategy
CompoundingStakingStrategyView
has two view functions forCompoundingStakingSSVStrategy
BeaconProofs
called fromCompoundingValidatorManager
The following libraries have also be built
BeaconRoots
BeaconProofsLib
PartialWithdrawal
Merkle
Endian
Hoodi
States
Beacon chain data
Beacon Container Specs
To get the consolidate beacon chain specification for the last Pectra release
build/lib/eth2spec/electra/mainnet.py
will have the full, consolidated changes. This includes all the beacon chain container definitions. eg BeaconBlock, BeaconBlockBody, BeaconState, Validator...Processes
Register a SSV validator
Initial deposit to a new validator
Deposit more to existing validator
Verify validator
Verify deposit to validator
Update strategy balances
Withdrawals
Admin
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:
Hoodi Testnet
Hoodi faucet: https://hoodi-faucet.pk910.de/
SSV faucet: https://faucet.ssv.network/
Contracts have been deployed to Hoodi with deployment scripts in
contracts/deploy/hoodi
export DEPLOYER_PK= yarn run deploy:hoodi npx hardhat etherscan-verify --network hoodi --api-url https://api-hoodi.etherscan.io
Hoodi testing using Hardhat tasks
Deployment
Hoodi
Code Change Checklist
To be completed before internal review begins:
Internal review: