diff --git a/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol b/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol index baa5e2bf52..00ee3e8bee 100644 --- a/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol +++ b/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol @@ -1,55 +1,40 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import { Address } from "@openzeppelin/contracts/utils/Address.sol"; + import { Governable } from "../../governance/Governable.sol"; import { IWETH9 } from "../../interfaces/IWETH9.sol"; /** * @title Fee Accumulator for Native Staking SSV Strategy - * @notice This contract is setup to receive fees from processing transactions on the beacon chain - * which includes priority fees and any MEV rewards. - * It does NOT include swept ETH from consensus rewards or withdrawals. + * @notice Receives execution rewards which includes tx fees and + * MEV rewards like tx priority and tx ordering. + * It does NOT include swept ETH from beacon chain consensus rewards or full validator withdrawals. * @author Origin Protocol Inc */ contract FeeAccumulator is Governable { - /// @notice The address the WETH is sent to on `collect` which is the Native Staking Strategy - address public immutable COLLECTOR; - /// @notice The address of the Wrapped ETH (WETH) token contract - address public immutable WETH_TOKEN_ADDRESS; - - error CallerNotCollector(address caller, address expectedCaller); - - // For future use - uint256[50] private __gap; + /// @notice The address of the Native Staking Strategy + address public immutable STRATEGY; /** - * @param _collector Address of the contract that collects the fees - * @param _weth Address of the Wrapped ETH (WETH) token contract + * @param _strategy Address of the Native Staking Strategy */ - constructor(address _collector, address _weth) { - COLLECTOR = _collector; - WETH_TOKEN_ADDRESS = _weth; + constructor(address _strategy) { + STRATEGY = _strategy; } /** - * @dev Asserts that the caller is the collector + * @notice sends all ETH in this FeeAccumulator contract to the Native Staking Strategy. + * @return eth The amount of execution rewards that were sent to the Native Staking Strategy */ - function _assertIsCollector() internal view { - if (msg.sender != COLLECTOR) { - revert CallerNotCollector(msg.sender, COLLECTOR); - } - } + function collect() external returns (uint256 eth) { + require(msg.sender == STRATEGY, "Caller is not the Strategy"); - /** - * @notice Converts ETH to WETH and sends the WETH to the collector - * @return weth The amount of WETH sent to the collector - */ - function collect() external returns (uint256 weth) { - _assertIsCollector(); - weth = address(this).balance; - if (weth > 0) { - IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: weth }(); - IWETH9(WETH_TOKEN_ADDRESS).transfer(COLLECTOR, weth); + eth = address(this).balance; + if (eth > 0) { + // Send the ETH to the Native Staking Strategy + Address.sendValue(payable(STRATEGY), eth); } } } diff --git a/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol b/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol index 7bbd0345bd..1c4f44f02d 100644 --- a/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol +++ b/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol @@ -38,13 +38,6 @@ contract NativeStakingSSVStrategy is // For future use uint256[50] private __gap; - error EmptyRecipient(); - error NotWeth(); - error InsufficientWethBalance( - uint256 requiredBalance, - uint256 availableBalance - ); - /// @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI, /// and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy /// @param _wethAddress Address of the Erc20 WETH Token contract @@ -88,73 +81,36 @@ contract NativeStakingSSVStrategy is ); } - /// @notice return the WETH balance on the contract that can be used to for beacon chain - /// staking - staking on the validators. Because WETH on this contract can be present as - /// a result of deposits and beacon chain rewards this function needs to return only WETH - /// that is present due to deposits. - function getWETHBalanceEligibleForStaking() - public - view - override - returns (uint256 _amount) - { - // if below amount results in a negative number there is a bug with accounting - _amount = - IWETH9(WETH_TOKEN_ADDRESS).balanceOf(address(this)) - - beaconChainRewardWETH; - } - - /// @notice Convert accumulated ETH to WETH and send to the Harvester. - /// Only callable by the Harvester. - function collectRewardTokens() - external - virtual - override - onlyHarvester - nonReentrant - { - // collect WETH from fee collector and wrap it into WETH - uint256 wethCollected = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS) + /// @dev Convert accumulated ETH to WETH and send to the Harvester. + function _collectRewardTokens() internal override { + // collect ETH from execution rewards from the fee accumulator + uint256 executionRewards = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS) .collect(); - /* add up the WETH collected from the fee accumulator to beaconChainRewardWETH - * so it can be sent to the harvester in one swoop in the "_collectRewardTokens" - * step. - */ - beaconChainRewardWETH += wethCollected; - _collectRewardTokens(); - } + // total ETH rewards to be harvested = execution rewards + consensus rewards + uint256 ethRewards = executionRewards + consensusRewards; - /// @dev Need to override this function since the strategy doesn't allow for all the WETH - /// to be collected. Some might be there as a result of deposit and is waiting for the Registrar - /// to be deposited to the validators. - function _collectRewardTokens() internal override { - uint256 rewardTokenCount = rewardTokenAddresses.length; - for (uint256 i = 0; i < rewardTokenCount; ++i) { - IERC20 rewardToken = IERC20(rewardTokenAddresses[i]); - uint256 balance = rewardToken.balanceOf(address(this)); - if (balance > 0) { - if (address(rewardToken) == WETH_TOKEN_ADDRESS) { - if (beaconChainRewardWETH > balance) { - revert InsufficientWethBalance( - beaconChainRewardWETH, - balance - ); - } + require( + address(this).balance >= ethRewards, + "insufficient eth balance" + ); + + if (ethRewards > 0) { + // reset the counter keeping track of beacon chain consensus rewards + consensusRewards = 0; - // only allow for the WETH that is part of beacon chain rewards to be harvested - balance = beaconChainRewardWETH; - // reset the counter keeping track of beacon chain WETH rewards - beaconChainRewardWETH = 0; - } + // Convert ETH rewards to WETH + IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: ethRewards }(); - emit RewardTokenCollected( - harvesterAddress, - address(rewardToken), - balance - ); - rewardToken.safeTransfer(harvesterAddress, balance); - } + emit RewardTokenCollected( + harvesterAddress, + WETH_TOKEN_ADDRESS, + ethRewards + ); + IERC20(WETH_TOKEN_ADDRESS).safeTransfer( + harvesterAddress, + ethRewards + ); } } @@ -218,9 +174,7 @@ contract NativeStakingSSVStrategy is uint256 _amount ) internal { require(_amount > 0, "Must withdraw something"); - if (_recipient == address(0)) { - revert EmptyRecipient(); - } + require(_recipient != address(0), "Must specify recipient"); emit Withdrawal(_asset, address(0), _amount); IERC20(_asset).safeTransfer(_recipient, _amount); @@ -239,7 +193,10 @@ contract NativeStakingSSVStrategy is function _abstractSetPToken(address _asset, address) internal override {} /// @notice Returns the total value of (W)ETH that is staked to the validators - /// and also present on the native staking and fee accumulator contracts. + /// and WETH deposits that are still to be staked. + /// This does not include ETH from consensus rewards sitting in this strategy + /// or ETH from MEV rewards in the FeeAccumulator. These rewards are harvested + /// and sent to the Dripper so will eventually be sent to the Vault as WETH. /// @param _asset Address of weth asset /// @return balance Total value of (W)ETH function checkBalance(address _asset) @@ -248,13 +205,14 @@ contract NativeStakingSSVStrategy is override returns (uint256 balance) { - if (_asset != WETH_TOKEN_ADDRESS) { - revert NotWeth(); - } + require(_asset == WETH_TOKEN_ADDRESS, "Unsupported asset"); - balance = activeDepositedValidators * 32 ether; - balance += beaconChainRewardWETH; - balance += FEE_ACCUMULATOR_ADDRESS.balance; + balance = + // add the ETH that has been staked in validators + activeDepositedValidators * + 32 ether + + // add the WETH in the strategy from deposits that are still to be staked + IERC20(WETH_TOKEN_ADDRESS).balanceOf(address(this)); } function pause() external onlyStrategist { @@ -293,4 +251,16 @@ contract NativeStakingSSVStrategy is cluster ); } + + /** + * @notice Only accept ETH from the FeeAccumulator + * @dev don't want to receive donations from anyone else as this will + * mess with the accounting of the consensus rewards and validator full withdrawals + */ + receive() external payable { + require( + msg.sender == FEE_ACCUMULATOR_ADDRESS, + "eth not sent from Fee Accumulator" + ); + } } diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol index 8570e96f16..f6d919b4b7 100644 --- a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol +++ b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol @@ -6,9 +6,10 @@ import { ValidatorRegistrator } from "./ValidatorRegistrator.sol"; import { IVault } from "../../interfaces/IVault.sol"; import { IWETH9 } from "../../interfaces/IWETH9.sol"; -/// @title Accountant of the rewards Beacon Chain ETH -/// @notice This contract contains the logic to attribute the Beacon Chain swept ETH either to full -/// or partial withdrawals +/// @title Validator Accountant +/// @notice Attributes the ETH swept from beacon chain validators to this strategy contract +/// as either full or partial withdrawals. Partial withdrawals being consensus rewards. +/// Full withdrawals are from exited validators. /// @author Origin Protocol Inc abstract contract ValidatorAccountant is ValidatorRegistrator { /// @notice The maximum amount of ETH that can be staked by a validator @@ -17,16 +18,8 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { /// @notice Address of the OETH Vault proxy contract address public immutable VAULT_ADDRESS; - /// @dev The WETH present on this contract will come from 2 sources: - /// - as a result of deposits from the VaultAdmin - /// - accounting function converting beaconChain rewards from ETH to WETH - /// - /// We need to be able to keep a separate accounting of the WETH so we understand how much we can pass oh to - /// the harvester as a consequence of rewards harvesting and how much registrator can pick up as a result of WETH - /// deposit into the strategy contract. - /// To achieve this the beacon chain rewards are accounted for using below variable, all other WETH is assumed to be - /// present as a result of a deposit. - uint256 public beaconChainRewardWETH = 0; + /// @notice Keeps track of the total consensus rewards swept from the beacon chain + uint256 public consensusRewards = 0; /// @notice start of fuse interval uint256 public fuseIntervalStart = 0; @@ -61,18 +54,12 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { event AccountingManuallyFixed( uint256 oldActiveDepositedValidators, uint256 activeDepositedValidators, - uint256 oldBeaconChainRewardWETH, - uint256 beaconChainRewardWETH, + uint256 oldBeaconChainRewards, + uint256 beaconChainRewards, uint256 ethToWeth, uint256 wethToBeSentToVault ); - error UnexpectedEthAccountingInterval(uint256 errorneousEthAmount); - error ManualFixAccountingThresholdReached(); - error FuseIntervalValuesIncorrect(); - error NotPaused(); - error InsuffiscientETHbalance(); - /// @dev Throws if called by any account other than the Accounting Governor modifier onlyAccountingGovernor() { require( @@ -120,14 +107,13 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { uint256 _fuseIntervalStart, uint256 _fuseIntervalEnd ) external onlyGovernor { - if ( - _fuseIntervalStart > _fuseIntervalEnd || - _fuseIntervalStart >= 32 ether || - _fuseIntervalEnd >= 32 ether || - _fuseIntervalEnd - _fuseIntervalStart < 4 ether - ) { - revert FuseIntervalValuesIncorrect(); - } + require( + _fuseIntervalStart < _fuseIntervalEnd && + _fuseIntervalStart < 32 ether && + _fuseIntervalEnd < 32 ether && + _fuseIntervalEnd - _fuseIntervalStart >= 4 ether, + "incorrect fuse interval" + ); emit FuseIntervalUpdated( fuseIntervalStart, @@ -157,12 +143,13 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { onlyRegistrator returns (bool accountingValid) { - uint256 ethBalance = address(this).balance; + // Calculate all the new ETH that has been swept to the contract since the last accounting + uint256 newSweptETH = address(this).balance - consensusRewards; accountingValid = true; - // send the WETH that is from fully withdrawn validators to the Vault - if (ethBalance >= MAX_STAKE) { - uint256 fullyWithdrawnValidators = ethBalance / MAX_STAKE; + // send the ETH that is from fully withdrawn validators to the Vault + if (newSweptETH >= MAX_STAKE) { + uint256 fullyWithdrawnValidators = newSweptETH / MAX_STAKE; activeDepositedValidators -= fullyWithdrawnValidators; uint256 wethToVault = MAX_STAKE * fullyWithdrawnValidators; @@ -177,16 +164,13 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { } uint256 ethRemaining = address(this).balance; - // should never happen - if (ethRemaining > 32 ether) { - revert UnexpectedEthAccountingInterval(ethRemaining); - } + // should be less than a whole validator stake + require(ethRemaining < 32 ether, "unexpected accounting"); // Beacon chain rewards swept (partial validator withdrawals) if (ethRemaining <= fuseIntervalStart) { - IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: ethRemaining }(); // solhint-disable-next-line reentrancy - beaconChainRewardWETH += ethRemaining; + consensusRewards += ethRemaining; emit AccountingBeaconChainRewards(ethRemaining); } // Beacon chain rewards swept but also a slashed validator fully exited @@ -213,7 +197,7 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { /// @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 _beaconChainRewardWETH the override value for beaconChainRewardWETH + /// @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 @@ -222,40 +206,37 @@ abstract contract ValidatorAccountant is ValidatorRegistrator { uint256 _activeDepositedValidators, uint256 _ethToWeth, uint256 _wethToBeSentToVault, - uint256 _beaconChainRewardWETH, + uint256 _consensusRewards, uint256 _ethThresholdCheck, uint256 _wethThresholdCheck ) external onlyAccountingGovernor { - if (!paused()) { - revert NotPaused(); - } + require(paused(), "not paused"); uint256 ethBalance = address(this).balance; uint256 wethBalance = IWETH9(WETH_TOKEN_ADDRESS).balanceOf( address(this) ); - if ( - ethBalance > _ethThresholdCheck || wethBalance > _wethThresholdCheck - ) { - revert ManualFixAccountingThresholdReached(); - } + require( + ethBalance <= _ethThresholdCheck && + wethBalance <= _wethThresholdCheck, + "over accounting threshold" + ); emit AccountingManuallyFixed( activeDepositedValidators, _activeDepositedValidators, - beaconChainRewardWETH, - _beaconChainRewardWETH, + consensusRewards, + _consensusRewards, _ethToWeth, _wethToBeSentToVault ); activeDepositedValidators = _activeDepositedValidators; - beaconChainRewardWETH = _beaconChainRewardWETH; + consensusRewards = _consensusRewards; if (_ethToWeth > 0) { - if (ethBalance < _ethToWeth) { - revert InsuffiscientETHbalance(); - } + require(_ethToWeth <= ethBalance, "insufficient ETH"); + IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: _ethToWeth }(); } if (_wethToBeSentToVault > 0) { diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol index 1674b47730..27aaaf247c 100644 --- a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol +++ b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol @@ -51,9 +51,6 @@ abstract contract ValidatorRegistrator is Governable, Pausable { event SSVValidatorExitInitiated(bytes pubkey, uint64[] operatorIds); event SSVValidatorExitCompleted(bytes pubkey, uint64[] operatorIds); - error InsufficientWETH(uint256 wethBalance, uint256 requiredWethBalance); - error ValidatorInUnexpectedState(bytes pubkey, VALIDATOR_STATE state); - /// @dev Throws if called by any account other than the Registrator modifier onlyRegistrator() { require( @@ -82,14 +79,6 @@ abstract contract ValidatorRegistrator is Governable, Pausable { validatorRegistrator = _address; } - /// @notice return the WETH balance on the contract that can be used to for beacon chain - /// staking - staking on the validators - function getWETHBalanceEligibleForStaking() - public - view - virtual - returns (uint256 _amount); - /// @notice Stakes WETH to the node validators /// @param validators A list of validator data needed to stake. /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot. @@ -99,32 +88,51 @@ abstract contract ValidatorRegistrator is Governable, Pausable { onlyRegistrator whenNotPaused { - uint256 requiredWETH = validators.length * 32 ether; - uint256 wethBalance = getWETHBalanceEligibleForStaking(); - if (wethBalance < requiredWETH) { - revert InsufficientWETH(wethBalance, requiredWETH); - } + uint256 requiredETH = validators.length * 32 ether; + + // Check there is enough WETH from the deposits sitting in this strategy contract + require( + requiredETH <= IWETH9(WETH_TOKEN_ADDRESS).balanceOf(address(this)), + "insufficient WETH" + ); - // Convert WETH asset to native ETH - IWETH9(WETH_TOKEN_ADDRESS).withdraw(wethBalance); + // Convert required ETH from WETH + IWETH9(WETH_TOKEN_ADDRESS).withdraw(requiredETH); // For each validator for (uint256 i = 0; i < validators.length; ) { bytes32 pubkeyHash = keccak256(validators[i].pubkey); VALIDATOR_STATE currentState = validatorsStates[pubkeyHash]; - if (currentState != VALIDATOR_STATE.REGISTERED) { - revert ValidatorInUnexpectedState( - validators[i].pubkey, - currentState - ); - } + require( + currentState == VALIDATOR_STATE.REGISTERED, + "Validator not registered" + ); - _stakeEth( + /* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function + * can sweep funds to. + * bytes11(0) to fill up the required zeros + * remaining bytes20 are for the address + */ + bytes memory withdrawal_credentials = abi.encodePacked( + bytes1(0x01), + bytes11(0), + address(this) + ); + IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit( validators[i].pubkey, + withdrawal_credentials, validators[i].signature, validators[i].depositDataRoot ); + + activeDepositedValidators += 1; + emit ETHStaked( + validators[i].pubkey, + 32 ether, + withdrawal_credentials + ); + validatorsStates[pubkeyHash] = VALIDATOR_STATE.STAKED; unchecked { @@ -133,34 +141,6 @@ abstract contract ValidatorRegistrator is Governable, Pausable { } } - /// @dev Deposit WETH to the beacon chain deposit contract. - /// The public functions that call this internal function are responsible for access control. - function _stakeEth( - bytes calldata pubkey, - bytes calldata signature, - bytes32 depositDataRoot - ) internal { - /* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function - * can sweep funds to. - * bytes11(0) to fill up the required zeros - * remaining bytes20 are for the address - */ - bytes memory withdrawal_credentials = abi.encodePacked( - bytes1(0x01), - bytes11(0), - address(this) - ); - IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit( - pubkey, - withdrawal_credentials, - signature, - depositDataRoot - ); - - activeDepositedValidators += 1; - emit ETHStaked(pubkey, 32 ether, withdrawal_credentials); - } - /// @notice Registers a new validator in the SSV Cluster. /// Only the registrator can call this function. function registerSsvValidator( @@ -189,9 +169,7 @@ abstract contract ValidatorRegistrator is Governable, Pausable { uint64[] calldata operatorIds ) external onlyRegistrator whenNotPaused { VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)]; - if (currentState != VALIDATOR_STATE.STAKED) { - revert ValidatorInUnexpectedState(publicKey, currentState); - } + require(currentState == VALIDATOR_STATE.STAKED, "Validator not staked"); ISSVNetwork(SSV_NETWORK_ADDRESS).exitValidator(publicKey, operatorIds); emit SSVValidatorExitInitiated(publicKey, operatorIds); @@ -209,9 +187,10 @@ abstract contract ValidatorRegistrator is Governable, Pausable { Cluster calldata cluster ) external onlyRegistrator whenNotPaused { VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)]; - if (currentState != VALIDATOR_STATE.EXITING) { - revert ValidatorInUnexpectedState(publicKey, currentState); - } + require( + currentState == VALIDATOR_STATE.EXITING, + "Validator not exiting" + ); ISSVNetwork(SSV_NETWORK_ADDRESS).removeValidator( publicKey, diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js index b3cb95cea2..6dcf9545d8 100644 --- a/contracts/deploy/001_core.js +++ b/contracts/deploy/001_core.js @@ -807,7 +807,6 @@ const deployNativeStakingSSVStrategy = async () => { log("Deploy fee accumulator implementation"); const dFeeAccumulator = await deployWithConfirmation("FeeAccumulator", [ cNativeStakingSSVStrategyProxy.address, // _collector - assetAddresses.WETH, // _weth ]); const cFeeAccumulator = await ethers.getContractAt( "FeeAccumulator", diff --git a/contracts/deploy/091_native_ssv_staking.js b/contracts/deploy/091_native_ssv_staking.js index 82f14f4bf7..36e0c3393a 100644 --- a/contracts/deploy/091_native_ssv_staking.js +++ b/contracts/deploy/091_native_ssv_staking.js @@ -96,7 +96,6 @@ module.exports = deploymentWithGovernanceProposal( // 5. Deploy the new fee accumulator implementation const dFeeAccumulator = await deployWithConfirmation("FeeAccumulator", [ cStrategyProxy.address, // _collector - addresses.mainnet.WETH, // _weth ]); const cFeeAccumulator = await ethers.getContractAt( "FeeAccumulator", diff --git a/contracts/docs/FeeAccumulatorSquashed.svg b/contracts/docs/FeeAccumulatorSquashed.svg index 3c2c1558ab..c41be6097f 100644 --- a/contracts/docs/FeeAccumulatorSquashed.svg +++ b/contracts/docs/FeeAccumulatorSquashed.svg @@ -4,40 +4,37 @@ - - + + UmlClassDiagram - + 278 - -FeeAccumulator -../contracts/strategies/NativeStaking/FeeAccumulator.sol - -Private: -   governorPosition: bytes32 <<Governable>> -   pendingGovernorPosition: bytes32 <<Governable>> -   reentryStatusPosition: bytes32 <<Governable>> -   __gap: uint256[50] <<FeeAccumulator>> -Public: -   _NOT_ENTERED: uint256 <<Governable>> -   _ENTERED: uint256 <<Governable>> -   COLLECTOR: address <<FeeAccumulator>> -   WETH_TOKEN_ADDRESS: address <<FeeAccumulator>> - -Internal: -    _governor(): (governorOut: address) <<Governable>> -    _pendingGovernor(): (pendingGovernor: address) <<Governable>> -    _setGovernor(newGovernor: address) <<Governable>> -    _setPendingGovernor(newGovernor: address) <<Governable>> -    _changeGovernor(_newGovernor: address) <<Governable>> -    _assertIsCollector() <<FeeAccumulator>> + +FeeAccumulator +../contracts/strategies/NativeStaking/FeeAccumulator.sol + +Private: +   governorPosition: bytes32 <<Governable>> +   pendingGovernorPosition: bytes32 <<Governable>> +   reentryStatusPosition: bytes32 <<Governable>> +Public: +   _NOT_ENTERED: uint256 <<Governable>> +   _ENTERED: uint256 <<Governable>> +   STRATEGY: address <<FeeAccumulator>> + +Internal: +    _governor(): (governorOut: address) <<Governable>> +    _pendingGovernor(): (pendingGovernor: address) <<Governable>> +    _setGovernor(newGovernor: address) <<Governable>> +    _setPendingGovernor(newGovernor: address) <<Governable>> +    _changeGovernor(_newGovernor: address) <<Governable>> External:    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>>    claimGovernance() <<Governable>> -    collect(): (weth: uint256) <<FeeAccumulator>> +    collect(): (eth: uint256) <<FeeAccumulator>> Public:    <<event>> PendingGovernorshipTransfer(previousGovernor: address, newGovernor: address) <<Governable>>    <<event>> GovernorshipTransferred(previousGovernor: address, newGovernor: address) <<Governable>> @@ -46,7 +43,7 @@    constructor() <<Governable>>    governor(): address <<Governable>>    isGovernor(): bool <<Governable>> -    constructor(_collector: address, _weth: address) <<FeeAccumulator>> +    constructor(_strategy: address) <<FeeAccumulator>> diff --git a/contracts/docs/NativeStakingSSVStrategySquashed.svg b/contracts/docs/NativeStakingSSVStrategySquashed.svg index 838bee6efe..a12c2db559 100644 --- a/contracts/docs/NativeStakingSSVStrategySquashed.svg +++ b/contracts/docs/NativeStakingSSVStrategySquashed.svg @@ -4,146 +4,143 @@ - - + + 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>> -   validatorRegistrator: address <<ValidatorRegistrator>> -   activeDepositedValidators: uint256 <<ValidatorRegistrator>> -   validatorsStates: mapping(bytes32=>VALIDATOR_STATE) <<ValidatorRegistrator>> -   VAULT_ADDRESS: address <<ValidatorAccountant>> -   beaconChainRewardWETH: uint256 <<ValidatorAccountant>> -   fuseIntervalStart: uint256 <<ValidatorAccountant>> -   fuseIntervalEnd: uint256 <<ValidatorAccountant>> -   accountingGovernor: address <<ValidatorAccountant>> -   strategist: 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>> -    _stakeEth(pubkey: bytes, signature: bytes, depositDataRoot: bytes32) <<ValidatorRegistrator>> -    _initialize(_rewardTokenAddresses: address[], _assets: address[], _pTokens: address[]) <<InitializableAbstractStrategy>> -    _collectRewardTokens() <<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: -    transferGovernance(_newGovernor: address) <<onlyGovernor>> <<Governable>> -    claimGovernance() <<Governable>> -    setRegistratorAddress(_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>> -    setAccountingGovernor(_address: address) <<onlyGovernor>> <<ValidatorAccountant>> -    setStrategist(_address: address) <<onlyGovernor>> <<ValidatorAccountant>> -    setFuseInterval(_fuseIntervalStart: uint256, _fuseIntervalEnd: uint256) <<onlyGovernor>> <<ValidatorAccountant>> -    doAccounting(): (accountingValid: bool) <<onlyRegistrator>> <<ValidatorAccountant>> -    manuallyFixAccounting(_activeDepositedValidators: uint256, _ethToWeth: uint256, _wethToBeSentToVault: uint256, _beaconChainRewardWETH: uint256, _ethThresholdCheck: uint256, _wethThresholdCheck: uint256) <<onlyAccountingGovernor>> <<ValidatorAccountant>> -    collectRewardTokens() <<onlyHarvester, nonReentrant>> <<NativeStakingSSVStrategy>> -    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>> -    depositSSV(operatorIds: uint64[], amount: uint256, cluster: Cluster) <<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>> RegistratorAddressChanged(oldAddress: address, 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(oldStart: uint256, oldEnd: uint256, start: uint256, end: uint256) <<ValidatorAccountant>> -    <<event>> AccuntingFullyWithdrawnValidator(noOfValidators: uint256, remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> -    <<event>> AccuntingValidatorSlashed(remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> -    <<event>> AccountingGovernorAddressChanged(oldAddress: address, newAddress: address) <<ValidatorAccountant>> -    <<event>> AccountingBeaconChainRewards(amount: uint256) <<ValidatorAccountant>> -    <<event>> StrategistAddressChanged(oldStrategist: address, newStrategist: address) <<ValidatorAccountant>> -    <<event>> AccountingManuallyFixed(oldActiveDepositedValidators: uint256, activeDepositedValidators: uint256, oldBeaconChainRewardWETH: uint256, beaconChainRewardWETH: 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>> onlyAccountingGovernor() <<ValidatorAccountant>> -    <<modifier>> onlyStrategist() <<ValidatorAccountant>> -    <<modifier>> initializer() <<Initializable>> -    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> -    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> -    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> -    constructor() <<Pausable>> -    governor(): address <<Governable>> -    isGovernor(): bool <<Governable>> -    paused(): bool <<Pausable>> -    constructor(_wethAddress: address, _beaconChainDepositContract: address, _ssvNetwork: address) <<ValidatorRegistrator>> -    getWETHBalanceEligibleForStaking(): (_amount: uint256) <<NativeStakingSSVStrategy>> + +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>> +   validatorRegistrator: address <<ValidatorRegistrator>> +   activeDepositedValidators: uint256 <<ValidatorRegistrator>> +   validatorsStates: mapping(bytes32=>VALIDATOR_STATE) <<ValidatorRegistrator>> +   MAX_STAKE: uint256 <<ValidatorAccountant>> +   VAULT_ADDRESS: address <<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() <<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>> +    setRegistratorAddress(_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>> +    setAccountingGovernor(_address: address) <<onlyGovernor>> <<ValidatorAccountant>> +    setFuseInterval(_fuseIntervalStart: uint256, _fuseIntervalEnd: uint256) <<onlyGovernor>> <<ValidatorAccountant>> +    doAccounting(): (accountingValid: bool) <<onlyRegistrator>> <<ValidatorAccountant>> +    manuallyFixAccounting(_activeDepositedValidators: uint256, _ethToWeth: uint256, _wethToBeSentToVault: uint256, _consensusRewards: uint256, _ethThresholdCheck: uint256, _wethThresholdCheck: uint256) <<onlyAccountingGovernor>> <<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>> +    depositSSV(operatorIds: uint64[], amount: uint256, cluster: Cluster) <<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>> RegistratorAddressChanged(oldAddress: address, 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(oldStart: uint256, oldEnd: uint256, start: uint256, end: uint256) <<ValidatorAccountant>> +    <<event>> AccountingFullyWithdrawnValidator(noOfValidators: uint256, remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> +    <<event>> AccountingValidatorSlashed(remainingValidators: uint256, wethSentToVault: uint256) <<ValidatorAccountant>> +    <<event>> AccountingGovernorAddressChanged(oldAddress: address, newAddress: address) <<ValidatorAccountant>> +    <<event>> AccountingBeaconChainRewards(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>> onlyAccountingGovernor() <<ValidatorAccountant>> +    <<modifier>> onlyStrategist() <<ValidatorAccountant>> +    <<modifier>> initializer() <<Initializable>> +    <<modifier>> onlyVault() <<InitializableAbstractStrategy>> +    <<modifier>> onlyHarvester() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernor() <<InitializableAbstractStrategy>> +    <<modifier>> onlyVaultOrGovernorOrStrategist() <<InitializableAbstractStrategy>> +    constructor() <<Pausable>> +    governor(): address <<Governable>> +    isGovernor(): bool <<Governable>> +    paused(): bool <<Pausable>> +    constructor(_wethAddress: address, _beaconChainDepositContract: address, _ssvNetwork: address) <<ValidatorRegistrator>>    constructor(_wethAddress: address, _vaultAddress: address, _beaconChainDepositContract: address, _ssvNetwork: address) <<ValidatorAccountant>>    constructor(_config: BaseStrategyConfig) <<InitializableAbstractStrategy>>    transferToken(_asset: address, _amount: uint256) <<onlyGovernor>> <<InitializableAbstractStrategy>> diff --git a/contracts/docs/NativeStakingSSVStrategyStorage.svg b/contracts/docs/NativeStakingSSVStrategyStorage.svg index 41964933b0..058cea8538 100644 --- a/contracts/docs/NativeStakingSSVStrategyStorage.svg +++ b/contracts/docs/NativeStakingSSVStrategyStorage.svg @@ -4,90 +4,84 @@ - - + + 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 +56 -58-107 +57-106 -108 +107 -109-158 +108-157 -159 +158 -160 +159 -161 +160 -162 +161 -163 +162 -164 +163 -165 +164 -166 +165 -167-264 +166-263 -265-314 - -type: <inherited contract>.variable (bytes) - -unallocated (11) - -address: ValidatorRegistrator.validatorRegistrator (20) - -bool: Pausable._paused (1) +264-313 + +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.beaconChainRewardWETH (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) -unallocated (12) - -address: ValidatorAccountant.strategist (20) +unallocated (12) + +address: ValidatorAccountant.accountingGovernor (20) uint256[50]: ValidatorAccountant.__gap (1600) @@ -130,48 +124,48 @@ 1 - -address[]: assetsMapped <<Array>> -0xaaf4f58de99300cfadc4585755f376d5fa747d5bc561d5bd9d710de1f91bf42d - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: assetsMapped <<Array>> +0xaadc37b8ba5645e62f4546802db221593a94729ccbfc5a97d01365a88f649878 + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) -3:19->1 - - +3:18->1 + + 2 - -address[]: rewardTokenAddresses <<Array>> -0x2da56674729343acc9933752c8c469a244252915242eb6d4c02d11ddd69164a1 - -offset - -0 - -type: variable (bytes) - -unallocated (12) - -address (20) + +address[]: rewardTokenAddresses <<Array>> +0xb29a2b3b6f2ff1b765777a231725941da5072cc4fcc30ac4a2ce09706e8ddeff + +offset + +0 + +type: variable (bytes) + +unallocated (12) + +address (20) -3:24->2 - - +3:23->2 + + diff --git a/contracts/docs/plantuml/oethProcesses.png b/contracts/docs/plantuml/oethProcesses.png new file mode 100644 index 0000000000..ded50a16e0 Binary files /dev/null and b/contracts/docs/plantuml/oethProcesses.png differ diff --git a/contracts/docs/plantuml/oethProcesses.puml b/contracts/docs/plantuml/oethProcesses.puml new file mode 100644 index 0000000000..b97837a856 --- /dev/null +++ b/contracts/docs/plantuml/oethProcesses.puml @@ -0,0 +1,279 @@ +@startuml + +skinparam tabSize 2 +hide footbox + +title "Origin ETH processes" + +actor "Anyone" as sender +actor "Registrator" as reg <> +actor "Governor" as gov <> +actor "Treasury" as treasury <> +participant "API" as api <> +actor "Operators" as ssvOp <> + +box "Execution Chain" +participant "Harvester" as harv <> +participant "Native\nStaking SSV\nStrategy" as nativeStrat <> +participant "Fee\nAccumulator" as feeAccum <> +participant "SSV Network" as ssvNet <> +participant "Token" as ssv <> +participant "WETH" as weth <> +participant "Deposit" as dep <> +end box + +box "Beacon chain" +participant "Validator" as val <> +end box + +group Governor initializes the Native Staking Strategy + +gov -> nativeStrat : initialize() +activate nativeStrat +nativeStrat -> ssv : approve(\nSSV Network,\namount) +activate ssv +note right : Native Staking Strategy approves\nSSV Network to spend\nSSV tokens +return +' nativeStrat -> ssvNet : setFeeRecipientAddress(\nrecipient) +' activate ssvNet +' note right : NodeDelegator set as the fee recipient +' return +return + +gov -> ssv : transfer(\nfrom\nto\namount) +activate ssv +note right : transfer SSV tokens\nfrom Governor\nto Native Staking Strategy +return + +end group + +group Registrator creates a new SSV validator + +reg -> api: POST\neth/staking/ssv/request/create\nuuid,\nvalidatorsCount,\ntype,\nwithdrawalAddress,\nfeeRecipientAddress,\nssvOwnerAddress,\noperationPeriodInDays +activate api +note right +withdrawalAddress is Native Staking Strategy +feeRecipientAddress is FeeAccumulator contract +ssvOwnerAddress is Native Staking Strategy +type is without-encrypt-key +end note +api -> api: private key +note right : generate a validator private key +api -> api: split(key) +note right : splits validator key into multiple KeyShares +return + +reg -> api: status(uuid) +activate api +return status,\nvalidatorRegistration,\nshareData +note right : validatorRegistration contains the pubkey, operatorIds and cluster details + +reg -> nativeStrat : registerSsvValidator(\npublicKey,\noperatorIds,\nsharesData,\namount,\ncluster,\nwithdrawal_credentials,\nsignature,\ndepositDataRoot) +activate nativeStrat +note right +cluster data: +The number of validators in the cluster +The index of network fees related to this cluster +The last index calculated for the cluster +Flag indicating whether the cluster is active +The SSV balance of the cluster +end note +nativeStrat -> ssvNet : registerValidator(\npublicKey,\noperatorIds,\nsharesData,\namount,\ncluster) +activate ssvNet +ssvNet -> ssv : transferFrom(\nfrom\nto\namount) +activate ssv +note right: transfer SSV tokens\nfrom NodeDelegator\nto SSV Network +return +return + +nativeStrat -> weth : withdraw(\namount) +activate weth +note right : WETH burned for ETH +return ETH + +nativeStrat -> dep : stake(\npubkey,\nwithdrawal_credentials,\nsignature,\ndepositDataRoot) +activate dep +note left : 32 ETH from Native Staking Strategy\nis sent to Beacon Deposit +return +return + +note over val : Pending Deposit + +... 1024 execution blocks (~4 hours) ... +... 32 consensus epochs (~3.5 hours) ... + +dep -> val : 32 ETH + +note over val : Pending Activation + +... four validators are activated each epoch from the Validator Queue (~1.5 days) ... + +note over val : Active + +end group + + +group Registrator deposits more SSV to SSV cluster + +treasury -> ssv : transfer(\nto\namount) +activate ssv +note right : transfer SSV tokens\nfrom Treasury\nto Native Staking Strategy +return + +group SSV ClusterScanner +reg -> ssvNet : getPastEvents(filter) +activate ssvNet +note right : get all events where the ownerAddress\nis the Native Staking Strategy +return events + +reg -> reg : getCluster(events):\n cluster +note right +cluster data: + validatorCount + networkFeeIndex + index + active + balance +end note +end group + +reg -> nativeStrat : depositSSV(\noperatorIds,\namount,\ncluster) +activate nativeStrat +nativeStrat -> ssvNet : deposit(\nclusterOwner,\noperatorIds,\namount,\ncluster) +activate ssvNet +note right +clusterOwner is Native Staking Strategy +operatorIds are the SSV Operators +amount of SSV tokens +end note +ssvNet -> ssv : transferFrom(\nfrom\nto\namount) +activate ssv +note right: transfer SSV tokens\nfrom Native Staking Strategy\nto SSV Network +return +return +return +end group + +group Consensus Rewards + +note over val +attesting to blocks +proposing blocks +participating in sync committees +end note +val -> val : ETH + +... swept every 8-10 days ... + +note over val : partial withdraw of excess ETH\nfrom validator to the Native Staking Strategy +val -> nativeStrat : ETH + +note over nativeStrat : Native Staking Strategy's\nWETH balance does not change + +end group + +group Registrator full withdraw from validator + +reg -> nativeStrat : exitSsvValidator(\npublicKey\noperatorIds) +activate nativeStrat +nativeStrat -> ssvNet : exitValidator(\npublicKey\noperatorIds) +activate ssvNet +return +return + +ssvOp -> ssvOp : sign(\npk,\nexit message) +note right : voluntary exit message signed by the validator private key +ssvOp -> val : signed voluntary exit message +activate val +return + +... wait until validator has exited.\nmin four epochs (~25 min), currently 1.5 hours but can take a number of days depending on the number of validators in the exit queue ... + +reg -> nativeStrat : removeSsvValidator(\npublicKey,\noperatorIds,\ncluster) +activate nativeStrat +nativeStrat -> ssvNet : removeValidator(\npublicKey\noperatorIds,\ncluster) +activate ssvNet +note right : stop paying SSV to Operators\n and reduce required SSV collateral +return +return + +... wait for the validator to be swept on the Beacon chain\ncurrent time is every 8.5 days ... + +val -> nativeStrat : ETH +note left : transfer staked ETH and rewards\nfrom Beacon Deposit\nto Native Staking Strategy + +note over nativeStrat : Native Staking Strategy's\nWETH balance does not change + +end group + +group Registrator does accounting of consensus rewards and validator withdrawals + +reg -> nativeStrat : doAccounting() +activate nativeStrat + +note over nativeStrat +ETH received since last accounting = current ETH balance - previous consensus rewards +validator withdrawals = ETH received / 32 ETH +end note + +nativeStrat -> weth : deposit(\nwithdrawn ETH) +activate weth +note left : convert ETH from full withdrawals to WETH +return + +nativeStrat -> weth : transfer(\nvault,\nwithdrawn ETH) +activate weth +note left : transfer withdrawn WETH\nfrom Native Staking Strategy\nto OETH Vault +return + +note over nativeStrat +Add remaining ETH to consensus rewards. +ETH from consensus rewards stays in the Native Staking Strategy. +end note + +return accounting valid flag + +end group + +group Execution Rewards + +sender -> feeAccum : ETH +note right : tx fees and MEV rewards + +note over nativeStrat : Native Staking Strategy's\nWETH balance does not change + +end group + +group Harvester collects ETH rewards + +sender -> harv : harvestAndSwap(\nstrtaegy) +activate harv +harv -> nativeStrat : collectRewardTokens() +activate nativeStrat + +nativeStrat -> feeAccum : collect() +activate feeAccum +feeAccum -> nativeStrat : ETH +note right : send all execution rewards in the FeeAccumulator\nto the Native Staking Stragegy +return execution rewards + +note over nativeStrat : total rewards = execution rewards + consensus rewards + +note over nativeStrat : reset consensus rewards to zero + +nativeStrat -> weth : deposit(\ntotal rewards) +activate weth +note left : convert ETH rewards to WETH +return + +nativeStrat -> weth : transfer(\nHarvester,\ntotal ETH rewards) +activate weth +note left : transfer rewards as WETH\nfrom Native Staking Strategy\nto Harvester +return + +return +return + +end group + +@enduml \ No newline at end of file diff --git a/contracts/test/strategies/nativeSSVStaking.js b/contracts/test/strategies/nativeSSVStaking.js index 910a5d8ba0..dafaed2aff 100644 --- a/contracts/test/strategies/nativeSSVStaking.js +++ b/contracts/test/strategies/nativeSSVStaking.js @@ -1,6 +1,6 @@ const { expect } = require("chai"); -const ethers = require("ethers"); -const { utils, BigNumber } = ethers; +const { BigNumber } = require("ethers"); +const { formatUnits, parseEther } = require("ethers").utils; const { setBalance } = require("@nomicfoundation/hardhat-network-helpers"); const { isCI } = require("../helpers"); @@ -57,11 +57,11 @@ describe("Unit test: Native SSV Staking Strategy", function () { ); const tx = { to: nativeStakingSSVStrategy.address, - value: ethers.utils.parseEther("2", "ether"), + value: parseEther("2", "ether"), }; await expect(signer.sendTransaction(tx)).to.be.revertedWith( - "function selector was not recognized and there's no fallback nor receive function" + "eth not sent from Fee Accumulator" ); }); @@ -115,7 +115,7 @@ describe("Unit test: Native SSV Staking Strategy", function () { await expect( nativeStakingSSVStrategy .connect(strategist) - .setFuseInterval(utils.parseEther("21.6"), utils.parseEther("25.6")) + .setFuseInterval(parseEther("21.6"), parseEther("25.6")) ).to.be.revertedWith("Caller is not the Governor"); }); @@ -125,8 +125,8 @@ describe("Unit test: Native SSV Staking Strategy", function () { await expect( nativeStakingSSVStrategy .connect(governor) - .setFuseInterval(utils.parseEther("25.6"), utils.parseEther("21.6")) - ).to.be.revertedWith("FuseIntervalValuesIncorrect"); + .setFuseInterval(parseEther("25.6"), parseEther("21.6")) + ).to.be.revertedWith("incorrect fuse interval"); }); it("There should be at least 4 ETH between interval start and interval end", async () => { @@ -135,8 +135,8 @@ describe("Unit test: Native SSV Staking Strategy", function () { await expect( nativeStakingSSVStrategy .connect(governor) - .setFuseInterval(utils.parseEther("21.6"), utils.parseEther("25.5")) - ).to.be.revertedWith("FuseIntervalValuesIncorrect"); + .setFuseInterval(parseEther("21.6"), parseEther("25.5")) + ).to.be.revertedWith("incorrect fuse interval"); }); it("Revert when fuse intervals are larger than 32 ether", async () => { @@ -145,17 +145,17 @@ describe("Unit test: Native SSV Staking Strategy", function () { await expect( nativeStakingSSVStrategy .connect(governor) - .setFuseInterval(utils.parseEther("32.1"), utils.parseEther("32.1")) - ).to.be.revertedWith("FuseIntervalValuesIncorrect"); + .setFuseInterval(parseEther("32.1"), parseEther("32.1")) + ).to.be.revertedWith("incorrect fuse interval"); }); it("Governor should be able to change fuse interval", async () => { const { nativeStakingSSVStrategy, governor } = fixture; - const oldFuseStartBn = utils.parseEther("21.6"); - const oldFuseEndBn = utils.parseEther("25.6"); - const fuseStartBn = utils.parseEther("22.6"); - const fuseEndBn = utils.parseEther("26.6"); + const oldFuseStartBn = parseEther("21.6"); + const oldFuseEndBn = parseEther("25.6"); + const fuseStartBn = parseEther("22.6"); + const fuseEndBn = parseEther("26.6"); const tx = await nativeStakingSSVStrategy .connect(governor) @@ -213,56 +213,56 @@ describe("Unit test: Native SSV Staking Strategy", function () { const testCases = [ // normal beacon chain rewards { - ethBalance: utils.parseEther("14"), - expectedRewards: utils.parseEther("14"), + ethBalance: parseEther("14"), + expectedRewards: parseEther("14"), expectedValidatorsFullWithdrawals: 0, slashDetected: false, fuseBlown: false, }, // normal beacon chain rewards + 1 withdrawn validator { - ethBalance: utils.parseEther("34"), - expectedRewards: utils.parseEther("2"), + ethBalance: parseEther("34"), + expectedRewards: parseEther("2"), expectedValidatorsFullWithdrawals: 1, slashDetected: false, fuseBlown: false, }, // 8 withdrawn validators + beacon chain rewards { - ethBalance: utils.parseEther("276"), - expectedRewards: utils.parseEther("20"), + ethBalance: parseEther("276"), + expectedRewards: parseEther("20"), expectedValidatorsFullWithdrawals: 8, slashDetected: false, fuseBlown: false, }, // fuse blown { - ethBalance: utils.parseEther("22"), - expectedRewards: utils.parseEther("0"), + ethBalance: parseEther("22"), + expectedRewards: parseEther("0"), expectedValidatorsFullWithdrawals: 0, slashDetected: false, fuseBlown: true, }, // fuse blown + 1 full withdrawal { - ethBalance: utils.parseEther("54"), - expectedRewards: utils.parseEther("0"), + ethBalance: parseEther("54"), + expectedRewards: parseEther("0"), expectedValidatorsFullWithdrawals: 1, slashDetected: false, fuseBlown: true, }, // 1 validator slashed { - ethBalance: utils.parseEther("26.6"), - expectedRewards: utils.parseEther("0"), + ethBalance: parseEther("26.6"), + expectedRewards: parseEther("0"), expectedValidatorsFullWithdrawals: 0, slashDetected: true, fuseBlown: false, }, // 1 validator fully withdrawn + 1 slashed { - ethBalance: utils.parseEther("58.6"), // 26.6 + 32 - expectedRewards: utils.parseEther("0"), + ethBalance: parseEther("58.6"), // 26.6 + 32 + expectedRewards: parseEther("0"), expectedValidatorsFullWithdrawals: 1, slashDetected: true, fuseBlown: false, @@ -277,9 +277,9 @@ describe("Unit test: Native SSV Staking Strategy", function () { slashDetected, fuseBlown, } = testCase; - it(`Expect that ${utils.formatUnits( + it(`Expect that ${formatUnits( ethBalance - )} ETH will result in ${utils.formatUnits( + )} ETH will result in ${formatUnits( expectedRewards )} ETH rewards and ${expectedValidatorsFullWithdrawals} validators withdrawn.`, async () => { const { nativeStakingSSVStrategy, governor, strategist } = fixture; @@ -290,11 +290,11 @@ describe("Unit test: Native SSV Staking Strategy", function () { await nativeStakingSSVStrategy.connect(strategist).pause(); await nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( 30, // activeDepositedValidators - ethers.utils.parseEther("0", "ether"), //_ethToWeth - ethers.utils.parseEther("0", "ether"), //_wethToBeSentToVault - ethers.utils.parseEther("0", "ether"), //_beaconChainRewardWETH - ethers.utils.parseEther("3000", "ether"), //_ethThresholdCheck - ethers.utils.parseEther("3000", "ether") //_wethThresholdCheck + parseEther("0", "ether"), //_ethToWeth + parseEther("0", "ether"), //_wethToBeSentToVault + parseEther("0", "ether"), //_beaconChainRewardWETH + parseEther("3000", "ether"), //_ethThresholdCheck + parseEther("3000", "ether") //_wethThresholdCheck ); // check accounting values @@ -328,9 +328,9 @@ describe("Unit test: Native SSV Staking Strategy", function () { ); // weth sent to vault expect(WithdrawnEvent.args[2]).to.equal( - utils - .parseEther("32") - .mul(BigNumber.from(`${expectedValidatorsFullWithdrawals}`)) + parseEther("32").mul( + BigNumber.from(`${expectedValidatorsFullWithdrawals}`) + ) ); } else { expect(WithdrawnEvent).to.be.undefined; @@ -365,11 +365,11 @@ describe("Unit test: Native SSV Staking Strategy", function () { await expect( nativeStakingSSVStrategy.connect(strategist).manuallyFixAccounting( 10, //_activeDepositedValidators - ethers.utils.parseEther("2", "ether"), //_ethToWeth - ethers.utils.parseEther("2", "ether"), //_wethToBeSentToVault - ethers.utils.parseEther("2", "ether"), //_beaconChainRewardWETH - ethers.utils.parseEther("0", "ether"), //_ethThresholdCheck - ethers.utils.parseEther("0", "ether") //_wethThresholdCheck + parseEther("2", "ether"), //_ethToWeth + parseEther("2", "ether"), //_wethToBeSentToVault + parseEther("2", "ether"), //_beaconChainRewardWETH + parseEther("0", "ether"), //_ethThresholdCheck + parseEther("0", "ether") //_wethThresholdCheck ) ).to.be.revertedWith("Caller is not the Accounting Governor"); }); @@ -381,13 +381,13 @@ describe("Unit test: Native SSV Staking Strategy", function () { await expect( nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( 10, //_activeDepositedValidators - ethers.utils.parseEther("2", "ether"), //_ethToWeth - ethers.utils.parseEther("2", "ether"), //_wethToBeSentToVault - ethers.utils.parseEther("2", "ether"), //_beaconChainRewardWETH - ethers.utils.parseEther("1", "ether"), //_ethThresholdCheck - ethers.utils.parseEther("0", "ether") //_wethThresholdCheck + parseEther("2", "ether"), //_ethToWeth + parseEther("2", "ether"), //_wethToBeSentToVault + parseEther("2", "ether"), //_beaconChainRewardWETH + parseEther("1", "ether"), //_ethThresholdCheck + parseEther("0", "ether") //_wethThresholdCheck ) - ).to.be.revertedWith("NotPaused"); + ).to.be.revertedWith("not paused"); }); it("Should not execute manual recovery if eth threshold reached", async () => { @@ -396,26 +396,23 @@ describe("Unit test: Native SSV Staking Strategy", function () { await setBalance( nativeStakingSSVStrategy.address, - ethers.utils.parseEther("6", "ether") + parseEther("6", "ether") ); await weth .connect(josh) - .transfer( - nativeStakingSSVStrategy.address, - ethers.utils.parseEther("5", "ether") - ); + .transfer(nativeStakingSSVStrategy.address, parseEther("5", "ether")); await nativeStakingSSVStrategy.connect(strategist).pause(); await expect( nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( 10, //_activeDepositedValidators - ethers.utils.parseEther("2", "ether"), //_ethToWeth - ethers.utils.parseEther("2", "ether"), //_wethToBeSentToVault - ethers.utils.parseEther("2", "ether"), //_beaconChainRewardWETH - ethers.utils.parseEther("5", "ether"), //_ethThresholdCheck - ethers.utils.parseEther("5", "ether") //_wethThresholdCheck + parseEther("2", "ether"), //_ethToWeth + parseEther("2", "ether"), //_wethToBeSentToVault + parseEther("2", "ether"), //_beaconChainRewardWETH + parseEther("5", "ether"), //_ethThresholdCheck + parseEther("5", "ether") //_wethThresholdCheck ) - ).to.be.revertedWith("ManualFixAccountingThresholdReached"); + ).to.be.revertedWith("over accounting threshold"); }); it("Should not execute manual recovery if weth threshold reached", async () => { @@ -424,26 +421,23 @@ describe("Unit test: Native SSV Staking Strategy", function () { await setBalance( nativeStakingSSVStrategy.address, - ethers.utils.parseEther("5", "ether") + parseEther("5", "ether") ); await weth .connect(josh) - .transfer( - nativeStakingSSVStrategy.address, - ethers.utils.parseEther("6", "ether") - ); + .transfer(nativeStakingSSVStrategy.address, parseEther("6", "ether")); await nativeStakingSSVStrategy.connect(strategist).pause(); await expect( nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( 10, //_activeDepositedValidators - ethers.utils.parseEther("2", "ether"), //_ethToWeth - ethers.utils.parseEther("2", "ether"), //_wethToBeSentToVault - ethers.utils.parseEther("2", "ether"), //_beaconChainRewardWETH - ethers.utils.parseEther("5", "ether"), //_ethThresholdCheck - ethers.utils.parseEther("5", "ether") //_wethThresholdCheck + parseEther("2", "ether"), //_ethToWeth + parseEther("2", "ether"), //_wethToBeSentToVault + parseEther("2", "ether"), //_beaconChainRewardWETH + parseEther("5", "ether"), //_ethThresholdCheck + parseEther("5", "ether") //_wethThresholdCheck ) - ).to.be.revertedWith("ManualFixAccountingThresholdReached"); + ).to.be.revertedWith("over accounting threshold"); }); it("Should allow 5/8 governor to recover paused contract and correct the accounting state", async () => { @@ -452,14 +446,11 @@ describe("Unit test: Native SSV Staking Strategy", function () { await setBalance( nativeStakingSSVStrategy.address, - ethers.utils.parseEther("5", "ether") + parseEther("5", "ether") ); await weth .connect(josh) - .transfer( - nativeStakingSSVStrategy.address, - ethers.utils.parseEther("5", "ether") - ); + .transfer(nativeStakingSSVStrategy.address, parseEther("5", "ether")); await nativeStakingSSVStrategy.connect(strategist).pause(); // unit test fixture sets OUSD governor as accounting governor @@ -467,11 +458,11 @@ describe("Unit test: Native SSV Staking Strategy", function () { .connect(governor) .manuallyFixAccounting( 3, //_activeDepositedValidators - ethers.utils.parseEther("2.1", "ether"), //_ethToWeth - ethers.utils.parseEther("2.2", "ether"), //_wethToBeSentToVault - ethers.utils.parseEther("2.3", "ether"), //_beaconChainRewardWETH - ethers.utils.parseEther("5", "ether"), //_ethThresholdCheck - ethers.utils.parseEther("5", "ether") //_wethThresholdCheck + parseEther("2.1", "ether"), //_ethToWeth + parseEther("2.2", "ether"), //_wethToBeSentToVault + parseEther("2.3", "ether"), //_beaconChainRewardWETH + parseEther("5", "ether"), //_ethThresholdCheck + parseEther("5", "ether") //_wethThresholdCheck ); const events = (await tx.wait()).events || []; @@ -486,16 +477,16 @@ describe("Unit test: Native SSV Staking Strategy", function () { expect(AccountingManuallyFixedEvent.args[0]).to.equal(0); // oldActiveDepositedValidators expect(AccountingManuallyFixedEvent.args[1]).to.equal(3); // activeDepositedValidators expect(AccountingManuallyFixedEvent.args[2]).to.equal( - ethers.utils.parseEther("0", "ether") + parseEther("0", "ether") ); // oldBeaconChainRewardWETH expect(AccountingManuallyFixedEvent.args[3]).to.equal( - ethers.utils.parseEther("2.3", "ether") + parseEther("2.3", "ether") ); // beaconChainRewardWETH expect(AccountingManuallyFixedEvent.args[4]).to.equal( - ethers.utils.parseEther("2.1", "ether") + parseEther("2.1", "ether") ); // ethToWeth expect(AccountingManuallyFixedEvent.args[5]).to.equal( - ethers.utils.parseEther("2.2", "ether") + parseEther("2.2", "ether") ); // wethToBeSentToVault }); }); @@ -503,174 +494,183 @@ describe("Unit test: Native SSV Staking Strategy", function () { describe("General functionality", function () { const rewardTestCases = [ { - feeAccumulatorEth: utils.parseEther("2.2"), - beaconChainRewardEth: utils.parseEther("16.3"), - wethFromDeposits: utils.parseEther("100"), + feeAccumulatorEth: 2.2, + consensusRewards: 16.3, + deposits: 100, nrOfActiveDepositedValidators: 7, - expectedEthSentToHarvester: utils.parseEther("18.5"), + expectedHarvester: 18.5, + expectedBalance: 100 + 7 * 32, }, { - feeAccumulatorEth: utils.parseEther("10.2"), - beaconChainRewardEth: utils.parseEther("21.6"), - wethFromDeposits: utils.parseEther("0"), + feeAccumulatorEth: 10.2, + consensusRewards: 21.6, + deposits: 0, nrOfActiveDepositedValidators: 5, - expectedEthSentToHarvester: utils.parseEther("31.8"), + expectedHarvester: 31.8, + expectedBalance: 0 + 5 * 32, }, { - feeAccumulatorEth: utils.parseEther("10.2"), - beaconChainRewardEth: utils.parseEther("21.6"), - wethFromDeposits: utils.parseEther("1"), + feeAccumulatorEth: 10.2, + consensusRewards: 21.6, + deposits: 1, nrOfActiveDepositedValidators: 0, - expectedEthSentToHarvester: utils.parseEther("31.8"), + expectedHarvester: 31.8, + expectedBalance: 1 + 0 * 32, }, { - feeAccumulatorEth: utils.parseEther("0"), - beaconChainRewardEth: utils.parseEther("0"), - wethFromDeposits: utils.parseEther("0"), + feeAccumulatorEth: 0, + consensusRewards: 0, + deposits: 0, nrOfActiveDepositedValidators: 0, - expectedEthSentToHarvester: utils.parseEther("0"), + expectedHarvester: 0, + expectedBalance: 0 + 0 * 32, }, ]; - for (const testCase of rewardTestCases) { - it("Collecting rewards and should correctly account for WETH", async () => { - const { - nativeStakingSSVStrategy, - governor, - // strategist, - oethHarvester, - weth, - josh, - } = fixture; - const { - feeAccumulatorEth, - beaconChainRewardEth, - wethFromDeposits, - expectedEthSentToHarvester, - } = testCase; - const feeAccumulatorAddress = - await nativeStakingSSVStrategy.FEE_ACCUMULATOR_ADDRESS(); - const sHarvester = await impersonateAndFund(oethHarvester.address); - - // setup state - if (beaconChainRewardEth.gt(BigNumber.from("0"))) { - // set the reward eth on the strategy - await setBalance( - nativeStakingSSVStrategy.address, - beaconChainRewardEth - ); - } - if (feeAccumulatorEth.gt(BigNumber.from("0"))) { - // set execution layer rewards on the fee accumulator - await setBalance(feeAccumulatorAddress, feeAccumulatorEth); - } - if (wethFromDeposits.gt(BigNumber.from("0"))) { - // send eth to the strategy as if Vault would send it via a Deposit function - await weth - .connect(josh) - .transfer(nativeStakingSSVStrategy.address, wethFromDeposits); - } - - // run the accounting - await nativeStakingSSVStrategy.connect(governor).doAccounting(); - - const harvesterWethBalance = await weth.balanceOf( - oethHarvester.address + describe("Collecting rewards and should correctly account for WETH", async () => { + for (const testCase of rewardTestCases) { + const feeAccumulatorEth = parseEther( + testCase.feeAccumulatorEth.toString() ); - const tx = await nativeStakingSSVStrategy - .connect(sHarvester) - .collectRewardTokens(); - const events = (await tx.wait()).events || []; - - const harvesterBalanceDiff = ( - await weth.balanceOf(oethHarvester.address) - ).sub(harvesterWethBalance); - expect(harvesterBalanceDiff).to.equal(expectedEthSentToHarvester); - - const rewardTokenCollectedEvent = events.find( - (e) => e.event === "RewardTokenCollected" + const consensusRewards = parseEther( + testCase.consensusRewards.toString() + ); + const deposits = parseEther(testCase.deposits.toString()); + const expectedHarvester = parseEther( + testCase.expectedHarvester.toString() ); - if (expectedEthSentToHarvester.gt(BigNumber.from("0"))) { - expect(rewardTokenCollectedEvent).to.not.be.undefined; - expect(rewardTokenCollectedEvent.event).to.equal( - "RewardTokenCollected" + it(`with ${testCase.feeAccumulatorEth} execution rewards, ${testCase.consensusRewards} consensus rewards and ${testCase.deposits} deposits. expect harvest ${testCase.expectedHarvester}`, async () => { + const { + nativeStakingSSVStrategy, + governor, + oethHarvester, + weth, + josh, + } = fixture; + const feeAccumulatorAddress = + await nativeStakingSSVStrategy.FEE_ACCUMULATOR_ADDRESS(); + const sHarvester = await impersonateAndFund(oethHarvester.address); + + // setup state + if (consensusRewards.gt(BigNumber.from("0"))) { + // set the reward eth on the strategy + await setBalance( + nativeStakingSSVStrategy.address, + consensusRewards + ); + } + if (feeAccumulatorEth.gt(BigNumber.from("0"))) { + // set execution layer rewards on the fee accumulator + await setBalance(feeAccumulatorAddress, feeAccumulatorEth); + } + if (deposits.gt(BigNumber.from("0"))) { + // send eth to the strategy as if Vault would send it via a Deposit function + await weth + .connect(josh) + .transfer(nativeStakingSSVStrategy.address, deposits); + } + + // run the accounting + await nativeStakingSSVStrategy.connect(governor).doAccounting(); + + const harvesterWethBalance = await weth.balanceOf( + oethHarvester.address ); - expect(rewardTokenCollectedEvent.args[1]).to.equal(weth.address); - expect(rewardTokenCollectedEvent.args[2]).to.equal( - expectedEthSentToHarvester + const tx = await nativeStakingSSVStrategy + .connect(sHarvester) + .collectRewardTokens(); + const events = (await tx.wait()).events || []; + + const harvesterBalanceDiff = ( + await weth.balanceOf(oethHarvester.address) + ).sub(harvesterWethBalance); + expect(harvesterBalanceDiff).to.equal(expectedHarvester); + + const rewardTokenCollectedEvent = events.find( + (e) => e.event === "RewardTokenCollected" ); - } else { - expect(rewardTokenCollectedEvent).to.be.undefined; - } - }); - } - for (const testCase of rewardTestCases) { - it("Checking balance should return the correct values", async () => { - const { - nativeStakingSSVStrategy, - governor, - strategist, - // oethHarvester, - weth, - josh, - } = fixture; - const { - feeAccumulatorEth, - beaconChainRewardEth, - wethFromDeposits, - expectedEthSentToHarvester, - nrOfActiveDepositedValidators, - } = testCase; - const feeAccumulatorAddress = - await nativeStakingSSVStrategy.FEE_ACCUMULATOR_ADDRESS(); - // const sHarvester = await impersonateAndFund(oethHarvester.address); - - // setup state - if (beaconChainRewardEth.gt(BigNumber.from("0"))) { - // set the reward eth on the strategy - await setBalance( - nativeStakingSSVStrategy.address, - beaconChainRewardEth - ); - } - if (feeAccumulatorEth.gt(BigNumber.from("0"))) { - // set execution layer rewards on the fee accumulator - await setBalance(feeAccumulatorAddress, feeAccumulatorEth); - } - if (wethFromDeposits.gt(BigNumber.from("0"))) { - // send eth to the strategy as if Vault would send it via a Deposit function - await weth - .connect(josh) - .transfer(nativeStakingSSVStrategy.address, wethFromDeposits); - } + if (expectedHarvester.gt(BigNumber.from("0"))) { + expect(rewardTokenCollectedEvent).to.not.be.undefined; + expect(rewardTokenCollectedEvent.event).to.equal( + "RewardTokenCollected" + ); + expect(rewardTokenCollectedEvent.args[1]).to.equal(weth.address); + expect(rewardTokenCollectedEvent.args[2]).to.equal( + expectedHarvester + ); + } else { + expect(rewardTokenCollectedEvent).to.be.undefined; + } + }); + } + }); - // set the correct amount of staked validators - await nativeStakingSSVStrategy.connect(strategist).pause(); - await nativeStakingSSVStrategy.connect(governor).manuallyFixAccounting( - nrOfActiveDepositedValidators, // activeDepositedValidators - ethers.utils.parseEther("0", "ether"), //_ethToWeth - ethers.utils.parseEther("0", "ether"), //_wethToBeSentToVault - ethers.utils.parseEther("0", "ether"), //_beaconChainRewardWETH - ethers.utils.parseEther("3000", "ether"), //_ethThresholdCheck - ethers.utils.parseEther("3000", "ether") //_wethThresholdCheck + describe("Checking balance should return the correct values", async () => { + for (const testCase of rewardTestCases) { + const feeAccumulatorEth = parseEther( + testCase.feeAccumulatorEth.toString() ); - - // run the accounting - await nativeStakingSSVStrategy.connect(governor).doAccounting(); - - expect( - await nativeStakingSSVStrategy.checkBalance(weth.address) - ).to.equal( - expectedEthSentToHarvester.add( - BigNumber.from(`${nrOfActiveDepositedValidators}`).mul( - utils.parseEther("32") - ) - ) + const consensusRewards = parseEther( + testCase.consensusRewards.toString() ); - }); - } + const deposits = parseEther(testCase.deposits.toString()); + const expectedBalance = parseEther(testCase.expectedBalance.toString()); + const { nrOfActiveDepositedValidators } = testCase; + it(`with ${testCase.feeAccumulatorEth} execution rewards, ${testCase.consensusRewards} consensus rewards, ${testCase.deposits} deposits and ${nrOfActiveDepositedValidators} validators. expected balance ${testCase.expectedBalance}`, async () => { + const { + nativeStakingSSVStrategy, + governor, + strategist, + // oethHarvester, + weth, + josh, + } = fixture; + const feeAccumulatorAddress = + await nativeStakingSSVStrategy.FEE_ACCUMULATOR_ADDRESS(); + + // setup state + if (consensusRewards.gt(BigNumber.from("0"))) { + // set the reward eth on the strategy + await setBalance( + nativeStakingSSVStrategy.address, + consensusRewards + ); + } + if (feeAccumulatorEth.gt(BigNumber.from("0"))) { + // set execution layer rewards on the fee accumulator + await setBalance(feeAccumulatorAddress, feeAccumulatorEth); + } + if (deposits.gt(BigNumber.from("0"))) { + // send eth to the strategy as if Vault would send it via a Deposit function + await weth + .connect(josh) + .transfer(nativeStakingSSVStrategy.address, deposits); + } + + // set the correct amount of staked validators + await nativeStakingSSVStrategy.connect(strategist).pause(); + await nativeStakingSSVStrategy + .connect(governor) + .manuallyFixAccounting( + nrOfActiveDepositedValidators, // activeDepositedValidators + parseEther("0", "ether"), //_ethToWeth + parseEther("0", "ether"), //_wethToBeSentToVault + parseEther("0", "ether"), //_beaconChainRewardWETH + parseEther("3000", "ether"), //_ethThresholdCheck + parseEther("3000", "ether") //_wethThresholdCheck + ); + + // run the accounting + await nativeStakingSSVStrategy.connect(governor).doAccounting(); + + expect( + await nativeStakingSSVStrategy.checkBalance(weth.address) + ).to.equal(expectedBalance); + }); + } + }); it("Should be able to collect the SSV reward token", async () => {}); });