diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol index 2f1843ee56..6edcf44f69 100644 --- a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol +++ b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol @@ -22,8 +22,6 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { uint256 public fuseIntervalStart = 0; /// @notice end of fuse interval uint256 public fuseIntervalEnd = 0; - /// @notice Governor that can manually correct the accounting - address public accountingGovernor; uint256[50] private __gap; @@ -37,27 +35,14 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { uint256 remainingValidators, uint256 wethSentToVault ); - event AccountingGovernorChanged(address newAddress); event AccountingConsensusRewards(uint256 amount); event AccountingManuallyFixed( - uint256 oldActiveDepositedValidators, - uint256 activeDepositedValidators, - uint256 oldBeaconChainRewards, - uint256 beaconChainRewards, - uint256 ethToWeth, - uint256 wethToBeSentToVault + int256 validatorsDelta, + int256 consensusRewardsDelta, + uint256 wethToVault ); - /// @dev Throws if called by any account other than the Accounting Governor - modifier onlyAccountingGovernor() { - require( - msg.sender == accountingGovernor, - "Caller is not the Accounting Governor" - ); - _; - } - /// @param _wethAddress Address of the Erc20 WETH Token contract /// @param _vaultAddress Address of the Vault /// @param _beaconChainDepositContract Address of the beacon chain deposit contract @@ -76,11 +61,6 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { ) {} - function setAccountingGovernor(address _address) external onlyGovernor { - emit AccountingGovernorChanged(_address); - accountingGovernor = _address; - } - /// @notice set fuse interval values function setFuseInterval( uint256 _fuseIntervalStart, @@ -111,16 +91,28 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { /// accounting is valid and fuse isn't "blown". Returns false when fuse is blown. /// @dev This function could in theory be permission-less but lets allow only the Registrator (Defender Action) to call it /// for now. + /// @return accountingValid true if accounting was successful, false if fuse is blown /* solhint-enable max-line-length */ function doAccounting() external onlyRegistrator whenNotPaused returns (bool accountingValid) + { + // pause the accounting on failure + accountingValid = _doAccounting(true); + } + + function _doAccounting(bool pauseOnFail) + internal + returns (bool accountingValid) { if (address(this).balance < consensusRewards) { - // pause and fail the accounting - _pause(); + // pause if not already + if (pauseOnFail) { + _pause(); + } + // fail the accounting return false; } @@ -170,66 +162,66 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { ethRemaining ); } - // Oh no... Fuse is blown. The governor (Multisig not OGV Governor) needs to set the - // record straight by manually set the accounting values. + // Oh no... Fuse is blown. The Strategist needs to adjust the accounting values. else { - // will emit a paused event - _pause(); + // pause if not already + if (pauseOnFail) { + _pause(); + } + // fail the accounting accountingValid = false; } } - /// @dev allow the accounting governor to fix the accounting of this strategy and unpause - /// @param _activeDepositedValidators the override value of activeDepositedValidators - /// @param _ethToWeth the amount of ETH to be converted to WETH - /// @param _wethToBeSentToVault the amount of WETH to be sent to the Vault - /// @param _consensusRewards the override value for consensusRewards - /// @param _ethThresholdCheck maximum allowed ETH balance on the contract for the function to run - /// @param _wethThresholdCheck maximum allowed WETH balance on the contract for the function to run - /// the above 2 checks are done so transaction doesn't get front run and cause - /// unexpected behaviour + /// @notice Allow the Strategist to fix the accounting of this strategy and unpause. + /// @param _validatorsDelta adjust the active validators by plus one, minus one or unchanged with zero + /// @param _wethToVaultAmount the amount of WETH to be sent to the Vault + /// @param _consensusRewardsDelta adjust the accounted for consensus rewards up or down function manuallyFixAccounting( - uint256 _activeDepositedValidators, - uint256 _ethToWeth, - uint256 _wethToBeSentToVault, - uint256 _consensusRewards, - uint256 _ethThresholdCheck, - uint256 _wethThresholdCheck - ) external onlyAccountingGovernor whenPaused { - uint256 ethBalance = address(this).balance; - uint256 wethBalance = IWETH9(WETH_TOKEN_ADDRESS).balanceOf( - address(this) + int256 _validatorsDelta, + int256 _consensusRewardsDelta, + uint256 _wethToVaultAmount + ) external onlyStrategist whenPaused { + require( + _validatorsDelta >= -3 && + _validatorsDelta <= 3 && + // new value must be positive + int256(activeDepositedValidators) + _validatorsDelta >= 0, + "invalid validatorsDelta" ); - require( - ethBalance <= _ethThresholdCheck && - wethBalance <= _wethThresholdCheck, - "over accounting threshold" + _consensusRewardsDelta >= -332 ether && + _consensusRewardsDelta <= 332 ether && + // new value must be positive + int256(consensusRewards) + _consensusRewardsDelta >= 0, + "invalid consensusRewardsDelta" ); + require(_wethToVaultAmount <= 32 ether, "invalid wethToVaultAmount"); emit AccountingManuallyFixed( - activeDepositedValidators, - _activeDepositedValidators, - consensusRewards, - _consensusRewards, - _ethToWeth, - _wethToBeSentToVault + _validatorsDelta, + _consensusRewardsDelta, + _wethToVaultAmount ); - activeDepositedValidators = _activeDepositedValidators; - consensusRewards = _consensusRewards; - if (_ethToWeth > 0) { - require(_ethToWeth <= ethBalance, "insufficient ETH"); - - IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: _ethToWeth }(); - } - if (_wethToBeSentToVault > 0) { + activeDepositedValidators = uint256( + int256(activeDepositedValidators) + _validatorsDelta + ); + consensusRewards = uint256( + int256(consensusRewards) + _consensusRewardsDelta + ); + if (_wethToVaultAmount > 0) { IWETH9(WETH_TOKEN_ADDRESS).transfer( VAULT_ADDRESS, - _wethToBeSentToVault + _wethToVaultAmount ); } + // rerun the accounting to see if it has now been fixed. + // Do not pause the accounting on failure as it is already paused + require(_doAccounting(false), "fuse still blown"); + + // unpause since doAccounting was successful _unpause(); } } diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol index 30270331f4..b6f61eecd2 100644 --- a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol +++ b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol @@ -34,7 +34,7 @@ abstract contract ValidatorRegistrator is Governable, Pausable { /// @notice The number of validators that have 32 (!) ETH actively deposited. When a new deposit /// to a validator happens this number increases, when a validator exit is detected this number /// decreases. - uint256 activeDepositedValidators; + uint256 public activeDepositedValidators; /// @notice State of the validators keccak256(pubKey) => state mapping(bytes32 => VALIDATOR_STATE) public validatorsStates; diff --git a/contracts/deploy/mainnet/091_native_ssv_staking.js b/contracts/deploy/mainnet/091_native_ssv_staking.js index 77cb1135e8..817a620993 100644 --- a/contracts/deploy/mainnet/091_native_ssv_staking.js +++ b/contracts/deploy/mainnet/091_native_ssv_staking.js @@ -152,12 +152,6 @@ module.exports = deploymentWithGovernanceProposal( ethers.utils.parseEther("25.6"), ], }, - // 5. configure the accounting governor - { - contract: cStrategy, - signature: "setAccountingGovernor(address)", - args: [deployerAddr], // TODO: change this to the defender action - }, ], }; } diff --git a/contracts/docs/NativeStakingSSVStrategyHierarchy.svg b/contracts/docs/NativeStakingSSVStrategyHierarchy.svg index 3007a5a443..0686fa8c78 100644 --- a/contracts/docs/NativeStakingSSVStrategyHierarchy.svg +++ b/contracts/docs/NativeStakingSSVStrategyHierarchy.svg @@ -90,17 +90,17 @@ - + -336 +340 <<Abstract>> Pausable ../node_modules/@openzeppelin/contracts/security/Pausable.sol - + -283->336 +283->340 @@ -124,17 +124,17 @@ - + -341 +345 <<Abstract>> Context ../node_modules/@openzeppelin/contracts/utils/Context.sol - + -336->341 +340->345 diff --git a/contracts/docs/NativeStakingSSVStrategySquashed.svg b/contracts/docs/NativeStakingSSVStrategySquashed.svg index 392788f64f..e66ffdaf08 100644 --- a/contracts/docs/NativeStakingSSVStrategySquashed.svg +++ b/contracts/docs/NativeStakingSSVStrategySquashed.svg @@ -4,133 +4,130 @@ - - + + UmlClassDiagram - + 280 - -NativeStakingSSVStrategy -../contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol - -Private: -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   _paused: bool <<Pausable>> -   __gap: uint256[50] <<ValidatorRegistrator>> -   __gap: uint256[50] <<ValidatorAccountant>> -   initialized: bool <<Initializable>> -   initializing: bool <<Initializable>> -   ______gap: uint256[50] <<Initializable>> -   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> -   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> -   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> -   _reserved: int256[98] <<InitializableAbstractStrategy>> -   __gap: uint256[50] <<NativeStakingSSVStrategy>> -Internal: -   assetsMapped: address[] <<InitializableAbstractStrategy>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   WETH_TOKEN_ADDRESS: address <<ValidatorRegistrator>> -   BEACON_CHAIN_DEPOSIT_CONTRACT: address <<ValidatorRegistrator>> -   SSV_NETWORK_ADDRESS: address <<ValidatorRegistrator>> -   VAULT_ADDRESS: address <<ValidatorRegistrator>> -   validatorRegistrator: address <<ValidatorRegistrator>> -   activeDepositedValidators: uint256 <<ValidatorRegistrator>> -   validatorsStates: mapping(bytes32=>VALIDATOR_STATE) <<ValidatorRegistrator>> -   MAX_STAKE: uint256 <<ValidatorAccountant>> -   consensusRewards: uint256 <<ValidatorAccountant>> -   fuseIntervalStart: uint256 <<ValidatorAccountant>> -   fuseIntervalEnd: uint256 <<ValidatorAccountant>> -   accountingGovernor: address <<ValidatorAccountant>> -   platformAddress: address <<InitializableAbstractStrategy>> -   vaultAddress: address <<InitializableAbstractStrategy>> -   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> -   harvesterAddress: address <<InitializableAbstractStrategy>> -   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> -   SSV_TOKEN_ADDRESS: address <<NativeStakingSSVStrategy>> -   FEE_ACCUMULATOR_ADDRESS: address <<NativeStakingSSVStrategy>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _msgSender(): address <<Context>> -    _msgData(): bytes <<Context>> -    _pause() <<whenNotPaused>> <<Pausable>> -    _unpause() <<whenPaused>> <<Pausable>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<whenNotPaused>> <<NativeStakingSSVStrategy>> -    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    _abstractSetPToken(_asset: address, address) <<NativeStakingSSVStrategy>> -    _deposit(_asset: address, _amount: uint256) <<NativeStakingSSVStrategy>> -    _withdraw(_recipient: address, _asset: address, _amount: uint256) <<NativeStakingSSVStrategy>> -External: -    <<payable>> null() <<NativeStakingSSVStrategy>> -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setRegistrator(_address: address) <<onlyGovernor>> <<ValidatorRegistrator>> -    stakeEth(validators: ValidatorStakeData[]) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> -    registerSsvValidator(publicKey: bytes, operatorIds: uint64[], sharesData: bytes, amount: uint256, cluster: Cluster) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> -    exitSsvValidator(publicKey: bytes, operatorIds: uint64[]) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> -    removeSsvValidator(publicKey: bytes, operatorIds: uint64[], cluster: Cluster) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> -    depositSSV(operatorIds: uint64[], amount: uint256, cluster: Cluster) <<onlyStrategist>> <<ValidatorRegistrator>> -    setAccountingGovernor(_address: address) <<onlyGovernor>> <<ValidatorAccountant>> -    setFuseInterval(_fuseIntervalStart: uint256, _fuseIntervalEnd: uint256) <<onlyGovernor>> <<ValidatorAccountant>> -    doAccounting(): (accountingValid: bool) <<onlyRegistrator, whenNotPaused>> <<ValidatorAccountant>> -    manuallyFixAccounting(_activeDepositedValidators: uint256, _ethToWeth: uint256, _wethToBeSentToVault: uint256, _consensusRewards: uint256, _ethThresholdCheck: uint256, _wethThresholdCheck: uint256) <<onlyAccountingGovernor, whenPaused>> <<ValidatorAccountant>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<InitializableAbstractStrategy>> -    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> -    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> -    safeApproveAllTokens() <<NativeStakingSSVStrategy>> -    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<NativeStakingSSVStrategy>> -    depositAll() <<onlyVault, nonReentrant>> <<NativeStakingSSVStrategy>> -    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<NativeStakingSSVStrategy>> -    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<NativeStakingSSVStrategy>> -    checkBalance(_asset: address): (balance: uint256) <<NativeStakingSSVStrategy>> -    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<NativeStakingSSVStrategy>> -    pause() <<onlyStrategist>> <<NativeStakingSSVStrategy>> -Public: -    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> -    <<event>> Paused(account: address) <<Pausable>> -    <<event>> Unpaused(account: address) <<Pausable>> -    <<event>> RegistratorChanged(newAddress: address) <<ValidatorRegistrator>> -    <<event>> ETHStaked(pubkey: bytes, amount: uint256, withdrawal_credentials: bytes) <<ValidatorRegistrator>> -    <<event>> SSVValidatorRegistered(pubkey: bytes, operatorIds: uint64[]) <<ValidatorRegistrator>> -    <<event>> SSVValidatorExitInitiated(pubkey: bytes, operatorIds: uint64[]) <<ValidatorRegistrator>> -    <<event>> SSVValidatorExitCompleted(pubkey: bytes, operatorIds: uint64[]) <<ValidatorRegistrator>> -    <<event>> FuseIntervalUpdated(start: uint256, end: uint256) <<ValidatorAccountant>> -    <<event>> AccountingFullyWithdrawnValidator(noOfValidators: uint256, remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> -    <<event>> AccountingValidatorSlashed(remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> -    <<event>> AccountingGovernorChanged(newAddress: address) <<ValidatorAccountant>> -    <<event>> AccountingConsensusRewards(amount: uint256) <<ValidatorAccountant>> -    <<event>> AccountingManuallyFixed(oldActiveDepositedValidators: uint256, activeDepositedValidators: uint256, oldBeaconChainRewards: uint256, beaconChainRewards: uint256, ethToWeth: uint256, wethToBeSentToVault: uint256) <<ValidatorAccountant>> -    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> -    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> -    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> -    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> -    <<modifier>> onlyGovernor() <<Governable>> -    <<modifier>> nonReentrant() <<Governable>> -    <<modifier>> whenNotPaused() <<Pausable>> -    <<modifier>> whenPaused() <<Pausable>> -    <<modifier>> onlyRegistrator() <<ValidatorRegistrator>> -    <<modifier>> onlyStrategist() <<ValidatorRegistrator>> -    <<modifier>> onlyAccountingGovernor() <<ValidatorAccountant>> + +NativeStakingSSVStrategy +../contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol + +Private: +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +   _paused: bool <<Pausable>> +   __gap: uint256[50] <<ValidatorRegistrator>> +   __gap: uint256[50] <<ValidatorAccountant>> +   initialized: bool <<Initializable>> +   initializing: bool <<Initializable>> +   ______gap: uint256[50] <<Initializable>> +   _deprecated_platformAddress: address <<InitializableAbstractStrategy>> +   _deprecated_vaultAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardTokenAddress: address <<InitializableAbstractStrategy>> +   _deprecated_rewardLiquidationThreshold: uint256 <<InitializableAbstractStrategy>> +   _reserved: int256[98] <<InitializableAbstractStrategy>> +   __gap: uint256[50] <<NativeStakingSSVStrategy>> +Internal: +   assetsMapped: address[] <<InitializableAbstractStrategy>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   WETH_TOKEN_ADDRESS: address <<ValidatorRegistrator>> +   BEACON_CHAIN_DEPOSIT_CONTRACT: address <<ValidatorRegistrator>> +   SSV_NETWORK_ADDRESS: address <<ValidatorRegistrator>> +   VAULT_ADDRESS: address <<ValidatorRegistrator>> +   validatorRegistrator: address <<ValidatorRegistrator>> +   activeDepositedValidators: uint256 <<ValidatorRegistrator>> +   validatorsStates: mapping(bytes32=>VALIDATOR_STATE) <<ValidatorRegistrator>> +   MAX_STAKE: uint256 <<ValidatorAccountant>> +   consensusRewards: uint256 <<ValidatorAccountant>> +   fuseIntervalStart: uint256 <<ValidatorAccountant>> +   fuseIntervalEnd: uint256 <<ValidatorAccountant>> +   platformAddress: address <<InitializableAbstractStrategy>> +   vaultAddress: address <<InitializableAbstractStrategy>> +   assetToPToken: mapping(address=>address) <<InitializableAbstractStrategy>> +   harvesterAddress: address <<InitializableAbstractStrategy>> +   rewardTokenAddresses: address[] <<InitializableAbstractStrategy>> +   SSV_TOKEN_ADDRESS: address <<NativeStakingSSVStrategy>> +   FEE_ACCUMULATOR_ADDRESS: address <<NativeStakingSSVStrategy>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> +    _msgSender(): address <<Context>> +    _msgData(): bytes <<Context>> +    _pause() <<whenNotPaused>> <<Pausable>> +    _unpause() <<whenPaused>> <<Pausable>> +    _doAccounting(pauseOnFail: bool): (accountingValid: bool) <<ValidatorAccountant>> +    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> +    _collectRewardTokens() <<whenNotPaused>> <<NativeStakingSSVStrategy>> +    _setPTokenAddress(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    _abstractSetPToken(_asset: address, address) <<NativeStakingSSVStrategy>> +    _deposit(_asset: address, _amount: uint256) <<NativeStakingSSVStrategy>> +    _withdraw(_recipient: address, _asset: address, _amount: uint256) <<NativeStakingSSVStrategy>> +External: +    <<payable>> null() <<NativeStakingSSVStrategy>> +    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> +    claimGovernance() <<Governable>> +    setRegistrator(_address: address) <<onlyGovernor>> <<ValidatorRegistrator>> +    stakeEth(validators: ValidatorStakeData[]) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> +    registerSsvValidator(publicKey: bytes, operatorIds: uint64[], sharesData: bytes, amount: uint256, cluster: Cluster) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> +    exitSsvValidator(publicKey: bytes, operatorIds: uint64[]) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> +    removeSsvValidator(publicKey: bytes, operatorIds: uint64[], cluster: Cluster) <<onlyRegistrator, whenNotPaused>> <<ValidatorRegistrator>> +    depositSSV(operatorIds: uint64[], amount: uint256, cluster: Cluster) <<onlyStrategist>> <<ValidatorRegistrator>> +    setFuseInterval(_fuseIntervalStart: uint256, _fuseIntervalEnd: uint256) <<onlyGovernor>> <<ValidatorAccountant>> +    doAccounting(): (accountingValid: bool) <<onlyRegistrator, whenNotPaused>> <<ValidatorAccountant>> +    manuallyFixAccounting(_validatorsDelta: int256, _consensusRewardsDelta: int256, _wethToVaultAmount: uint256) <<onlyStrategist, whenPaused>> <<ValidatorAccountant>> +    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<InitializableAbstractStrategy>> +    setRewardTokenAddresses(_rewardTokenAddresses: address[]) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    getRewardTokenAddresses(): address[] <<InitializableAbstractStrategy>> +    setPTokenAddress(_asset: address, _pToken: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    removePToken(_assetIndex: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    setHarvesterAddress(_harvesterAddress: address) <<onlyGovernor>> <<InitializableAbstractStrategy>> +    safeApproveAllTokens() <<NativeStakingSSVStrategy>> +    deposit(_asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<NativeStakingSSVStrategy>> +    depositAll() <<onlyVault, nonReentrant>> <<NativeStakingSSVStrategy>> +    withdraw(_recipient: address, _asset: address, _amount: uint256) <<onlyVault, nonReentrant>> <<NativeStakingSSVStrategy>> +    withdrawAll() <<onlyVaultOrGovernor, nonReentrant>> <<NativeStakingSSVStrategy>> +    checkBalance(_asset: address): (balance: uint256) <<NativeStakingSSVStrategy>> +    initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<onlyGovernor, initializer>> <<NativeStakingSSVStrategy>> +    pause() <<onlyStrategist>> <<NativeStakingSSVStrategy>> +Public: +    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> +    <<event>> Paused(account: address) <<Pausable>> +    <<event>> Unpaused(account: address) <<Pausable>> +    <<event>> RegistratorChanged(newAddress: address) <<ValidatorRegistrator>> +    <<event>> ETHStaked(pubkey: bytes, amount: uint256, withdrawal_credentials: bytes) <<ValidatorRegistrator>> +    <<event>> SSVValidatorRegistered(pubkey: bytes, operatorIds: uint64[]) <<ValidatorRegistrator>> +    <<event>> SSVValidatorExitInitiated(pubkey: bytes, operatorIds: uint64[]) <<ValidatorRegistrator>> +    <<event>> SSVValidatorExitCompleted(pubkey: bytes, operatorIds: uint64[]) <<ValidatorRegistrator>> +    <<event>> FuseIntervalUpdated(start: uint256, end: uint256) <<ValidatorAccountant>> +    <<event>> AccountingFullyWithdrawnValidator(noOfValidators: uint256, remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> +    <<event>> AccountingValidatorSlashed(remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> +    <<event>> AccountingConsensusRewards(amount: uint256) <<ValidatorAccountant>> +    <<event>> AccountingManuallyFixed(validatorsDelta: int256, consensusRewardsDelta: int256, wethToVault: uint256) <<ValidatorAccountant>> +    <<event>> PTokenAdded(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> PTokenRemoved(_asset: address, _pToken: address) <<InitializableAbstractStrategy>> +    <<event>> Deposit(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> Withdrawal(_asset: address, _pToken: address, _amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenCollected(recipient: address, rewardToken: address, amount: uint256) <<InitializableAbstractStrategy>> +    <<event>> RewardTokenAddressesUpdated(_oldAddresses: address[], _newAddresses: address[]) <<InitializableAbstractStrategy>> +    <<event>> HarvesterAddressesUpdated(_oldHarvesterAddress: address, _newHarvesterAddress: address) <<InitializableAbstractStrategy>> +    <<modifier>> onlyGovernor() <<Governable>> +    <<modifier>> nonReentrant() <<Governable>> +    <<modifier>> whenNotPaused() <<Pausable>> +    <<modifier>> whenPaused() <<Pausable>> +    <<modifier>> onlyRegistrator() <<ValidatorRegistrator>> +    <<modifier>> onlyStrategist() <<ValidatorRegistrator>>    <<modifier>> initializer() <<Initializable>>    <<modifier>> onlyVault() <<InitializableAbstractStrategy>>    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> diff --git a/contracts/docs/NativeStakingSSVStrategyStorage.svg b/contracts/docs/NativeStakingSSVStrategyStorage.svg index 058cea8538..e5aad7e6a1 100644 --- a/contracts/docs/NativeStakingSSVStrategyStorage.svg +++ b/contracts/docs/NativeStakingSSVStrategyStorage.svg @@ -4,84 +4,78 @@ - - + + StorageDiagram - + 3 - -NativeStakingSSVStrategy <<Contract>> - -slot - -0 + +NativeStakingSSVStrategy <<Contract>> + +slot -1 +0 -2 +1 -3-52 +2 -53 +3-52 -54 +53 -55 +54 -56 +55 -57-106 +56-105 -107 +106 -108-157 +107-156 -158 +157 -159 +158 -160 +159 -161 +160 -162 +161 -163 +162 -164 +163 -165 +164 -166-263 +165-262 -264-313 - -type: <inherited contract>.variable (bytes) - -unallocated (11) - -address: ValidatorRegistrator.validatorRegistrator (20) - -bool: Pausable._paused (1) +263-312 + +type: <inherited contract>.variable (bytes) -uint256: ValidatorRegistrator.activeDepositedValidators (32) +unallocated (11) + +address: ValidatorRegistrator.validatorRegistrator (20) + +bool: Pausable._paused (1) -mapping(bytes32=>VALIDATOR_STATE): ValidatorRegistrator.validatorsStates (32) +uint256: ValidatorRegistrator.activeDepositedValidators (32) -uint256[50]: ValidatorRegistrator.__gap (1600) +mapping(bytes32=>VALIDATOR_STATE): ValidatorRegistrator.validatorsStates (32) -uint256: ValidatorAccountant.consensusRewards (32) +uint256[50]: ValidatorRegistrator.__gap (1600) -uint256: ValidatorAccountant.fuseIntervalStart (32) +uint256: ValidatorAccountant.consensusRewards (32) -uint256: ValidatorAccountant.fuseIntervalEnd (32) +uint256: ValidatorAccountant.fuseIntervalStart (32) -unallocated (12) - -address: ValidatorAccountant.accountingGovernor (20) +uint256: ValidatorAccountant.fuseIntervalEnd (32) uint256[50]: ValidatorAccountant.__gap (1600) @@ -124,48 +118,48 @@ 1 - -address[]: assetsMapped <<Array>> -0xaadc37b8ba5645e62f4546802db221593a94729ccbfc5a97d01365a88f649878 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: assetsMapped <<Array>> +0x78fdc8d422c49ced035a9edf18d00d3c6a8d81df210f3e5e448e045e77b41e88 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) -3:18->1 - - +3:17->1 + + 2 - -address[]: rewardTokenAddresses <<Array>> -0xb29a2b3b6f2ff1b765777a231725941da5072cc4fcc30ac4a2ce09706e8ddeff - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: rewardTokenAddresses <<Array>> +0xe434dc35da084cf8d7e8186688ea2dacb53db7003d427af3abf351bd9d0a4e8d + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) -3:23->2 - - +3:22->2 + + diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js index 93c71a49bd..30d342bac3 100644 --- a/contracts/test/_fixture.js +++ b/contracts/test/_fixture.js @@ -1583,9 +1583,6 @@ async function nativeStakingSSVStrategyFixture() { .connect(sGovernor) .setRegistrator(governorAddr); - await nativeStakingSSVStrategy - .connect(sGovernor) - .setAccountingGovernor(governorAddr); } return fixture; diff --git a/contracts/test/strategies/nativeSSVStaking.js b/contracts/test/strategies/nativeSSVStaking.js index c176d3fea3..1fd21a0ae6 100644 --- a/contracts/test/strategies/nativeSSVStaking.js +++ b/contracts/test/strategies/nativeSSVStaking.js @@ -1,7 +1,10 @@ const { expect } = require("chai"); const { BigNumber } = require("ethers"); const { parseEther } = require("ethers").utils; -const { setBalance } = require("@nomicfoundation/hardhat-network-helpers"); +const { + setBalance, + setStorageAt, +} = require("@nomicfoundation/hardhat-network-helpers"); const { isCI } = require("../helpers"); const { shouldBehaveLikeGovernable } = require("../behaviour/governable"); @@ -57,7 +60,7 @@ describe("Unit test: Native SSV Staking Strategy", function () { ); const tx = { to: nativeStakingSSVStrategy.address, - value: parseEther("2", "ether"), + value: parseEther("2"), }; await expect(signer.sendTransaction(tx)).to.be.revertedWith( @@ -153,30 +156,6 @@ describe("Unit test: Native SSV Staking Strategy", function () { .to.emit(nativeStakingSSVStrategy, "FuseIntervalUpdated") .withArgs(fuseStartBn, fuseEndBn); }); - - it("Only accounting governor can call accounting", async () => {}); - - it("Only governor can change the accounting governor", async () => { - const { nativeStakingSSVStrategy, strategist } = fixture; - - await expect( - nativeStakingSSVStrategy - .connect(strategist) - .setAccountingGovernor(strategist.address) - ).to.be.revertedWith("Caller is not the Governor"); - }); - - it("Change the accounting governor", async () => { - const { nativeStakingSSVStrategy, governor, strategist } = fixture; - - const tx = await nativeStakingSSVStrategy - .connect(governor) - .setAccountingGovernor(strategist.address); - - await expect(tx) - .to.emit(nativeStakingSSVStrategy, "AccountingGovernorChanged") - .withArgs(strategist.address); - }); }); describe("Accounting", function () { @@ -466,24 +445,18 @@ describe("Unit test: Native SSV Staking Strategy", function () { } consensus rewards, ${expectedValidatorsFullWithdrawals} withdraws${ fuseBlown ? ", fuse blown" : "" }${slashDetected ? ", slash detected" : ""}.`, async () => { - const { nativeStakingSSVStrategy, governor, strategist } = fixture; + const { nativeStakingSSVStrategy, governor } = fixture; // setup state if (ethBalance.gt(0)) { await setBalance(nativeStakingSSVStrategy.address, ethBalance); } - // pause, so manuallyFixAccounting can be called - await nativeStakingSSVStrategy.connect(strategist).pause(); - await nativeStakingSSVStrategy - .connect(governor) - .manuallyFixAccounting( - 30, // activeDepositedValidators - 0, //_ethToWeth - 0, //_wethToBeSentToVault - previousConsensusRewards, //_consensusRewards - parseEther("3000"), //_ethThresholdCheck - parseEther("3000") //_wethThresholdCheck - ); + + await setActiveDepositedValidators(30, nativeStakingSSVStrategy); + await setConsensusRewards( + previousConsensusRewards, + nativeStakingSSVStrategy + ); // check accounting values const tx = await nativeStakingSSVStrategy @@ -541,124 +514,214 @@ describe("Unit test: Native SSV Staking Strategy", function () { } }); - it("Only accounting governor is allowed to manually fix accounting", async () => { - const { nativeStakingSSVStrategy, strategist } = fixture; + it("Only strategist is allowed to manually fix accounting", async () => { + const { nativeStakingSSVStrategy, strategist, governor } = fixture; await nativeStakingSSVStrategy.connect(strategist).pause(); // unit test fixture sets OUSD governor as accounting governor await expect( - nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( - 10, //_activeDepositedValidators - parseEther("2", "ether"), //_ethToWeth - parseEther("2", "ether"), //_wethToBeSentToVault - parseEther("2", "ether"), //_consensusRewards - parseEther("0", "ether"), //_ethThresholdCheck - parseEther("0", "ether") //_wethThresholdCheck + nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( + 1, //_validatorsDelta + parseEther("2"), //_consensusRewardsDelta + parseEther("2") //_wethToVault ) - ).to.be.revertedWith("Caller is not the Accounting Governor"); + ).to.be.revertedWith("Caller is not the Strategist"); }); it("Accounting needs to be paused in order to call fix accounting function", async () => { - const { nativeStakingSSVStrategy, governor } = fixture; + const { nativeStakingSSVStrategy, strategist } = fixture; // unit test fixture sets OUSD governor as accounting governor await expect( - nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( - 10, //_activeDepositedValidators - parseEther("2", "ether"), //_ethToWeth - parseEther("2", "ether"), //_wethToBeSentToVault - parseEther("2", "ether"), //_beaconChainRewardWETH - parseEther("1", "ether"), //_ethThresholdCheck - parseEther("0", "ether") //_wethThresholdCheck + nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( + 1, //_validatorsDelta + parseEther("2"), //_consensusRewardsDelta + parseEther("2") //_wethToVault ) ).to.be.revertedWith("Pausable: not paused"); }); - it("Should not execute manual recovery if eth threshold reached", async () => { - const { nativeStakingSSVStrategy, strategist, governor, josh, weth } = - fixture; - - await setBalance( - nativeStakingSSVStrategy.address, - parseEther("6", "ether") - ); - await weth - .connect(josh) - .transfer(nativeStakingSSVStrategy.address, parseEther("5", "ether")); + it("Validators delta should not be <-4 or >4 for fix accounting function", async () => { + const { nativeStakingSSVStrategy, strategist } = fixture; await nativeStakingSSVStrategy.connect(strategist).pause(); + await expect( - nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( - 10, //_activeDepositedValidators - parseEther("2", "ether"), //_ethToWeth - parseEther("2", "ether"), //_wethToBeSentToVault - parseEther("2", "ether"), //_beaconChainRewardWETH - parseEther("5", "ether"), //_ethThresholdCheck - parseEther("5", "ether") //_wethThresholdCheck + nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( + -4, //_validatorsDelta + 0, //_consensusRewardsDelta + 0 //_wethToVault ) - ).to.be.revertedWith("over accounting threshold"); + ).to.be.revertedWith("invalid validatorsDelta"); + + await expect( + nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( + 4, //_validatorsDelta + 0, //_consensusRewardsDelta + 0 //_wethToVault + ) + ).to.be.revertedWith("invalid validatorsDelta"); }); - it("Should not execute manual recovery if weth threshold reached", async () => { - const { nativeStakingSSVStrategy, strategist, governor, josh, weth } = - fixture; + it("Consensus rewards delta should not be <-333> and >333 for fix accounting function", async () => { + const { nativeStakingSSVStrategy, strategist } = fixture; - await setBalance( - nativeStakingSSVStrategy.address, - parseEther("5", "ether") - ); - await weth - .connect(josh) - .transfer(nativeStakingSSVStrategy.address, parseEther("6", "ether")); + await nativeStakingSSVStrategy.connect(strategist).pause(); + + await expect( + nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( + 0, //_validatorsDelta + parseEther("-333"), //_consensusRewardsDelta + 0 //_wethToVault + ) + ).to.be.revertedWith("invalid consensusRewardsDelta"); + + await expect( + nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( + 0, //_validatorsDelta + parseEther("333"), //_consensusRewardsDelta + 0 //_wethToVault + ) + ).to.be.revertedWith("invalid consensusRewardsDelta"); + }); + + it("WETH to Vault amount should not be >32 for fix accounting function", async () => { + const { nativeStakingSSVStrategy, strategist } = fixture; await nativeStakingSSVStrategy.connect(strategist).pause(); + await expect( - nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( - 10, //_activeDepositedValidators - parseEther("2", "ether"), //_ethToWeth - parseEther("2", "ether"), //_wethToBeSentToVault - parseEther("2", "ether"), //_beaconChainRewardWETH - parseEther("5", "ether"), //_ethThresholdCheck - parseEther("5", "ether") //_wethThresholdCheck + nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( + 0, //_validatorsDelta + 0, //_consensusRewardsDelta + parseEther("33") //_wethToVault ) - ).to.be.revertedWith("over accounting threshold"); + ).to.be.revertedWith("invalid wethToVaultAmount"); }); - it("Should allow 5/8 governor to recover paused contract and correct the accounting state", async () => { - const { nativeStakingSSVStrategy, strategist, governor, josh, weth } = - fixture; + describe("Should allow strategist to recover paused contract", async () => { + for (const validatorsDelta of [-3, -2, -1, 0, 1, 2, 3]) { + it(`by changing validators by ${validatorsDelta}`, async () => { + const { nativeStakingSSVStrategy, strategist } = fixture; - await setBalance( - nativeStakingSSVStrategy.address, - parseEther("5", "ether") - ); - await weth - .connect(josh) - .transfer(nativeStakingSSVStrategy.address, parseEther("5", "ether")); + await setActiveDepositedValidators(10, nativeStakingSSVStrategy); - await nativeStakingSSVStrategy.connect(strategist).pause(); - // unit test fixture sets OUSD governor as accounting governor - const tx = await nativeStakingSSVStrategy - .connect(governor) - .manuallyFixAccounting( - 3, //_activeDepositedValidators - parseEther("2.1", "ether"), //_ethToWeth - parseEther("2.2", "ether"), //_wethToBeSentToVault - parseEther("2.3", "ether"), //_beaconChainRewardWETH - parseEther("5", "ether"), //_ethThresholdCheck - parseEther("5", "ether") //_wethThresholdCheck - ); + await nativeStakingSSVStrategy.connect(strategist).pause(); + const activeDepositedValidatorsBefore = + await nativeStakingSSVStrategy.activeDepositedValidators(); - expect(tx) - .to.emit(nativeStakingSSVStrategy, "AccountingManuallyFixed") - .withArgs( - 0, // oldActiveDepositedValidators - 3, // activeDepositedValidators - 0, // oldBeaconChainRewardWETH - parseEther("2.3"), // beaconChainRewardWETH - parseEther("2.1"), // ethToWeth - parseEther("2.2") // wethToBeSentToVault - ); + const tx = await nativeStakingSSVStrategy + .connect(strategist) + .manuallyFixAccounting(validatorsDelta, 0, 0); + + expect(tx) + .to.emit(nativeStakingSSVStrategy, "AccountingManuallyFixed") + .withArgs(validatorsDelta, 0, 0); + + expect( + await nativeStakingSSVStrategy.activeDepositedValidators() + ).to.equal( + activeDepositedValidatorsBefore.add(validatorsDelta), + "active deposited validators not updated" + ); + }); + } + + for (const delta of [-332, -320, -1, 0, 1, 320, 332]) { + it(`by changing consensus rewards by ${delta}`, async () => { + const { nativeStakingSSVStrategy, strategist } = fixture; + + await setBalance(nativeStakingSSVStrategy.address, parseEther("670")); + await setConsensusRewards( + parseEther("336"), + nativeStakingSSVStrategy + ); + await setActiveDepositedValidators(10000, nativeStakingSSVStrategy); + + await nativeStakingSSVStrategy.connect(strategist).pause(); + const consensusRewardsDelta = parseEther(delta.toString()); + + const tx = await nativeStakingSSVStrategy + .connect(strategist) + .manuallyFixAccounting(0, consensusRewardsDelta, 0); + + expect(tx) + .to.emit(nativeStakingSSVStrategy, "AccountingManuallyFixed") + .withArgs(0, consensusRewardsDelta, 0); + + expect(await nativeStakingSSVStrategy.consensusRewards()).to.equal( + await nativeStakingSSVStrategy.provider.getBalance( + nativeStakingSSVStrategy.address + ), + "consensus rewards matches eth balance" + ); + }); + } + + it("by sending WETH to vault", async () => { + const { nativeStakingSSVStrategy, strategist, josh, weth } = fixture; + + await weth + .connect(josh) + .transfer(nativeStakingSSVStrategy.address, parseEther("100")); + + for (const wethInEthers of [0, 1, 26, 32]) { + await nativeStakingSSVStrategy.connect(strategist).pause(); + const wethBefore = await weth.balanceOf( + nativeStakingSSVStrategy.address + ); + const wethToVault = parseEther(wethInEthers.toString()); + + const tx = await nativeStakingSSVStrategy + .connect(strategist) + .manuallyFixAccounting(0, 0, wethToVault); + + expect(tx) + .to.emit(nativeStakingSSVStrategy, "AccountingManuallyFixed") + .withArgs(0, 0, wethToVault); + + expect( + await weth.balanceOf(nativeStakingSSVStrategy.address) + ).to.equal( + wethBefore.sub(wethToVault), + "consensus rewards not updated" + ); + + expect(await nativeStakingSSVStrategy.consensusRewards()).to.equal( + await nativeStakingSSVStrategy.provider.getBalance( + nativeStakingSSVStrategy.address + ), + "consensus rewards matches eth balance" + ); + } + }); + + it("by changing all three manuallyFixAccounting delta values", async () => { + const { nativeStakingSSVStrategy, strategist, josh, weth } = fixture; + + await setBalance(nativeStakingSSVStrategy.address, parseEther("5")); + await weth + .connect(josh) + .transfer(nativeStakingSSVStrategy.address, parseEther("5")); + + await nativeStakingSSVStrategy.connect(strategist).pause(); + // unit test fixture sets OUSD governor as accounting governor + const tx = await nativeStakingSSVStrategy + .connect(strategist) + .manuallyFixAccounting( + 1, //_validatorsDelta + parseEther("2.3"), //_consensusRewardsDeltaDelta + parseEther("2.2") //_wethToVault + ); + + expect(tx) + .to.emit(nativeStakingSSVStrategy, "AccountingManuallyFixed") + .withArgs( + 1, // validatorsDelta + parseEther("2.3"), // consensusRewards + parseEther("2.2") // wethToVault + ); + }); }); }); @@ -747,8 +810,7 @@ describe("Unit test: Native SSV Staking Strategy", function () { describe(`given ${testCase.feeAccumulatorEth} execution rewards, ${testCase.consensusRewards} consensus rewards, ${testCase.deposits} deposits and ${nrOfActiveDepositedValidators} validators`, () => { beforeEach(async () => { - const { nativeStakingSSVStrategy, governor, strategist, weth, josh } = - fixture; + const { nativeStakingSSVStrategy, governor, weth, josh } = fixture; const feeAccumulatorAddress = await nativeStakingSSVStrategy.FEE_ACCUMULATOR_ADDRESS(); @@ -772,17 +834,11 @@ describe("Unit test: Native SSV Staking Strategy", function () { } // set the correct amount of staked validators - await nativeStakingSSVStrategy.connect(strategist).pause(); - await nativeStakingSSVStrategy - .connect(governor) - .manuallyFixAccounting( - nrOfActiveDepositedValidators, // activeDepositedValidators - parseEther("0"), //_ethToWeth - parseEther("0"), //_wethToBeSentToVault - consensusRewards, //_consensusRewards - parseEther("3000"), //_ethThresholdCheck - parseEther("3000") //_wethThresholdCheck - ); + await setActiveDepositedValidators( + nrOfActiveDepositedValidators, + nativeStakingSSVStrategy + ); + await setConsensusRewards(consensusRewards, nativeStakingSSVStrategy); // run the accounting await nativeStakingSSVStrategy.connect(governor).doAccounting(); @@ -827,3 +883,27 @@ describe("Unit test: Native SSV Staking Strategy", function () { } }); }); + +const setActiveDepositedValidators = async ( + validators, + nativeStakingSSVStrategy +) => { + await setStorageAt(nativeStakingSSVStrategy.address, 52, validators); + + expect(await nativeStakingSSVStrategy.activeDepositedValidators()).to.equal( + validators, + "validators no set properly" + ); +}; + +const setConsensusRewards = async ( + consensusRewards, + nativeStakingSSVStrategy +) => { + await setStorageAt(nativeStakingSSVStrategy.address, 104, consensusRewards); + + expect(await nativeStakingSSVStrategy.consensusRewards()).to.equal( + consensusRewards, + "consensusRewards no set properly" + ); +};