From 483196633dd496325511ae49d1dc63231cc47fd8 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 23 May 2024 15:44:27 +0200 Subject: [PATCH 001/177] feat: multi-transaction third phase for Accounting oracle Depending on the size of the third-phase report, it may be split into multiple transactions. --- contracts/0.8.9/oracle/AccountingOracle.sol | 123 ++++--- .../OracleReportSanityChecker.sol | 109 +++--- .../OracleReportSanityCheckerMocks.sol | 2 +- lib/oracle.ts | 134 ++++++- .../accountingOracle.submitReport.test.ts | 15 +- ...untingOracle.submitReportExtraData.test.ts | 331 +++++++++++++----- .../baseOracleReportSanityChecker.test.ts | 140 +++++--- test/deploy/accountingOracle.ts | 19 +- 8 files changed, 620 insertions(+), 253 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 14dce0d59..999d9a3d2 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -52,7 +52,7 @@ interface ILegacyOracle { interface IOracleReportSanityChecker { function checkExitedValidatorsRatePerDay(uint256 _exitedValidatorsCount) external view; - function checkAccountingExtraDataListItemsCount(uint256 _extraDataListItemsCount) external view; + function checkExtraDataItemsCountPerTransaction(uint256 _extraDataListItemsCount) external view; function checkNodeOperatorsPerExtraDataItemCount(uint256 _itemIndex, uint256 _nodeOperatorsCount) external view; } @@ -98,9 +98,10 @@ contract AccountingOracle is BaseOracle { error UnsupportedExtraDataType(uint256 itemIndex, uint256 dataType); error CannotSubmitExtraDataBeforeMainData(); error ExtraDataAlreadyProcessed(); - error ExtraDataListOnlySupportsSingleTx(); + error ExtraDataProcessingInProgress(); error UnexpectedExtraDataHash(bytes32 consensusHash, bytes32 receivedHash); error UnexpectedExtraDataFormat(uint256 expectedFormat, uint256 receivedFormat); + error ExtraDataTransactionDoesNotContainsNextTransactionHash(); error ExtraDataItemsCountCannotBeZeroForNonEmptyData(); error ExtraDataHashCannotBeZeroForNonEmptyData(); error UnexpectedExtraDataItemsCount(uint256 expectedCount, uint256 receivedCount); @@ -133,6 +134,8 @@ contract AccountingOracle is BaseOracle { bytes32 internal constant EXTRA_DATA_PROCESSING_STATE_POSITION = keccak256("lido.AccountingOracle.extraDataProcessingState"); + bytes32 internal constant ZERO_HASH = bytes32(0); + address public immutable LIDO; ILidoLocator public immutable LOCATOR; address public immutable LEGACY_ORACLE; @@ -258,14 +261,22 @@ contract AccountingOracle is BaseOracle { bool isBunkerMode; /// - /// Extra data — the oracle information that allows asynchronous processing, potentially in + /// Extra data — the oracle information that allows asynchronous processing in /// chunks, after the main data is processed. The oracle doesn't enforce that extra data /// attached to some data report is processed in full before the processing deadline expires /// or a new data report starts being processed, but enforces that no processing of extra /// data for a report is possible after its processing deadline passes or a new data report /// arrives. /// - /// Extra data is an array of items, each item being encoded as follows: + /// Depending on the size of the extra data, the processing might need to be split into + /// multiple transactions. Each transaction contains a chunk of report data (an array of items) + /// and the hash of the next transaction. The last transaction will contain ZERO_HASH + /// as the next transaction hash. + /// + /// | 32 bytes | array of items + /// | nextHash | ... + /// + /// Each item being encoded as follows: /// /// 3 bytes 2 bytes X bytes /// | itemIndex | itemType | itemPayload | @@ -357,13 +368,15 @@ contract AccountingOracle is BaseOracle { uint256 public constant EXTRA_DATA_FORMAT_EMPTY = 0; /// @notice The list format for the extra data array. Used when all extra data processing - /// fits into a single transaction. + /// fits into a single or multiple transactions. /// - /// Extra data is passed within a single transaction as a bytearray containing all data items + /// Depend on the extra data size it passed within a single or multiple transactions. + /// Each transaction contains next transaction hash and a bytearray containing data items /// packed tightly. /// - /// Hash is a keccak256 hash calculated over the bytearray items. The Solidity equivalent of - /// the hash calculation code would be `keccak256(array)`, where `array` has the `bytes` type. + /// Hash is a keccak256 hash calculated over the transaction data (next transaction hash and bytearray items). + /// The Solidity equivalent of the hash calculation code would be `keccak256(data)`, + /// where `data` has the `bytes` type. /// uint256 public constant EXTRA_DATA_FORMAT_LIST = 1; @@ -400,11 +413,11 @@ contract AccountingOracle is BaseOracle { /// @notice Submits report extra data in the EXTRA_DATA_FORMAT_LIST format for processing. /// - /// @param items The extra data items list. See docs for the `EXTRA_DATA_FORMAT_LIST` + /// @param data The extra data chunk with items list. See docs for the `EXTRA_DATA_FORMAT_LIST` /// constant for details. /// - function submitReportExtraDataList(bytes calldata items) external { - _submitReportExtraDataList(items); + function submitReportExtraDataList(bytes calldata data) external { + _submitReportExtraDataList(data); } struct ProcessingState { @@ -441,7 +454,7 @@ contract AccountingOracle is BaseOracle { ConsensusReport memory report = _storageConsensusReport().value; result.currentFrameRefSlot = _getCurrentRefSlot(); - if (report.hash == bytes32(0) || result.currentFrameRefSlot != report.refSlot) { + if (report.hash == ZERO_HASH || result.currentFrameRefSlot != report.refSlot) { return result; } @@ -565,8 +578,8 @@ contract AccountingOracle is BaseOracle { function _handleConsensusReportData(ReportData calldata data, uint256 prevRefSlot) internal { if (data.extraDataFormat == EXTRA_DATA_FORMAT_EMPTY) { - if (data.extraDataHash != bytes32(0)) { - revert UnexpectedExtraDataHash(bytes32(0), data.extraDataHash); + if (data.extraDataHash != ZERO_HASH) { + revert UnexpectedExtraDataHash(ZERO_HASH, data.extraDataHash); } if (data.extraDataItemsCount != 0) { revert UnexpectedExtraDataItemsCount(0, data.extraDataItemsCount); @@ -578,14 +591,11 @@ contract AccountingOracle is BaseOracle { if (data.extraDataItemsCount == 0) { revert ExtraDataItemsCountCannotBeZeroForNonEmptyData(); } - if (data.extraDataHash == bytes32(0)) { + if (data.extraDataHash == ZERO_HASH) { revert ExtraDataHashCannotBeZeroForNonEmptyData(); } } - IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) - .checkAccountingExtraDataListItemsCount(data.extraDataItemsCount); - ILegacyOracle(LEGACY_ORACLE).handleConsensusLayerReport( data.refSlot, data.clBalanceGwei * 1e9, @@ -677,7 +687,11 @@ contract AccountingOracle is BaseOracle { function _submitReportExtraDataEmpty() internal { ExtraDataProcessingState memory procState = _storageExtraDataProcessingState().value; _checkCanSubmitExtraData(procState, EXTRA_DATA_FORMAT_EMPTY); - if (procState.submitted) revert ExtraDataAlreadyProcessed(); + if (procState.submitted) { + revert ExtraDataAlreadyProcessed(); + } else if (procState.itemsProcessed < procState.itemsCount ) { + revert ExtraDataProcessingInProgress(); + } IStakingRouter(LOCATOR.stakingRouter()).onValidatorsCountsByNodeOperatorReportingFinished(); _storageExtraDataProcessingState().value.submitted = true; emit ExtraDataSubmitted(procState.refSlot, 0, 0); @@ -690,7 +704,7 @@ contract AccountingOracle is BaseOracle { ConsensusReport memory report = _storageConsensusReport().value; - if (report.hash == bytes32(0) || procState.refSlot != report.refSlot) { + if (report.hash == ZERO_HASH || procState.refSlot != report.refSlot) { revert CannotSubmitExtraDataBeforeMainData(); } @@ -703,6 +717,7 @@ contract AccountingOracle is BaseOracle { struct ExtraDataIterState { // volatile + bool started; uint256 index; uint256 itemType; uint256 dataOffset; @@ -711,7 +726,7 @@ contract AccountingOracle is BaseOracle { address stakingRouter; } - function _submitReportExtraDataList(bytes calldata items) internal { + function _submitReportExtraDataList(bytes calldata data) internal { ExtraDataProcessingState memory procState = _storageExtraDataProcessingState().value; _checkCanSubmitExtraData(procState, EXTRA_DATA_FORMAT_LIST); @@ -719,37 +734,59 @@ contract AccountingOracle is BaseOracle { revert ExtraDataAlreadyProcessed(); } - if (procState.itemsProcessed != 0) { - revert ExtraDataListOnlySupportsSingleTx(); - } - - bytes32 dataHash = keccak256(items); + bytes32 dataHash = keccak256(data); if (dataHash != procState.dataHash) { revert UnexpectedExtraDataHash(procState.dataHash, dataHash); } + uint256 initialDataOffset = 32; + + if(data.length < initialDataOffset) { + revert ExtraDataTransactionDoesNotContainsNextTransactionHash(); + } + + bytes32 nextHash; + assembly { + nextHash := calldataload(data.offset) + } + + bool started = procState.itemsProcessed > 0; + ExtraDataIterState memory iter = ExtraDataIterState({ - index: 0, + started: started, + index: started ? procState.itemsProcessed - 1 : 0, itemType: 0, - dataOffset: 0, - lastSortingKey: 0, + dataOffset: initialDataOffset, + lastSortingKey: procState.lastSortingKey, stakingRouter: LOCATOR.stakingRouter() }); - _processExtraDataItems(items, iter); + _processExtraDataItems(data, iter); uint256 itemsProcessed = iter.index + 1; - if (itemsProcessed != procState.itemsCount) { - revert UnexpectedExtraDataItemsCount(procState.itemsCount, itemsProcessed); - } + if(nextHash == ZERO_HASH) { + if (itemsProcessed != procState.itemsCount) { + revert UnexpectedExtraDataItemsCount(procState.itemsCount, itemsProcessed); + } - procState.submitted = true; - procState.itemsProcessed = uint64(itemsProcessed); - procState.lastSortingKey = iter.lastSortingKey; - _storageExtraDataProcessingState().value = procState; + procState.submitted = true; + procState.itemsProcessed = uint64(itemsProcessed); + procState.lastSortingKey = iter.lastSortingKey; + _storageExtraDataProcessingState().value = procState; + + IStakingRouter(iter.stakingRouter).onValidatorsCountsByNodeOperatorReportingFinished(); + } else { + if (itemsProcessed >= procState.itemsCount) { + revert UnexpectedExtraDataItemsCount(procState.itemsCount, itemsProcessed); + } - IStakingRouter(iter.stakingRouter).onValidatorsCountsByNodeOperatorReportingFinished(); - emit ExtraDataSubmitted(procState.refSlot, itemsProcessed, itemsProcessed); + procState.dataHash = nextHash; + procState.itemsProcessed = uint64(itemsProcessed); + procState.lastSortingKey = iter.lastSortingKey; + _storageExtraDataProcessingState().value = procState; + } + + emit ExtraDataSubmitted(procState.refSlot, procState.itemsProcessed, procState.itemsCount); } function _processExtraDataItems(bytes calldata data, ExtraDataIterState memory iter) internal { @@ -772,10 +809,12 @@ contract AccountingOracle is BaseOracle { dataOffset := add(dataOffset, 5) } - if (iter.itemType == 0) { + if (!iter.started) { if (index != 0) { revert UnexpectedExtraDataIndex(0, index); } + + iter.started = true; } else if (index != iter.index + 1) { revert UnexpectedExtraDataIndex(iter.index + 1, index); } @@ -802,6 +841,10 @@ contract AccountingOracle is BaseOracle { } assert(maxNodeOperatorsPerItem > 0); + + IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) + .checkExtraDataItemsCountPerTransaction(iter.index + 1); + IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) .checkNodeOperatorsPerExtraDataItemCount(maxNodeOperatorItemIndex, maxNodeOperatorsPerItem); } diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index b147bc9b7..36b4ea42f 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -37,15 +37,17 @@ interface IWithdrawalQueue { /// @notice The set of restrictions used in the sanity checks of the oracle report /// @dev struct is loaded from the storage and stored in memory during the tx running struct LimitsList { - /// @notice The max possible number of validators that might been reported as `appeared` or `exited` - /// during a single day - /// NB: `appeared` means `pending` (maybe not `activated` yet), see further explanations - // in docs for the `setChurnValidatorsPerDayLimit` func below. + /// @notice The max possible number of validators that might be reported as `exited` + /// per single day, depends on the Consensus Layer churn limit /// @dev Must fit into uint16 (<= 65_535) - uint256 churnValidatorsPerDayLimit; + uint256 exitedValidatorsPerDayLimit; + + /// @notice The max possible number of validators that might be reported as `appeared` + /// per single day, limited by the max daily deposits via DepositSecurityModule in practice + /// isn't limited by a consensus layer (because `appeared` includes `pending`, i.e., not `activated` yet) + /// @dev Must fit into uint16 (<= 65_535) + uint256 appearedValidatorsPerDayLimit; - /// @notice The max decrease of the total validators' balances on the Consensus Layer since - /// the previous oracle report /// @dev Represented in the Basis Points (100% == 10_000) uint256 oneOffCLBalanceDecreaseBPLimit; @@ -62,7 +64,7 @@ struct LimitsList { /// @notice The max number of exit requests allowed in report to ValidatorsExitBusOracle uint256 maxValidatorExitRequestsPerReport; - /// @notice The max number of data list items reported to accounting oracle in extra data + /// @notice The max number of data list items reported to accounting oracle in extra data per single transaction /// @dev Must fit into uint16 (<= 65_535) uint256 maxAccountingExtraDataListItemsCount; @@ -81,7 +83,8 @@ struct LimitsList { /// @dev The packed version of the LimitsList struct to be effectively persisted in storage struct LimitsListPacked { - uint16 churnValidatorsPerDayLimit; + uint16 exitedValidatorsPerDayLimit; + uint16 appearedValidatorsPerDayLimit; uint16 oneOffCLBalanceDecreaseBPLimit; uint16 annualBalanceIncreaseBPLimit; uint16 simulatedShareRateDeviationBPLimit; @@ -104,8 +107,10 @@ contract OracleReportSanityChecker is AccessControlEnumerable { using PositiveTokenRebaseLimiter for TokenRebaseLimiterData; bytes32 public constant ALL_LIMITS_MANAGER_ROLE = keccak256("ALL_LIMITS_MANAGER_ROLE"); - bytes32 public constant CHURN_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE = - keccak256("CHURN_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE"); + bytes32 public constant EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE = + keccak256("EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE"); + bytes32 public constant APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE = + keccak256("APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE"); bytes32 public constant ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE = keccak256("ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE"); bytes32 public constant ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE = @@ -132,7 +137,8 @@ contract OracleReportSanityChecker is AccessControlEnumerable { struct ManagersRoster { address[] allLimitsManagers; - address[] churnValidatorsPerDayLimitManagers; + address[] exitedValidatorsPerDayLimitManagers; + address[] appearedValidatorsPerDayLimitManagers; address[] oneOffCLBalanceDecreaseLimitManagers; address[] annualBalanceIncreaseLimitManagers; address[] shareRateDeviationLimitManagers; @@ -160,7 +166,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { _grantRole(DEFAULT_ADMIN_ROLE, _admin); _grantRole(ALL_LIMITS_MANAGER_ROLE, _managersRoster.allLimitsManagers); - _grantRole(CHURN_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE, _managersRoster.churnValidatorsPerDayLimitManagers); + _grantRole(EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE, _managersRoster.exitedValidatorsPerDayLimitManagers); + _grantRole(APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE, + _managersRoster.appearedValidatorsPerDayLimitManagers); _grantRole(ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE, _managersRoster.oneOffCLBalanceDecreaseLimitManagers); _grantRole(ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE, _managersRoster.annualBalanceIncreaseLimitManagers); @@ -218,23 +226,35 @@ contract OracleReportSanityChecker is AccessControlEnumerable { _updateLimits(_limitsList); } - /// @notice Sets the new value for the churnValidatorsPerDayLimit - /// The limit is applicable for `appeared` and `exited` validators + /// @notice Sets the new value for the exitedValidatorsPerDayLimit + /// + /// NB: AccountingOracle reports validators as exited once they passed the `EXIT_EPOCH` on Consensus Layer + /// therefore, the value should be set in accordance to the consensus layer churn limit /// - /// NB: AccountingOracle reports validators as `appeared` once them become `pending` - /// (might be not `activated` yet). Thus, this limit should be high enough for such cases - /// because Consensus Layer has no intrinsic churn limit for the amount of `pending` validators - /// (only for `activated` instead). For Lido it's limited by the max daily deposits via DepositSecurityModule + /// @param _exitedValidatorsPerDayLimit new exitedValidatorsPerDayLimit value + function setExitedValidatorsPerDayLimit(uint256 _exitedValidatorsPerDayLimit) + external + onlyRole(EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE) + { + LimitsList memory limitsList = _limits.unpack(); + limitsList.exitedValidatorsPerDayLimit = _exitedValidatorsPerDayLimit; + _updateLimits(limitsList); + } + + /// @notice Sets the new value for the appearedValidatorsPerDayLimit /// - /// In contrast, `exited` are reported according to the Consensus Layer churn limit. + /// NB: AccountingOracle reports validators as appeared once they become `pending` + /// (might be not `activated` yet). Thus, this limit should be high enough because consensus layer + /// has no intrinsic churn limit for the amount of `pending` validators (only for `activated` instead). + /// For Lido it depends on the amount of deposits that can be made via DepositSecurityModule daily. /// - /// @param _churnValidatorsPerDayLimit new churnValidatorsPerDayLimit value - function setChurnValidatorsPerDayLimit(uint256 _churnValidatorsPerDayLimit) + /// @param _appearedValidatorsPerDayLimit new appearedValidatorsPerDayLimit value + function setAppearedValidatorsPerDayLimit(uint256 _appearedValidatorsPerDayLimit) external - onlyRole(CHURN_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE) + onlyRole(APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE) { LimitsList memory limitsList = _limits.unpack(); - limitsList.churnValidatorsPerDayLimit = _churnValidatorsPerDayLimit; + limitsList.appearedValidatorsPerDayLimit = _appearedValidatorsPerDayLimit; _updateLimits(limitsList); } @@ -459,16 +479,16 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external view { - uint256 limit = _limits.unpack().churnValidatorsPerDayLimit; - if (_exitedValidatorsCount > limit) { - revert ExitedValidatorsLimitExceeded(limit, _exitedValidatorsCount); + uint256 exitedValidatorsLimit = _limits.unpack().exitedValidatorsPerDayLimit; + if (_exitedValidatorsCount > exitedValidatorsLimit) { + revert ExitedValidatorsLimitExceeded(exitedValidatorsLimit, _exitedValidatorsCount); } } /// @notice Check number of node operators reported per extra data item in accounting oracle /// @param _itemIndex Index of item in extra data /// @param _nodeOperatorsCount Number of validator exit requests supplied per oracle report - /// @dev Checks against the same limit as used in checkAccountingExtraDataListItemsCount + /// @dev Checks against the same limit as used in checkExtraDataItemsCountPerTransaction function checkNodeOperatorsPerExtraDataItemCount(uint256 _itemIndex, uint256 _nodeOperatorsCount) external view @@ -479,9 +499,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { } } - /// @notice Check max accounting extra data list items count - /// @param _extraDataListItemsCount Number of validator exit requests supplied per oracle report - function checkAccountingExtraDataListItemsCount(uint256 _extraDataListItemsCount) + /// @notice Check max accounting extra data list items count per transaction + /// @param _extraDataListItemsCount Number of items per single transaction in oracle report + function checkExtraDataItemsCountPerTransaction(uint256 _extraDataListItemsCount) external view { @@ -609,9 +629,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { _timeElapsed = DEFAULT_TIME_ELAPSED; } - uint256 churnLimit = (_limitsList.churnValidatorsPerDayLimit * _timeElapsed) / SECONDS_PER_DAY; + uint256 appearedLimit = (_limitsList.appearedValidatorsPerDayLimit * _timeElapsed) / SECONDS_PER_DAY; - if (_appearedValidators > churnLimit) revert IncorrectAppearedValidators(_appearedValidators); + if (_appearedValidators > appearedLimit) revert IncorrectAppearedValidators(_appearedValidators); } function _checkLastFinalizableId( @@ -686,9 +706,13 @@ contract OracleReportSanityChecker is AccessControlEnumerable { function _updateLimits(LimitsList memory _newLimitsList) internal { LimitsList memory _oldLimitsList = _limits.unpack(); - if (_oldLimitsList.churnValidatorsPerDayLimit != _newLimitsList.churnValidatorsPerDayLimit) { - _checkLimitValue(_newLimitsList.churnValidatorsPerDayLimit, 0, type(uint16).max); - emit ChurnValidatorsPerDayLimitSet(_newLimitsList.churnValidatorsPerDayLimit); + if (_oldLimitsList.exitedValidatorsPerDayLimit != _newLimitsList.exitedValidatorsPerDayLimit) { + _checkLimitValue(_newLimitsList.exitedValidatorsPerDayLimit, 0, type(uint16).max); + emit ExitedValidatorsPerDayLimitSet(_newLimitsList.exitedValidatorsPerDayLimit); + } + if (_oldLimitsList.appearedValidatorsPerDayLimit != _newLimitsList.appearedValidatorsPerDayLimit) { + _checkLimitValue(_newLimitsList.appearedValidatorsPerDayLimit, 0, type(uint16).max); + emit AppearedValidatorsPerDayLimitSet(_newLimitsList.appearedValidatorsPerDayLimit); } if (_oldLimitsList.oneOffCLBalanceDecreaseBPLimit != _newLimitsList.oneOffCLBalanceDecreaseBPLimit) { _checkLimitValue(_newLimitsList.oneOffCLBalanceDecreaseBPLimit, 0, MAX_BASIS_POINTS); @@ -731,7 +755,8 @@ contract OracleReportSanityChecker is AccessControlEnumerable { } } - event ChurnValidatorsPerDayLimitSet(uint256 churnValidatorsPerDayLimit); + event ExitedValidatorsPerDayLimitSet(uint256 exitedValidatorsPerDayLimit); + event AppearedValidatorsPerDayLimitSet(uint256 appearedValidatorsPerDayLimit); event OneOffCLBalanceDecreaseBPLimitSet(uint256 oneOffCLBalanceDecreaseBPLimit); event AnnualBalanceIncreaseBPLimitSet(uint256 annualBalanceIncreaseBPLimit); event SimulatedShareRateDeviationBPLimitSet(uint256 simulatedShareRateDeviationBPLimit); @@ -747,9 +772,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { error IncorrectSharesRequestedToBurn(uint256 actualSharesToBurn); error IncorrectCLBalanceDecrease(uint256 oneOffCLBalanceDecreaseBP); error IncorrectCLBalanceIncrease(uint256 annualBalanceDiff); - error IncorrectAppearedValidators(uint256 churnLimit); + error IncorrectAppearedValidators(uint256 appearedValidatorsLimit); error IncorrectNumberOfExitRequestsPerReport(uint256 maxRequestsCount); - error IncorrectExitedValidators(uint256 churnLimit); + error IncorrectExitedValidators(uint256 exitedValudatorsLimit); error IncorrectRequestFinalization(uint256 requestCreationBlock); error ActualShareRateIsZero(); error IncorrectSimulatedShareRate(uint256 simulatedShareRate, uint256 actualShareRate); @@ -761,7 +786,8 @@ contract OracleReportSanityChecker is AccessControlEnumerable { library LimitsListPacker { function pack(LimitsList memory _limitsList) internal pure returns (LimitsListPacked memory res) { - res.churnValidatorsPerDayLimit = SafeCast.toUint16(_limitsList.churnValidatorsPerDayLimit); + res.exitedValidatorsPerDayLimit = SafeCast.toUint16(_limitsList.exitedValidatorsPerDayLimit); + res.appearedValidatorsPerDayLimit = SafeCast.toUint16(_limitsList.appearedValidatorsPerDayLimit); res.oneOffCLBalanceDecreaseBPLimit = _toBasisPoints(_limitsList.oneOffCLBalanceDecreaseBPLimit); res.annualBalanceIncreaseBPLimit = _toBasisPoints(_limitsList.annualBalanceIncreaseBPLimit); res.simulatedShareRateDeviationBPLimit = _toBasisPoints(_limitsList.simulatedShareRateDeviationBPLimit); @@ -780,7 +806,8 @@ library LimitsListPacker { library LimitsListUnpacker { function unpack(LimitsListPacked memory _limitsList) internal pure returns (LimitsList memory res) { - res.churnValidatorsPerDayLimit = _limitsList.churnValidatorsPerDayLimit; + res.exitedValidatorsPerDayLimit = _limitsList.exitedValidatorsPerDayLimit; + res.appearedValidatorsPerDayLimit = _limitsList.appearedValidatorsPerDayLimit; res.oneOffCLBalanceDecreaseBPLimit = _limitsList.oneOffCLBalanceDecreaseBPLimit; res.annualBalanceIncreaseBPLimit = _limitsList.annualBalanceIncreaseBPLimit; res.simulatedShareRateDeviationBPLimit = _limitsList.simulatedShareRateDeviationBPLimit; diff --git a/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol b/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol index f2f5e4df7..c2143a994 100644 --- a/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol +++ b/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol @@ -155,5 +155,5 @@ contract OracleReportSanityCheckerStub { sharesToBurn = _etherToLockForWithdrawals; } - function checkAccountingExtraDataListItemsCount(uint256 _extraDataListItemsCount) external view {} + function checkExtraDataItemsCountPerTransaction(uint256 _extraDataListItemsCount) external view {} } diff --git a/lib/oracle.ts b/lib/oracle.ts index ca1df184b..c32909620 100644 --- a/lib/oracle.ts +++ b/lib/oracle.ts @@ -1,6 +1,6 @@ import { bigintToHex } from "bigint-conversion"; import { assert } from "chai"; -import { keccak256 } from "ethers"; +import { keccak256, ZeroHash } from "ethers"; import { ethers } from "hardhat"; import { AccountingOracle, HashConsensus } from "typechain-types"; @@ -9,6 +9,15 @@ import { CONSENSUS_VERSION } from "lib/constants"; import { numberToHex } from "./string"; +function splitArrayIntoChunks(inputArray: T[], maxItemsPerChunk: number): T[][] { + const result: T[][] = []; + for (let i = 0; i < inputArray.length; i += maxItemsPerChunk) { + const chunk: T[] = inputArray.slice(i, i + maxItemsPerChunk); + result.push(chunk); + } + return result; +} + export type OracleReport = AccountingOracle.ReportDataStruct; export type ReportAsArray = ReturnType; @@ -16,6 +25,8 @@ export type ReportAsArray = ReturnType; export type KeyType = { moduleId: number; nodeOpIds: number[]; keysCounts: number[] }; export type ExtraDataType = { stuckKeys: KeyType[]; exitedKeys: KeyType[] }; +export type ItemType = KeyType & { type: bigint }; + export const EXTRA_DATA_FORMAT_EMPTY = 0n; export const EXTRA_DATA_FORMAT_LIST = 1n; @@ -126,17 +137,6 @@ export async function reportOracle( return { report, submitDataTx, submitExtraDataTx }; } -// FIXME: kept for compat, remove after refactoring tests -export function pushOracleReport( - consensus: HashConsensus, - oracle: AccountingOracle, - numValidators: bigint, - clBalance: bigint, - elRewardsVaultBalance: bigint, -) { - return reportOracle(consensus, oracle, { numValidators, clBalance, elRewardsVaultBalance }); -} - export function encodeExtraDataItem( itemIndex: number, itemType: bigint, @@ -151,23 +151,119 @@ export function encodeExtraDataItem( return "0x" + itemHeader + payloadHeader + operatorIdsPayload + keysCountsPayload; } +export function encodeExtraDataItemsArray(items: ItemType[]): string[] { + return items.map((item, index) => + encodeExtraDataItem(index, item.type, item.moduleId, item.nodeOpIds, item.keysCounts), + ); +} + export function encodeExtraDataItems(data: ExtraDataType) { - const items: string[] = []; - const encodeItem = (item: KeyType, type: bigint) => - encodeExtraDataItem(items.length, type, item.moduleId, item.nodeOpIds, item.keysCounts); - data.stuckKeys.forEach((item: KeyType) => items.push(encodeItem(item, EXTRA_DATA_TYPE_STUCK_VALIDATORS))); - data.exitedKeys.forEach((item: KeyType) => items.push(encodeItem(item, EXTRA_DATA_TYPE_EXITED_VALIDATORS))); - return items; + const itemsWithType: ItemType[] = []; + + const toItemWithType = (keys: KeyType[], type: bigint) => keys.map((item) => ({ ...item, type })); + + itemsWithType.push(...toItemWithType(data.stuckKeys, EXTRA_DATA_TYPE_STUCK_VALIDATORS)); + itemsWithType.push(...toItemWithType(data.exitedKeys, EXTRA_DATA_TYPE_EXITED_VALIDATORS)); + + return encodeExtraDataItemsArray(itemsWithType); +} + +function packChunk(extraDataItems: string[], nextHash: string) { + const extraDataItemsBytes = extraDataItems.map((s) => s.substring(2)).join(""); + return `${nextHash}${extraDataItemsBytes}`; +} + +export function packExtraDataItemsToChunksLinkedByHash(extraDataItems: string[], maxItemsPerChunk: number) { + const chunks = splitArrayIntoChunks(extraDataItems, maxItemsPerChunk); + const packedChunks = []; + + let nextHash = ethers.ZeroHash; + for (let i = chunks.length - 1; i >= 0; i--) { + const packed = packChunk(chunks[i], nextHash); + packedChunks.push(packed); + nextHash = calcExtraDataListHash(packed); + } + + return packedChunks.reverse(); } export function packExtraDataList(extraDataItems: string[]) { - return "0x" + extraDataItems.map((s) => s.substring(2)).join(""); + const [chunk] = packExtraDataItemsToChunksLinkedByHash(extraDataItems, extraDataItems.length); + + return chunk; } export function calcExtraDataListHash(packedExtraDataList: string) { return keccak256(packedExtraDataList); } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isItemTypeArray(items: any[]): items is ItemType[] { + return items.every((item) => item.hasOwnProperty("moduleId") && item.hasOwnProperty("type")); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isExtraDataType(data: any): data is ExtraDataType { + return data.hasOwnProperty("stuckKeys") && data.hasOwnProperty("exitedKeys"); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isStringArray(items: any[]): items is string[] { + return items.every((item) => typeof item === "string"); +} + +type ExtraDataConfig = { + maxItemsPerChunk?: number; +}; + +export type ReportFieldsWithoutExtraData = Omit< + OracleReport, + "extraDataHash" | "extraDataItemsCount" | "extraDataFormat" +>; + +export type ExtraData = string[] | ItemType[] | ExtraDataType; +export type OracleReportProps = { + reportFieldsWithoutExtraData: ReportFieldsWithoutExtraData; + extraData: ExtraData; + config?: ExtraDataConfig; +}; + +export function constructOracleReport({ reportFieldsWithoutExtraData, extraData, config }: OracleReportProps) { + const extraDataItems: string[] = []; + + if (Array.isArray(extraData)) { + if (isStringArray(extraData)) { + extraDataItems.push(...extraData); + } else if (isItemTypeArray(extraData)) { + extraDataItems.push(...encodeExtraDataItemsArray(extraData)); + } + } else if (isExtraDataType(extraData)) { + extraDataItems.push(...encodeExtraDataItems(extraData)); + } + + const extraDataItemsCount = extraDataItems.length; + const maxItemsPerChunk = config?.maxItemsPerChunk || extraDataItemsCount; + const extraDataChunks = packExtraDataItemsToChunksLinkedByHash(extraDataItems, maxItemsPerChunk); + const extraDataChunkHashes = extraDataChunks.map((chunk) => calcExtraDataListHash(chunk)); + + const report: OracleReport = { + ...reportFieldsWithoutExtraData, + extraDataHash: extraDataItems.length ? extraDataChunkHashes[0] : ZeroHash, + extraDataItemsCount: extraDataItems.length, + extraDataFormat: extraDataItems.length ? EXTRA_DATA_FORMAT_LIST : EXTRA_DATA_FORMAT_EMPTY, + }; + + const reportHash = calcReportDataHash(getReportDataItems(report)); + + return { + extraDataChunks, + extraDataChunkHashes, + extraDataItemsCount, + report, + reportHash, + }; +} + export async function getSecondsPerFrame(consensus: HashConsensus) { const [chainConfig, frameConfig] = await Promise.all([consensus.getChainConfig(), consensus.getFrameConfig()]); return chainConfig.secondsPerSlot * chainConfig.slotsPerEpoch * frameConfig.epochsPerFrame; diff --git a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts index 5109013f8..a38185cf3 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts @@ -373,20 +373,21 @@ describe("AccountingOracle.sol:submitReport", () => { }); context("enforces data safety boundaries", () => { - it("reverts with MaxAccountingExtraDataItemsCountExceeded if data limit exceeds", async () => { + it("passes fine when extra data do not feet in a single third phase transaction", async () => { const MAX_ACCOUNTING_EXTRA_DATA_LIMIT = 1; + + expect(reportFields.extraDataItemsCount).to.be.greaterThan(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); + await sanityChecker.connect(admin).setMaxAccountingExtraDataListItemsCount(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); expect((await sanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount).to.be.equal( MAX_ACCOUNTING_EXTRA_DATA_LIMIT, ); - await expect(oracle.connect(member1).submitReportData(reportFields, oracleVersion)) - .to.be.revertedWithCustomError(sanityChecker, "MaxAccountingExtraDataItemsCountExceeded") - .withArgs(MAX_ACCOUNTING_EXTRA_DATA_LIMIT, reportFields.extraDataItemsCount); + await oracle.connect(member1).submitReportData(reportFields, oracleVersion); }); - it("passes fine on borderline data limit value — when it equals to count of passed items", async () => { + it("passes fine when extra data feet in a single third phase transaction", async () => { const MAX_ACCOUNTING_EXTRA_DATA_LIMIT = reportFields.extraDataItemsCount; await sanityChecker.connect(admin).setMaxAccountingExtraDataListItemsCount(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); @@ -429,8 +430,8 @@ describe("AccountingOracle.sol:submitReport", () => { 0, ); const exitingRateLimit = getBigInt(totalExitedValidators) - 1n; - await sanityChecker.setChurnValidatorsPerDayLimit(exitingRateLimit); - expect((await sanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit).to.be.equal(exitingRateLimit); + await sanityChecker.setExitedValidatorsPerDayLimit(exitingRateLimit); + expect((await sanityChecker.getOracleReportLimits()).exitedValidatorsPerDayLimit).to.be.equal(exitingRateLimit); await expect(oracle.connect(member1).submitReportData(reportFields, oracleVersion)) .to.be.revertedWithCustomError(sanityChecker, "ExitedValidatorsLimitExceeded") .withArgs(exitingRateLimit, totalExitedValidators); diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index ee969b11c..a74a6acf4 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { ZeroHash } from "ethers"; +import { BigNumberish, ZeroHash } from "ethers"; import { ethers } from "hardhat"; import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; @@ -16,25 +16,29 @@ import { calcExtraDataListHash, calcReportDataHash, CONSENSUS_VERSION, + constructOracleReport, encodeExtraDataItem, encodeExtraDataItems, ether, EXTRA_DATA_FORMAT_EMPTY, EXTRA_DATA_FORMAT_LIST, EXTRA_DATA_TYPE_STUCK_VALIDATORS, + ExtraData, ExtraDataType, getReportDataItems, numberToHex, ONE_GWEI, OracleReport, + OracleReportProps, packExtraDataList, + ReportFieldsWithoutExtraData, shareRate, } from "lib"; import { deployAndConfigureAccountingOracle } from "test/deploy"; import { Snapshot } from "test/suite"; -const getDefaultExtraData = () => ({ +const getDefaultExtraData = (): ExtraDataType => ({ stuckKeys: [ { moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, { moduleId: 2, nodeOpIds: [0], keysCounts: [2] }, @@ -87,12 +91,6 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await consensus.connect(admin).addMember(member1, 1); }); - interface ReportDataArgs { - extraData?: ExtraDataType; - extraDataItems?: string[]; - reportFields?: object; - } - async function takeSnapshot() { snapshot = await Snapshot.take(); } @@ -101,40 +99,101 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await Snapshot.restore(snapshot); } - function getReportData({ extraData, extraDataItems, reportFields }: ReportDataArgs = {}) { + type ConstructOracleReportWithDefaultValuesProps = Pick, "config" | "extraData"> & { + reportFieldsWithoutExtraData?: Partial; + }; + + function constructOracleReportWithDefaultValues({ + reportFieldsWithoutExtraData, + extraData, + config, + }: ConstructOracleReportWithDefaultValuesProps) { + const reportFieldsValue = getDefaultReportFields({ + ...reportFieldsWithoutExtraData, + }); + const extraDataValue = extraData || getDefaultExtraData(); - const extraDataItemsValue = extraDataItems || encodeExtraDataItems(extraDataValue); - const extraDataList = packExtraDataList(extraDataItemsValue); - const extraDataHash = calcExtraDataListHash(extraDataList); - - const reportFieldsArg = getDefaultReportFields({ - extraDataHash, - extraDataItemsCount: extraDataItemsValue.length, - ...reportFields, + + const report = constructOracleReport({ + reportFieldsWithoutExtraData: reportFieldsValue, + extraData: extraDataValue, + config, }); - const reportItems = getReportDataItems(reportFieldsArg); - const reportHash = calcReportDataHash(reportItems); + return { + ...report, + reportInput: { + reportFieldsValue, + extraDataValue, + }, + }; + } + + interface ReportDataArgs { + extraData?: ExtraData; + reportFields?: Partial; + } + + function constructOracleReportWithSingeExtraDataTransaction({ extraData, reportFields }: ReportDataArgs = {}) { + const extraDataValue = extraData || getDefaultExtraData(); + + const { extraDataChunks, extraDataChunkHashes, extraDataItemsCount, report, reportHash, reportInput } = + constructOracleReportWithDefaultValues({ + reportFieldsWithoutExtraData: reportFields, + extraData: extraDataValue, + }); return { - extraData: extraDataValue, - extraDataItems: extraDataItemsValue, - extraDataList, - extraDataHash, - reportFields: reportFieldsArg, - reportItems, + extraDataItemsCount, + extraDataList: extraDataChunks[0], + extraDataHash: extraDataChunkHashes[0], + reportFields: report, reportHash, + reportInput, }; } - async function prepareReport({ extraData, extraDataItems, reportFields }: ReportDataArgs = {}) { + async function constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot({ + extraData, + reportFields, + }: ReportDataArgs = {}) { + const { refSlot } = await consensus.getCurrentFrame(); + return constructOracleReportWithSingeExtraDataTransaction({ + extraData, + reportFields: { ...reportFields, refSlot } as OracleReport, + }); + } + + async function oracleMemberSubmitReportHash(refSlot: BigNumberish, reportHash: string) { + return await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION); + } + + async function oracleMemberSubmitReportData(report: OracleReport) { + return await oracle.connect(member1).submitReportData(report, oracleVersion); + } + + async function oracleMemberSubmitExtraData(extraDataList: string) { + return await oracle.connect(member1).submitReportExtraDataList(extraDataList); + } + + async function constructOracleReportForCurrentFrameAndSubmitReportHash({ + extraData, + reportFieldsWithoutExtraData, + config, + }: ConstructOracleReportWithDefaultValuesProps) { const { refSlot } = await consensus.getCurrentFrame(); - return getReportData({ extraData, extraDataItems, reportFields: { ...reportFields, refSlot } as OracleReport }); + const data = await constructOracleReportWithDefaultValues({ + extraData, + reportFieldsWithoutExtraData: { ...reportFieldsWithoutExtraData, refSlot }, + config, + }); + await oracleMemberSubmitReportHash(data.report.refSlot, data.reportHash); + return data; } - async function submitReportHash({ extraData, extraDataItems, reportFields }: ReportDataArgs = {}) { - const data = await prepareReport({ extraData, extraDataItems, reportFields }); - await consensus.connect(member1).submitReport(data.reportFields.refSlot, data.reportHash, CONSENSUS_VERSION); + async function submitReportHash({ extraData, reportFields }: ReportDataArgs = {}) { + const data = await constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot({ extraData, reportFields }); + await oracleMemberSubmitReportHash(data.reportFields.refSlot, data.reportHash); return data; } @@ -153,49 +212,119 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { beforeEach(takeSnapshot); afterEach(rollback); + context("submit third phase transactions successfully", () => { + it("submit extra data report within single transaction", async () => { + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({}); + expect(extraDataChunks.length).to.be.equal(1); + await oracleMemberSubmitReportData(report); + const tx = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, 5, 5); + }); + + it("submit extra data report within two transaction", async () => { + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + await oracleMemberSubmitReportData(report); + const tx1 = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx1).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, 3, 5); + const tx2 = await oracleMemberSubmitExtraData(extraDataChunks[1]); + await expect(tx2).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, 5, 5); + }); + }); + context("enforces the deadline", () => { - it("reverts with ProcessingDeadlineMissed if deadline missed", async () => { + it("reverts with ProcessingDeadlineMissed if deadline missed for the single transaction of extra data report", async () => { await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash(); + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({}); const deadline = (await oracle.getConsensusReport()).processingDeadlineTime; - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); + await oracleMemberSubmitReportData(report); await consensus.advanceTimeToNextFrameStart(); - await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) + await expect(oracle.connect(member1).submitReportExtraDataList(extraDataChunks[0])) + .to.be.revertedWithCustomError(oracle, "ProcessingDeadlineMissed") + .withArgs(deadline); + }); + + it("reverts with ProcessingDeadlineMissed if deadline missed for the first transaction of extra data report", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime; + await oracleMemberSubmitReportData(report); + await consensus.advanceTimeToNextFrameStart(); + await expect(oracle.connect(member1).submitReportExtraDataList(extraDataChunks[0])) + .to.be.revertedWithCustomError(oracle, "ProcessingDeadlineMissed") + .withArgs(deadline); + }); + + it("reverts with ProcessingDeadlineMissed if deadline missed for the second transaction of extra data report", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime; + await oracleMemberSubmitReportData(report); + await oracleMemberSubmitExtraData(extraDataChunks[0]); + await consensus.advanceTimeToNextFrameStart(); + await expect(oracleMemberSubmitExtraData(extraDataChunks[1])) .to.be.revertedWithCustomError(oracle, "ProcessingDeadlineMissed") .withArgs(deadline); }); it("pass successfully if time is equals exactly to deadline value", async () => { await consensus.advanceTimeToNextFrameStart(); - const { extraDataList, reportFields } = await submitReportHash(); - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({}); + expect(extraDataChunks.length).to.be.equal(1); + await oracleMemberSubmitReportData(report); const deadline = (await oracle.getConsensusReport()).processingDeadlineTime; await consensus.setTime(deadline); - const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); - await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); + const tx = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); + }); + + it("pass successfully if the last transaction time is equals exactly to deadline value", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + await oracleMemberSubmitReportData(report); + await oracleMemberSubmitExtraData(extraDataChunks[0]); + const deadline = (await oracle.getConsensusReport()).processingDeadlineTime; + await consensus.setTime(deadline); + const tx = await oracleMemberSubmitExtraData(extraDataChunks[1]); + await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); }); }); context("checks ref slot", () => { it("reverts with CannotSubmitExtraDataBeforeMainData in attempt of try to pass extra data ahead of submitReportData", async () => { const { refSlot } = await consensus.getCurrentFrame(); - const { reportHash, extraDataList } = getReportData({ reportFields: { refSlot } }); + const { reportHash, extraDataChunks } = constructOracleReportWithDefaultValues({ + reportFieldsWithoutExtraData: { refSlot }, + }); await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION); // No submitReportData here — trying to send extra data ahead of it - await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)).to.be.revertedWithCustomError( - oracle, - "CannotSubmitExtraDataBeforeMainData", - ); + await expect( + oracle.connect(member1).submitReportExtraDataList(extraDataChunks[0]), + ).to.be.revertedWithCustomError(oracle, "CannotSubmitExtraDataBeforeMainData"); }); it("pass successfully ", async () => { const { refSlot } = await consensus.getCurrentFrame(); - const { reportFields, reportHash, extraDataList } = getReportData({ reportFields: { refSlot } }); + const { report, reportHash, extraDataChunks } = constructOracleReportWithDefaultValues({ + reportFieldsWithoutExtraData: { refSlot }, + }); + await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION); // Now submitReportData on it's place - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); - const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); - await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); + await oracle.connect(member1).submitReportData(report, oracleVersion); + const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataChunks[0]); + await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); }); }); @@ -225,29 +354,34 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { context("checks items count", () => { it("reverts with UnexpectedExtraDataItemsCount if there was wrong amount of items", async () => { - const wrongItemsCount = 1; await consensus.advanceTimeToNextFrameStart(); - const { extraDataList, extraDataItems, reportFields } = await submitReportHash({ - reportFields: { extraDataItemsCount: wrongItemsCount }, - }); - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); - await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) + const { extraDataList, extraDataItemsCount, reportFields } = + await constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot(); + + const wrongItemsCount = 1; + const reportWithWrongItemsCount = { ...reportFields, extraDataItemsCount: wrongItemsCount }; + const hashOfReportWithWrongItemsCount = calcReportDataHash(getReportDataItems(reportWithWrongItemsCount)); + + await oracleMemberSubmitReportHash(reportWithWrongItemsCount.refSlot, hashOfReportWithWrongItemsCount); + await oracleMemberSubmitReportData(reportWithWrongItemsCount); + await expect(oracleMemberSubmitExtraData(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataItemsCount") - .withArgs(reportFields.extraDataItemsCount, extraDataItems.length); + .withArgs(reportWithWrongItemsCount.extraDataItemsCount, extraDataItemsCount); }); }); context("enforces data format", () => { it("reverts with UnexpectedExtraDataFormat if there was empty format submitted on first phase", async () => { - const reportFieldsConsts = { - extraDataHash: ZeroHash, - extraDataFormat: EXTRA_DATA_FORMAT_EMPTY, - extraDataItemsCount: 0, - }; await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ reportFields: reportFieldsConsts }); - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); - await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) + const { reportFields: emptyReport, reportHash: emptyReportHash } = + await constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot({ + extraData: { stuckKeys: [], exitedKeys: [] }, + }); + const { extraDataList } = await constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot(); + + await oracleMemberSubmitReportHash(emptyReport.refSlot, emptyReportHash); + await oracleMemberSubmitReportData(emptyReport); + await expect(oracleMemberSubmitExtraData(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataFormat") .withArgs(EXTRA_DATA_FORMAT_EMPTY, EXTRA_DATA_FORMAT_LIST); }); @@ -298,9 +432,9 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }; it("if first item index is not zero", async () => { - const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(1, 1); + const { extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(1, 1); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataIndex") @@ -308,9 +442,9 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("if next index is greater than previous for more than +1", async () => { - const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(2, 2); + const { extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(2, 2); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataIndex") @@ -318,9 +452,9 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("if next index equals to previous", async () => { - const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(3, 1); + const { extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(3, 1); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataIndex") @@ -328,9 +462,9 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("if next index less than previous", async () => { - const { extraData, extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(3, 0); + const { extraDataItems, lastIndexDefault, lastIndexCustom } = getExtraWithCustomLastIndex(3, 0); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataIndex") @@ -338,9 +472,9 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("succeeds if indexes were passed sequentially", async () => { - const { extraData, extraDataItems } = getExtraWithCustomLastIndex(3, 2); + const { extraDataItems } = getExtraWithCustomLastIndex(3, 2); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); @@ -366,9 +500,9 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }; it("if type `0` was passed", async () => { - const { extraData, extraDataItems, wrongTypedIndex, typeCustom } = getExtraWithCustomType(0n); + const { extraDataItems, wrongTypedIndex, typeCustom } = getExtraWithCustomType(0n); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnsupportedExtraDataType") @@ -376,9 +510,9 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("if type `3` was passed", async () => { - const { extraData, extraDataItems, wrongTypedIndex, typeCustom } = getExtraWithCustomType(3n); + const { extraDataItems, wrongTypedIndex, typeCustom } = getExtraWithCustomType(3n); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "UnsupportedExtraDataType") @@ -386,18 +520,18 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("succeeds if `1` was passed", async () => { - const { extraData, extraDataItems } = getExtraWithCustomType(1n); + const { extraDataItems } = getExtraWithCustomType(1n); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); }); it("succeeds if `2` was passed", async () => { - const { extraData, extraDataItems } = getExtraWithCustomType(2n); + const { extraDataItems } = getExtraWithCustomType(2n); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); @@ -453,7 +587,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { const cutStop = 36; extraDataItems[invalidItemIndex] = extraDataItems[invalidItemIndex].slice(0, cutStop); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "InvalidExtraDataItem") @@ -475,7 +609,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { const cutStop = extraDataItems[invalidItemIndex].length - 2; extraDataItems[invalidItemIndex] = extraDataItems[invalidItemIndex].slice(0, cutStop); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData: extraDataItems }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "InvalidExtraDataItem") @@ -510,9 +644,8 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { ], exitedKeys: [], }; - const extraDataItems = encodeExtraDataItems(extraData); await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData, extraDataItems }); + const { reportFields, extraDataList } = await submitReportHash({ extraData }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(oracle, "InvalidExtraDataItem") @@ -549,17 +682,19 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { context("delivers the data to staking router", () => { it("calls reportStakingModuleStuckValidatorsCountByNodeOperator on StakingRouter", async () => { await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraData, extraDataList } = await submitReportHash(); + const { reportFields, reportInput, extraDataList } = await submitReportHash(); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await oracle.connect(member1).submitReportExtraDataList(extraDataList); const callsCount = await stakingRouter.totalCalls_reportStuckKeysByNodeOperator(); - expect(callsCount).to.be.equal(extraData.stuckKeys.length); + + const extraDataValue = reportInput.extraDataValue as ExtraDataType; + expect(callsCount).to.be.equal(extraDataValue.stuckKeys.length); for (let i = 0; i < callsCount; i++) { const call = await stakingRouter.calls_reportStuckKeysByNodeOperator(i); - const item = extraData.stuckKeys[i]; + const item = extraDataValue.stuckKeys[i]; expect(call.stakingModuleId).to.be.equal(item.moduleId); expect(call.nodeOperatorIds).to.be.equal("0x" + item.nodeOpIds.map((id) => numberToHex(id, 8)).join("")); expect(call.keysCounts).to.be.equal("0x" + item.keysCounts.map((count) => numberToHex(count, 16)).join("")); @@ -568,17 +703,19 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { it("calls reportStakingModuleExitedValidatorsCountByNodeOperator on StakingRouter", async () => { await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraData, extraDataList } = await submitReportHash(); + const { reportFields, reportInput, extraDataList } = await submitReportHash(); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await oracle.connect(member1).submitReportExtraDataList(extraDataList); const callsCount = await stakingRouter.totalCalls_reportExitedKeysByNodeOperator(); - expect(callsCount).to.be.equal(extraData.exitedKeys.length); + + const extraDataValue = reportInput.extraDataValue as ExtraDataType; + expect(callsCount).to.be.equal(extraDataValue.exitedKeys.length); for (let i = 0; i < callsCount; i++) { const call = await stakingRouter.calls_reportExitedKeysByNodeOperator(i); - const item = extraData.exitedKeys[i]; + const item = extraDataValue.exitedKeys[i]; expect(call.stakingModuleId).to.be.equal(item.moduleId); expect(call.nodeOperatorIds).to.be.equal("0x" + item.nodeOpIds.map((id) => numberToHex(id, 8)).join("")); expect(call.keysCounts).to.be.equal("0x" + item.keysCounts.map((count) => numberToHex(count, 16)).join("")); @@ -598,11 +735,11 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { it("reverts if extraData has already been processed", async () => { await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataItems, extraDataList } = await submitReportHash(); + const { reportFields, extraDataItemsCount, extraDataList } = await submitReportHash(); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await oracle.connect(member1).submitReportExtraDataList(extraDataList); const state = await oracle.getExtraDataProcessingState(); - expect(state.itemsCount).to.be.equal(extraDataItems.length); + expect(state.itemsCount).to.be.equal(extraDataItemsCount); expect(state.itemsCount).to.be.equal(state.itemsProcessed); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)).to.be.revertedWithCustomError( oracle, @@ -612,7 +749,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { it("reverts if main data has not been processed yet", async () => { await consensus.advanceTimeToNextFrameStart(); - const report1 = await prepareReport(); + const report1 = await constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot(); await expect( oracle.connect(member1).submitReportExtraDataList(report1.extraDataList), @@ -641,7 +778,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { it("updates extra data processing state", async () => { await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataItems, extraDataHash, extraDataList } = await submitReportHash(); + const { reportFields, extraDataItemsCount, extraDataHash, extraDataList } = await submitReportHash(); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); const stateBefore = await oracle.getExtraDataProcessingState(); @@ -649,7 +786,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { expect(stateBefore.refSlot).to.be.equal(reportFields.refSlot); expect(stateBefore.dataFormat).to.be.equal(EXTRA_DATA_FORMAT_LIST); expect(stateBefore.submitted).to.be.false; - expect(stateBefore.itemsCount).to.be.equal(extraDataItems.length); + expect(stateBefore.itemsCount).to.be.equal(extraDataItemsCount); expect(stateBefore.itemsProcessed).to.be.equal(0); expect(stateBefore.lastSortingKey).to.be.equal("0"); expect(stateBefore.dataHash).to.be.equal(extraDataHash); @@ -661,8 +798,8 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { expect(stateAfter.refSlot).to.be.equal(reportFields.refSlot); expect(stateAfter.dataFormat).to.be.equal(EXTRA_DATA_FORMAT_LIST); expect(stateAfter.submitted).to.be.true; - expect(stateAfter.itemsCount).to.be.equal(extraDataItems.length); - expect(stateAfter.itemsProcessed).to.be.equal(extraDataItems.length); + expect(stateAfter.itemsCount).to.be.equal(extraDataItemsCount); + expect(stateAfter.itemsProcessed).to.be.equal(extraDataItemsCount); // TODO: figure out how to build this value and test it properly expect(stateAfter.lastSortingKey).to.be.equal( "3533694129556768659166595001485837031654967793751237971583444623713894401", diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index 196d831cf..5e7af7735 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -22,7 +22,8 @@ describe("OracleReportSanityChecker.sol", () => { let managersRoster: Record; const defaultLimitsList = { - churnValidatorsPerDayLimit: 55, + exitedValidatorsPerDayLimit: 55, + appearedValidatorsPerDayLimit: 100, oneOffCLBalanceDecreaseBPLimit: 5_00, // 5% annualBalanceIncreaseBPLimit: 10_00, // 10% simulatedShareRateDeviationBPLimit: 2_50, // 2.5% @@ -71,15 +72,16 @@ describe("OracleReportSanityChecker.sol", () => { // const accounts = signers.map(s => s.address); managersRoster = { allLimitsManagers: accounts.slice(0, 2), - churnValidatorsPerDayLimitManagers: accounts.slice(2, 4), - oneOffCLBalanceDecreaseLimitManagers: accounts.slice(4, 6), - annualBalanceIncreaseLimitManagers: accounts.slice(6, 8), - shareRateDeviationLimitManagers: accounts.slice(8, 10), - maxValidatorExitRequestsPerReportManagers: accounts.slice(10, 12), - maxAccountingExtraDataListItemsCountManagers: accounts.slice(12, 14), - maxNodeOperatorsPerExtraDataItemCountManagers: accounts.slice(14, 16), - requestTimestampMarginManagers: accounts.slice(16, 18), - maxPositiveTokenRebaseManagers: accounts.slice(18, 20), + exitedValidatorsPerDayLimitManagers: accounts.slice(2, 4), + appearedValidatorsPerDayLimitManagers: accounts.slice(4, 6), + oneOffCLBalanceDecreaseLimitManagers: accounts.slice(6, 8), + annualBalanceIncreaseLimitManagers: accounts.slice(8, 10), + shareRateDeviationLimitManagers: accounts.slice(10, 12), + maxValidatorExitRequestsPerReportManagers: accounts.slice(12, 14), + maxAccountingExtraDataListItemsCountManagers: accounts.slice(14, 16), + maxNodeOperatorsPerExtraDataItemCountManagers: accounts.slice(16, 18), + requestTimestampMarginManagers: accounts.slice(18, 20), + maxPositiveTokenRebaseManagers: accounts.slice(20, 22), }; oracleReportSanityChecker = await ethers.deployContract("OracleReportSanityChecker", [ await lidoLocatorMock.getAddress(), @@ -113,7 +115,8 @@ describe("OracleReportSanityChecker.sol", () => { describe("setOracleReportLimits()", () => { it("sets limits correctly", async () => { const newLimitsList = { - churnValidatorsPerDayLimit: 50, + exitedValidatorsPerDayLimit: 50, + appearedValidatorsPerDayLimit: 75, oneOffCLBalanceDecreaseBPLimit: 10_00, annualBalanceIncreaseBPLimit: 15_00, simulatedShareRateDeviationBPLimit: 1_50, // 1.5% @@ -124,7 +127,8 @@ describe("OracleReportSanityChecker.sol", () => { maxPositiveTokenRebase: 10_000_000, }; const limitsBefore = await oracleReportSanityChecker.getOracleReportLimits(); - expect(limitsBefore.churnValidatorsPerDayLimit).to.not.equal(newLimitsList.churnValidatorsPerDayLimit); + expect(limitsBefore.exitedValidatorsPerDayLimit).to.not.equal(newLimitsList.exitedValidatorsPerDayLimit); + expect(limitsBefore.appearedValidatorsPerDayLimit).to.not.equal(newLimitsList.appearedValidatorsPerDayLimit); expect(limitsBefore.oneOffCLBalanceDecreaseBPLimit).to.not.equal(newLimitsList.oneOffCLBalanceDecreaseBPLimit); expect(limitsBefore.annualBalanceIncreaseBPLimit).to.not.equal(newLimitsList.annualBalanceIncreaseBPLimit); expect(limitsBefore.simulatedShareRateDeviationBPLimit).to.not.equal( @@ -153,7 +157,8 @@ describe("OracleReportSanityChecker.sol", () => { await oracleReportSanityChecker.connect(managersRoster.allLimitsManagers[0]).setOracleReportLimits(newLimitsList); const limitsAfter = await oracleReportSanityChecker.getOracleReportLimits(); - expect(limitsAfter.churnValidatorsPerDayLimit).to.equal(newLimitsList.churnValidatorsPerDayLimit); + expect(limitsAfter.exitedValidatorsPerDayLimit).to.equal(newLimitsList.exitedValidatorsPerDayLimit); + expect(limitsAfter.appearedValidatorsPerDayLimit).to.equal(newLimitsList.appearedValidatorsPerDayLimit); expect(limitsAfter.oneOffCLBalanceDecreaseBPLimit).to.equal(newLimitsList.oneOffCLBalanceDecreaseBPLimit); expect(limitsAfter.annualBalanceIncreaseBPLimit).to.equal(newLimitsList.annualBalanceIncreaseBPLimit); expect(limitsAfter.simulatedShareRateDeviationBPLimit).to.equal(newLimitsList.simulatedShareRateDeviationBPLimit); @@ -1021,65 +1026,102 @@ describe("OracleReportSanityChecker.sol", () => { }); }); - describe("churn limit", () => { - it("setChurnValidatorsPerDayLimit works", async () => { - const oldChurnLimit = defaultLimitsList.churnValidatorsPerDayLimit; - await oracleReportSanityChecker.checkExitedValidatorsRatePerDay(oldChurnLimit); - await expect(oracleReportSanityChecker.checkExitedValidatorsRatePerDay(oldChurnLimit + 1)) + describe("validators limits", () => { + it("setExitedValidatorsPerDayLimit works", async () => { + const oldExitedLimit = defaultLimitsList.exitedValidatorsPerDayLimit; + await oracleReportSanityChecker.checkExitedValidatorsRatePerDay(oldExitedLimit); + await expect(oracleReportSanityChecker.checkExitedValidatorsRatePerDay(oldExitedLimit + 1)) .to.be.revertedWithCustomError(oracleReportSanityChecker, "ExitedValidatorsLimitExceeded") - .withArgs(oldChurnLimit, oldChurnLimit + 1); - expect((await oracleReportSanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit).to.be.equal( - oldChurnLimit, + .withArgs(oldExitedLimit, oldExitedLimit + 1); + expect((await oracleReportSanityChecker.getOracleReportLimits()).exitedValidatorsPerDayLimit).to.be.equal( + oldExitedLimit, ); - const newChurnLimit = 30; - expect(newChurnLimit).to.not.equal(oldChurnLimit); + const newExitedLimit = 30; + expect(newExitedLimit).to.not.equal(oldExitedLimit); await expect( - oracleReportSanityChecker.connect(deployer).setChurnValidatorsPerDayLimit(newChurnLimit), + oracleReportSanityChecker.connect(deployer).setExitedValidatorsPerDayLimit(newExitedLimit), ).to.be.revertedWithOZAccessControlError( deployer.address, - await oracleReportSanityChecker.CHURN_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), + await oracleReportSanityChecker.EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), ); const tx = await oracleReportSanityChecker - .connect(managersRoster.churnValidatorsPerDayLimitManagers[0]) - .setChurnValidatorsPerDayLimit(newChurnLimit); + .connect(managersRoster.exitedValidatorsPerDayLimitManagers[0]) + .setExitedValidatorsPerDayLimit(newExitedLimit); - await expect(tx).to.emit(oracleReportSanityChecker, "ChurnValidatorsPerDayLimitSet").withArgs(newChurnLimit); - // assert.emits(tx, 'ChurnValidatorsPerDayLimitSet', { churnValidatorsPerDayLimit: newChurnLimit }) - expect((await oracleReportSanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit).to.be.equal( - newChurnLimit, + await expect(tx).to.emit(oracleReportSanityChecker, "ExitedValidatorsPerDayLimitSet").withArgs(newExitedLimit); + + expect((await oracleReportSanityChecker.getOracleReportLimits()).exitedValidatorsPerDayLimit).to.be.equal( + newExitedLimit, ); - await oracleReportSanityChecker.checkExitedValidatorsRatePerDay(newChurnLimit); - await expect(oracleReportSanityChecker.checkExitedValidatorsRatePerDay(newChurnLimit + 1)) + await oracleReportSanityChecker.checkExitedValidatorsRatePerDay(newExitedLimit); + await expect(oracleReportSanityChecker.checkExitedValidatorsRatePerDay(newExitedLimit + 1)) .to.be.revertedWithCustomError(oracleReportSanityChecker, "ExitedValidatorsLimitExceeded") - .withArgs(newChurnLimit, newChurnLimit + 1); + .withArgs(newExitedLimit, newExitedLimit + 1); }); - it("checkAccountingOracleReport: churnLimit works", async () => { - const churnLimit = defaultLimitsList.churnValidatorsPerDayLimit; - expect((await oracleReportSanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit).to.be.equal( - churnLimit, + it("setAppearedValidatorsPerDayLimit works", async () => { + const oldAppearedLimit = defaultLimitsList.appearedValidatorsPerDayLimit; + + await oracleReportSanityChecker.checkAccountingOracleReport( + ...(Object.values({ + ...correctLidoOracleReport, + postCLValidators: oldAppearedLimit, + }) as CheckAccountingOracleReportParameters), + ); + + await expect( + oracleReportSanityChecker.checkAccountingOracleReport( + ...(Object.values({ + ...correctLidoOracleReport, + postCLValidators: oldAppearedLimit + 1, + }) as CheckAccountingOracleReportParameters), + ), + ) + .to.be.revertedWithCustomError(oracleReportSanityChecker, `IncorrectAppearedValidators`) + .withArgs(oldAppearedLimit + 1); + + const newAppearedLimit = 30; + expect(newAppearedLimit).not.equal(oldAppearedLimit); + + await expect( + oracleReportSanityChecker.connect(deployer).setAppearedValidatorsPerDayLimit(newAppearedLimit), + ).to.be.revertedWithOZAccessControlError( + deployer.address, + await oracleReportSanityChecker.APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), + ); + + const tx = await oracleReportSanityChecker + .connect(managersRoster.appearedValidatorsPerDayLimitManagers[0]) + .setAppearedValidatorsPerDayLimit(newAppearedLimit); + + await expect(tx) + .to.emit(oracleReportSanityChecker, "AppearedValidatorsPerDayLimitSet") + .withArgs(newAppearedLimit); + + expect((await oracleReportSanityChecker.getOracleReportLimits()).appearedValidatorsPerDayLimit).to.be.equal( + newAppearedLimit, ); await oracleReportSanityChecker.checkAccountingOracleReport( ...(Object.values({ ...correctLidoOracleReport, - postCLValidators: churnLimit, + postCLValidators: newAppearedLimit, }) as CheckAccountingOracleReportParameters), ); await expect( oracleReportSanityChecker.checkAccountingOracleReport( ...(Object.values({ ...correctLidoOracleReport, - postCLValidators: churnLimit + 1, + postCLValidators: newAppearedLimit + 1, }) as CheckAccountingOracleReportParameters), ), ) .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectAppearedValidators") - .withArgs(churnLimit + 1); + .withArgs(newAppearedLimit + 1); }); }); @@ -1199,12 +1241,12 @@ describe("OracleReportSanityChecker.sol", () => { .withArgs(12, maxCount + 1n); }); - it("checkAccountingExtraDataListItemsCount", async () => { + it("checkExtraDataItemsCountPerTransaction", async () => { const maxCount = (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount; - await oracleReportSanityChecker.checkAccountingExtraDataListItemsCount(maxCount); + await oracleReportSanityChecker.checkExtraDataItemsCountPerTransaction(maxCount); - await expect(oracleReportSanityChecker.checkAccountingExtraDataListItemsCount(maxCount + 1n)) + await expect(oracleReportSanityChecker.checkExtraDataItemsCountPerTransaction(maxCount + 1n)) .to.be.revertedWithCustomError(oracleReportSanityChecker, "MaxAccountingExtraDataItemsCountExceeded") .withArgs(maxCount, maxCount + 1n); }); @@ -1247,7 +1289,15 @@ describe("OracleReportSanityChecker.sol", () => { await expect( oracleReportSanityChecker .connect(managersRoster.allLimitsManagers[0]) - .setOracleReportLimits({ ...defaultLimitsList, churnValidatorsPerDayLimit: INVALID_VALUE }), + .setOracleReportLimits({ ...defaultLimitsList, exitedValidatorsPerDayLimit: INVALID_VALUE }), + ) + .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectLimitValue") + .withArgs(INVALID_VALUE, 0, MAX_UINT_16); + + await expect( + oracleReportSanityChecker + .connect(managersRoster.allLimitsManagers[0]) + .setOracleReportLimits({ ...defaultLimitsList, appearedValidatorsPerDayLimit: INVALID_VALUE }), ) .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectLimitValue") .withArgs(INVALID_VALUE, 0, MAX_UINT_16); diff --git a/test/deploy/accountingOracle.ts b/test/deploy/accountingOracle.ts index 28ca61524..fcf648bcd 100644 --- a/test/deploy/accountingOracle.ts +++ b/test/deploy/accountingOracle.ts @@ -152,9 +152,22 @@ export async function initAccountingOracle({ } async function deployOracleReportSanityCheckerForAccounting(lidoLocator: string, admin: string) { - const churnValidatorsPerDayLimit = 100; - const limitsList = [churnValidatorsPerDayLimit, 0, 0, 0, 32 * 12, 15, 16, 0, 0]; - const managersRoster = [[admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin], [admin]]; + const exitedValidatorsPerDayLimit = 55; + const appearedValidatorsPerDayLimit = 100; + const limitsList = [exitedValidatorsPerDayLimit, appearedValidatorsPerDayLimit, 0, 0, 0, 32 * 12, 15, 16, 0, 0]; + const managersRoster = [ + [admin], + [admin], + [admin], + [admin], + [admin], + [admin], + [admin], + [admin], + [admin], + [admin], + [admin], + ]; return await ethers.deployContract("OracleReportSanityChecker", [lidoLocator, admin, limitsList, managersRoster]); } From 32cbe0ed619d2d7965e230f3cc96d44ec18a138e Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 24 May 2024 11:27:56 +0200 Subject: [PATCH 002/177] feat: add addition tests for multi transactions --- ...untingOracle.submitReportExtraData.test.ts | 290 +++++++++++++++--- 1 file changed, 239 insertions(+), 51 deletions(-) diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index a74a6acf4..bc05ddafa 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -22,6 +22,7 @@ import { ether, EXTRA_DATA_FORMAT_EMPTY, EXTRA_DATA_FORMAT_LIST, + EXTRA_DATA_TYPE_EXITED_VALIDATORS, EXTRA_DATA_TYPE_STUCK_VALIDATORS, ExtraData, ExtraDataType, @@ -100,16 +101,19 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { } type ConstructOracleReportWithDefaultValuesProps = Pick, "config" | "extraData"> & { - reportFieldsWithoutExtraData?: Partial; + reportFields?: Partial>; }; - function constructOracleReportWithDefaultValues({ - reportFieldsWithoutExtraData, + async function constructOracleReportWithDefaultValuesForCurrentRefSlot({ + reportFields, extraData, config, }: ConstructOracleReportWithDefaultValuesProps) { + const { refSlot } = await consensus.getCurrentFrame(); + const reportFieldsValue = getDefaultReportFields({ - ...reportFieldsWithoutExtraData, + ...reportFields, + refSlot, }); const extraDataValue = extraData || getDefaultExtraData(); @@ -134,12 +138,15 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { reportFields?: Partial; } - function constructOracleReportWithSingeExtraDataTransaction({ extraData, reportFields }: ReportDataArgs = {}) { + async function constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot({ + extraData, + reportFields, + }: ReportDataArgs = {}) { const extraDataValue = extraData || getDefaultExtraData(); const { extraDataChunks, extraDataChunkHashes, extraDataItemsCount, report, reportHash, reportInput } = - constructOracleReportWithDefaultValues({ - reportFieldsWithoutExtraData: reportFields, + await constructOracleReportWithDefaultValuesForCurrentRefSlot({ + reportFields: reportFields, extraData: extraDataValue, }); @@ -153,17 +160,6 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }; } - async function constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot({ - extraData, - reportFields, - }: ReportDataArgs = {}) { - const { refSlot } = await consensus.getCurrentFrame(); - return constructOracleReportWithSingeExtraDataTransaction({ - extraData, - reportFields: { ...reportFields, refSlot } as OracleReport, - }); - } - async function oracleMemberSubmitReportHash(refSlot: BigNumberish, reportHash: string) { return await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION); } @@ -176,15 +172,18 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { return await oracle.connect(member1).submitReportExtraDataList(extraDataList); } + async function oracleMemberSubmitExtraDataEmpty() { + return await oracle.connect(member1).submitReportExtraDataEmpty(); + } + async function constructOracleReportForCurrentFrameAndSubmitReportHash({ extraData, - reportFieldsWithoutExtraData, + reportFields, config, }: ConstructOracleReportWithDefaultValuesProps) { - const { refSlot } = await consensus.getCurrentFrame(); - const data = await constructOracleReportWithDefaultValues({ + const data = await constructOracleReportWithDefaultValuesForCurrentRefSlot({ extraData, - reportFieldsWithoutExtraData: { ...reportFieldsWithoutExtraData, refSlot }, + reportFields, config, }); await oracleMemberSubmitReportHash(data.report.refSlot, data.reportHash); @@ -222,15 +221,54 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("submit extra data report within two transaction", async () => { - const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ - config: { maxItemsPerChunk: 3 }, - }); + const { report, extraDataChunks, extraDataChunkHashes } = + await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + + const defaultExtraData = getDefaultExtraData(); expect(extraDataChunks.length).to.be.equal(2); await oracleMemberSubmitReportData(report); + + const calcSortingKey = (itemType: bigint, moduleId: number, firstNodeOpId: number) => + (BigInt(itemType) << 240n) | (BigInt(moduleId) << 64n) | BigInt(firstNodeOpId); + + const stateBeforeProcessingStart = await oracle.getExtraDataProcessingState(); + expect(stateBeforeProcessingStart.itemsCount).to.be.equal(5); + expect(stateBeforeProcessingStart.itemsProcessed).to.be.equal(0); + expect(stateBeforeProcessingStart.submitted).to.be.equal(false); + expect(stateBeforeProcessingStart.lastSortingKey).to.be.equal(0); + expect(stateBeforeProcessingStart.dataHash).to.be.equal(extraDataChunkHashes[0]); + const tx1 = await oracleMemberSubmitExtraData(extraDataChunks[0]); await expect(tx1).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, 3, 5); + const state1 = await oracle.getExtraDataProcessingState(); + expect(state1.itemsCount).to.be.equal(5); + expect(state1.itemsProcessed).to.be.equal(3); + expect(state1.submitted).to.be.equal(false); + expect(state1.lastSortingKey).to.be.equal( + calcSortingKey( + EXTRA_DATA_TYPE_STUCK_VALIDATORS, + defaultExtraData.stuckKeys[2].moduleId, + defaultExtraData.stuckKeys[2].nodeOpIds[0], + ), + ); + expect(state1.dataHash).to.be.equal(extraDataChunkHashes[1]); + const tx2 = await oracleMemberSubmitExtraData(extraDataChunks[1]); await expect(tx2).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, 5, 5); + const state2 = await oracle.getExtraDataProcessingState(); + expect(state2.itemsCount).to.be.equal(5); + expect(state2.itemsProcessed).to.be.equal(5); + expect(state2.submitted).to.be.equal(true); + expect(state2.lastSortingKey).to.be.equal( + calcSortingKey( + EXTRA_DATA_TYPE_EXITED_VALIDATORS, + defaultExtraData.exitedKeys[1].moduleId, + defaultExtraData.exitedKeys[1].nodeOpIds[0], + ), + ); + expect(state2.dataHash).to.be.equal(extraDataChunkHashes[1]); }); }); @@ -303,11 +341,10 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { context("checks ref slot", () => { it("reverts with CannotSubmitExtraDataBeforeMainData in attempt of try to pass extra data ahead of submitReportData", async () => { - const { refSlot } = await consensus.getCurrentFrame(); - const { reportHash, extraDataChunks } = constructOracleReportWithDefaultValues({ - reportFieldsWithoutExtraData: { refSlot }, - }); - await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION); + const { report, reportHash, extraDataChunks } = await constructOracleReportWithDefaultValuesForCurrentRefSlot( + {}, + ); + await consensus.connect(member1).submitReport(report.refSlot, reportHash, CONSENSUS_VERSION); // No submitReportData here — trying to send extra data ahead of it await expect( oracle.connect(member1).submitReportExtraDataList(extraDataChunks[0]), @@ -315,12 +352,11 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("pass successfully ", async () => { - const { refSlot } = await consensus.getCurrentFrame(); - const { report, reportHash, extraDataChunks } = constructOracleReportWithDefaultValues({ - reportFieldsWithoutExtraData: { refSlot }, - }); + const { report, reportHash, extraDataChunks } = await constructOracleReportWithDefaultValuesForCurrentRefSlot( + {}, + ); - await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION); + await consensus.connect(member1).submitReport(report.refSlot, reportHash, CONSENSUS_VERSION); // Now submitReportData on it's place await oracle.connect(member1).submitReportData(report, oracleVersion); const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataChunks[0]); @@ -343,12 +379,84 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { .withArgs(extraDataHash, incorrectExtraDataHash); }); + it("reverts with UnexpectedDataHash if second transaction hash did not match", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks, extraDataChunkHashes } = + await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + await oracleMemberSubmitReportData(report); + const tx1 = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx1).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); + + const incorrectExtraData = getDefaultExtraData(); + ++incorrectExtraData.exitedKeys[0].nodeOpIds[0]; + + const { extraDataChunks: incorrectExtraDataChunks, extraDataChunkHashes: incorrectExtraDataChunkHashes } = + await constructOracleReportWithDefaultValuesForCurrentRefSlot({ + extraData: incorrectExtraData, + config: { maxItemsPerChunk: 3 }, + }); + + expect(extraDataChunkHashes[0]).to.be.not.equal(incorrectExtraDataChunkHashes[0]); + expect(extraDataChunkHashes[1]).to.be.not.equal(incorrectExtraDataChunkHashes[1]); + + await expect(oracleMemberSubmitExtraData(incorrectExtraDataChunks[1])) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataHash") + .withArgs(extraDataChunkHashes[1], incorrectExtraDataChunkHashes[1]); + }); + + it("reverts with UnexpectedDataHash if second transaction send before the first one", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks, extraDataChunkHashes } = + await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + await oracleMemberSubmitReportData(report); + await expect(oracleMemberSubmitExtraData(extraDataChunks[1])) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataHash") + .withArgs(extraDataChunkHashes[0], extraDataChunkHashes[1]); + + const tx1 = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx1).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); + + const tx2 = await oracleMemberSubmitExtraData(extraDataChunks[1]); + await expect(tx2).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); + }); + + it("reverts with UnexpectedDataHash if the first transaction sended twice", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks, extraDataChunkHashes } = + await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + await oracleMemberSubmitReportData(report); + await expect(oracleMemberSubmitExtraData(extraDataChunks[1])) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataHash") + .withArgs(extraDataChunkHashes[0], extraDataChunkHashes[1]); + + const tx1 = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx1).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); + + await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataHash") + .withArgs(extraDataChunkHashes[1], extraDataChunkHashes[0]); + + const tx2 = await oracleMemberSubmitExtraData(extraDataChunks[1]); + await expect(tx2).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); + }); + it("pass successfully if data hash matches", async () => { await consensus.advanceTimeToNextFrameStart(); - const { extraDataList, reportFields } = await submitReportHash(); + const { extraDataList, reportFields, extraDataItemsCount } = await submitReportHash(); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); - await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); + await expect(tx) + .to.emit(oracle, "ExtraDataSubmitted") + .withArgs(reportFields.refSlot, extraDataItemsCount, extraDataItemsCount); }); }); @@ -368,6 +476,43 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataItemsCount") .withArgs(reportWithWrongItemsCount.extraDataItemsCount, extraDataItemsCount); }); + + it("reverts with UnexpectedExtraDataItemsCount if there was wrong amount of items in the first transaction", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks } = await constructOracleReportWithDefaultValuesForCurrentRefSlot({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + + const wrongItemsCount = 1; + const reportWithWrongItemsCount = { ...report, extraDataItemsCount: wrongItemsCount }; + const hashOfReportWithWrongItemsCount = calcReportDataHash(getReportDataItems(reportWithWrongItemsCount)); + + await oracleMemberSubmitReportHash(reportWithWrongItemsCount.refSlot, hashOfReportWithWrongItemsCount); + await oracleMemberSubmitReportData(reportWithWrongItemsCount); + await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataItemsCount") + .withArgs(reportWithWrongItemsCount.extraDataItemsCount, 3); + }); + + it("reverts with UnexpectedExtraDataItemsCount if there was wrong amount of items in the second transaction", async () => { + await consensus.advanceTimeToNextFrameStart(); + const { report, extraDataChunks } = await constructOracleReportWithDefaultValuesForCurrentRefSlot({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); + + const wrongItemsCount = 4; + const reportWithWrongItemsCount = { ...report, extraDataItemsCount: wrongItemsCount }; + const hashOfReportWithWrongItemsCount = calcReportDataHash(getReportDataItems(reportWithWrongItemsCount)); + + await oracleMemberSubmitReportHash(reportWithWrongItemsCount.refSlot, hashOfReportWithWrongItemsCount); + await oracleMemberSubmitReportData(reportWithWrongItemsCount); + await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(oracleMemberSubmitExtraData(extraDataChunks[1])) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataItemsCount") + .withArgs(reportWithWrongItemsCount.extraDataItemsCount, 5); + }); }); context("enforces data format", () => { @@ -385,6 +530,63 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataFormat") .withArgs(EXTRA_DATA_FORMAT_EMPTY, EXTRA_DATA_FORMAT_LIST); }); + + it("reverts with UnexpectedExtraDataFormat if there was list format submitted on first phase", async () => { + const { report, extraDataChunks, extraDataItemsCount } = + await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + + await oracleMemberSubmitReportData(report); + + await expect(oracleMemberSubmitExtraDataEmpty()) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataFormat") + .withArgs(EXTRA_DATA_FORMAT_LIST, EXTRA_DATA_FORMAT_EMPTY); + + const tx1 = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx1).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, anyValue, anyValue); + + await expect(oracleMemberSubmitExtraDataEmpty()) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataFormat") + .withArgs(EXTRA_DATA_FORMAT_LIST, EXTRA_DATA_FORMAT_EMPTY); + + const tx2 = await oracleMemberSubmitExtraData(extraDataChunks[1]); + await expect(tx2) + .to.emit(oracle, "ExtraDataSubmitted") + .withArgs(report.refSlot, extraDataItemsCount, extraDataItemsCount); + }); + }); + + context("enforces protection from double extra data submit", () => { + it("reverts with ExtraDataAlreadyProcessed if extraData has already been processed", async () => { + const { report, extraDataChunks, extraDataItemsCount } = + await constructOracleReportForCurrentFrameAndSubmitReportHash({}); + await oracleMemberSubmitReportData(report); + await oracleMemberSubmitExtraData(extraDataChunks[0]); + const state = await oracle.getExtraDataProcessingState(); + expect(state.itemsCount).to.be.equal(extraDataItemsCount); + expect(state.itemsCount).to.be.equal(state.itemsProcessed); + await expect(oracleMemberSubmitExtraData(extraDataChunks[0])).to.be.revertedWithCustomError( + oracle, + "ExtraDataAlreadyProcessed", + ); + }); + + it("reverts with ExtraDataAlreadyProcessed if empty extraData has already been processed", async () => { + const { report: emptyReport } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + extraData: { stuckKeys: [], exitedKeys: [] }, + }); + + await oracleMemberSubmitReportData(emptyReport); + await oracleMemberSubmitExtraDataEmpty(); + const state = await oracle.getExtraDataProcessingState(); + expect(state.itemsCount).to.be.equal(0); + expect(state.itemsProcessed).to.be.equal(0); + await expect(oracleMemberSubmitExtraDataEmpty()).to.be.revertedWithCustomError( + oracle, + "ExtraDataAlreadyProcessed", + ); + }); }); context("enforces module ids sorting order", () => { @@ -733,20 +935,6 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); }); - it("reverts if extraData has already been processed", async () => { - await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataItemsCount, extraDataList } = await submitReportHash(); - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); - await oracle.connect(member1).submitReportExtraDataList(extraDataList); - const state = await oracle.getExtraDataProcessingState(); - expect(state.itemsCount).to.be.equal(extraDataItemsCount); - expect(state.itemsCount).to.be.equal(state.itemsProcessed); - await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)).to.be.revertedWithCustomError( - oracle, - "ExtraDataAlreadyProcessed", - ); - }); - it("reverts if main data has not been processed yet", async () => { await consensus.advanceTimeToNextFrameStart(); const report1 = await constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot(); From 1cee804fd0c09023f4113dfdee53e8c2ae591a2e Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 24 May 2024 18:26:15 +0200 Subject: [PATCH 003/177] feat: remove redundant code --- contracts/0.8.9/oracle/AccountingOracle.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 999d9a3d2..8d3191e12 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -98,7 +98,6 @@ contract AccountingOracle is BaseOracle { error UnsupportedExtraDataType(uint256 itemIndex, uint256 dataType); error CannotSubmitExtraDataBeforeMainData(); error ExtraDataAlreadyProcessed(); - error ExtraDataProcessingInProgress(); error UnexpectedExtraDataHash(bytes32 consensusHash, bytes32 receivedHash); error UnexpectedExtraDataFormat(uint256 expectedFormat, uint256 receivedFormat); error ExtraDataTransactionDoesNotContainsNextTransactionHash(); @@ -687,11 +686,8 @@ contract AccountingOracle is BaseOracle { function _submitReportExtraDataEmpty() internal { ExtraDataProcessingState memory procState = _storageExtraDataProcessingState().value; _checkCanSubmitExtraData(procState, EXTRA_DATA_FORMAT_EMPTY); - if (procState.submitted) { - revert ExtraDataAlreadyProcessed(); - } else if (procState.itemsProcessed < procState.itemsCount ) { - revert ExtraDataProcessingInProgress(); - } + if (procState.submitted) revert ExtraDataAlreadyProcessed(); + IStakingRouter(LOCATOR.stakingRouter()).onValidatorsCountsByNodeOperatorReportingFinished(); _storageExtraDataProcessingState().value.submitted = true; emit ExtraDataSubmitted(procState.refSlot, 0, 0); From a6d21e4c244926a33bf9921d10c825c4717aadb4 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 28 May 2024 12:41:42 +0200 Subject: [PATCH 004/177] feat: fix sanity check --- contracts/0.8.9/oracle/AccountingOracle.sol | 4 ++- ...untingOracle.submitReportExtraData.test.ts | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 8d3191e12..a699617ae 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -789,8 +789,10 @@ contract AccountingOracle is BaseOracle { uint256 dataOffset = iter.dataOffset; uint256 maxNodeOperatorsPerItem = 0; uint256 maxNodeOperatorItemIndex = 0; + uint256 itemsCount = 0; while (dataOffset < data.length) { + itemsCount++; uint256 index; uint256 itemType; @@ -839,7 +841,7 @@ contract AccountingOracle is BaseOracle { assert(maxNodeOperatorsPerItem > 0); IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) - .checkExtraDataItemsCountPerTransaction(iter.index + 1); + .checkExtraDataItemsCountPerTransaction(itemsCount); IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) .checkNodeOperatorsPerExtraDataItemCount(maxNodeOperatorItemIndex, maxNodeOperatorsPerItem); diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index bc05ddafa..3744d054e 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -771,6 +771,36 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); }); + + it("should revert in case when items count exceed limit", async () => { + const maxItemsPerChunk = 3; + const extraData = getDefaultExtraData(); + const itemsCount = extraData.exitedKeys.length + extraData.stuckKeys.length; + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + extraData, + config: { maxItemsPerChunk }, + }); + + expect(itemsCount).to.be.equal(5); + expect(extraDataChunks.length).to.be.equal(2); + + await oracleMemberSubmitReportData(report); + + await sanityChecker.setMaxAccountingExtraDataListItemsCount(maxItemsPerChunk - 1); + await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) + .to.be.revertedWithCustomError(sanityChecker, "MaxAccountingExtraDataItemsCountExceeded") + .withArgs(maxItemsPerChunk - 1, maxItemsPerChunk); + + await sanityChecker.setMaxAccountingExtraDataListItemsCount(maxItemsPerChunk); + + const tx0 = await oracleMemberSubmitExtraData(extraDataChunks[0]); + await expect(tx0) + .to.emit(oracle, "ExtraDataSubmitted") + .withArgs(report.refSlot, maxItemsPerChunk, itemsCount); + + const tx1 = await oracleMemberSubmitExtraData(extraDataChunks[1]); + await expect(tx1).to.emit(oracle, "ExtraDataSubmitted").withArgs(report.refSlot, itemsCount, itemsCount); + }); }); context("checks for InvalidExtraDataItem reverts", () => { From 0033f101d0f3851805c4b0afdce0e4d111175d83 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 28 May 2024 12:43:36 +0200 Subject: [PATCH 005/177] feat: add additional test --- ...untingOracle.submitReportExtraData.test.ts | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 3744d054e..1806d5a4c 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -609,6 +609,31 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { .to.be.revertedWithCustomError(oracle, "InvalidExtraDataSortOrder") .withArgs(4); }); + + it("second transaction should revert if extra data not sorted", async () => { + const invalidExtraData = { + stuckKeys: [ + { moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, + { moduleId: 2, nodeOpIds: [3], keysCounts: [2] }, + // Items for second transaction. + // Break report data sorting order, nodeOpId 3 already processed. + { moduleId: 2, nodeOpIds: [3], keysCounts: [4] }, + ], + exitedKeys: [{ moduleId: 2, nodeOpIds: [1, 2], keysCounts: [1, 3] }], + }; + + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + extraData: invalidExtraData, + config: { maxItemsPerChunk: 2 }, + }); + + await oracleMemberSubmitReportData(report); + await oracleMemberSubmitExtraData(extraDataChunks[0]); + + await expect(oracleMemberSubmitExtraData(extraDataChunks[1])) + .to.be.revertedWithCustomError(oracle, "InvalidExtraDataSortOrder") + .withArgs(2); + }); }); context("enforces data safety boundaries", () => { @@ -956,10 +981,18 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { it("calls onValidatorsCountsByNodeOperatorReportingFinished on StakingRouter", async () => { await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash(); - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); + const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + config: { maxItemsPerChunk: 3 }, + }); + expect(extraDataChunks.length).to.be.equal(2); - await oracle.connect(member1).submitReportExtraDataList(extraDataList); + await oracleMemberSubmitReportData(report); + await oracleMemberSubmitExtraData(extraDataChunks[0]); + const callsCountAfterFirstChunk = + await stakingRouter.totalCalls_onValidatorsCountsByNodeOperatorReportingFinished(); + expect(callsCountAfterFirstChunk).to.be.equal(0); + + await oracleMemberSubmitExtraData(extraDataChunks[1]); const callsCount = await stakingRouter.totalCalls_onValidatorsCountsByNodeOperatorReportingFinished(); expect(callsCount).to.be.equal(1); }); From 0f89eb421e4de13d374b0b6a8026fcbf9fcd49fc Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 28 May 2024 20:56:26 +0200 Subject: [PATCH 006/177] test: updates extra data state after previous day report --- ...untingOracle.submitReportExtraData.test.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 1806d5a4c..82f7e4497 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -1057,5 +1057,64 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { ); expect(stateAfter.dataHash).to.be.equal(extraDataHash); }); + + it("updates extra data state after previous day report fail", async () => { + await consensus.advanceTimeToNextFrameStart(); + + const extraDataDay1 = { + stuckKeys: [ + { moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, + { moduleId: 2, nodeOpIds: [0], keysCounts: [2] }, + { moduleId: 3, nodeOpIds: [2], keysCounts: [3] }, + ], + exitedKeys: [ + { moduleId: 2, nodeOpIds: [1, 2], keysCounts: [1, 3] }, + { moduleId: 3, nodeOpIds: [1], keysCounts: [2] }, + ], + }; + + const { report: reportDay1, extraDataChunks: extraDataChunksDay1 } = + await constructOracleReportWithDefaultValuesForCurrentRefSlot({ + extraData: extraDataDay1, + config: { maxItemsPerChunk: 4 }, + }); + + expect(extraDataChunksDay1.length).to.be.equal(2); + + const validExtraDataItemsCount = 5; + const invalidExtraDataItemsCount = 7; + const reportDay1WithInvalidItemsCount = { ...reportDay1, extraDataItemsCount: invalidExtraDataItemsCount }; + + const hashOfReportWithInvalidItemsCount = calcReportDataHash(getReportDataItems(reportDay1WithInvalidItemsCount)); + await oracleMemberSubmitReportHash(reportDay1.refSlot, hashOfReportWithInvalidItemsCount); + await oracleMemberSubmitReportData(reportDay1WithInvalidItemsCount); + await oracleMemberSubmitExtraData(extraDataChunksDay1[0]); + + await expect(oracleMemberSubmitExtraData(extraDataChunksDay1[1])) + .to.be.revertedWithCustomError(oracle, "UnexpectedExtraDataItemsCount") + .withArgs(invalidExtraDataItemsCount, validExtraDataItemsCount); + + const callsCountAfterDay1 = await stakingRouter.totalCalls_onValidatorsCountsByNodeOperatorReportingFinished(); + expect(callsCountAfterDay1).to.be.equal(0); + + await consensus.advanceTimeToNextFrameStart(); + + const extraDataDay2 = JSON.parse(JSON.stringify(extraDataDay1)); + extraDataDay2.stuckKeys[0].keysCounts = [2]; + extraDataDay2.exitedKeys[0].keysCounts = [1, 4]; + + const { report: reportDay2, extraDataChunks: extraDataChunksDay2 } = + await constructOracleReportForCurrentFrameAndSubmitReportHash({ + extraData: extraDataDay2, + config: { maxItemsPerChunk: 4 }, + }); + + await oracleMemberSubmitReportData(reportDay2); + await oracleMemberSubmitExtraData(extraDataChunksDay2[0]); + await oracleMemberSubmitExtraData(extraDataChunksDay2[1]); + + const callsCountAfterDay2 = await stakingRouter.totalCalls_onValidatorsCountsByNodeOperatorReportingFinished(); + expect(callsCountAfterDay2).to.be.equal(1); + }); }); }); From 344651dab936658f5f3daf68c56890a677e63502 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 29 May 2024 21:58:10 +0200 Subject: [PATCH 007/177] feat: decouple reward distribution from the Accounting Oracle report Distribute reward separately using the permissionless method in each staking module. --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 21765d131..83c446f21 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -44,6 +44,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { event KeysOpIndexSet(uint256 keysOpIndex); event StakingModuleTypeSet(bytes32 moduleType); event RewardsDistributed(address indexed rewardAddress, uint256 sharesAmount); + event RewardDistributionStateChanged(RewardDistributionState state); event LocatorContractSet(address locatorAddress); event VettedSigningKeysCountChanged(uint256 indexed nodeOperatorId, uint256 approvedValidatorsCount); event DepositedSigningKeysCountChanged(uint256 indexed nodeOperatorId, uint256 depositedValidatorsCount); @@ -61,6 +62,15 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount); event NodeOperatorPenalized(address indexed recipientAddress, uint256 sharesPenalizedAmount); + // Enum to represent the state of the reward distribution process + enum RewardDistributionState { + TransferredToModule, // New reward portion minted and transferred to the module + ReadyForDistribution, // Operators' statistics updated, reward ready for distribution + Distributed // Reward distributed among operators + } + + RewardDistributionState public rewardDistributionState = RewardDistributionState.Distributed; + // // ACL // @@ -413,8 +423,10 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { } /// @notice Called by StakingRouter to signal that stETH rewards were minted for this module. - function onRewardsMinted(uint256 /* _totalShares */) external view { + function onRewardsMinted(uint256 /* _totalShares */) external { _auth(STAKING_ROUTER_ROLE); + _updateRewardDistributionState(RewardDistributionState.TransferredToModule); + // since we're pushing rewards to operators after exited validators counts are // updated (as opposed to pulling by node ops), we don't need any handling here // see `onExitedAndStuckValidatorsCountsUpdated()` @@ -511,6 +523,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _updateRefundValidatorsKeysCount(_nodeOperatorId, _refundedValidatorsCount); } + // Function to distribute rewards + function distributeReward() external { + require(rewardDistributionState == RewardDistributionState.ReadyForDistribution, "Reward distribution is not ready"); + _updateRewardDistributionState(RewardDistributionState.Distributed); + _distributeRewards(); + } + /// @notice Called by StakingRouter after it finishes updating exited and stuck validators /// counts for this module's node operators. /// @@ -520,9 +539,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// is the same as StakingRouter expects based on the total count received from the oracle. function onExitedAndStuckValidatorsCountsUpdated() external { _auth(STAKING_ROUTER_ROLE); - // for the permissioned module, we're distributing rewards within oracle operation - // since the number of node ops won't be high and thus gas costs are limited - _distributeRewards(); + _updateRewardDistributionState(RewardDistributionState.ReadyForDistribution); } /// @notice Unsafely updates the number of validators in the EXITED/STUCK states for node operator with given id @@ -1449,4 +1466,9 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { function _onlyNonZeroAddress(address _a) internal pure { require(_a != address(0), "ZERO_ADDRESS"); } + + function _updateRewardDistributionState(RewardDistributionState _state) internal { + rewardDistributionState = _state; + emit RewardDistributionStateChanged(_state); + } } From c03af31ef352a7a3706b8f716ca925c924c9736f Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 30 May 2024 20:00:41 +0200 Subject: [PATCH 008/177] fix: data length check --- contracts/0.8.9/oracle/AccountingOracle.sol | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index a699617ae..34efda0ab 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -100,7 +100,7 @@ contract AccountingOracle is BaseOracle { error ExtraDataAlreadyProcessed(); error UnexpectedExtraDataHash(bytes32 consensusHash, bytes32 receivedHash); error UnexpectedExtraDataFormat(uint256 expectedFormat, uint256 receivedFormat); - error ExtraDataTransactionDoesNotContainsNextTransactionHash(); + error UnexpectedExtraDataLength(); error ExtraDataItemsCountCannotBeZeroForNonEmptyData(); error ExtraDataHashCannotBeZeroForNonEmptyData(); error UnexpectedExtraDataItemsCount(uint256 expectedCount, uint256 receivedCount); @@ -730,20 +730,19 @@ contract AccountingOracle is BaseOracle { revert ExtraDataAlreadyProcessed(); } + // at least 32 bytes for the next hash value + 35 bytes for the first item with 1 node operator + if(data.length < 67) { + revert UnexpectedExtraDataLength(); + } + bytes32 dataHash = keccak256(data); if (dataHash != procState.dataHash) { revert UnexpectedExtraDataHash(procState.dataHash, dataHash); } - uint256 initialDataOffset = 32; - - if(data.length < initialDataOffset) { - revert ExtraDataTransactionDoesNotContainsNextTransactionHash(); - } - - bytes32 nextHash; + // load the next hash value assembly { - nextHash := calldataload(data.offset) + dataHash := calldataload(data.offset) } bool started = procState.itemsProcessed > 0; @@ -752,7 +751,7 @@ contract AccountingOracle is BaseOracle { started: started, index: started ? procState.itemsProcessed - 1 : 0, itemType: 0, - dataOffset: initialDataOffset, + dataOffset: 32, // skip the next hash bytes lastSortingKey: procState.lastSortingKey, stakingRouter: LOCATOR.stakingRouter() }); @@ -760,7 +759,7 @@ contract AccountingOracle is BaseOracle { _processExtraDataItems(data, iter); uint256 itemsProcessed = iter.index + 1; - if(nextHash == ZERO_HASH) { + if(dataHash == ZERO_HASH) { if (itemsProcessed != procState.itemsCount) { revert UnexpectedExtraDataItemsCount(procState.itemsCount, itemsProcessed); } @@ -776,7 +775,8 @@ contract AccountingOracle is BaseOracle { revert UnexpectedExtraDataItemsCount(procState.itemsCount, itemsProcessed); } - procState.dataHash = nextHash; + // save the next hash value + procState.dataHash = dataHash; procState.itemsProcessed = uint64(itemsProcessed); procState.lastSortingKey = iter.lastSortingKey; _storageExtraDataProcessingState().value = procState; From 1319d978dddb46cfac657c06554e94be69d2f08a Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 30 May 2024 20:20:19 +0200 Subject: [PATCH 009/177] fix: redundant vars --- contracts/0.8.9/oracle/AccountingOracle.sol | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 34efda0ab..6135c4abb 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -712,8 +712,6 @@ contract AccountingOracle is BaseOracle { } struct ExtraDataIterState { - // volatile - bool started; uint256 index; uint256 itemType; uint256 dataOffset; @@ -745,11 +743,8 @@ contract AccountingOracle is BaseOracle { dataHash := calldataload(data.offset) } - bool started = procState.itemsProcessed > 0; - ExtraDataIterState memory iter = ExtraDataIterState({ - started: started, - index: started ? procState.itemsProcessed - 1 : 0, + index: procState.itemsProcessed > 0 ? procState.itemsProcessed - 1 : 0, itemType: 0, dataOffset: 32, // skip the next hash bytes lastSortingKey: procState.lastSortingKey, @@ -790,12 +785,10 @@ contract AccountingOracle is BaseOracle { uint256 maxNodeOperatorsPerItem = 0; uint256 maxNodeOperatorItemIndex = 0; uint256 itemsCount = 0; - + uint256 index; + uint256 itemType; + while (dataOffset < data.length) { - itemsCount++; - uint256 index; - uint256 itemType; - /// @solidity memory-safe-assembly assembly { // layout at the dataOffset: @@ -807,12 +800,10 @@ contract AccountingOracle is BaseOracle { dataOffset := add(dataOffset, 5) } - if (!iter.started) { + if (iter.lastSortingKey == 0) { if (index != 0) { revert UnexpectedExtraDataIndex(0, index); } - - iter.started = true; } else if (index != iter.index + 1) { revert UnexpectedExtraDataIndex(iter.index + 1, index); } @@ -836,6 +827,10 @@ contract AccountingOracle is BaseOracle { assert(iter.dataOffset > dataOffset); dataOffset = iter.dataOffset; + unchecked { + // oberflow is not possible here + ++itemsCount; + } } assert(maxNodeOperatorsPerItem > 0); From 4c308be4af878f3bfee22d4dd2a4db21a095d899 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 30 May 2024 20:33:32 +0200 Subject: [PATCH 010/177] fix: check NO ids order in lastSortingKey --- contracts/0.8.9/oracle/AccountingOracle.sol | 35 ++++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 6135c4abb..0695712c5 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -784,10 +784,10 @@ contract AccountingOracle is BaseOracle { uint256 dataOffset = iter.dataOffset; uint256 maxNodeOperatorsPerItem = 0; uint256 maxNodeOperatorItemIndex = 0; - uint256 itemsCount = 0; + uint256 itemsCount; uint256 index; uint256 itemType; - + while (dataOffset < data.length) { /// @solidity memory-safe-assembly assembly { @@ -846,7 +846,7 @@ contract AccountingOracle is BaseOracle { uint256 dataOffset = iter.dataOffset; uint256 moduleId; uint256 nodeOpsCount; - uint256 firstNodeOpId; + uint256 lastNodeOpId; bytes calldata nodeOpIds; bytes calldata valuesCounts; @@ -866,7 +866,8 @@ contract AccountingOracle is BaseOracle { nodeOpsCount := and(shr(168, header), 0xffffffffffffffff) nodeOpIds.offset := add(data.offset, add(dataOffset, 11)) nodeOpIds.length := mul(nodeOpsCount, 8) - firstNodeOpId := shr(192, calldataload(nodeOpIds.offset)) + // read the 1st node operator id for checking the sorting order later + lastNodeOpId := shr(192, calldataload(nodeOpIds.offset)) valuesCounts.offset := add(nodeOpIds.offset, nodeOpIds.length) valuesCounts.length := mul(nodeOpsCount, 16) dataOffset := sub(add(valuesCounts.offset, valuesCounts.length), data.offset) @@ -877,13 +878,31 @@ contract AccountingOracle is BaseOracle { } unchecked { - // | 2 bytes | 19 bytes | 3 bytes | 8 bytes | - // | itemType | 00000000 | moduleId | firstNodeOpId | - uint256 sortingKey = (iter.itemType << 240) | (moduleId << 64) | firstNodeOpId; + // firstly, check the sorting order between the 1st item's element and the last one of the previous item + + // | 2 bytes | 19 bytes | 3 bytes | 8 bytes | + // | itemType | 00000000 | moduleId | lastNodeOpId | + uint256 sortingKey = (iter.itemType << 240) | (moduleId << 64) | lastNodeOpId; if (sortingKey <= iter.lastSortingKey) { revert InvalidExtraDataSortOrder(iter.index); } - iter.lastSortingKey = sortingKey; + + // secondly, check the sorting order between the rest of the elements + uint256 tmpNodeOpId; + for (uint256 i = 1; i < nodeOpsCount;) { + /// @solidity memory-safe-assembly + assembly { + tmpNodeOpId := shr(192, calldataload(add(nodeOpIds.offset, mul(i, 8)))) + i := add(i, 1) + } + if (tmpNodeOpId <= lastNodeOpId) { + revert InvalidExtraDataSortOrder(iter.index); + } + lastNodeOpId = tmpNodeOpId; + } + + // update the last sorting key with the last item's element + iter.lastSortingKey = (sortingKey & ~uint256(0xffffffffffffffff)) | lastNodeOpId; } if (dataOffset > data.length || nodeOpsCount == 0) { From ae2110f68884a95b1bf8f14bcb36a0ef6792581e Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 30 May 2024 21:29:18 +0200 Subject: [PATCH 011/177] fix: bitwise op refactor --- contracts/0.8.9/oracle/AccountingOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 0695712c5..8054b3ec0 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -902,7 +902,7 @@ contract AccountingOracle is BaseOracle { } // update the last sorting key with the last item's element - iter.lastSortingKey = (sortingKey & ~uint256(0xffffffffffffffff)) | lastNodeOpId; + iter.lastSortingKey = ((sortingKey >> 64) << 64) | lastNodeOpId; } if (dataOffset > data.length || nodeOpsCount == 0) { From 55ba2b33e56296faeefaca35290dc5e9592c1184 Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 31 May 2024 21:45:04 +0200 Subject: [PATCH 012/177] test: enforces extra data sorting order --- ...untingOracle.submitReportExtraData.test.ts | 140 +++++++++++++++--- 1 file changed, 119 insertions(+), 21 deletions(-) diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 82f7e4497..88870b07c 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -190,6 +190,17 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { return data; } + async function submitOracleReport({ extraData, reportFields, config }: ConstructOracleReportWithDefaultValuesProps) { + const data = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + extraData, + reportFields, + config, + }); + + await oracleMemberSubmitReportData(data.report); + return data; + } + async function submitReportHash({ extraData, reportFields }: ReportDataArgs = {}) { const data = await constructOracleReportWithSingeExtraDataTransactionForCurrentRefSlot({ extraData, reportFields }); await oracleMemberSubmitReportHash(data.reportFields.refSlot, data.reportHash); @@ -221,12 +232,13 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); it("submit extra data report within two transaction", async () => { + const defaultExtraData = getDefaultExtraData(); + const { report, extraDataChunks, extraDataChunkHashes } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ config: { maxItemsPerChunk: 3 }, }); - const defaultExtraData = getDefaultExtraData(); expect(extraDataChunks.length).to.be.equal(2); await oracleMemberSubmitReportData(report); @@ -270,6 +282,36 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { ); expect(state2.dataHash).to.be.equal(extraDataChunkHashes[1]); }); + + it("submit extra data with multiple items per module splitted on many transactions", async () => { + const validExtraDataForLargeReport = { + stuckKeys: [ + { moduleId: 1, nodeOpIds: [1, 2, 3], keysCounts: [1, 2, 3] }, + { moduleId: 1, nodeOpIds: [4, 5, 6], keysCounts: [4, 5, 6] }, + { moduleId: 2, nodeOpIds: [0], keysCounts: [1] }, + ], + exitedKeys: [ + { moduleId: 1, nodeOpIds: [1, 2, 3], keysCounts: [1, 2, 3] }, + { moduleId: 1, nodeOpIds: [4, 5], keysCounts: [4, 5] }, + { moduleId: 2, nodeOpIds: [0], keysCounts: [1] }, + ], + }; + + // Submit report with extra data splitted on many transactions + // It does not matter on how many transactions we will split extra data + for (let i = 1; i <= 6; i++) { + await consensus.advanceTimeToNextFrameStart(); + + const { extraDataChunks } = await submitOracleReport({ + extraData: validExtraDataForLargeReport, + config: { maxItemsPerChunk: i }, + }); + + for (let j = 0; j < extraDataChunks.length; j++) { + await oracleMemberSubmitExtraData(extraDataChunks[j]); + } + } + }); }); context("enforces the deadline", () => { @@ -589,28 +631,86 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { }); }); - context("enforces module ids sorting order", () => { - it("should revert if incorrect extra data list stuckKeys moduleId", async () => { - const extraDataDefault = getDefaultExtraData(); - const invalidExtraData = { - ...extraDataDefault, - stuckKeys: [ - ...extraDataDefault.stuckKeys, - { moduleId: 4, nodeOpIds: [1], keysCounts: [2] }, - { moduleId: 4, nodeOpIds: [1], keysCounts: [2] }, - ], - }; - + context("enforces extra data sorting order", () => { + /* + Extra data report sorted by composite key which is calculated as: + - item type (stuck = 1 or exited = 2 validators) + - module id + - node operator id + */ + async function extraDataSubmitShouldRevertWithSortOrderError( + incorrectlySortedExtraData: ExtraData, + invalidItemIndex: number, + ) { await consensus.advanceTimeToNextFrameStart(); - const { reportFields, extraDataList } = await submitReportHash({ extraData: invalidExtraData }); - await oracle.connect(member1).submitReportData(reportFields, oracleVersion); - await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) + const { extraDataChunks } = await submitOracleReport({ extraData: incorrectlySortedExtraData }); + + await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) .to.be.revertedWithCustomError(oracle, "InvalidExtraDataSortOrder") - .withArgs(4); + .withArgs(invalidItemIndex); + } + + it("should revert if stuckKeys processed after exitedKeys", async () => { + const invalidExtraDataItemsArray = [ + { type: EXTRA_DATA_TYPE_EXITED_VALIDATORS, moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, + { type: EXTRA_DATA_TYPE_EXITED_VALIDATORS, moduleId: 2, nodeOpIds: [0], keysCounts: [1] }, + { type: EXTRA_DATA_TYPE_STUCK_VALIDATORS, moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, + ]; + + await extraDataSubmitShouldRevertWithSortOrderError(invalidExtraDataItemsArray, 2); + }); + + it("should revert if modules items not sorted by module id", async () => { + const invalidStuckKeysItemsOrder = [ + { type: EXTRA_DATA_TYPE_STUCK_VALIDATORS, moduleId: 2, nodeOpIds: [0], keysCounts: [1] }, + { type: EXTRA_DATA_TYPE_STUCK_VALIDATORS, moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, + ]; + + await extraDataSubmitShouldRevertWithSortOrderError(invalidStuckKeysItemsOrder, 1); + + const invalidExitedKeysItemsOrder = [ + { type: EXTRA_DATA_TYPE_EXITED_VALIDATORS, moduleId: 2, nodeOpIds: [0], keysCounts: [1] }, + { type: EXTRA_DATA_TYPE_EXITED_VALIDATORS, moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, + ]; + + await extraDataSubmitShouldRevertWithSortOrderError(invalidExitedKeysItemsOrder, 1); + }); + + it("should revert if node operators not sorted", async () => { + async function check(nodeOpIds: number[]) { + function data(type: bigint) { + return [{ type, moduleId: 1, nodeOpIds, keysCounts: Array(nodeOpIds.length).fill(1) }]; + } + + await extraDataSubmitShouldRevertWithSortOrderError(data(EXTRA_DATA_TYPE_STUCK_VALIDATORS), 0); + await extraDataSubmitShouldRevertWithSortOrderError(data(EXTRA_DATA_TYPE_EXITED_VALIDATORS), 0); + } + + // operator id duplicated in item + await check([1, 2, 2, 3]); + + // operator id not sorted + await check([1, 3, 2]); }); - it("second transaction should revert if extra data not sorted", async () => { + it("should revert if module node operators not sorted within multiple items", async () => { + const invalidStuckKeysItemsOrder = [ + { type: EXTRA_DATA_TYPE_STUCK_VALIDATORS, moduleId: 1, nodeOpIds: [1, 2, 3], keysCounts: [1, 2, 3] }, + { type: EXTRA_DATA_TYPE_STUCK_VALIDATORS, moduleId: 1, nodeOpIds: [3, 4, 5], keysCounts: [1, 2, 3] }, + ]; + + await extraDataSubmitShouldRevertWithSortOrderError(invalidStuckKeysItemsOrder, 1); + + const invalidExitedKeysItemsOrder = [ + { type: EXTRA_DATA_TYPE_EXITED_VALIDATORS, moduleId: 1, nodeOpIds: [1, 2, 3], keysCounts: [1, 2, 3] }, + { type: EXTRA_DATA_TYPE_EXITED_VALIDATORS, moduleId: 1, nodeOpIds: [3, 4, 5], keysCounts: [1, 2, 3] }, + ]; + + await extraDataSubmitShouldRevertWithSortOrderError(invalidExitedKeysItemsOrder, 1); + }); + + it("should revert if second transaction extra data not sorted", async () => { const invalidExtraData = { stuckKeys: [ { moduleId: 1, nodeOpIds: [0], keysCounts: [1] }, @@ -622,12 +722,10 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { exitedKeys: [{ moduleId: 2, nodeOpIds: [1, 2], keysCounts: [1, 3] }], }; - const { report, extraDataChunks } = await constructOracleReportForCurrentFrameAndSubmitReportHash({ + const { extraDataChunks } = await submitOracleReport({ extraData: invalidExtraData, config: { maxItemsPerChunk: 2 }, }); - - await oracleMemberSubmitReportData(report); await oracleMemberSubmitExtraData(extraDataChunks[0]); await expect(oracleMemberSubmitExtraData(extraDataChunks[1])) From 53ceb24245c94287c0bce7dd595013f2479bea17 Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 31 May 2024 22:00:08 +0200 Subject: [PATCH 013/177] feat: change variable name --- contracts/0.8.9/oracle/AccountingOracle.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 8054b3ec0..07dd4c963 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -828,7 +828,7 @@ contract AccountingOracle is BaseOracle { assert(iter.dataOffset > dataOffset); dataOffset = iter.dataOffset; unchecked { - // oberflow is not possible here + // overflow is not possible here ++itemsCount; } } @@ -846,7 +846,7 @@ contract AccountingOracle is BaseOracle { uint256 dataOffset = iter.dataOffset; uint256 moduleId; uint256 nodeOpsCount; - uint256 lastNodeOpId; + uint256 nodeOpId; bytes calldata nodeOpIds; bytes calldata valuesCounts; @@ -867,7 +867,7 @@ contract AccountingOracle is BaseOracle { nodeOpIds.offset := add(data.offset, add(dataOffset, 11)) nodeOpIds.length := mul(nodeOpsCount, 8) // read the 1st node operator id for checking the sorting order later - lastNodeOpId := shr(192, calldataload(nodeOpIds.offset)) + nodeOpId := shr(192, calldataload(nodeOpIds.offset)) valuesCounts.offset := add(nodeOpIds.offset, nodeOpIds.length) valuesCounts.length := mul(nodeOpsCount, 16) dataOffset := sub(add(valuesCounts.offset, valuesCounts.length), data.offset) @@ -881,8 +881,8 @@ contract AccountingOracle is BaseOracle { // firstly, check the sorting order between the 1st item's element and the last one of the previous item // | 2 bytes | 19 bytes | 3 bytes | 8 bytes | - // | itemType | 00000000 | moduleId | lastNodeOpId | - uint256 sortingKey = (iter.itemType << 240) | (moduleId << 64) | lastNodeOpId; + // | itemType | 00000000 | moduleId | nodeOpId | + uint256 sortingKey = (iter.itemType << 240) | (moduleId << 64) | nodeOpId; if (sortingKey <= iter.lastSortingKey) { revert InvalidExtraDataSortOrder(iter.index); } @@ -895,14 +895,14 @@ contract AccountingOracle is BaseOracle { tmpNodeOpId := shr(192, calldataload(add(nodeOpIds.offset, mul(i, 8)))) i := add(i, 1) } - if (tmpNodeOpId <= lastNodeOpId) { + if (tmpNodeOpId <= nodeOpId) { revert InvalidExtraDataSortOrder(iter.index); } - lastNodeOpId = tmpNodeOpId; + nodeOpId = tmpNodeOpId; } // update the last sorting key with the last item's element - iter.lastSortingKey = ((sortingKey >> 64) << 64) | lastNodeOpId; + iter.lastSortingKey = ((sortingKey >> 64) << 64) | nodeOpId; } if (dataOffset > data.length || nodeOpsCount == 0) { From a5b55713c3893d7d7b1cff02840d690231cc7d97 Mon Sep 17 00:00:00 2001 From: maxim Date: Mon, 3 Jun 2024 18:23:46 +0200 Subject: [PATCH 014/177] feat: node operator registry contract initialisation Update contract version and reward distribution state. --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 83c446f21..4565fbeb8 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -69,8 +69,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Distributed // Reward distributed among operators } - RewardDistributionState public rewardDistributionState = RewardDistributionState.Distributed; - // // ACL // @@ -210,12 +208,20 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { mapping(uint256 => NodeOperator) internal _nodeOperators; NodeOperatorSummary internal _nodeOperatorSummary; + /// @dev Current reward distribution state, reward distribution bot monitor this state + /// and distribute rewards (call distributeReward methoid) among operators when it's `ReadyForDistribution` + RewardDistributionState public rewardDistributionState; + // // METHODS // function initialize(address _locator, bytes32 _type, uint256 _stuckPenaltyDelay) public onlyInit { // Initializations for v1 --> v2 _initialize_v2(_locator, _type, _stuckPenaltyDelay); + + // Initializations for v2 --> v3 + _initialize_v3(); + initialized(); } @@ -290,6 +296,17 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { emit StakingModuleTypeSet(_type); } + function finalizeUpgrade_v3() external { + require(hasInitialized(), "CONTRACT_NOT_INITIALIZED"); + _checkContractVersion(2); + _initialize_v3(); + } + + function _initialize_v3() internal { + _setContractVersion(3); + rewardDistributionState = RewardDistributionState.Distributed; + } + /// @notice Add node operator named `name` with reward address `rewardAddress` and staking limit = 0 validators /// @param _name Human-readable name /// @param _rewardAddress Ethereum 1 address which receives stETH rewards for this operator @@ -426,10 +443,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { function onRewardsMinted(uint256 /* _totalShares */) external { _auth(STAKING_ROUTER_ROLE); _updateRewardDistributionState(RewardDistributionState.TransferredToModule); - - // since we're pushing rewards to operators after exited validators counts are - // updated (as opposed to pulling by node ops), we don't need any handling here - // see `onExitedAndStuckValidatorsCountsUpdated()` } function _checkReportPayload(uint256 idsLength, uint256 countsLength) internal pure returns (uint256 count) { @@ -523,9 +536,37 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _updateRefundValidatorsKeysCount(_nodeOperatorId, _refundedValidatorsCount); } - // Function to distribute rewards + /// @notice Permissionless method for distributing all accumulated module rewards among node operators + /// based on the latest accounting report. + /// + /// @dev Rewards can be distributed after node operators' statistics are updated + /// until the next reward is transferred to the module during the next oracle frame. + /// + /// ===================================== Start report frame 1 ===================================== + /// + /// 1. Oracle first phase: Reach hash consensus. + /// 2. Oracle second phase: Module receives rewards. + /// 3. Oracle third phase: Operator statistics are updated. + /// + /// ... Reward can be distributed ... + /// + /// ===================================== Start report frame 2 ===================================== + /// + /// ... Reward can be distributed ... + /// (if not distributed yet) + /// + /// 1. Oracle first phase: Reach hash consensus. + /// 2. Oracle second phase: Module receives rewards. + /// + /// ... Reward CANNOT be distributed ... + /// + /// 3. Oracle third phase: Operator statistics are updated. + /// + /// ... Reward can be distributed ... + /// + /// ===================================== Start report frame 3 ===================================== function distributeReward() external { - require(rewardDistributionState == RewardDistributionState.ReadyForDistribution, "Reward distribution is not ready"); + require(rewardDistributionState == RewardDistributionState.ReadyForDistribution, "DISTRIBUTION_NOT_READY"); _updateRewardDistributionState(RewardDistributionState.Distributed); _distributeRewards(); } From 06090697be2bc231bd7b5ccba0d0c5ab37805a18 Mon Sep 17 00:00:00 2001 From: maxim Date: Mon, 3 Jun 2024 18:38:05 +0200 Subject: [PATCH 015/177] feat: update deployment scripts for accounting sanity checker --- scripts/scratch/deployed-testnet-defaults.json | 3 ++- scripts/scratch/steps/09-deploy-non-aragon-contracts.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/scratch/deployed-testnet-defaults.json b/scripts/scratch/deployed-testnet-defaults.json index 5717913dd..4c0ec821c 100644 --- a/scripts/scratch/deployed-testnet-defaults.json +++ b/scripts/scratch/deployed-testnet-defaults.json @@ -102,7 +102,8 @@ }, "oracleReportSanityChecker": { "deployParameters": { - "churnValidatorsPerDayLimit": 1500, + "exitedValidatorsPerDayLimit": 1500, + "appearedValidatorsPerDayLimit": 1500, "oneOffCLBalanceDecreaseBPLimit": 500, "annualBalanceIncreaseBPLimit": 1000, "simulatedShareRateDeviationBPLimit": 250, diff --git a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts index fae746433..cfa4cd9b9 100644 --- a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts +++ b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts @@ -76,7 +76,8 @@ async function main() { locator.address, admin, [ - sanityChecks.churnValidatorsPerDayLimit, + sanityChecks.exitedValidatorsPerDayLimit, + sanityChecks.appearedValidatorsPerDayLimit, sanityChecks.oneOffCLBalanceDecreaseBPLimit, sanityChecks.annualBalanceIncreaseBPLimit, sanityChecks.simulatedShareRateDeviationBPLimit, @@ -86,7 +87,7 @@ async function main() { sanityChecks.requestTimestampMargin, sanityChecks.maxPositiveTokenRebase, ], - [[], [], [], [], [], [], [], [], [], []], + [[], [], [], [], [], [], [], [], [], [], []], ]; const oracleReportSanityChecker = await deployWithoutProxy( Sk.oracleReportSanityChecker, From 334ba92fabc5059e8a55adee8de76dd17f4d91e1 Mon Sep 17 00:00:00 2001 From: Maksim Kuraian Date: Mon, 3 Jun 2024 22:10:23 +0200 Subject: [PATCH 016/177] Update contracts/0.8.9/oracle/AccountingOracle.sol Co-authored-by: avsetsin --- contracts/0.8.9/oracle/AccountingOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 07dd4c963..cf584ad95 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -774,7 +774,7 @@ contract AccountingOracle is BaseOracle { procState.dataHash = dataHash; procState.itemsProcessed = uint64(itemsProcessed); procState.lastSortingKey = iter.lastSortingKey; - _storageExtraDataProcessingState().value = procState; + _storageExtraDataProcessingState().value = procState; } emit ExtraDataSubmitted(procState.refSlot, procState.itemsProcessed, procState.itemsCount); From 093722dff4d534876c02eb97465308df62379c18 Mon Sep 17 00:00:00 2001 From: maxim Date: Mon, 3 Jun 2024 23:35:14 +0200 Subject: [PATCH 017/177] feat: update contract and consensus version in accounting oracle --- contracts/0.8.9/oracle/AccountingOracle.sol | 11 ++++++++++- lib/constants.ts | 2 +- test/0.8.9/oracle/baseOracle.accessControl.test.ts | 4 ++-- test/0.8.9/oracle/baseOracle.consensus.test.ts | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index cf584ad95..8bed8e8f9 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -169,6 +169,8 @@ contract AccountingOracle is BaseOracle { uint256 lastProcessingRefSlot = _checkOracleMigration(LEGACY_ORACLE, consensusContract); _initialize(admin, consensusContract, consensusVersion, lastProcessingRefSlot); + + _updateContractVersion(2); } function initializeWithoutMigration( @@ -180,6 +182,13 @@ contract AccountingOracle is BaseOracle { if (admin == address(0)) revert AdminCannotBeZero(); _initialize(admin, consensusContract, consensusVersion, lastProcessingRefSlot); + + _updateContractVersion(2); + } + + function finalizeUpgrade_v2(uint256 consensusVersion) external { + _updateContractVersion(2); + _setConsensusVersion(consensusVersion); } /// @@ -774,7 +783,7 @@ contract AccountingOracle is BaseOracle { procState.dataHash = dataHash; procState.itemsProcessed = uint64(itemsProcessed); procState.lastSortingKey = iter.lastSortingKey; - _storageExtraDataProcessingState().value = procState; + _storageExtraDataProcessingState().value = procState; } emit ExtraDataSubmitted(procState.refSlot, procState.itemsProcessed, procState.itemsCount); diff --git a/lib/constants.ts b/lib/constants.ts index 606c6122b..9d7de2dff 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -26,7 +26,7 @@ export const EPOCHS_PER_FRAME = 225n; // one day; // Oracle report related export const GENESIS_TIME = 100n; export const SLOTS_PER_EPOCH = 32n; -export const CONSENSUS_VERSION = 1n; +export const CONSENSUS_VERSION = 2n; export const INITIAL_EPOCH = 1n; export const INITIAL_FAST_LANE_LENGTH_SLOTS = 0n; diff --git a/test/0.8.9/oracle/baseOracle.accessControl.test.ts b/test/0.8.9/oracle/baseOracle.accessControl.test.ts index 80cbd5bdd..d4732f155 100644 --- a/test/0.8.9/oracle/baseOracle.accessControl.test.ts +++ b/test/0.8.9/oracle/baseOracle.accessControl.test.ts @@ -91,9 +91,9 @@ describe("BaseOracle:accessControl", () => { const role = await oracle.MANAGE_CONSENSUS_VERSION_ROLE(); await oracle.grantRole(role, manager); - await oracle.connect(manager).setConsensusVersion(2); + await oracle.connect(manager).setConsensusVersion(3); - expect(await oracle.getConsensusVersion()).to.be.equal(2); + expect(await oracle.getConsensusVersion()).to.be.equal(3); }); }); diff --git a/test/0.8.9/oracle/baseOracle.consensus.test.ts b/test/0.8.9/oracle/baseOracle.consensus.test.ts index d3b35b00d..cf10a19e3 100644 --- a/test/0.8.9/oracle/baseOracle.consensus.test.ts +++ b/test/0.8.9/oracle/baseOracle.consensus.test.ts @@ -140,13 +140,13 @@ describe("BaseOracle:consensus", () => { }); it("Updates consensus version", async () => { - await expect(baseOracle.setConsensusVersion(2)) + await expect(baseOracle.setConsensusVersion(3)) .to.emit(baseOracle, "ConsensusVersionSet") - .withArgs(2, CONSENSUS_VERSION); + .withArgs(3, CONSENSUS_VERSION); const versionInState = await baseOracle.getConsensusVersion(); - expect(versionInState).to.equal(2); + expect(versionInState).to.equal(3); }); }); From 21fb9bef6ea54b14b794460113eaeb0bb7badf27 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 09:44:42 +0200 Subject: [PATCH 018/177] test: submit extra data adjust node operators ids --- .../oracle/accountingOracle.submitReportExtraData.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 88870b07c..ed89f774d 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -286,12 +286,12 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { it("submit extra data with multiple items per module splitted on many transactions", async () => { const validExtraDataForLargeReport = { stuckKeys: [ - { moduleId: 1, nodeOpIds: [1, 2, 3], keysCounts: [1, 2, 3] }, - { moduleId: 1, nodeOpIds: [4, 5, 6], keysCounts: [4, 5, 6] }, + { moduleId: 1, nodeOpIds: [0, 1, 2], keysCounts: [1, 2, 3] }, + { moduleId: 1, nodeOpIds: [3, 4, 5], keysCounts: [4, 5, 6] }, { moduleId: 2, nodeOpIds: [0], keysCounts: [1] }, ], exitedKeys: [ - { moduleId: 1, nodeOpIds: [1, 2, 3], keysCounts: [1, 2, 3] }, + { moduleId: 1, nodeOpIds: [0, 1, 2, 3], keysCounts: [1, 2, 3, 4] }, { moduleId: 1, nodeOpIds: [4, 5], keysCounts: [4, 5] }, { moduleId: 2, nodeOpIds: [0], keysCounts: [1] }, ], From c8f9a529321190e79b0bec4188280e2824f7ef80 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 10:03:08 +0200 Subject: [PATCH 019/177] feat: update the function description --- .../0.8.9/sanity_checks/OracleReportSanityChecker.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 36b4ea42f..13cbead87 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -485,10 +485,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { } } - /// @notice Check number of node operators reported per extra data item in accounting oracle + /// @notice check the number of node operators reported per extra data item in the accounting oracle report. /// @param _itemIndex Index of item in extra data /// @param _nodeOperatorsCount Number of validator exit requests supplied per oracle report - /// @dev Checks against the same limit as used in checkExtraDataItemsCountPerTransaction function checkNodeOperatorsPerExtraDataItemCount(uint256 _itemIndex, uint256 _nodeOperatorsCount) external view @@ -499,8 +498,8 @@ contract OracleReportSanityChecker is AccessControlEnumerable { } } - /// @notice Check max accounting extra data list items count per transaction - /// @param _extraDataListItemsCount Number of items per single transaction in oracle report + /// @notice Check the number of extra data list items per transaction in the accounting oracle report. + /// @param _extraDataListItemsCount Number of items per single transaction in the accounting oracle report function checkExtraDataItemsCountPerTransaction(uint256 _extraDataListItemsCount) external view From 85a70fb92aac1cdfe8114139171c99007e892875 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 10:06:52 +0200 Subject: [PATCH 020/177] feat: accounting oracle contract improve formating --- contracts/0.8.9/oracle/AccountingOracle.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 8bed8e8f9..ed20cf447 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -599,7 +599,7 @@ contract AccountingOracle is BaseOracle { if (data.extraDataItemsCount == 0) { revert ExtraDataItemsCountCannotBeZeroForNonEmptyData(); } - if (data.extraDataHash == ZERO_HASH) { + if (data.extraDataHash == ZERO_HASH) { revert ExtraDataHashCannotBeZeroForNonEmptyData(); } } @@ -838,7 +838,7 @@ contract AccountingOracle is BaseOracle { dataOffset = iter.dataOffset; unchecked { // overflow is not possible here - ++itemsCount; + ++itemsCount; } } @@ -889,7 +889,7 @@ contract AccountingOracle is BaseOracle { unchecked { // firstly, check the sorting order between the 1st item's element and the last one of the previous item - // | 2 bytes | 19 bytes | 3 bytes | 8 bytes | + // | 2 bytes | 19 bytes | 3 bytes | 8 bytes | // | itemType | 00000000 | moduleId | nodeOpId | uint256 sortingKey = (iter.itemType << 240) | (moduleId << 64) | nodeOpId; if (sortingKey <= iter.lastSortingKey) { From ef9ddf50a6fcd103c6503bb9a8d40b45f473ccd9 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 11:27:57 +0200 Subject: [PATCH 021/177] feat: use unstructured storage pattern for reward distribution state --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 4565fbeb8..7b30d58de 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -161,6 +161,9 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { // bytes32 internal constant STUCK_PENALTY_DELAY_POSITION = keccak256("lido.NodeOperatorsRegistry.stuckPenaltyDelay"); bytes32 internal constant STUCK_PENALTY_DELAY_POSITION = 0x8e3a1f3826a82c1116044b334cae49f3c3d12c3866a1c4b18af461e12e58a18e; + // bytes32 internal constant REWARD_DISTRIBUTION_STATE = keccak256("lido.NodeOperatorsRegistry.rewardDistributionState"); + bytes32 internal constant REWARD_DISTRIBUTION_STATE = 0x4ddbb0dcdc5f7692e494c15a7fca1f9eb65f31da0b5ce1c3381f6a1a1fd579b6; + // // DATA TYPES // @@ -208,10 +211,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { mapping(uint256 => NodeOperator) internal _nodeOperators; NodeOperatorSummary internal _nodeOperatorSummary; - /// @dev Current reward distribution state, reward distribution bot monitor this state - /// and distribute rewards (call distributeReward methoid) among operators when it's `ReadyForDistribution` - RewardDistributionState public rewardDistributionState; - // // METHODS // @@ -304,7 +303,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { function _initialize_v3() internal { _setContractVersion(3); - rewardDistributionState = RewardDistributionState.Distributed; + _updateRewardDistributionState(RewardDistributionState.Distributed); } /// @notice Add node operator named `name` with reward address `rewardAddress` and staking limit = 0 validators @@ -566,7 +565,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// /// ===================================== Start report frame 3 ===================================== function distributeReward() external { - require(rewardDistributionState == RewardDistributionState.ReadyForDistribution, "DISTRIBUTION_NOT_READY"); + require(getRewardDistributionState() == RewardDistributionState.ReadyForDistribution, "DISTRIBUTION_NOT_READY"); _updateRewardDistributionState(RewardDistributionState.Distributed); _distributeRewards(); } @@ -1412,6 +1411,18 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _setStuckPenaltyDelay(_delay); } + /// @dev Current reward distribution state, reward distribution bot monitor this state + /// and distribute rewards (call distributeReward method) among operators when it's `ReadyForDistribution` + function getRewardDistributionState() public view returns (RewardDistributionState) { + uint256 state = REWARD_DISTRIBUTION_STATE.getStorageUint256(); + return RewardDistributionState(state); + } + + function _updateRewardDistributionState(RewardDistributionState _state) internal { + REWARD_DISTRIBUTION_STATE.setStorageUint256(uint256(_state)); + emit RewardDistributionStateChanged(_state); + } + /// @dev set new stuck penalty delay, duration in sec function _setStuckPenaltyDelay(uint256 _delay) internal { _requireValidRange(_delay <= MAX_STUCK_PENALTY_DELAY); @@ -1507,9 +1518,4 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { function _onlyNonZeroAddress(address _a) internal pure { require(_a != address(0), "ZERO_ADDRESS"); } - - function _updateRewardDistributionState(RewardDistributionState _state) internal { - rewardDistributionState = _state; - emit RewardDistributionStateChanged(_state); - } } From 0200458943f19b94650514e5a086dd50bebc10d4 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 12:05:10 +0200 Subject: [PATCH 022/177] feat: reshape limit list structure new parameter placed at the end of struct to preserve data order compatibility on read --- .../sanity_checks/OracleReportSanityChecker.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 13cbead87..9980d8dd4 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -42,12 +42,6 @@ struct LimitsList { /// @dev Must fit into uint16 (<= 65_535) uint256 exitedValidatorsPerDayLimit; - /// @notice The max possible number of validators that might be reported as `appeared` - /// per single day, limited by the max daily deposits via DepositSecurityModule in practice - /// isn't limited by a consensus layer (because `appeared` includes `pending`, i.e., not `activated` yet) - /// @dev Must fit into uint16 (<= 65_535) - uint256 appearedValidatorsPerDayLimit; - /// @dev Represented in the Basis Points (100% == 10_000) uint256 oneOffCLBalanceDecreaseBPLimit; @@ -79,6 +73,12 @@ struct LimitsList { /// @notice The positive token rebase allowed per single LidoOracle report /// @dev uses 1e9 precision, e.g.: 1e6 - 0.1%; 1e9 - 100%, see `setMaxPositiveTokenRebase()` uint256 maxPositiveTokenRebase; + + /// @notice The max possible number of validators that might be reported as `appeared` + /// per single day, limited by the max daily deposits via DepositSecurityModule in practice + /// isn't limited by a consensus layer (because `appeared` includes `pending`, i.e., not `activated` yet) + /// @dev Must fit into uint16 (<= 65_535) + uint256 appearedValidatorsPerDayLimit; } /// @dev The packed version of the LimitsList struct to be effectively persisted in storage From 5f9c9c08fdfc63fe3d919d8d036f5bbc2e196ede Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 12:08:41 +0200 Subject: [PATCH 023/177] feat: update comment on get reward distribution state method in nor contract --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 7b30d58de..ee73fa688 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -1411,8 +1411,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _setStuckPenaltyDelay(_delay); } - /// @dev Current reward distribution state, reward distribution bot monitor this state - /// and distribute rewards (call distributeReward method) among operators when it's `ReadyForDistribution` + /// @dev Get the current reward distribution state, anyone can monitor this state + /// and distribute reward (call distributeReward method) among operators when it's `ReadyForDistribution` function getRewardDistributionState() public view returns (RewardDistributionState) { uint256 state = REWARD_DISTRIBUTION_STATE.getStorageUint256(); return RewardDistributionState(state); From 616e3ec0e064a24d69732cfa3bb02e9fbb48ea96 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 12:17:26 +0200 Subject: [PATCH 024/177] test: fix sanity checker tests --- test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index 5e7af7735..e62ec5764 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -23,7 +23,6 @@ describe("OracleReportSanityChecker.sol", () => { const defaultLimitsList = { exitedValidatorsPerDayLimit: 55, - appearedValidatorsPerDayLimit: 100, oneOffCLBalanceDecreaseBPLimit: 5_00, // 5% annualBalanceIncreaseBPLimit: 10_00, // 10% simulatedShareRateDeviationBPLimit: 2_50, // 2.5% @@ -32,6 +31,7 @@ describe("OracleReportSanityChecker.sol", () => { maxNodeOperatorsPerExtraDataItemCount: 16, requestTimestampMargin: 128, maxPositiveTokenRebase: 5_000_000, // 0.05% + appearedValidatorsPerDayLimit: 100, }; const correctLidoOracleReport = { From 7779be8a6dfea3e17a4d8a466d4dba098c9e63f2 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 8 Apr 2024 13:44:15 +0200 Subject: [PATCH 025/177] fix: make lib as external contract (reduce bytecode) --- contracts/common/lib/MinFirstAllocationStrategy.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/common/lib/MinFirstAllocationStrategy.sol b/contracts/common/lib/MinFirstAllocationStrategy.sol index 22e30fb04..fa3d67e70 100644 --- a/contracts/common/lib/MinFirstAllocationStrategy.sol +++ b/contracts/common/lib/MinFirstAllocationStrategy.sol @@ -31,7 +31,7 @@ library MinFirstAllocationStrategy { uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize - ) internal pure returns (uint256 allocated) { + ) public pure returns (uint256 allocated) { uint256 allocatedToBestCandidate = 0; while (allocated < allocationSize) { allocatedToBestCandidate = allocateToBestCandidate(buckets, capacities, allocationSize - allocated); @@ -63,7 +63,7 @@ library MinFirstAllocationStrategy { uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize - ) internal pure returns (uint256 allocated) { + ) public pure returns (uint256 allocated) { uint256 bestCandidateIndex = buckets.length; uint256 bestCandidateAllocation = MAX_UINT256; uint256 bestCandidatesCount = 0; From 9c5f9cf014a3957a85e7163bff9ec9b719e12d3a Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 8 Apr 2024 13:46:09 +0200 Subject: [PATCH 026/177] fix: add priorityExitShareThreshold param --- contracts/0.8.9/StakingRouter.sol | 55 ++++++++++++++++++------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index adcb1d6bb..49c696e90 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -20,7 +20,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @dev events event StakingModuleAdded(uint256 indexed stakingModuleId, address stakingModule, string name, address createdBy); - event StakingModuleTargetShareSet(uint256 indexed stakingModuleId, uint256 targetShare, address setBy); + event StakingModuleShareLimitSet(uint256 indexed stakingModuleId, uint256 stakeShareLimit, uint256 priorityExitShareThreshold, address setBy); event StakingModuleFeesSet(uint256 indexed stakingModuleId, uint256 stakingModuleFee, uint256 treasuryFee, address setBy); event StakingModuleStatusSet(uint256 indexed stakingModuleId, StakingModuleStatus status, address setBy); event StakingModuleExitedValidatorsIncompleteReporting(uint256 indexed stakingModuleId, uint256 unreportedExitedValidatorsCount); @@ -59,6 +59,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error StakingModuleAddressExists(); error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength); error UnrecoverableModuleError(); + error InvalidPriorityExitShareThreshold(); enum StakingModuleStatus { Active, // deposits and rewards allowed @@ -75,10 +76,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint16 stakingModuleFee; /// @notice part of the fee taken from staking rewards that goes to the treasury uint16 treasuryFee; - /// @notice target percent of total validators in protocol, in BP - uint16 targetShare; + /// @notice maximum stake share that can be allocated to a module, in BP + uint16 stakeShareLimit; // formerly known as `targetShare` /// @notice staking module status if staking module can not accept the deposits or can participate in further reward distribution uint8 status; + /// @notice module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP + uint16 priorityExitShareThreshold; /// @notice name of staking module string name; /// @notice block.timestamp of the last deposit of the staking module @@ -96,7 +99,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint24 stakingModuleId; uint16 stakingModuleFee; uint16 treasuryFee; - uint16 targetShare; + uint16 stakeShareLimit; StakingModuleStatus status; uint256 activeValidatorsCount; uint256 availableValidatorsCount; @@ -168,25 +171,25 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * @notice register a new staking module * @param _name name of staking module * @param _stakingModuleAddress address of staking module - * @param _targetShare target total stake share + * @param _stakeShareLimit maximum share that can be allocated to a module + * @param _priorityExitShareThreshold module's proirity exit share threshold * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards * @param _treasuryFee treasury fee */ function addStakingModule( string calldata _name, address _stakingModuleAddress, - uint256 _targetShare, + uint256 _stakeShareLimit, + uint256 _priorityExitShareThreshold, uint256 _stakingModuleFee, uint256 _treasuryFee ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { - if (_targetShare > TOTAL_BASIS_POINTS) - revert ValueOver100Percent("_targetShare"); - if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) - revert ValueOver100Percent("_stakingModuleFee + _treasuryFee"); - if (_stakingModuleAddress == address(0)) - revert ZeroAddress("_stakingModuleAddress"); - if (bytes(_name).length == 0 || bytes(_name).length > MAX_STAKING_MODULE_NAME_LENGTH) - revert StakingModuleWrongName(); + if (_stakeShareLimit > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakeShareLimit"); + if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_priorityExitShareThreshold"); + if (_stakeShareLimit > _priorityExitShareThreshold) revert InvalidPriorityExitShareThreshold(); + if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakingModuleFee + _treasuryFee"); + if (_stakingModuleAddress == address(0)) revert ZeroAddress("_stakingModuleAddress"); + if (bytes(_name).length == 0 || bytes(_name).length > MAX_STAKING_MODULE_NAME_LENGTH) revert StakingModuleWrongName(); uint256 newStakingModuleIndex = getStakingModulesCount(); @@ -207,7 +210,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version newStakingModule.id = newStakingModuleId; newStakingModule.name = _name; newStakingModule.stakingModuleAddress = _stakingModuleAddress; - newStakingModule.targetShare = uint16(_targetShare); + newStakingModule.stakeShareLimit = uint16(_stakeShareLimit); + newStakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThreshold); newStakingModule.stakingModuleFee = uint16(_stakingModuleFee); newStakingModule.treasuryFee = uint16(_treasuryFee); /// @dev since `enum` is `uint8` by nature, so the `status` is stored as `uint8` to avoid @@ -228,33 +232,38 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version STAKING_MODULES_COUNT_POSITION.setStorageUint256(newStakingModuleIndex + 1); emit StakingModuleAdded(newStakingModuleId, _stakingModuleAddress, _name, msg.sender); - emit StakingModuleTargetShareSet(newStakingModuleId, _targetShare, msg.sender); + emit StakingModuleShareLimitSet(newStakingModuleId, _stakeShareLimit, _priorityExitShareThreshold, msg.sender); emit StakingModuleFeesSet(newStakingModuleId, _stakingModuleFee, _treasuryFee, msg.sender); } /** * @notice Update staking module params * @param _stakingModuleId staking module id - * @param _targetShare target total stake share + * @param _stakeShareLimit target total stake share + * @param _priorityExitShareThreshold module's proirity exit share threshold * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards * @param _treasuryFee treasury fee */ function updateStakingModule( uint256 _stakingModuleId, - uint256 _targetShare, + uint256 _stakeShareLimit, + uint256 _priorityExitShareThreshold, uint256 _stakingModuleFee, uint256 _treasuryFee ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { - if (_targetShare > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_targetShare"); + if (_stakeShareLimit > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakeShareLimit"); + if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_priorityExitShareThreshold"); + if (_stakeShareLimit > _priorityExitShareThreshold) revert InvalidPriorityExitShareThreshold(); if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakingModuleFee + _treasuryFee"); StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); - stakingModule.targetShare = uint16(_targetShare); + stakingModule.stakeShareLimit = uint16(_stakeShareLimit); + stakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThreshold); stakingModule.treasuryFee = uint16(_treasuryFee); stakingModule.stakingModuleFee = uint16(_stakingModuleFee); - emit StakingModuleTargetShareSet(_stakingModuleId, _targetShare, msg.sender); + emit StakingModuleShareLimitSet(_stakingModuleId, _stakeShareLimit, _priorityExitShareThreshold, msg.sender); emit StakingModuleFeesSet(_stakingModuleId, _stakingModuleFee, _treasuryFee, msg.sender); } @@ -1218,7 +1227,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version cacheItem.stakingModuleId = stakingModuleData.id; cacheItem.stakingModuleFee = stakingModuleData.stakingModuleFee; cacheItem.treasuryFee = stakingModuleData.treasuryFee; - cacheItem.targetShare = stakingModuleData.targetShare; + cacheItem.stakeShareLimit = stakingModuleData.stakeShareLimit; cacheItem.status = StakingModuleStatus(stakingModuleData.status); ( @@ -1264,7 +1273,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version for (uint256 i; i < stakingModulesCount; ) { allocations[i] = stakingModulesCache[i].activeValidatorsCount; - targetValidators = (stakingModulesCache[i].targetShare * totalActiveValidators) / TOTAL_BASIS_POINTS; + targetValidators = (stakingModulesCache[i].stakeShareLimit * totalActiveValidators) / TOTAL_BASIS_POINTS; capacities[i] = Math256.min(targetValidators, stakingModulesCache[i].activeValidatorsCount + stakingModulesCache[i].availableValidatorsCount); unchecked { ++i; From 63876802b52862d147ece7b7861d6640a50d0353 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 8 Apr 2024 14:51:49 +0200 Subject: [PATCH 027/177] test: fix lib deploy in SR test --- test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts b/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts index f59ac1e2a..6c0e04779 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts @@ -4,7 +4,7 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { OssifiableProxy, StakingRouter, StakingRouter__factory } from "typechain-types"; +import { MinFirstAllocationStrategy, OssifiableProxy, StakingRouter, StakingRouter__factory } from "typechain-types"; import { MAX_UINT256, randomAddress } from "lib"; @@ -12,6 +12,7 @@ describe("StakingRouter:Versioned", () => { let admin: HardhatEthersSigner; let user: HardhatEthersSigner; + let allocLib: MinFirstAllocationStrategy; let impl: StakingRouter; let proxy: OssifiableProxy; let versioned: StakingRouter; @@ -23,7 +24,10 @@ describe("StakingRouter:Versioned", () => { const depositContract = randomAddress(); - impl = await ethers.deployContract("StakingRouter", [depositContract]); + allocLib = await ethers.deployContract("MinFirstAllocationStrategy", []); + impl = await ethers.deployContract("StakingRouter", [depositContract], { + libraries: { MinFirstAllocationStrategy: await allocLib.getAddress() }, + }); proxy = await ethers.deployContract("OssifiableProxy", [await impl.getAddress(), admin.address, new Uint8Array()], { from: admin, From d090d4a2cc833940ac7d067483189398d5380a43 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 15:26:45 +0200 Subject: [PATCH 028/177] feat: targetLimitMode --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 19 ++++++++++--------- contracts/0.8.9/StakingRouter.sol | 14 +++++++------- contracts/0.8.9/interfaces/IStakingModule.sol | 8 ++++---- contracts/0.8.9/test_helpers/ModuleSolo.sol | 4 ++-- .../0.8.9/test_helpers/StakingModuleMock.sol | 14 +++++++------- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index ee73fa688..5252c7616 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -101,8 +101,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint8 internal constant TOTAL_DEPOSITED_KEYS_COUNT_OFFSET = 3; // TargetValidatorsStats - /// @dev Flag enable/disable limiting target active validators count for operator - uint8 internal constant IS_TARGET_LIMIT_ACTIVE_OFFSET = 0; + /// @dev Target limit mode, allows limiting target active validators count for operator, >1 means forced target limit enabled + uint8 internal constant TARGET_LIMIT_MODE_OFFSET = 0; /// @dev relative target active validators limit for operator, set by DAO /// @notice used to check how many keys should go to exit, 0 - means all deposited keys would be exited uint8 internal constant TARGET_VALIDATORS_COUNT_OFFSET = 1; @@ -637,15 +637,15 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// @notice Updates the limit of the validators that can be used for deposit by DAO /// @param _nodeOperatorId Id of the node operator /// @param _targetLimit Target limit of the node operator - /// @param _isTargetLimitActive active flag - function updateTargetValidatorsLimits(uint256 _nodeOperatorId, bool _isTargetLimitActive, uint256 _targetLimit) external { + /// @param _targetLimitMode target limit mode, >1 means forced target limit + function updateTargetValidatorsLimits(uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit) external { _onlyExistedNodeOperator(_nodeOperatorId); _auth(STAKING_ROUTER_ROLE); _requireValidRange(_targetLimit <= UINT64_MAX); Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); - operatorTargetStats.set(IS_TARGET_LIMIT_ACTIVE_OFFSET, _isTargetLimitActive ? 1 : 0); - operatorTargetStats.set(TARGET_VALIDATORS_COUNT_OFFSET, _isTargetLimitActive ? _targetLimit : 0); + operatorTargetStats.set(TARGET_LIMIT_MODE_OFFSET, _targetLimitMode); + operatorTargetStats.set(TARGET_VALIDATORS_COUNT_OFFSET, _targetLimitMode > 0 ? _targetLimit : 0); _saveOperatorTargetValidatorsStats(_nodeOperatorId, operatorTargetStats); emit TargetValidatorsCountChanged(_nodeOperatorId, _targetLimit); @@ -845,7 +845,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { if (!isOperatorPenaltyCleared(_nodeOperatorId)) { // when the node operator is penalized zeroing its depositable validators count newMaxSigningKeysCount = depositedSigningKeysCount; - } else if (operatorTargetStats.get(IS_TARGET_LIMIT_ACTIVE_OFFSET) != 0) { + } else if (operatorTargetStats.get(TARGET_LIMIT_MODE_OFFSET) != 0) { // apply target limit when it's active and the node operator is not penalized newMaxSigningKeysCount = Math256.max( // max validators count can't be less than the deposited validators count @@ -860,6 +860,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { + operatorTargetStats.get(TARGET_VALIDATORS_COUNT_OFFSET) ) ); + } } oldMaxSigningKeysCount = operatorTargetStats.get(MAX_VALIDATORS_COUNT_OFFSET); @@ -1251,7 +1252,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { external view returns ( - bool isTargetLimitActive, + uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -1265,7 +1266,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats(_nodeOperatorId); - isTargetLimitActive = operatorTargetStats.get(IS_TARGET_LIMIT_ACTIVE_OFFSET) != 0; + targetLimitMode = operatorTargetStats.get(TARGET_LIMIT_MODE_OFFSET); targetValidatorsCount = operatorTargetStats.get(TARGET_VALIDATORS_COUNT_OFFSET); stuckValidatorsCount = stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET); refundedValidatorsCount = stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET); diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 49c696e90..2346ec4cf 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -270,17 +270,17 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @notice Updates the limit of the validators that can be used for deposit /// @param _stakingModuleId Id of the staking module /// @param _nodeOperatorId Id of the node operator - /// @param _isTargetLimitActive Active flag + /// @param _targetLimitMode Target limit mode /// @param _targetLimit Target limit of the node operator function updateTargetValidatorsLimits( uint256 _stakingModuleId, uint256 _nodeOperatorId, - bool _isTargetLimitActive, + uint265 _targetLimitMode, uint256 _targetLimit ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; IStakingModule(moduleAddr) - .updateTargetValidatorsLimits(_nodeOperatorId, _isTargetLimitActive, _targetLimit); + .updateTargetValidatorsLimits(_nodeOperatorId, _targetLimitMode, _targetLimit); } /// @notice Updates the number of the refunded validators in the staking module with the given @@ -498,7 +498,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version address moduleAddr = stakingModule.stakingModuleAddress; ( - /* bool isTargetLimitActive */, + /* uint156 targetLimitMode */, /* uint256 targetValidatorsCount */, uint256 stuckValidatorsCount, /* uint256 refundedValidatorsCount */, @@ -677,7 +677,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @notice A summary of node operator and its validators struct NodeOperatorSummary { /// @notice Shows whether the current target limit applied to the node operator - bool isTargetLimitActive; + uint256 targetLimitMode; /// @notice Relative target active validators limit for operator uint256 targetValidatorsCount; @@ -735,7 +735,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @dev using intermediate variables below due to "Stack too deep" error in case of /// assigning directly into the NodeOperatorSummary struct ( - bool isTargetLimitActive, + uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -744,7 +744,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 totalDepositedValidators, uint256 depositableValidatorsCount ) = stakingModule.getNodeOperatorSummary(_nodeOperatorId); - summary.isTargetLimitActive = isTargetLimitActive; + summary.targetLimitMode = targetLimitMode; summary.targetValidatorsCount = targetValidatorsCount; summary.stuckValidatorsCount = stuckValidatorsCount; summary.refundedValidatorsCount = refundedValidatorsCount; diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index 416f89da4..f0ae1693f 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -23,7 +23,7 @@ interface IStakingModule { /// @notice Returns all-validators summary belonging to the node operator with the given id /// @param _nodeOperatorId id of the operator to return report for - /// @return isTargetLimitActive shows whether the current target limit applied to the node operator + /// @return targetLimitMode shows whether the current target limit applied to the node operator (1 = soft mode, 2 = forced mode) /// @return targetValidatorsCount relative target active validators limit for operator /// @return stuckValidatorsCount number of validators with an expired request to exit time /// @return refundedValidatorsCount number of validators that can't be withdrawn, but deposit @@ -37,7 +37,7 @@ interface IStakingModule { /// EXITED state this counter is not decreasing /// @return depositableValidatorsCount number of validators in the set available for deposit function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( - bool isTargetLimitActive, + uint265 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -110,11 +110,11 @@ interface IStakingModule { /// @notice Updates the limit of the validators that can be used for deposit /// @param _nodeOperatorId Id of the node operator - /// @param _isTargetLimitActive Active flag + /// @param _targetLimitMode taret limit mode /// @param _targetLimit Target limit of the node operator function updateTargetValidatorsLimits( uint256 _nodeOperatorId, - bool _isTargetLimitActive, + uint265 _targetLimitMode, uint256 _targetLimit ) external; diff --git a/contracts/0.8.9/test_helpers/ModuleSolo.sol b/contracts/0.8.9/test_helpers/ModuleSolo.sol index 4bbe96f06..00d553d1b 100644 --- a/contracts/0.8.9/test_helpers/ModuleSolo.sol +++ b/contracts/0.8.9/test_helpers/ModuleSolo.sol @@ -41,7 +41,7 @@ contract ModuleSolo is IStakingModule { } function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( - bool isTargetLimitActive, + uint265 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -92,7 +92,7 @@ contract ModuleSolo is IStakingModule { function updateTargetValidatorsLimits( uint256 _nodeOperatorId, - bool _isTargetLimitActive, + uint265 _targetLimitMode, uint256 _targetLimit ) external {} diff --git a/contracts/0.8.9/test_helpers/StakingModuleMock.sol b/contracts/0.8.9/test_helpers/StakingModuleMock.sol index 05ede6be6..7f685c045 100644 --- a/contracts/0.8.9/test_helpers/StakingModuleMock.sol +++ b/contracts/0.8.9/test_helpers/StakingModuleMock.sol @@ -35,7 +35,7 @@ contract StakingModuleMock is IStakingModule { } struct NodeOperatorSummary { - bool isTargetLimitActive; + uint265 targetLimitMode, uint256 targetValidatorsCount; uint256 stuckValidatorsCount; uint256 refundedValidatorsCount; @@ -46,7 +46,7 @@ contract StakingModuleMock is IStakingModule { } mapping(uint256 => NodeOperatorSummary) internal nodeOperatorsSummary; function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( - bool isTargetLimitActive, + uint265 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -56,7 +56,7 @@ contract StakingModuleMock is IStakingModule { uint256 depositableValidatorsCount ) { NodeOperatorSummary storage _summary = nodeOperatorsSummary[_nodeOperatorId]; - isTargetLimitActive = _summary.isTargetLimitActive; + targetLimitMode = _summary.targetLimitMode; targetValidatorsCount = _summary.targetValidatorsCount; stuckValidatorsCount = _summary.stuckValidatorsCount; refundedValidatorsCount = _summary.refundedValidatorsCount; @@ -67,7 +67,7 @@ contract StakingModuleMock is IStakingModule { } function setNodeOperatorSummary(uint256 _nodeOperatorId, NodeOperatorSummary memory _summary) external { NodeOperatorSummary storage summary = nodeOperatorsSummary[_nodeOperatorId]; - summary.isTargetLimitActive = _summary.isTargetLimitActive; + summary.targetLimitMode = _summary.targetLimitMode; summary.targetValidatorsCount = _summary.targetValidatorsCount; summary.stuckValidatorsCount = _summary.stuckValidatorsCount; summary.refundedValidatorsCount = _summary.refundedValidatorsCount; @@ -165,7 +165,7 @@ contract StakingModuleMock is IStakingModule { // solhint-disable-next-line struct Call_updateTargetValidatorsLimits { uint256 nodeOperatorId; - bool isTargetLimitActive; + uint256 targetLimitMode; uint256 targetLimit; uint256 callCount; } @@ -173,11 +173,11 @@ contract StakingModuleMock is IStakingModule { Call_updateTargetValidatorsLimits public lastCall_updateTargetValidatorsLimits; function updateTargetValidatorsLimits( uint256 _nodeOperatorId, - bool _isTargetLimitActive, + uint256 _targetLimitMode, uint256 _targetLimit ) external { lastCall_updateTargetValidatorsLimits.nodeOperatorId = _nodeOperatorId; - lastCall_updateTargetValidatorsLimits.isTargetLimitActive = _isTargetLimitActive; + lastCall_updateTargetValidatorsLimits.targetLimitMode = _targetLimitMode; lastCall_updateTargetValidatorsLimits.targetLimit = _targetLimit; ++lastCall_updateTargetValidatorsLimits.callCount; } From 97b5cbe53a448338aa0d350cae53348f6bd0fa18 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 18:17:23 +0200 Subject: [PATCH 029/177] fix: allocation strategy --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 7 +++---- contracts/0.8.9/StakingRouter.sol | 4 ++-- contracts/0.8.9/interfaces/IStakingModule.sol | 4 ++-- contracts/common/lib/MinFirstAllocationStrategy.sol | 5 +++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 5252c7616..ee65fe219 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -59,7 +59,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint256 refundedValidatorsCount, uint256 stuckPenaltyEndTimestamp ); - event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount); + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); event NodeOperatorPenalized(address indexed recipientAddress, uint256 sharesPenalizedAmount); // Enum to represent the state of the reward distribution process @@ -648,7 +648,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { operatorTargetStats.set(TARGET_VALIDATORS_COUNT_OFFSET, _targetLimitMode > 0 ? _targetLimit : 0); _saveOperatorTargetValidatorsStats(_nodeOperatorId, operatorTargetStats); - emit TargetValidatorsCountChanged(_nodeOperatorId, _targetLimit); + emit TargetValidatorsCountChanged(_nodeOperatorId, _targetLimit, _targetLimitMode); _updateSummaryMaxValidatorsCount(_nodeOperatorId); _increaseValidatorsKeysNonce(); @@ -860,7 +860,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { + operatorTargetStats.get(TARGET_VALIDATORS_COUNT_OFFSET) ) ); - } } oldMaxSigningKeysCount = operatorTargetStats.get(MAX_VALIDATORS_COUNT_OFFSET); @@ -910,7 +909,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { } } - allocatedKeysCount = + (allocatedKeysCount, activeKeyCountsAfterAllocation) = MinFirstAllocationStrategy.allocate(activeKeyCountsAfterAllocation, activeKeysCapacities, _keysCount); /// @dev method NEVER allocates more keys than was requested diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 2346ec4cf..56cd3c5e2 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -275,7 +275,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version function updateTargetValidatorsLimits( uint256 _stakingModuleId, uint256 _nodeOperatorId, - uint265 _targetLimitMode, + uint256 _targetLimitMode, uint256 _targetLimit ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; @@ -1280,7 +1280,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - allocated = MinFirstAllocationStrategy.allocate(allocations, capacities, _depositsToAllocate); + (allocated, allocations) = MinFirstAllocationStrategy.allocate(allocations, capacities, _depositsToAllocate); } } diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index f0ae1693f..66040d535 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -37,7 +37,7 @@ interface IStakingModule { /// EXITED state this counter is not decreasing /// @return depositableValidatorsCount number of validators in the set available for deposit function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( - uint265 targetLimitMode, + uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -114,7 +114,7 @@ interface IStakingModule { /// @param _targetLimit Target limit of the node operator function updateTargetValidatorsLimits( uint256 _nodeOperatorId, - uint265 _targetLimitMode, + uint256 _targetLimitMode, uint256 _targetLimit ) external; diff --git a/contracts/common/lib/MinFirstAllocationStrategy.sol b/contracts/common/lib/MinFirstAllocationStrategy.sol index fa3d67e70..606b35d48 100644 --- a/contracts/common/lib/MinFirstAllocationStrategy.sol +++ b/contracts/common/lib/MinFirstAllocationStrategy.sol @@ -31,7 +31,7 @@ library MinFirstAllocationStrategy { uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize - ) public pure returns (uint256 allocated) { + ) public pure returns (uint256 allocated, uint256[] memory) { uint256 allocatedToBestCandidate = 0; while (allocated < allocationSize) { allocatedToBestCandidate = allocateToBestCandidate(buckets, capacities, allocationSize - allocated); @@ -40,6 +40,7 @@ library MinFirstAllocationStrategy { } allocated += allocatedToBestCandidate; } + return (allocated, buckets); } /// @notice Allocates the max allowed value not exceeding allocationSize to the bucket with the least value. @@ -63,7 +64,7 @@ library MinFirstAllocationStrategy { uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize - ) public pure returns (uint256 allocated) { + ) internal pure returns (uint256 allocated) { uint256 bestCandidateIndex = buckets.length; uint256 bestCandidateAllocation = MAX_UINT256; uint256 bestCandidatesCount = 0; From 31ed3fd0ca4e766bfbe50913506fc98c31a35a22 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 18:20:08 +0200 Subject: [PATCH 030/177] test: fix contracts mocks --- ...ationStrategyConsumerMockLegacyVersion.sol | 3 +- ...ationStrategyConsumerMockModernVersion.sol | 3 +- contracts/0.8.9/test_helpers/ModuleSolo.sol | 4 +- .../0.8.9/test_helpers/StakingModuleMock.sol | 4 +- test/0.8.9/contracts/StakingModule__Mock.sol | 17 ++++--- test/common/minFirstAllocationStrategy.t.sol | 45 +++++-------------- 6 files changed, 26 insertions(+), 50 deletions(-) diff --git a/contracts/0.4.24/test_helpers/MinFirstAllocationStrategyConsumerMockLegacyVersion.sol b/contracts/0.4.24/test_helpers/MinFirstAllocationStrategyConsumerMockLegacyVersion.sol index e6846a0ed..588bc246f 100644 --- a/contracts/0.4.24/test_helpers/MinFirstAllocationStrategyConsumerMockLegacyVersion.sol +++ b/contracts/0.4.24/test_helpers/MinFirstAllocationStrategyConsumerMockLegacyVersion.sol @@ -12,8 +12,7 @@ contract MinFirstAllocationStrategyConsumerMockLegacyVersion { uint256[] memory capacities, uint256 maxAllocationSize ) public pure returns (uint256 allocated, uint256[] memory newAllocations) { - allocated = MinFirstAllocationStrategy.allocate(allocations, capacities, maxAllocationSize); - newAllocations = allocations; + (allocated, newAllocations) = MinFirstAllocationStrategy.allocate(allocations, capacities, maxAllocationSize); } function allocateToBestCandidate( diff --git a/contracts/0.8.9/test_helpers/MinFirstAllocationStrategyConsumerMockModernVersion.sol b/contracts/0.8.9/test_helpers/MinFirstAllocationStrategyConsumerMockModernVersion.sol index 9ea4a2e7a..fe8ab2963 100644 --- a/contracts/0.8.9/test_helpers/MinFirstAllocationStrategyConsumerMockModernVersion.sol +++ b/contracts/0.8.9/test_helpers/MinFirstAllocationStrategyConsumerMockModernVersion.sol @@ -12,8 +12,7 @@ contract MinFirstAllocationStrategyConsumerMockModernVersion { uint256[] memory capacities, uint256 maxAllocationSize ) external pure returns (uint256 allocated, uint256[] memory newAllocations) { - allocated = MinFirstAllocationStrategy.allocate(allocations, capacities, maxAllocationSize); - newAllocations = allocations; + (allocated, newAllocations) = MinFirstAllocationStrategy.allocate(allocations, capacities, maxAllocationSize); } function allocateToBestCandidate( diff --git a/contracts/0.8.9/test_helpers/ModuleSolo.sol b/contracts/0.8.9/test_helpers/ModuleSolo.sol index 00d553d1b..800b23c34 100644 --- a/contracts/0.8.9/test_helpers/ModuleSolo.sol +++ b/contracts/0.8.9/test_helpers/ModuleSolo.sol @@ -41,7 +41,7 @@ contract ModuleSolo is IStakingModule { } function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( - uint265 targetLimitMode, + uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -92,7 +92,7 @@ contract ModuleSolo is IStakingModule { function updateTargetValidatorsLimits( uint256 _nodeOperatorId, - uint265 _targetLimitMode, + uint256 _targetLimitMode, uint256 _targetLimit ) external {} diff --git a/contracts/0.8.9/test_helpers/StakingModuleMock.sol b/contracts/0.8.9/test_helpers/StakingModuleMock.sol index 7f685c045..b8beca59c 100644 --- a/contracts/0.8.9/test_helpers/StakingModuleMock.sol +++ b/contracts/0.8.9/test_helpers/StakingModuleMock.sol @@ -35,7 +35,7 @@ contract StakingModuleMock is IStakingModule { } struct NodeOperatorSummary { - uint265 targetLimitMode, + uint256 targetLimitMode; uint256 targetValidatorsCount; uint256 stuckValidatorsCount; uint256 refundedValidatorsCount; @@ -46,7 +46,7 @@ contract StakingModuleMock is IStakingModule { } mapping(uint256 => NodeOperatorSummary) internal nodeOperatorsSummary; function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns ( - uint265 targetLimitMode, + uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, diff --git a/test/0.8.9/contracts/StakingModule__Mock.sol b/test/0.8.9/contracts/StakingModule__Mock.sol index d870a7422..253198a1e 100644 --- a/test/0.8.9/contracts/StakingModule__Mock.sol +++ b/test/0.8.9/contracts/StakingModule__Mock.sol @@ -7,7 +7,7 @@ pragma solidity 0.8.9; import {IStakingModule} from "contracts/0.8.9/interfaces/IStakingModule.sol"; contract StakingModule__Mock is IStakingModule { - event Mock__TargetValidatorsLimitsUpdated(uint256 _nodeOperatorId, bool _isTargetLimitActive, uint256 _targetLimit); + event Mock__TargetValidatorsLimitsUpdated(uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit); event Mock__RefundedValidatorsCountUpdated(uint256 _nodeOperatorId, uint256 _refundedValidatorsCount); event Mock__OnRewardsMinted(uint256 _totalShares); event Mock__ExitedValidatorsCountUpdated(bytes _nodeOperatorIds, bytes _stuckValidatorsCounts); @@ -39,8 +39,7 @@ contract StakingModule__Mock is IStakingModule { totalDepositedValidators__mocked = totalDepositedValidators; depositableValidatorsCount__mocked = depositableValidatorsCount; } - - bool private nodeOperatorIsTargetLimitActive__mocked; + uint256 private nodeOperatorTargetLimitMode__mocked; uint256 private nodeOperatorTargetValidatorsCount__mocked; uint256 private nodeOperatorStuckValidatorsCount__mocked; uint256 private nodeOperatorRefundedValidatorsCount__mocked; @@ -55,7 +54,7 @@ contract StakingModule__Mock is IStakingModule { external view returns ( - bool isTargetLimitActive, + uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -65,7 +64,7 @@ contract StakingModule__Mock is IStakingModule { uint256 depositableValidatorsCount ) { - isTargetLimitActive = nodeOperatorIsTargetLimitActive__mocked; + targetLimitMode = nodeOperatorTargetLimitMode__mocked; targetValidatorsCount = nodeOperatorTargetValidatorsCount__mocked; stuckValidatorsCount = nodeOperatorStuckValidatorsCount__mocked; refundedValidatorsCount = nodeOperatorRefundedValidatorsCount__mocked; @@ -76,7 +75,7 @@ contract StakingModule__Mock is IStakingModule { } function mock__getNodeOperatorSummary( - bool isTargetLimitActive, + uint256 targetLimitMode, uint256 targetValidatorsCount, uint256 stuckValidatorsCount, uint256 refundedValidatorsCount, @@ -85,7 +84,7 @@ contract StakingModule__Mock is IStakingModule { uint256 totalDepositedValidators, uint256 depositableValidatorsCount ) external { - nodeOperatorIsTargetLimitActive__mocked = isTargetLimitActive; + nodeOperatorTargetLimitMode__mocked = targetLimitMode; nodeOperatorTargetValidatorsCount__mocked = targetValidatorsCount; nodeOperatorStuckValidatorsCount__mocked = stuckValidatorsCount; nodeOperatorRefundedValidatorsCount__mocked = refundedValidatorsCount; @@ -175,10 +174,10 @@ contract StakingModule__Mock is IStakingModule { function updateTargetValidatorsLimits( uint256 _nodeOperatorId, - bool _isTargetLimitActive, + uint256 _targetLimitMode, uint256 _targetLimit ) external { - emit Mock__TargetValidatorsLimitsUpdated(_nodeOperatorId, _isTargetLimitActive, _targetLimit); + emit Mock__TargetValidatorsLimitsUpdated(_nodeOperatorId, _targetLimitMode, _targetLimit); } event Mock__ValidatorsCountUnsafelyUpdated( diff --git a/test/common/minFirstAllocationStrategy.t.sol b/test/common/minFirstAllocationStrategy.t.sol index 9695dac35..7ed0bf276 100644 --- a/test/common/minFirstAllocationStrategy.t.sol +++ b/test/common/minFirstAllocationStrategy.t.sol @@ -170,41 +170,25 @@ contract MinFirstAllocationStrategyBase { TestOutput internal _actual; TestOutput internal _expected; - function getInput() - external - view - returns (uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize) - { + function getInput() external view returns (uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize) { buckets = _input.buckets; capacities = _input.capacities; allocationSize = _input.allocationSize; } - function getExpectedOutput() - external - view - returns (uint256[] memory buckets, uint256[] memory capacities, uint256 allocated) - { + function getExpectedOutput() external view returns (uint256[] memory buckets, uint256[] memory capacities, uint256 allocated) { buckets = _expected.buckets; capacities = _expected.capacities; allocated = _expected.allocated; } - function getActualOutput() - external - view - returns (uint256[] memory buckets, uint256[] memory capacities, uint256 allocated) - { + function getActualOutput() external view returns (uint256[] memory buckets, uint256[] memory capacities, uint256 allocated) { buckets = _actual.buckets; capacities = _actual.capacities; allocated = _actual.allocated; } - function _fillTestInput( - uint256[] memory _fuzzBuckets, - uint256[] memory _fuzzCapacities, - uint256 _fuzzAllocationSize - ) internal { + function _fillTestInput(uint256[] memory _fuzzBuckets, uint256[] memory _fuzzCapacities, uint256 _fuzzAllocationSize) internal { uint256 bucketsCount = Math256.min(_fuzzBuckets.length, _fuzzCapacities.length) % MAX_BUCKETS_COUNT; _input.buckets = new uint256[](bucketsCount); _input.capacities = new uint256[](bucketsCount); @@ -217,9 +201,7 @@ contract MinFirstAllocationStrategyBase { } contract MinFirstAllocationStrategyAllocateHandler is MinFirstAllocationStrategyBase { - function allocate(uint256[] memory _fuzzBuckets, uint256[] memory _fuzzCapacities, uint256 _fuzzAllocationSize) - public - { + function allocate(uint256[] memory _fuzzBuckets, uint256[] memory _fuzzCapacities, uint256 _fuzzAllocationSize) public { _fillTestInput(_fuzzBuckets, _fuzzCapacities, _fuzzAllocationSize); _fillActualAllocateOutput(); @@ -243,10 +225,11 @@ contract MinFirstAllocationStrategyAllocateHandler is MinFirstAllocationStrategy uint256[] memory capacities = _input.capacities; uint256 allocationSize = _input.allocationSize; - uint256 allocated = MinFirstAllocationStrategy.allocate(buckets, capacities, allocationSize); + // uint256 allocated = MinFirstAllocationStrategy.allocate(buckets, capacities, allocationSize); + (_actual.allocated, _actual.buckets) = MinFirstAllocationStrategy.allocate(buckets, capacities, allocationSize); - _actual.allocated = allocated; - _actual.buckets = buckets; + // _actual.allocated = allocated; + // _actual.buckets = buckets; _actual.capacities = capacities; } } @@ -257,8 +240,8 @@ contract MinFirstAllocationStrategy__Harness { pure returns (uint256 allocated, uint256[] memory newBuckets, uint256[] memory newCapacities) { - allocated = MinFirstAllocationStrategy.allocate(_buckets, _capacities, _allocationSize); - newBuckets = _buckets; + (allocated, newBuckets) = MinFirstAllocationStrategy.allocate(_buckets, _capacities, _allocationSize); + // newBuckets = _buckets; newCapacities = _capacities; } @@ -276,11 +259,7 @@ contract MinFirstAllocationStrategy__Harness { library NaiveMinFirstAllocationStrategy { uint256 private constant MAX_UINT256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; - function allocate(uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize) - internal - pure - returns (uint256 allocated) - { + function allocate(uint256[] memory buckets, uint256[] memory capacities, uint256 allocationSize) internal pure returns (uint256 allocated) { while (allocated < allocationSize) { uint256 bestCandidateIndex = MAX_UINT256; uint256 bestCandidateAllocation = MAX_UINT256; From e1b98a2e543d730075556b5ef7a06c827a43108a Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 18:22:53 +0200 Subject: [PATCH 031/177] test: fix SR tests --- .../stakingRouter/stakingRouter.misc.test.ts | 13 +- .../stakingRouter.module-management.test.ts | 228 +++++++++++++++--- .../stakingRouter.module-sync.test.ts | 37 ++- .../stakingRouter.rewards.test.ts | 79 ++++-- .../stakingRouter.status-control.test.ts | 10 +- 5 files changed, 303 insertions(+), 64 deletions(-) diff --git a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts index 873a7f0f8..cea631156 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts @@ -7,9 +7,11 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { DepositContract__MockForBeaconChainDepositor, DepositContract__MockForBeaconChainDepositor__factory, + MinFirstAllocationStrategy__factory, StakingRouter, StakingRouter__factory, } from "typechain-types"; +import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; import { certainAddress, ether, proxify } from "lib"; @@ -20,7 +22,6 @@ describe("StakingRouter", () => { let user: HardhatEthersSigner; let depositContract: DepositContract__MockForBeaconChainDepositor; - let stakingRouterImpl: StakingRouter; let stakingRouter: StakingRouter; const lido = certainAddress("test:staking-router:lido"); @@ -30,8 +31,14 @@ describe("StakingRouter", () => { [deployer, proxyAdmin, stakingRouterAdmin, user] = await ethers.getSigners(); depositContract = await new DepositContract__MockForBeaconChainDepositor__factory(deployer).deploy(); - stakingRouterImpl = await new StakingRouter__factory(deployer).deploy(depositContract); - [stakingRouter] = await proxify({ impl: stakingRouterImpl, admin: proxyAdmin, caller: user }); + + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: StakingRouterLibraryAddresses = { + ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), + }; + + const impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); + [stakingRouter] = await proxify({ impl, admin: proxyAdmin, caller: user }); }); context("initialize", () => { diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts index 3738a3404..a638b045e 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts @@ -6,9 +6,11 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { DepositContract__MockForBeaconChainDepositor__factory, + MinFirstAllocationStrategy__factory, StakingRouter, StakingRouter__factory, } from "typechain-types"; +import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; import { certainAddress, getNextBlock, proxify, randomString } from "lib"; @@ -23,8 +25,13 @@ describe("StakingRouter:module-management", () => { [deployer, admin, user] = await ethers.getSigners(); const depositContract = await new DepositContract__MockForBeaconChainDepositor__factory(deployer).deploy(); - const impl = await new StakingRouter__factory(deployer).deploy(depositContract); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: StakingRouterLibraryAddresses = { + ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), + }; + + const impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); [stakingRouter] = await proxify({ impl, admin }); // initialize staking router @@ -41,40 +48,80 @@ describe("StakingRouter:module-management", () => { context("addStakingModule", () => { const NAME = "StakingModule"; const ADDRESS = certainAddress("test:staking-router:staking-module"); - const TARGET_SHARE = 1_00n; + const STAKE_SHARE_LIMIT = 1_00n; + const PRIORITY_EXIT_SHARE_THRESHOLD = STAKE_SHARE_LIMIT; const MODULE_FEE = 5_00n; const TREASURY_FEE = 5_00n; it("Reverts if the caller does not have the role", async () => { await expect( - stakingRouter.connect(user).addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE), + stakingRouter + .connect(user) + .addStakingModule(NAME, ADDRESS, STAKE_SHARE_LIMIT, PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE), ).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.STAKING_MODULE_MANAGE_ROLE()); }); - + //todo priority < share + //todo priority > 100 it("Reverts if the target share is greater than 100%", async () => { const TARGET_SHARE_OVER_100 = 100_01; - await expect(stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE_OVER_100, MODULE_FEE, TREASURY_FEE)) + await expect( + stakingRouter.addStakingModule( + NAME, + ADDRESS, + TARGET_SHARE_OVER_100, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ), + ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_targetShare"); + .withArgs("_stakeShareLimit"); }); it("Reverts if the sum of module and treasury fees is greater than 100%", async () => { const MODULE_FEE_INVALID = 100_01n - TREASURY_FEE; - await expect(stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE_INVALID, TREASURY_FEE)) + await expect( + stakingRouter.addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE_INVALID, + TREASURY_FEE, + ), + ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") .withArgs("_stakingModuleFee + _treasuryFee"); const TREASURY_FEE_INVALID = 100_01n - MODULE_FEE; - await expect(stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE_INVALID)) + await expect( + stakingRouter.addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE_INVALID, + ), + ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") .withArgs("_stakingModuleFee + _treasuryFee"); }); it("Reverts if the staking module address is zero address", async () => { - await expect(stakingRouter.addStakingModule(NAME, ZeroAddress, TARGET_SHARE, MODULE_FEE, TREASURY_FEE)) + await expect( + stakingRouter.addStakingModule( + NAME, + ZeroAddress, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ), + ) .to.be.revertedWithCustomError(stakingRouter, "ZeroAddress") .withArgs("_stakingModuleAddress"); }); @@ -83,7 +130,14 @@ describe("StakingRouter:module-management", () => { const NAME_EMPTY_STRING = ""; await expect( - stakingRouter.addStakingModule(NAME_EMPTY_STRING, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE), + stakingRouter.addStakingModule( + NAME_EMPTY_STRING, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModuleWrongName"); }); @@ -92,7 +146,14 @@ describe("StakingRouter:module-management", () => { const NAME_TOO_LONG = randomString(Number(MAX_STAKING_MODULE_NAME_LENGTH + 1n)); await expect( - stakingRouter.addStakingModule(NAME_TOO_LONG, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE), + stakingRouter.addStakingModule( + NAME_TOO_LONG, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModuleWrongName"); }); @@ -106,21 +167,43 @@ describe("StakingRouter:module-management", () => { 1_00, 1_00, 1_00, + 1_00, ); } expect(await stakingRouter.getStakingModulesCount()).to.equal(MAX_STAKING_MODULES_COUNT); await expect( - stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE), + stakingRouter.addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModulesLimitExceeded"); }); it("Reverts if adding a module with the same address", async () => { - await stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE); + await stakingRouter.addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ); await expect( - stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE), + stakingRouter.addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModuleAddressExists"); }); @@ -128,13 +211,22 @@ describe("StakingRouter:module-management", () => { const stakingModuleId = (await stakingRouter.getStakingModulesCount()) + 1n; const moduleAddedBlock = await getNextBlock(); - await expect(stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE)) + await expect( + stakingRouter.addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ), + ) .to.be.emit(stakingRouter, "StakingRouterETHDeposited") .withArgs(stakingModuleId, 0) .and.to.be.emit(stakingRouter, "StakingModuleAdded") .withArgs(stakingModuleId, ADDRESS, NAME, admin.address) - .and.to.be.emit(stakingRouter, "StakingModuleTargetShareSet") - .withArgs(stakingModuleId, TARGET_SHARE, admin.address) + .and.to.be.emit(stakingRouter, "StakingModuleShareLimitSet") + .withArgs(stakingModuleId, STAKE_SHARE_LIMIT, PRIORITY_EXIT_SHARE_THRESHOLD, admin.address) .and.to.be.emit(stakingRouter, "StakingModuleFeesSet") .withArgs(stakingModuleId, MODULE_FEE, TREASURY_FEE, admin.address); @@ -143,8 +235,9 @@ describe("StakingRouter:module-management", () => { ADDRESS, MODULE_FEE, TREASURY_FEE, - TARGET_SHARE, + STAKE_SHARE_LIMIT, 0n, // status active + PRIORITY_EXIT_SHARE_THRESHOLD, NAME, moduleAddedBlock.timestamp, moduleAddedBlock.number, @@ -156,18 +249,28 @@ describe("StakingRouter:module-management", () => { context("updateStakingModule", () => { const NAME = "StakingModule"; const ADDRESS = certainAddress("test:staking-router-modules:staking-module"); - const TARGET_SHARE = 1_00n; + const STAKE_SHARE_LIMIT = 1_00n; + const PRIORITY_EXIT_SHARE_THRESHOLD = STAKE_SHARE_LIMIT; const MODULE_FEE = 5_00n; const TREASURY_FEE = 5_00n; let ID: bigint; - const NEW_TARGET_SHARE = 2_00; + const NEW_STAKE_SHARE_LIMIT = 2_00; + const NEW_PRIORITY_EXIT_SHARE_THRESHOLD = NEW_STAKE_SHARE_LIMIT; + const NEW_MODULE_FEE = 6_00n; const NEW_TREASURY_FEE = 4_00n; beforeEach(async () => { - await stakingRouter.addStakingModule(NAME, ADDRESS, TARGET_SHARE, MODULE_FEE, TREASURY_FEE); + await stakingRouter.addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + ); ID = await stakingRouter.getStakingModulesCount(); }); @@ -175,34 +278,101 @@ describe("StakingRouter:module-management", () => { stakingRouter = stakingRouter.connect(user); await expect( - stakingRouter.updateStakingModule(ID, NEW_TARGET_SHARE, NEW_MODULE_FEE, NEW_TREASURY_FEE), + stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + ), ).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.STAKING_MODULE_MANAGE_ROLE()); }); it("Reverts if the new target share is greater than 100%", async () => { const NEW_TARGET_SHARE_OVER_100 = 100_01; - await expect(stakingRouter.updateStakingModule(ID, NEW_TARGET_SHARE_OVER_100, NEW_MODULE_FEE, NEW_TREASURY_FEE)) + await expect( + stakingRouter.updateStakingModule( + ID, + NEW_TARGET_SHARE_OVER_100, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") + .withArgs("_stakeShareLimit"); + }); + + it("Reverts if the new priority exit share is greater than 100%", async () => { + const NEW_PRIORITY_EXIT_SHARE_THRESHOLD_OVER_100 = 100_01; + await expect( + stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD_OVER_100, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + ), + ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_targetShare"); + .withArgs("_priorityExitShareThreshold"); + }); + + it("Reverts if the new priority exit share is less than stake share limit", async () => { + const NEW_STAKE_SHARE_LIMIT = 55_00n; + const NEW_PRIORITY_EXIT_SHARE_THRESHOLD = 50_00n; + await expect( + stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + ), + ).to.be.revertedWithCustomError(stakingRouter, "InvalidPriorityExitShareThreshold"); }); it("Reverts if the sum of the new module and treasury fees is greater than 100%", async () => { const NEW_MODULE_FEE_INVALID = 100_01n - TREASURY_FEE; - await expect(stakingRouter.updateStakingModule(ID, TARGET_SHARE, NEW_MODULE_FEE_INVALID, TREASURY_FEE)) + await expect( + stakingRouter.updateStakingModule( + ID, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE_INVALID, + TREASURY_FEE, + ), + ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") .withArgs("_stakingModuleFee + _treasuryFee"); const NEW_TREASURY_FEE_INVALID = 100_01n - MODULE_FEE; - await expect(stakingRouter.updateStakingModule(ID, TARGET_SHARE, MODULE_FEE, NEW_TREASURY_FEE_INVALID)) + await expect( + stakingRouter.updateStakingModule( + ID, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + NEW_TREASURY_FEE_INVALID, + ), + ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") .withArgs("_stakingModuleFee + _treasuryFee"); }); it("Update target share, module and treasury fees and emits events", async () => { - await expect(stakingRouter.updateStakingModule(ID, NEW_TARGET_SHARE, NEW_MODULE_FEE, NEW_TREASURY_FEE)) - .to.be.emit(stakingRouter, "StakingModuleTargetShareSet") - .withArgs(ID, NEW_TARGET_SHARE, admin.address) + await expect( + stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + ), + ) + .to.be.emit(stakingRouter, "StakingModuleShareLimitSet") + .withArgs(ID, NEW_STAKE_SHARE_LIMIT, NEW_PRIORITY_EXIT_SHARE_THRESHOLD, admin.address) .and.to.be.emit(stakingRouter, "StakingModuleFeesSet") .withArgs(ID, NEW_MODULE_FEE, NEW_TREASURY_FEE, admin.address); }); diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts index 2c80abd1a..61539cb2c 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts @@ -8,11 +8,13 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { DepositContract__MockForBeaconChainDepositor, DepositContract__MockForBeaconChainDepositor__factory, + MinFirstAllocationStrategy__factory, StakingModule__Mock, StakingModule__Mock__factory, StakingRouter, StakingRouter__factory, } from "typechain-types"; +import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; import { ether, getNextBlock, proxify } from "lib"; @@ -35,13 +37,20 @@ describe("StakingRouter:module-sync", () => { const name = "myStakingModule"; const stakingModuleFee = 5_00n; const treasuryFee = 5_00n; - const targetShare = 1_00n; + const stakeShareLimit = 1_00n; + const priorityExitShareThreshold = 2_00n; beforeEach(async () => { [deployer, admin, user, lido] = await ethers.getSigners(); depositContract = await new DepositContract__MockForBeaconChainDepositor__factory(deployer).deploy(); - const impl = await new StakingRouter__factory(deployer).deploy(depositContract); + + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: StakingRouterLibraryAddresses = { + ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), + }; + + const impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); [stakingRouter] = await proxify({ impl, admin }); @@ -70,13 +79,20 @@ describe("StakingRouter:module-sync", () => { lastDepositAt = timestamp; lastDepositBlock = number; - await stakingRouter.addStakingModule(name, stakingModuleAddress, targetShare, stakingModuleFee, treasuryFee); + await stakingRouter.addStakingModule( + name, + stakingModuleAddress, + stakeShareLimit, + priorityExitShareThreshold, + stakingModuleFee, + treasuryFee, + ); moduleId = await stakingRouter.getStakingModulesCount(); }); context("Getters", () => { - let stakingModuleInfo: [bigint, string, bigint, bigint, bigint, bigint, string, bigint, bigint, bigint]; + let stakingModuleInfo: [bigint, string, bigint, bigint, bigint, bigint, bigint, string, bigint, bigint, bigint]; // module mock state const stakingModuleSummary: Parameters = [ @@ -86,7 +102,7 @@ describe("StakingRouter:module-sync", () => { ]; const nodeOperatorSummary: Parameters = [ - true, // isTargetLimitActive + 1, // targetLimitMode 100n, // targetValidatorsCount 1n, // stuckValidatorsCount 5n, // refundedValidatorsCount @@ -109,8 +125,9 @@ describe("StakingRouter:module-sync", () => { stakingModuleAddress, stakingModuleFee, treasuryFee, - targetShare, + stakeShareLimit, 0n, // status + priorityExitShareThreshold, name, lastDepositAt, lastDepositBlock, @@ -291,23 +308,23 @@ describe("StakingRouter:module-sync", () => { context("updateTargetValidatorsLimits", () => { const NODE_OPERATOR_ID = 0n; - const IS_TARGET_LIMIT_ACTIVE = true; + const TARGET_LIMIT_MODE = 1; // 1 - soft, i.e. on WQ request; 2 - forced const TARGET_LIMIT = 100n; it("Reverts if the caller does not have the role", async () => { await expect( stakingRouter .connect(user) - .updateTargetValidatorsLimits(moduleId, NODE_OPERATOR_ID, IS_TARGET_LIMIT_ACTIVE, TARGET_LIMIT), + .updateTargetValidatorsLimits(moduleId, NODE_OPERATOR_ID, TARGET_LIMIT_MODE, TARGET_LIMIT), ).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.STAKING_MODULE_MANAGE_ROLE()); }); it("Redirects the call to the staking module", async () => { await expect( - stakingRouter.updateTargetValidatorsLimits(moduleId, NODE_OPERATOR_ID, IS_TARGET_LIMIT_ACTIVE, TARGET_LIMIT), + stakingRouter.updateTargetValidatorsLimits(moduleId, NODE_OPERATOR_ID, TARGET_LIMIT_MODE, TARGET_LIMIT), ) .to.emit(stakingModule, "Mock__TargetValidatorsLimitsUpdated") - .withArgs(NODE_OPERATOR_ID, IS_TARGET_LIMIT_ACTIVE, TARGET_LIMIT); + .withArgs(NODE_OPERATOR_ID, TARGET_LIMIT_MODE, TARGET_LIMIT); }); }); diff --git a/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts b/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts index 16fa1d31f..2d64a1921 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts @@ -6,11 +6,13 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { DepositContract__MockForBeaconChainDepositor__factory, + MinFirstAllocationStrategy__factory, StakingModule__Mock, StakingModule__Mock__factory, StakingRouter, StakingRouter__factory, } from "typechain-types"; +import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; import { certainAddress, ether, proxify } from "lib"; @@ -21,13 +23,24 @@ describe("StakingRouter:deposits", () => { let stakingRouter: StakingRouter; const DEPOSIT_VALUE = ether("32.0"); - const DEFAULT_CONFIG: ModuleConfig = { targetShare: 100_00n, moduleFee: 5_00n, treasuryFee: 5_00n }; + const DEFAULT_CONFIG: ModuleConfig = { + stakeShareLimit: 100_00n, + priorityExitShareThreshold: 100_00n, + moduleFee: 5_00n, + treasuryFee: 5_00n, + }; beforeEach(async () => { [deployer, admin] = await ethers.getSigners(); const depositContract = await new DepositContract__MockForBeaconChainDepositor__factory(deployer).deploy(); - const impl = await new StakingRouter__factory(deployer).deploy(depositContract); + + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: StakingRouterLibraryAddresses = { + ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), + }; + + const impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); [stakingRouter] = await proxify({ impl, admin }); @@ -71,7 +84,7 @@ describe("StakingRouter:deposits", () => { const config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, depositable: 50n, }; @@ -106,7 +119,8 @@ describe("StakingRouter:deposits", () => { it("Allocates evenly if target shares are equal and capacities allow for that", async () => { const config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, depositable: 50n, }; @@ -122,13 +136,15 @@ describe("StakingRouter:deposits", () => { it("Allocates according to capacities at equal target shares", async () => { const module1Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, depositable: 100n, }; const module2Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, depositable: 50n, }; @@ -144,13 +160,15 @@ describe("StakingRouter:deposits", () => { it("Allocates according to target shares", async () => { const module1Config = { ...DEFAULT_CONFIG, - targetShare: 60_00n, + stakeShareLimit: 60_00n, + priorityExitShareThreshold: 60_00n, depositable: 100n, }; const module2Config = { ...DEFAULT_CONFIG, - targetShare: 40_00n, + stakeShareLimit: 40_00n, + priorityExitShareThreshold: 40_00n, depositable: 100n, }; @@ -208,7 +226,8 @@ describe("StakingRouter:deposits", () => { it("Distributes rewards evenly between multiple module if fees are the same", async () => { const config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, deposited: 1000n, }; @@ -235,13 +254,15 @@ describe("StakingRouter:deposits", () => { it("Does not distribute rewards to modules with no active validators", async () => { const module1Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, deposited: 1000n, }; const module2Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, deposited: 0n, }; @@ -289,7 +310,8 @@ describe("StakingRouter:deposits", () => { it("Distributes rewards between multiple module if according to the set fees", async () => { const module1Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, moduleFee: 1_00n, treasuryFee: 9_00n, deposited: 1000n, @@ -297,7 +319,8 @@ describe("StakingRouter:deposits", () => { const module2Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, moduleFee: 8_00n, treasuryFee: 2_00n, deposited: 1000n, @@ -338,7 +361,8 @@ describe("StakingRouter:deposits", () => { it("Returns fee aggregates with two modules with different fees", async () => { const module1Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, moduleFee: 4_00n, treasuryFee: 6_00n, deposited: 1000n, @@ -346,7 +370,8 @@ describe("StakingRouter:deposits", () => { const module2Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, moduleFee: 6_00n, treasuryFee: 4_00n, deposited: 1000n, @@ -373,7 +398,8 @@ describe("StakingRouter:deposits", () => { it("Returns fee aggregates with two modules with different fees", async () => { const module1Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, moduleFee: 4_00n, treasuryFee: 6_00n, deposited: 1000n, @@ -381,7 +407,8 @@ describe("StakingRouter:deposits", () => { const module2Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, moduleFee: 6_00n, treasuryFee: 4_00n, deposited: 1000n, @@ -402,7 +429,8 @@ describe("StakingRouter:deposits", () => { it("Returns total fee value in 1e4 precision", async () => { const module1Config = { ...DEFAULT_CONFIG, - targetShare: 50_00n, + stakeShareLimit: 50_00n, + priorityExitShareThreshold: 50_00n, moduleFee: 5_00n, treasuryFee: 5_00n, deposited: 1000n, @@ -415,7 +443,8 @@ describe("StakingRouter:deposits", () => { }); async function setupModule({ - targetShare, + stakeShareLimit, + priorityExitShareThreshold, moduleFee, treasuryFee, exited = 0n, @@ -428,7 +457,14 @@ describe("StakingRouter:deposits", () => { await stakingRouter .connect(admin) - .addStakingModule(randomBytes(8).toString(), await module.getAddress(), targetShare, moduleFee, treasuryFee); + .addStakingModule( + randomBytes(8).toString(), + await module.getAddress(), + stakeShareLimit, + priorityExitShareThreshold, + moduleFee, + treasuryFee, + ); const moduleId = modulesCount + 1n; expect(await stakingRouter.getStakingModulesCount()).to.equal(modulesCount + 1n); @@ -450,7 +486,8 @@ enum Status { } interface ModuleConfig { - targetShare: bigint; + stakeShareLimit: bigint; + priorityExitShareThreshold: bigint; moduleFee: bigint; treasuryFee: bigint; exited?: bigint; diff --git a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts index 2a9aadeae..d0bcd1f44 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts @@ -7,9 +7,11 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { DepositContract__MockForBeaconChainDepositor__factory, + MinFirstAllocationStrategy__factory, StakingRouter, StakingRouter__factory, } from "typechain-types"; +import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; import { certainAddress, proxify } from "lib"; @@ -32,7 +34,12 @@ context("StakingRouter:status-control", () => { // deploy staking router const depositContract = await new DepositContract__MockForBeaconChainDepositor__factory(deployer).deploy(); - const impl = await new StakingRouter__factory(deployer).deploy(depositContract); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: StakingRouterLibraryAddresses = { + ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), + }; + + const impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); [stakingRouter] = await proxify({ impl, admin }); @@ -54,6 +61,7 @@ context("StakingRouter:status-control", () => { "myStakingModule", certainAddress("test:staking-router-status:staking-module"), // mock staking module address 1_00, // target share + 1_00, // target share 5_00, // module fee 5_00, // treasury fee ); From b8a35dea310fa7774efe77b1f3bd758c84893d22 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 18:30:31 +0200 Subject: [PATCH 032/177] test: update SR version test --- .../stakingRouter.versioned.test.ts | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts b/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts index 6c0e04779..2f90c3b0d 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts @@ -4,36 +4,32 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { MinFirstAllocationStrategy, OssifiableProxy, StakingRouter, StakingRouter__factory } from "typechain-types"; +import { MinFirstAllocationStrategy__factory, StakingRouter, StakingRouter__factory } from "typechain-types"; +import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; -import { MAX_UINT256, randomAddress } from "lib"; +import { MAX_UINT256, proxify, randomAddress } from "lib"; describe("StakingRouter:Versioned", () => { + let deployer: HardhatEthersSigner; let admin: HardhatEthersSigner; - let user: HardhatEthersSigner; - let allocLib: MinFirstAllocationStrategy; let impl: StakingRouter; - let proxy: OssifiableProxy; let versioned: StakingRouter; const petrifiedVersion = MAX_UINT256; before(async () => { - [admin, user] = await ethers.getSigners(); + [deployer, admin] = await ethers.getSigners(); + // deploy staking router const depositContract = randomAddress(); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: StakingRouterLibraryAddresses = { + ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), + }; - allocLib = await ethers.deployContract("MinFirstAllocationStrategy", []); - impl = await ethers.deployContract("StakingRouter", [depositContract], { - libraries: { MinFirstAllocationStrategy: await allocLib.getAddress() }, - }); - - proxy = await ethers.deployContract("OssifiableProxy", [await impl.getAddress(), admin.address, new Uint8Array()], { - from: admin, - }); - - versioned = StakingRouter__factory.connect(await proxy.getAddress(), user); + impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); + [versioned] = await proxify({ impl, admin }); }); context("constructor", () => { From 3211f0d50fe1faf290a0afa9a8b5231e48812004 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 22:11:40 +0200 Subject: [PATCH 033/177] test: nor target limit mode --- .../nor/node-operators-registry.test.ts | 318 ++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 test/0.4.24/nor/node-operators-registry.test.ts diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts new file mode 100644 index 000000000..9abb3a3f1 --- /dev/null +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -0,0 +1,318 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; + +import { + ACL, + Kernel, + Lido, + LidoLocator, + LidoLocator__factory, + MinFirstAllocationStrategy__factory, + NodeOperatorsRegistryMock, + NodeOperatorsRegistryMock__factory, +} from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; + +import { addAragonApp, deployLidoDao } from "lib"; + +const CURATED_TYPE = "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000"; // "curated-onchain-v1" +const PENALTY_DELAY = 2 * 24 * 60 * 60; // 2 days +const ADDRESS_1 = "0x0000000000000000000000000000000000000001"; +const ADDRESS_2 = "0x0000000000000000000000000000000000000002"; +const ADDRESS_3 = "0x0000000000000000000000000000000000000003"; +// const ADDRESS_4 = "0x0000000000000000000000000000000000000005"; + +const NODE_OPERATORS: NodeOperatorConfig[] = [ + { + name: "foo", + rewardAddress: ADDRESS_1, + totalSigningKeysCount: 10, + depositedSigningKeysCount: 5, + exitedSigningKeysCount: 1, + vettedSigningKeysCount: 6, + stuckValidatorsCount: 0, + refundedValidatorsCount: 0, + stuckPenaltyEndAt: 0, + }, + { + name: " bar", + rewardAddress: ADDRESS_2, + totalSigningKeysCount: 15, + depositedSigningKeysCount: 7, + exitedSigningKeysCount: 0, + vettedSigningKeysCount: 10, + stuckValidatorsCount: 0, + refundedValidatorsCount: 0, + stuckPenaltyEndAt: 0, + }, + { + name: "deactivated", + isActive: false, + rewardAddress: ADDRESS_3, + totalSigningKeysCount: 10, + depositedSigningKeysCount: 0, + exitedSigningKeysCount: 0, + vettedSigningKeysCount: 5, + stuckValidatorsCount: 0, + refundedValidatorsCount: 0, + stuckPenaltyEndAt: 0, + }, +]; +describe("NodeOperatorsRegistry:targetLimitMode", () => { + let deployer: HardhatEthersSigner; + let user: HardhatEthersSigner; + let stranger: HardhatEthersSigner; + + let limitsManager: HardhatEthersSigner; + let nodeOperatorsManager: HardhatEthersSigner; + let signingKeysManager: HardhatEthersSigner; + let stakingRouter: HardhatEthersSigner; + let lido: Lido; + let dao: Kernel; + let acl: ACL; + let locator: LidoLocator; + + let nor: NodeOperatorsRegistryMock; + + beforeEach(async () => { + [deployer, user, stranger, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager] = + await ethers.getSigners(); + + ({ lido, dao, acl } = await deployLidoDao({ + rootAccount: deployer, + initialized: true, + locatorConfig: { + stakingRouter, + }, + })); + + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + const impl = await new NodeOperatorsRegistryMock__factory(allocLibAddr, deployer).deploy(); + const norProxy = await addAragonApp({ + dao, + name: "node-operators-registry", + impl, + rootAccount: deployer, + }); + + nor = NodeOperatorsRegistryMock__factory.connect(norProxy, deployer); + + await acl.createPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), deployer); + await acl.createPermission(signingKeysManager, nor, await nor.MANAGE_SIGNING_KEYS(), deployer); + await acl.createPermission(nodeOperatorsManager, nor, await nor.MANAGE_NODE_OPERATOR_ROLE(), deployer); + await acl.createPermission(limitsManager, nor, await nor.SET_NODE_OPERATOR_LIMIT_ROLE(), deployer); + + // grant role to app itself cause it uses solidity's call method to itself + // inside the testing_requestValidatorsKeysForDeposits() method + await acl.grantPermission(nor, nor, await nor.STAKING_ROUTER_ROLE()); + + locator = LidoLocator__factory.connect(await lido.getLidoLocator(), user); + + await expect(nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( + "CONTRACT_NOT_INITIALIZED", + ); + + // Initialize the app's proxy. + await expect(nor.initialize(locator, CURATED_TYPE, PENALTY_DELAY)) + .to.emit(nor, "ContractVersionSet") + .withArgs(2) + .and.to.emit(nor, "LocatorContractSet") + .withArgs(locator) + .and.to.emit(nor, "StakingModuleTypeSet") + .withArgs(CURATED_TYPE); + + // Implementation initializer reverts because initialization block was set to max(uint256) + // in the Autopetrified base contract + await expect(impl.connect(stranger).initialize(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( + "INIT_ALREADY_INITIALIZED", + ); + + nor = nor.connect(user); + }); + + context("updateTargetValidatorsLimits", () => { + const firstNodeOperatorId = 0; + const secondNodeOperatorId = 1; + let targetLimitMode = 0; + let targetLimit = 0; + + beforeEach(async () => { + expect(await addNodeOperator(nor, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal(firstNodeOperatorId); + expect(await addNodeOperator(nor, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal(secondNodeOperatorId); + }); + + it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { + const hasPermission = await dao.hasPermission(stranger, nor, await nor.STAKING_ROUTER_ROLE(), "0x"); + expect(hasPermission).to.be.false; + await expect( + nor.updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + ).to.be.revertedWith("APP_AUTH_FAILED"); + }); + + it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { + const targetLimitWrong = BigInt("0x10000000000000000"); + + await expect( + nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimitWrong), + ).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it("updates node operator target limit if called by sender with STAKING_ROUTER_ROLE", async () => { + const hasPermission = await dao.hasPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), "0x"); + expect(hasPermission).to.be.true; + + targetLimitMode = 1; + targetLimit = 10; + + await expect( + nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + ) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + + const keysStatTotal = await nor.getStakingModuleSummary(); + const expectedExitedValidatorsCount = + NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount + + NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; + expect(keysStatTotal.totalExitedValidators).to.equal(expectedExitedValidatorsCount); + + const expectedDepositedValidatorsCount = + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount + + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; + expect(keysStatTotal.totalDepositedValidators).to.equal(expectedDepositedValidatorsCount); + + const firstNodeOperatorDepositableValidators = + NODE_OPERATORS[firstNodeOperatorId].vettedSigningKeysCount - + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount; + + const secondNodeOperatorDepositableValidators = + NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; + + const expectedDepositableValidatorsCount = + Math.min(targetLimit, firstNodeOperatorDepositableValidators) + secondNodeOperatorDepositableValidators; + expect(keysStatTotal.depositableValidatorsCount).to.equal(expectedDepositableValidatorsCount); + }); + + it("updates node operator target limit mode correctly", async () => { + const hasPermission = await dao.hasPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), "0x"); + expect(hasPermission).to.be.true; + + targetLimitMode = 1; + targetLimit = 10; + + await expect( + nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + ) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(targetLimitMode); + + targetLimitMode = 2; + await expect( + nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + ) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(targetLimitMode); + }); + }); +}); + +interface NodeOperatorConfig { + name: string; + rewardAddress: string; + totalSigningKeysCount: number; + depositedSigningKeysCount: number; + exitedSigningKeysCount: number; + vettedSigningKeysCount: number; + stuckValidatorsCount: number; + refundedValidatorsCount: number; + stuckPenaltyEndAt: number; + isActive?: boolean; +} + +/*** + * Adds new Node Operator to the registry and configures it + * @param {object} norMock Node operators registry mocked instance + * @param {object} config Configuration of the added node operator + * @param {string} config.name Name of the new node operator + * @param {string} config.rewardAddress Reward address of the new node operator + * @param {number} config.totalSigningKeysCount Count of the validators in the new node operator + * @param {number} config.depositedSigningKeysCount Count of used signing keys in the new node operator + * @param {number} config.exitedSigningKeysCount Count of stopped signing keys in the new node operator + * @param {number} config.vettedSigningKeysCount Staking limit of the new node operator + * @param {number} config.stuckValidatorsCount Stuck keys count of the new node operator + * @param {number} config.refundedValidatorsKeysCount Repaid keys count of the new node operator + * @param {number} config.isActive The active state of new node operator + * @returns {bigint} newOperatorId Id of newly added Node Operator + */ +async function addNodeOperator(norMock: NodeOperatorsRegistryMock, config: NodeOperatorConfig): Promise { + const isActive = config.isActive === undefined ? true : config.isActive; + + if (config.vettedSigningKeysCount < config.depositedSigningKeysCount) { + throw new Error("Invalid keys config: vettedSigningKeysCount < depositedSigningKeysCount"); + } + + if (config.vettedSigningKeysCount > config.totalSigningKeysCount) { + throw new Error("Invalid keys config: vettedSigningKeysCount > totalSigningKeysCount"); + } + + if (config.exitedSigningKeysCount > config.depositedSigningKeysCount) { + throw new Error("Invalid keys config: depositedSigningKeysCount < exitedSigningKeysCount"); + } + + if (config.stuckValidatorsCount > config.depositedSigningKeysCount - config.exitedSigningKeysCount) { + throw new Error("Invalid keys config: stuckValidatorsCount > depositedSigningKeysCount - exitedSigningKeysCount"); + } + + if (config.totalSigningKeysCount < config.exitedSigningKeysCount + config.depositedSigningKeysCount) { + throw new Error("Invalid keys config: totalSigningKeys < stoppedValidators + usedSigningKeys"); + } + + const newOperatorId = await norMock.getNodeOperatorsCount(); + await norMock.testing_addNodeOperator( + config.name, + config.rewardAddress, + config.totalSigningKeysCount, + config.vettedSigningKeysCount, + config.depositedSigningKeysCount, + config.exitedSigningKeysCount, + ); + await norMock.testing_setNodeOperatorLimits( + newOperatorId, + config.stuckValidatorsCount, + config.refundedValidatorsCount, + config.stuckPenaltyEndAt, + ); + + if (!isActive) { + await norMock.testing_unsafeDeactivateNodeOperator(newOperatorId); + } + + const nodeOperatorsSummary = await norMock.getNodeOperatorSummary(newOperatorId); + const nodeOperator = await norMock.getNodeOperator(newOperatorId, true); + + if (isActive) { + expect(nodeOperator.totalVettedValidators).to.equal(config.vettedSigningKeysCount); + expect(nodeOperator.totalAddedValidators).to.equal(config.totalSigningKeysCount); + expect(nodeOperatorsSummary.totalExitedValidators).to.equal(config.exitedSigningKeysCount); + expect(nodeOperatorsSummary.totalDepositedValidators).to.equal(config.depositedSigningKeysCount); + expect(nodeOperatorsSummary.depositableValidatorsCount).to.equal( + config.vettedSigningKeysCount - config.depositedSigningKeysCount, + ); + } else { + expect(nodeOperatorsSummary.totalExitedValidators).to.equal(config.exitedSigningKeysCount); + expect(nodeOperatorsSummary.totalDepositedValidators).to.equal(config.depositedSigningKeysCount); + expect(nodeOperatorsSummary.depositableValidatorsCount).to.equal(0); + } + return newOperatorId; +} From 7045b119e4fffe8f0e9249a64ab27cd8d7c54c42 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 23:48:51 +0200 Subject: [PATCH 034/177] fix: target limit update event --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index ee65fe219..bc68889de 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -638,14 +638,17 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// @param _nodeOperatorId Id of the node operator /// @param _targetLimit Target limit of the node operator /// @param _targetLimitMode target limit mode, >1 means forced target limit - function updateTargetValidatorsLimits(uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit) external { + function updateTargetValidatorsLimits(uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit) public { _onlyExistedNodeOperator(_nodeOperatorId); _auth(STAKING_ROUTER_ROLE); _requireValidRange(_targetLimit <= UINT64_MAX); Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); operatorTargetStats.set(TARGET_LIMIT_MODE_OFFSET, _targetLimitMode); - operatorTargetStats.set(TARGET_VALIDATORS_COUNT_OFFSET, _targetLimitMode > 0 ? _targetLimit : 0); + if (_targetLimitMode == 0) { + _targetLimit = 0; + } + operatorTargetStats.set(TARGET_VALIDATORS_COUNT_OFFSET, _targetLimit); _saveOperatorTargetValidatorsStats(_nodeOperatorId, operatorTargetStats); emit TargetValidatorsCountChanged(_nodeOperatorId, _targetLimit, _targetLimitMode); From 047fe3ab88dfac94bb349651bbeb2f87f8e9af64 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 15 Apr 2024 23:51:25 +0200 Subject: [PATCH 035/177] test: partial NOR tests migrate from old repo --- .../nor/node-operators-registry.test.ts | 281 ++++++++++++++++-- test/deploy/dao.ts | 12 +- 2 files changed, 260 insertions(+), 33 deletions(-) diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index 9abb3a3f1..a4c842700 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -1,4 +1,7 @@ +import assert from "node:assert"; + import { expect } from "chai"; +import { ZeroAddress } from "ethers"; import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; @@ -15,14 +18,14 @@ import { } from "typechain-types"; import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; -import { addAragonApp, deployLidoDao } from "lib"; +import { addAragonApp, deployLidoDao, hasPermission } from "lib"; const CURATED_TYPE = "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000"; // "curated-onchain-v1" const PENALTY_DELAY = 2 * 24 * 60 * 60; // 2 days const ADDRESS_1 = "0x0000000000000000000000000000000000000001"; const ADDRESS_2 = "0x0000000000000000000000000000000000000002"; const ADDRESS_3 = "0x0000000000000000000000000000000000000003"; -// const ADDRESS_4 = "0x0000000000000000000000000000000000000005"; +const ADDRESS_4 = "0x0000000000000000000000000000000000000005"; const NODE_OPERATORS: NodeOperatorConfig[] = [ { @@ -60,7 +63,8 @@ const NODE_OPERATORS: NodeOperatorConfig[] = [ stuckPenaltyEndAt: 0, }, ]; -describe("NodeOperatorsRegistry:targetLimitMode", () => { + +describe("NodeOperatorsRegistry", () => { let deployer: HardhatEthersSigner; let user: HardhatEthersSigner; let stranger: HardhatEthersSigner; @@ -74,6 +78,7 @@ describe("NodeOperatorsRegistry:targetLimitMode", () => { let acl: ACL; let locator: LidoLocator; + let impl: NodeOperatorsRegistryMock; let nor: NodeOperatorsRegistryMock; beforeEach(async () => { @@ -92,32 +97,29 @@ describe("NodeOperatorsRegistry:targetLimitMode", () => { const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), }; - const impl = await new NodeOperatorsRegistryMock__factory(allocLibAddr, deployer).deploy(); - const norProxy = await addAragonApp({ + + impl = await new NodeOperatorsRegistryMock__factory(allocLibAddr, deployer).deploy(); + const appProxy = await addAragonApp({ dao, name: "node-operators-registry", impl, rootAccount: deployer, }); - nor = NodeOperatorsRegistryMock__factory.connect(norProxy, deployer); + nor = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); await acl.createPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), deployer); await acl.createPermission(signingKeysManager, nor, await nor.MANAGE_SIGNING_KEYS(), deployer); await acl.createPermission(nodeOperatorsManager, nor, await nor.MANAGE_NODE_OPERATOR_ROLE(), deployer); await acl.createPermission(limitsManager, nor, await nor.SET_NODE_OPERATOR_LIMIT_ROLE(), deployer); - // grant role to app itself cause it uses solidity's call method to itself + // grant role to nor itself cause it uses solidity's call method to itself // inside the testing_requestValidatorsKeysForDeposits() method await acl.grantPermission(nor, nor, await nor.STAKING_ROUTER_ROLE()); locator = LidoLocator__factory.connect(await lido.getLidoLocator(), user); - await expect(nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( - "CONTRACT_NOT_INITIALIZED", - ); - - // Initialize the app's proxy. + // Initialize the nor's proxy. await expect(nor.initialize(locator, CURATED_TYPE, PENALTY_DELAY)) .to.emit(nor, "ContractVersionSet") .withArgs(2) @@ -126,15 +128,221 @@ describe("NodeOperatorsRegistry:targetLimitMode", () => { .and.to.emit(nor, "StakingModuleTypeSet") .withArgs(CURATED_TYPE); - // Implementation initializer reverts because initialization block was set to max(uint256) - // in the Autopetrified base contract - await expect(impl.connect(stranger).initialize(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( - "INIT_ALREADY_INITIALIZED", - ); - nor = nor.connect(user); }); + context("initialize", () => { + it("sets module type correctly", async () => { + expect(await nor.getType()).to.be.equal(CURATED_TYPE); + }); + + it("sets locator correctly", async () => { + expect(await nor.getLocator()).to.be.equal(locator); + }); + + it("sets contract version correctly", async () => { + expect(await nor.getContractVersion()).to.be.equal(2); + }); + + it("sets hasInitialized() to true", async () => { + expect(await nor.hasInitialized()).to.be.true; + }); + + it("can't be initialized second time", async () => { + await expect(nor.initialize(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith("INIT_ALREADY_INITIALIZED"); + }); + + it('reverts with error "ZERO_ADDRESS" when locator is zero address', async () => { + const appProxy = await addAragonApp({ + dao, + name: "new-node-operators-registry", + impl, + rootAccount: deployer, + }); + const registry = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); + await expect(registry.initialize(ZeroAddress, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith("ZERO_ADDRESS"); + }); + + it('call on implementation reverts with error "INIT_ALREADY_INITIALIZED"', async () => { + // Implementation initializer reverts because initialization block was set to max(uint256) + // in the Autopetrified base contract + await expect(impl.initialize(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( + "INIT_ALREADY_INITIALIZED", + ); + }); + }); + + context("finalizeUpgrade_v2()", () => { + beforeEach(async () => { + // reset version there to test upgrade finalization + await nor.testing_setBaseVersion(0); + }); + + it("fails with CONTRACT_NOT_INITIALIZED error when called on implementation", async () => { + await expect(impl.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( + "CONTRACT_NOT_INITIALIZED", + ); + }); + + it("fails with CONTRACT_NOT_INITIALIZED error when nor instance not initialized yet", async () => { + const appProxy = await addAragonApp({ + dao, + name: "new-node-operators-registry", + impl, + rootAccount: deployer, + }); + const registry = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); + await expect(registry.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( + "CONTRACT_NOT_INITIALIZED", + ); + }); + + it("sets correct contract version", async () => { + await nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY); + expect(await nor.getContractVersion()).to.be.equal(2); + }); + + it("reverts with error UNEXPECTED_CONTRACT_VERSION when called on already initialized contract", async () => { + await nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY); + expect(await nor.getContractVersion()).to.be.equal(2); + await expect(nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( + "UNEXPECTED_CONTRACT_VERSION", + ); + }); + }); + + context("setNodeOperatorName()", async () => { + const firstNodeOperatorId = 0; + const secondNodeOperatorId = 1; + + beforeEach(async () => { + expect(await addNodeOperator(nor, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal(firstNodeOperatorId); + expect(await addNodeOperator(nor, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal(secondNodeOperatorId); + }); + + it('reverts with "OUT_OF_RANGE" error when called on non existent node operator', async () => { + const notExitedNodeOperatorId = await nor.getNodeOperatorsCount(); + await expect( + nor.connect(nodeOperatorsManager).setNodeOperatorName(notExitedNodeOperatorId, "new name"), + ).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it('reverts with "WRONG_NAME_LENGTH" error when called with empty name', async () => { + await expect(nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, "")).to.be.revertedWith( + "WRONG_NAME_LENGTH", + ); + }); + + it('reverts with "WRONG_NAME_LENGTH" error when name exceeds MAX_NODE_OPERATOR_NAME_LENGTH', async () => { + const maxNameLength = await nor.MAX_NODE_OPERATOR_NAME_LENGTH(); + const tooLongName = "#".repeat(Number(maxNameLength) + 1); + assert(tooLongName.length > maxNameLength); + await expect( + nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, tooLongName), + ).to.be.revertedWith("WRONG_NAME_LENGTH"); + }); + + it('reverts with "APP_AUTH_FAILED" error when called by address without MANAGE_NODE_OPERATOR_ROLE', async () => { + expect(await hasPermission(dao, nor, "MANAGE_NODE_OPERATOR_ROLE", stranger)).to.be.false; + await expect(nor.connect(stranger).setNodeOperatorName(firstNodeOperatorId, "new name")).to.be.revertedWith( + "APP_AUTH_FAILED", + ); + }); + + it('reverts with "VALUE_IS_THE_SAME" error when called with the same name', async () => { + const { name: currentName } = await nor.getNodeOperator(firstNodeOperatorId, true); + await expect( + nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, currentName), + ).to.be.revertedWith("VALUE_IS_THE_SAME"); + }); + + it("updates the node operator name", async () => { + const newName = "new name"; + await nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, newName); + const { name: nameAfter } = await nor.getNodeOperator(firstNodeOperatorId, true); + expect(nameAfter).to.be.equal(newName); + }); + + it("emits NodeOperatorNameSet event with correct params", async () => { + const newName = "new name"; + await expect(nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, newName)) + .to.emit(nor, "NodeOperatorNameSet") + .withArgs(firstNodeOperatorId, newName); + }); + + it("doesn't affect the names of other node operators", async () => { + const newName = "new name"; + const { name: anotherNodeOperatorNameBefore } = await nor.getNodeOperator(secondNodeOperatorId, true); + await nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, newName); + const { name: anotherNodeOperatorNameAfter } = await nor.getNodeOperator(secondNodeOperatorId, true); + expect(anotherNodeOperatorNameBefore).to.be.equal(anotherNodeOperatorNameAfter); + }); + }); + + context("setNodeOperatorRewardAddress()", async () => { + const firstNodeOperatorId = 0; + const secondNodeOperatorId = 1; + const notExistedNodeOperatorId = 2; + + beforeEach(async () => { + expect(await addNodeOperator(nor, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal(firstNodeOperatorId); + expect(await addNodeOperator(nor, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal(secondNodeOperatorId); + }); + + it('reverts with "OUT_OF_RANGE" error when called on non existent node operator', async () => { + await expect( + nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(notExistedNodeOperatorId, ADDRESS_4), + ).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it('reverts with "ZERO_ADDRESS" error when new address is zero', async () => { + await expect( + nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ZeroAddress), + ).to.be.revertedWith("ZERO_ADDRESS"); + }); + + it('reverts with error "LIDO_REWARD_ADDRESS" when new reward address is lido', async () => { + await expect( + nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, lido), + ).to.be.revertedWith("LIDO_REWARD_ADDRESS"); + }); + + it(`reverts with "APP_AUTH_FAILED" error when caller doesn't have MANAGE_NODE_OPERATOR_ROLE`, async () => { + expect(await hasPermission(dao, nor, "MANAGE_NODE_OPERATOR_ROLE", stranger)).to.be.false; + await expect( + nor.connect(stranger).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4), + ).to.be.revertedWith("APP_AUTH_FAILED"); + }); + + it(`reverts with "VALUE_IS_THE_SAME" error when new reward address is the same`, async () => { + const nodeOperator = await nor.getNodeOperator(firstNodeOperatorId, false); + await expect( + nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, nodeOperator.rewardAddress), + ).to.be.revertedWith("VALUE_IS_THE_SAME"); + }); + + it("updates the reward address of the node operator", async () => { + const { rewardAddress: rewardAddressBefore } = await nor.getNodeOperator(firstNodeOperatorId, false); + expect(rewardAddressBefore).to.be.not.equal(ADDRESS_4); + await nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4); + const { rewardAddress: rewardAddressAfter } = await nor.getNodeOperator(firstNodeOperatorId, false); + expect(rewardAddressAfter).to.be.equal(ADDRESS_4); + }); + + it('emits "NodeOperatorRewardAddressSet" event with correct params', async () => { + await expect(nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4)) + .to.emit(nor, "NodeOperatorRewardAddressSet") + .withArgs(firstNodeOperatorId, ADDRESS_4); + }); + + it("doesn't affect other node operators reward addresses", async () => { + const { rewardAddress: rewardAddressBefore } = await nor.getNodeOperator(secondNodeOperatorId, false); + await nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4); + const { rewardAddress: rewardAddressAfter } = await nor.getNodeOperator(secondNodeOperatorId, false); + expect(rewardAddressAfter).to.be.equal(rewardAddressBefore); + }); + }); + context("updateTargetValidatorsLimits", () => { const firstNodeOperatorId = 0; const secondNodeOperatorId = 1; @@ -147,8 +355,7 @@ describe("NodeOperatorsRegistry:targetLimitMode", () => { }); it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { - const hasPermission = await dao.hasPermission(stranger, nor, await nor.STAKING_ROUTER_ROLE(), "0x"); - expect(hasPermission).to.be.false; + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; await expect( nor.updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), ).to.be.revertedWith("APP_AUTH_FAILED"); @@ -163,8 +370,7 @@ describe("NodeOperatorsRegistry:targetLimitMode", () => { }); it("updates node operator target limit if called by sender with STAKING_ROUTER_ROLE", async () => { - const hasPermission = await dao.hasPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), "0x"); - expect(hasPermission).to.be.true; + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; targetLimitMode = 1; targetLimit = 10; @@ -200,29 +406,40 @@ describe("NodeOperatorsRegistry:targetLimitMode", () => { }); it("updates node operator target limit mode correctly", async () => { - const hasPermission = await dao.hasPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), "0x"); - expect(hasPermission).to.be.true; + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - targetLimitMode = 1; + const targetLimitMode1 = 1; + const targetLimitMode2 = 1; targetLimit = 10; await expect( - nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode1, targetLimit), ) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode1); let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(targetLimitMode); + expect(noSummary.targetLimitMode).to.equal(targetLimitMode1); - targetLimitMode = 2; await expect( - nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + nor.connect(stakingRouter).updateTargetValidatorsLimits(secondNodeOperatorId, targetLimitMode2, targetLimit), ) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + .withArgs(secondNodeOperatorId, targetLimit, targetLimitMode2); + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); + + // reset limit + await expect(nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, 0, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0, 0); // expect limit set to 0 + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(targetLimitMode); + expect(noSummary.targetLimitMode).to.equal(0); + + // mode for 2nt NO is not changed + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); }); }); }); diff --git a/test/deploy/dao.ts b/test/deploy/dao.ts index 3d7cf0a76..469e4edaf 100644 --- a/test/deploy/dao.ts +++ b/test/deploy/dao.ts @@ -1,4 +1,4 @@ -import { BaseContract } from "ethers"; +import { AddressLike, BaseContract, BytesLike } from "ethers"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; @@ -85,3 +85,13 @@ export async function deployLidoDao({ rootAccount, initialized, locatorConfig = return { lido, dao, acl }; } + +export async function hasPermission( + dao: Kernel, + app: BaseContract, + role: string, + who: AddressLike, + how: BytesLike = "0x", +): Promise { + return dao.hasPermission(who, app, await app.getFunction(role)(), how); +} From 27f1182699d971b4d2025907380b157a9e057a95 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Tue, 16 Apr 2024 19:19:18 +0300 Subject: [PATCH 036/177] feat: dsm 1.5 initial commit --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 59 +- contracts/0.8.9/DepositSecurityModule.sol | 259 ++-- contracts/0.8.9/StakingRouter.sol | 178 ++- contracts/0.8.9/interfaces/IStakingModule.sol | 12 +- contracts/0.8.9/test_helpers/ModuleSolo.sol | 5 + .../0.8.9/test_helpers/StakingModuleMock.sol | 18 + lib/dsm.ts | 50 +- ...kingRouterMockForDepositSecurityModule.sol | 31 + test/0.8.9/depositSecurityModule.test.ts | 1173 +++++++++-------- 9 files changed, 1061 insertions(+), 724 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index bc68889de..3d21456f9 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -416,6 +416,55 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _authP(SET_NODE_OPERATOR_LIMIT_ROLE, arr(uint256(_nodeOperatorId), uint256(_vettedSigningKeysCount))); _onlyCorrectNodeOperatorState(getNodeOperatorIsActive(_nodeOperatorId)); + _updateVettedSingingKeysCount(_nodeOperatorId, _vettedSigningKeysCount, true /* _allowIncrease */); + _increaseValidatorsKeysNonce(); + } + + /// @notice Called by StakingRouter to decrease the number of vetted keys for node operator with given id + /// @param _nodeOperatorIds bytes packed array of the node operators id + /// @param _vettedSigningKeysCounts bytes packed array of the new number of vetted keys for the node operators + function decreaseVettedSigningKeysCount( + bytes _nodeOperatorIds, + bytes _vettedSigningKeysCounts + ) external { + _auth(STAKING_ROUTER_ROLE); + uint256 nodeOperatorsCount = _checkReportPayload(_nodeOperatorIds.length, _vettedSigningKeysCounts.length); + uint256 totalNodeOperatorsCount = getNodeOperatorsCount(); + + uint256 nodeOperatorId; + uint256 vettedKeysCount; + uint256 _nodeOperatorIdsOffset; + uint256 _vettedKeysCountsOffset; + + /// @dev calldata layout: + /// | func sig (4 bytes) | ABI-enc data | + /// + /// ABI-enc data: + /// + /// | 32 bytes | 32 bytes | 32 bytes | ... | 32 bytes | ...... | + /// | ids len offset | counts len offset | ids len | ids | counts len | counts | + assembly { + _nodeOperatorIdsOffset := add(calldataload(4), 36) // arg1 calldata offset + 4 (signature len) + 32 (length slot) + _vettedKeysCountsOffset := add(calldataload(36), 36) // arg2 calldata offset + 4 (signature len) + 32 (length slot)) + } + for (uint256 i; i < nodeOperatorsCount;) { + /// @solidity memory-safe-assembly + assembly { + nodeOperatorId := shr(192, calldataload(add(_nodeOperatorIdsOffset, mul(i, 8)))) + vettedKeysCount := shr(128, calldataload(add(_vettedKeysCountsOffset, mul(i, 16)))) + i := add(i, 1) + } + _requireValidRange(nodeOperatorId < totalNodeOperatorsCount); + _updateVettedSingingKeysCount(nodeOperatorId, vettedKeysCount, false /* only decrease */); + } + _increaseValidatorsKeysNonce(); + } + + function _updateVettedSingingKeysCount( + uint256 _nodeOperatorId, + uint256 _vettedSigningKeysCount, + bool _allowIncrease + ) internal { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); uint256 vettedSigningKeysCountBefore = signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET); uint256 depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); @@ -425,9 +474,12 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { totalSigningKeysCount, Math256.max(_vettedSigningKeysCount, depositedSigningKeysCount) ); - if (vettedSigningKeysCountAfter == vettedSigningKeysCountBefore) { - return; - } + if (vettedSigningKeysCountAfter == vettedSigningKeysCountBefore) return; + + require( + _allowIncrease || vettedSigningKeysCountAfter < vettedSigningKeysCountBefore, + "VETTED_KEYS_COUNT_INCREASED" + ); signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCountAfter); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); @@ -435,7 +487,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { emit VettedSigningKeysCountChanged(_nodeOperatorId, vettedSigningKeysCountAfter); _updateSummaryMaxValidatorsCount(_nodeOperatorId); - _increaseValidatorsKeysNonce(); } /// @notice Called by StakingRouter to signal that stETH rewards were minted for this module. diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 2cc0a4194..5e7652580 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -7,11 +7,7 @@ pragma solidity 0.8.9; import {ECDSA} from "../common/lib/ECDSA.sol"; interface ILido { - function deposit( - uint256 _maxDepositsCount, - uint256 _stakingModuleId, - bytes calldata _depositCalldata - ) external; + function deposit(uint256 _maxDepositsCount, uint256 _stakingModuleId, bytes calldata _depositCalldata) external; function canDeposit() external view returns (bool); } @@ -20,13 +16,18 @@ interface IDepositContract { } interface IStakingRouter { - function pauseStakingModule(uint256 _stakingModuleId) external; - function resumeStakingModule(uint256 _stakingModuleId) external; + function getStakingModuleMinDepositBlockDistance(uint256 _stakingModuleId) external view returns (uint256); + function getStakingModuleMaxDepositsPerBlock(uint256 _stakingModuleId) external view returns (uint256); function getStakingModuleIsDepositsPaused(uint256 _stakingModuleId) external view returns (bool); function getStakingModuleIsActive(uint256 _stakingModuleId) external view returns (bool); function getStakingModuleNonce(uint256 _stakingModuleId) external view returns (uint256); function getStakingModuleLastDepositBlock(uint256 _stakingModuleId) external view returns (uint256); function hasStakingModule(uint256 _stakingModuleId) external view returns (bool); + function decreaseStakingModuleVettedKeysCountByNodeOperator( + uint256 _stakingModuleId, + bytes calldata _nodeOperatorIds, + bytes calldata _vettedSigningKeysCounts + ) external; } @@ -42,13 +43,14 @@ contract DepositSecurityModule { event OwnerChanged(address newValue); event PauseIntentValidityPeriodBlocksChanged(uint256 newValue); - event MaxDepositsChanged(uint256 newValue); - event MinDepositBlockDistanceChanged(uint256 newValue); + event UnvetIntentValidityPeriodBlocksChanged(uint256 newValue); + event MaxOperatorsPerUnvettingChanged(uint256 newValue); event GuardianQuorumChanged(uint256 newValue); event GuardianAdded(address guardian); event GuardianRemoved(address guardian); - event DepositsPaused(address indexed guardian, uint24 indexed stakingModuleId); - event DepositsUnpaused(uint24 indexed stakingModuleId); + event DepositsPaused(address indexed guardian); + event DepositsUnpaused(); + event LastDepositBlockChanged(uint256 newValue); error ZeroAddress(string field); error DuplicateAddress(address addr); @@ -60,26 +62,30 @@ contract DepositSecurityModule { error DepositInactiveModule(); error DepositTooFrequent(); error DepositUnexpectedBlockHash(); - error DepositNonceChanged(); + error DepositsNotPaused(); + error ModuleNonceChanged(); error PauseIntentExpired(); + error UnvetIntentExpired(); + error UnvetPayloadInvalid(); + error UnvetUnexpectedBlockHash(); error NotAGuardian(address addr); error ZeroParameter(string parameter); bytes32 public immutable ATTEST_MESSAGE_PREFIX; bytes32 public immutable PAUSE_MESSAGE_PREFIX; + bytes32 public immutable UNVET_MESSAGE_PREFIX; ILido public immutable LIDO; IStakingRouter public immutable STAKING_ROUTER; IDepositContract public immutable DEPOSIT_CONTRACT; - /** - * NB: both `maxDepositsPerBlock` and `minDepositBlockDistance` values - * must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit` - * (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function) - */ - uint256 internal maxDepositsPerBlock; - uint256 internal minDepositBlockDistance; + bool public isDepositsPaused; + + uint256 internal lastDepositBlock; + uint256 internal pauseIntentValidityPeriodBlocks; + uint256 internal unvetIntentValidityPeriodBlocks; + uint256 internal maxOperatorsPerUnvetting; address internal owner; @@ -91,13 +97,13 @@ contract DepositSecurityModule { address _lido, address _depositContract, address _stakingRouter, - uint256 _maxDepositsPerBlock, - uint256 _minDepositBlockDistance, - uint256 _pauseIntentValidityPeriodBlocks + uint256 _pauseIntentValidityPeriodBlocks, + uint256 _unvetIntentValidityPeriodBlocks, + uint256 _maxOperatorsPerUnvetting ) { if (_lido == address(0)) revert ZeroAddress("_lido"); - if (_depositContract == address(0)) revert ZeroAddress ("_depositContract"); - if (_stakingRouter == address(0)) revert ZeroAddress ("_stakingRouter"); + if (_depositContract == address(0)) revert ZeroAddress("_depositContract"); + if (_stakingRouter == address(0)) revert ZeroAddress("_stakingRouter"); LIDO = ILido(_lido); STAKING_ROUTER = IStakingRouter(_stakingRouter); @@ -121,10 +127,20 @@ contract DepositSecurityModule { ) ); + UNVET_MESSAGE_PREFIX = keccak256( + abi.encodePacked( + // keccak256("lido.DepositSecurityModule.UNVET_MESSAGE") + bytes32(0x2dd9727393562ed11c29080a884630e2d3a7078e71b313e713a8a1ef68948f6a), + block.chainid, + address(this) + ) + ); + _setOwner(msg.sender); - _setMaxDeposits(_maxDepositsPerBlock); - _setMinDepositBlockDistance(_minDepositBlockDistance); + _setLastDepositBlock(block.number); _setPauseIntentValidityPeriodBlocks(_pauseIntentValidityPeriodBlocks); + _setUnvetIntentValidityPeriodBlocks(_unvetIntentValidityPeriodBlocks); + _setMaxOperatorsPerUnvetting(_maxOperatorsPerUnvetting); } /** @@ -173,54 +189,48 @@ contract DepositSecurityModule { } /** - * Returns `maxDepositsPerBlock` (see `depositBufferedEther`). + * Returns current `unvetIntentValidityPeriodBlocks` contract parameter (see `unvetSigningKeys`). */ - function getMaxDeposits() external view returns (uint256) { - return maxDepositsPerBlock; + function getUnvetIntentValidityPeriodBlocks() external view returns (uint256) { + return unvetIntentValidityPeriodBlocks; } /** - * Sets `maxDepositsPerBlock`. Only callable by the owner. - * - * NB: the value must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit` - * (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function) + * Sets `unvetIntentValidityPeriodBlocks`. Only callable by the owner. */ - function setMaxDeposits(uint256 newValue) external onlyOwner { - _setMaxDeposits(newValue); + function setUnvetIntentValidityPeriodBlocks(uint256 newValue) external onlyOwner { + _setUnvetIntentValidityPeriodBlocks(newValue); } - function _setMaxDeposits(uint256 newValue) internal { - maxDepositsPerBlock = newValue; - emit MaxDepositsChanged(newValue); + function _setUnvetIntentValidityPeriodBlocks(uint256 newValue) internal { + if (newValue == 0) revert ZeroParameter("unvetIntentValidityPeriodBlocks"); + unvetIntentValidityPeriodBlocks = newValue; + emit UnvetIntentValidityPeriodBlocksChanged(newValue); } + /** - * Returns `minDepositBlockDistance` (see `depositBufferedEther`). + * Returns current `maxOperatorsPerUnvetting` contract parameter (see `unvetSigningKeys`). */ - function getMinDepositBlockDistance() external view returns (uint256) { - return minDepositBlockDistance; + function getMaxOperatorsPerUnvetting() external view returns (uint256) { + return maxOperatorsPerUnvetting; } /** - * Sets `minDepositBlockDistance`. Only callable by the owner. - * - * NB: the value must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit` - * (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function) + * Sets `maxOperatorsPerUnvetting`. Only callable by the owner. */ - function setMinDepositBlockDistance(uint256 newValue) external onlyOwner { - _setMinDepositBlockDistance(newValue); + function setMaxOperatorsPerUnvetting(uint256 newValue) external onlyOwner { + _setMaxOperatorsPerUnvetting(newValue); } - function _setMinDepositBlockDistance(uint256 newValue) internal { - if (newValue == 0) revert ZeroParameter("minDepositBlockDistance"); - if (newValue != minDepositBlockDistance) { - minDepositBlockDistance = newValue; - emit MinDepositBlockDistanceChanged(newValue); - } + function _setMaxOperatorsPerUnvetting(uint256 newValue) internal { + if (newValue == 0) revert ZeroParameter("maxOperatorsPerUnvetting"); + maxOperatorsPerUnvetting = newValue; + emit MaxOperatorsPerUnvettingChanged(newValue); } /** - * Returns number of valid guardian signatures required to vet (depositRoot, nonce) pair. + * Returns number of valid guardian signatures required to attest (depositRoot, nonce) pair. */ function getGuardianQuorum() external view returns (uint256) { return quorum; @@ -326,7 +336,7 @@ contract DepositSecurityModule { } /** - * Pauses deposits for staking module given that both conditions are satisfied (reverts otherwise): + * Pauses deposits if both conditions are satisfied (reverts otherwise): * * 1. The function is called by the guardian with index guardianIndex OR sig * is a valid signature by the guardian with index guardianIndex of the data @@ -337,50 +347,41 @@ contract DepositSecurityModule { * The signature, if present, must be produced for keccak256 hash of the following * message (each component taking 32 bytes): * - * | PAUSE_MESSAGE_PREFIX | blockNumber | stakingModuleId | + * | PAUSE_MESSAGE_PREFIX | blockNumber | */ - function pauseDeposits( - uint256 blockNumber, - uint256 stakingModuleId, - Signature memory sig - ) external { + function pauseDeposits(uint256 blockNumber, Signature memory sig) external { // In case of an emergency function `pauseDeposits` is supposed to be called // by all guardians. Thus only the first call will do the actual change. But // the other calls would be OK operations from the point of view of protocol’s logic. // Thus we prefer not to use “error” semantics which is implied by `require`. - /// @dev pause only active modules (not already paused, nor full stopped) - if (!STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId)) { - return; - } + if (isDepositsPaused) return; address guardianAddr = msg.sender; int256 guardianIndex = _getGuardianIndex(msg.sender); if (guardianIndex == -1) { - bytes32 msgHash = keccak256(abi.encodePacked(PAUSE_MESSAGE_PREFIX, blockNumber, stakingModuleId)); + bytes32 msgHash = keccak256(abi.encodePacked(PAUSE_MESSAGE_PREFIX, blockNumber)); guardianAddr = ECDSA.recover(msgHash, sig.r, sig.vs); guardianIndex = _getGuardianIndex(guardianAddr); if (guardianIndex == -1) revert InvalidSignature(); } - if (block.number - blockNumber > pauseIntentValidityPeriodBlocks) revert PauseIntentExpired(); + if (block.number - blockNumber > pauseIntentValidityPeriodBlocks) revert PauseIntentExpired(); - STAKING_ROUTER.pauseStakingModule(stakingModuleId); - emit DepositsPaused(guardianAddr, uint24(stakingModuleId)); + isDepositsPaused = true; + emit DepositsPaused(guardianAddr); } /** - * Unpauses deposits for staking module + * Unpauses deposits * * Only callable by the owner. */ - function unpauseDeposits(uint256 stakingModuleId) external onlyOwner { - /// @dev unpause only paused modules (skip stopped) - if (STAKING_ROUTER.getStakingModuleIsDepositsPaused(stakingModuleId)) { - STAKING_ROUTER.resumeStakingModule(stakingModuleId); - emit DepositsUnpaused(uint24(stakingModuleId)); - } + function unpauseDeposits() external onlyOwner { + if (!isDepositsPaused) revert DepositsNotPaused(); + isDepositsPaused = false; + emit DepositsUnpaused(); } /** @@ -392,16 +393,44 @@ contract DepositSecurityModule { if (!STAKING_ROUTER.hasStakingModule(stakingModuleId)) return false; bool isModuleActive = STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId); - uint256 lastDepositBlock = STAKING_ROUTER.getStakingModuleLastDepositBlock(stakingModuleId); + bool isDepositDistancePassed = _isMinDepositDistancePassed(stakingModuleId); bool isLidoCanDeposit = LIDO.canDeposit(); + return ( - isModuleActive + !isDepositsPaused + && isModuleActive && quorum > 0 - && block.number - lastDepositBlock >= minDepositBlockDistance + && isDepositDistancePassed && isLidoCanDeposit ); } + /** + * Returns the last block number when a deposit was made. + */ + function getLastDepositBlock() external view returns (uint256) { + return lastDepositBlock; + } + + function _setLastDepositBlock(uint256 newValue) internal { + lastDepositBlock = newValue; + emit LastDepositBlockChanged(newValue); + } + + /** + * Returns whether the deposit distance is greater than the minimum required. + */ + function isMinDepositDistancePassed(uint256 stakingModuleId) external view returns (bool) { + return _isMinDepositDistancePassed(stakingModuleId); + } + + function _isMinDepositDistancePassed(uint256 stakingModuleId) internal view returns (bool) { + uint256 lastDepositToModuleBlock = STAKING_ROUTER.getStakingModuleLastDepositBlock(stakingModuleId); + uint256 minDepositBlockDistance = STAKING_ROUTER.getStakingModuleMinDepositBlockDistance(stakingModuleId); + uint256 maxLastDepositBlock = lastDepositToModuleBlock >= lastDepositBlock ? lastDepositToModuleBlock : lastDepositBlock; + return block.number - maxLastDepositBlock >= minDepositBlockDistance; + } + /** * Calls LIDO.deposit(maxDepositsPerBlock, stakingModuleId, depositCalldata). * @@ -434,16 +463,82 @@ contract DepositSecurityModule { if (!STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId)) revert DepositInactiveModule(); - uint256 lastDepositBlock = STAKING_ROUTER.getStakingModuleLastDepositBlock(stakingModuleId); - if (block.number - lastDepositBlock < minDepositBlockDistance) revert DepositTooFrequent(); + uint256 maxDepositsPerBlock = STAKING_ROUTER.getStakingModuleMaxDepositsPerBlock(stakingModuleId); + + if (!_isMinDepositDistancePassed(stakingModuleId)) revert DepositTooFrequent(); if (blockHash == bytes32(0) || blockhash(blockNumber) != blockHash) revert DepositUnexpectedBlockHash(); uint256 onchainNonce = STAKING_ROUTER.getStakingModuleNonce(stakingModuleId); - if (nonce != onchainNonce) revert DepositNonceChanged(); + if (nonce != onchainNonce) revert ModuleNonceChanged(); _verifySignatures(depositRoot, blockNumber, blockHash, stakingModuleId, nonce, sortedGuardianSignatures); LIDO.deposit(maxDepositsPerBlock, stakingModuleId, depositCalldata); + _setLastDepositBlock(block.number); + } + + /** + * Unvetting signing keys for the given node operators. + * + * Reverts if any of the following is true: + * 1. nodeOperatorIds is not packed with 8 bytes per id. + * 2. vettedSigningKeysCounts is not packed with 16 bytes per count. + * 3. The number of node operators is greater than maxOperatorsPerUnvetting. + * 4. The nonce is not equal to the on-chain nonce of the staking module. + * 5. The signature is invalid or the signer is not a guardian. + * 6. block.number - blockNumber > unvetIntentValidityPeriodBlocks. + * + * The signature, if present, must be produced for keccak256 hash of the following message: + * + * | UNVET_MESSAGE_PREFIX | blockNumber | blockHash | stakingModuleId | nonce | nodeOperatorIds | vettedSigningKeysCounts | + */ + function unvetSigningKeys( + uint256 blockNumber, + bytes32 blockHash, + uint256 stakingModuleId, + uint256 nonce, + bytes calldata nodeOperatorIds, + bytes calldata vettedSigningKeysCounts, + Signature calldata sig + ) external { + uint256 onchainNonce = STAKING_ROUTER.getStakingModuleNonce(stakingModuleId); + if (nonce != onchainNonce) revert ModuleNonceChanged(); + + uint256 nodeOperatorsCount = nodeOperatorIds.length / 8; + + if ( + nodeOperatorIds.length % 8 != 0 || + vettedSigningKeysCounts.length % 16 != 0 || + vettedSigningKeysCounts.length / 16 != nodeOperatorsCount || + nodeOperatorsCount > maxOperatorsPerUnvetting + ) { + revert UnvetPayloadInvalid(); + } + + address guardianAddr = msg.sender; + int256 guardianIndex = _getGuardianIndex(msg.sender); + + if (guardianIndex == -1) { + bytes32 msgHash = keccak256(abi.encodePacked( + UNVET_MESSAGE_PREFIX, + blockNumber, + blockHash, + stakingModuleId, + nonce, + nodeOperatorIds, + vettedSigningKeysCounts + )); + guardianAddr = ECDSA.recover(msgHash, sig.r, sig.vs); + guardianIndex = _getGuardianIndex(guardianAddr); + if (guardianIndex == -1) revert InvalidSignature(); + } + + if (blockHash == bytes32(0) || blockhash(blockNumber) != blockHash) revert UnvetUnexpectedBlockHash(); + if (block.number - blockNumber > unvetIntentValidityPeriodBlocks) revert UnvetIntentExpired(); + + STAKING_ROUTER.decreaseStakingModuleVettedKeysCountByNodeOperator( + stakingModuleId, nodeOperatorIds, vettedSigningKeysCounts + ); } function _verifySignatures( diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 56cd3c5e2..4494d5fdd 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -24,6 +24,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version event StakingModuleFeesSet(uint256 indexed stakingModuleId, uint256 stakingModuleFee, uint256 treasuryFee, address setBy); event StakingModuleStatusSet(uint256 indexed stakingModuleId, StakingModuleStatus status, address setBy); event StakingModuleExitedValidatorsIncompleteReporting(uint256 indexed stakingModuleId, uint256 unreportedExitedValidatorsCount); + event StakingModuleMaxDepositsPerBlockSet( + uint256 indexed stakingModuleId, uint256 maxDepositsPerBlock, address setBy + ); + event StakingModuleMinDepositBlockDistanceSet( + uint256 indexed stakingModuleId, uint256 minDepositBlockDistance, address setBy + ); event WithdrawalCredentialsSet(bytes32 withdrawalCredentials, address setBy); event WithdrawalsCredentialsChangeFailed(uint256 indexed stakingModuleId, bytes lowLevelRevertData); event ExitedAndStuckValidatorsCountsUpdateFailed(uint256 indexed stakingModuleId, bytes lowLevelRevertData); @@ -60,11 +66,13 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength); error UnrecoverableModuleError(); error InvalidPriorityExitShareThreshold(); + error InvalidMinDepositBlockDistance(); enum StakingModuleStatus { Active, // deposits and rewards allowed DepositsPaused, // deposits NOT allowed, rewards allowed Stopped // deposits and rewards NOT allowed + } struct StakingModule { @@ -92,6 +100,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 lastDepositBlock; /// @notice number of exited validators uint256 exitedValidatorsCount; + /// @notice the maximum number of validators that can be deposited in a single block + uint64 maxDepositsPerBlock; + /// @notice the minimum distance between deposits in blocks + uint64 minDepositBlockDistance; } struct StakingModuleCache { @@ -109,6 +121,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version bytes32 public constant STAKING_MODULE_PAUSE_ROLE = keccak256("STAKING_MODULE_PAUSE_ROLE"); bytes32 public constant STAKING_MODULE_RESUME_ROLE = keccak256("STAKING_MODULE_RESUME_ROLE"); bytes32 public constant STAKING_MODULE_MANAGE_ROLE = keccak256("STAKING_MODULE_MANAGE_ROLE"); + bytes32 public constant STAKING_MODULE_VETTING_ROLE = keccak256("STAKING_MODULE_VETTING_ROLE"); bytes32 public constant REPORT_EXITED_VALIDATORS_ROLE = keccak256("REPORT_EXITED_VALIDATORS_ROLE"); bytes32 public constant UNSAFE_SET_EXITED_VALIDATORS_ROLE = keccak256("UNSAFE_SET_EXITED_VALIDATORS_ROLE"); bytes32 public constant REPORT_REWARDS_MINTED_ROLE = keccak256("REPORT_REWARDS_MINTED_ROLE"); @@ -175,6 +188,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * @param _priorityExitShareThreshold module's proirity exit share threshold * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards * @param _treasuryFee treasury fee + * @param _maxDepositsPerBlock the maximum number of validators that can be deposited in a single block + * @param _minDepositBlockDistance the minimum distance between deposits in blocks */ function addStakingModule( string calldata _name, @@ -182,12 +197,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 _stakeShareLimit, uint256 _priorityExitShareThreshold, uint256 _stakingModuleFee, - uint256 _treasuryFee + uint256 _treasuryFee, + uint256 _maxDepositsPerBlock, + uint256 _minDepositBlockDistance ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { - if (_stakeShareLimit > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakeShareLimit"); - if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_priorityExitShareThreshold"); - if (_stakeShareLimit > _priorityExitShareThreshold) revert InvalidPriorityExitShareThreshold(); - if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakingModuleFee + _treasuryFee"); if (_stakingModuleAddress == address(0)) revert ZeroAddress("_stakingModuleAddress"); if (bytes(_name).length == 0 || bytes(_name).length > MAX_STAKING_MODULE_NAME_LENGTH) revert StakingModuleWrongName(); @@ -210,10 +223,6 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version newStakingModule.id = newStakingModuleId; newStakingModule.name = _name; newStakingModule.stakingModuleAddress = _stakingModuleAddress; - newStakingModule.stakeShareLimit = uint16(_stakeShareLimit); - newStakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThreshold); - newStakingModule.stakingModuleFee = uint16(_stakingModuleFee); - newStakingModule.treasuryFee = uint16(_treasuryFee); /// @dev since `enum` is `uint8` by nature, so the `status` is stored as `uint8` to avoid /// possible problems when upgrading. But for human readability, we use `enum` as /// function parameter type. More about conversion in the docs @@ -232,8 +241,16 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version STAKING_MODULES_COUNT_POSITION.setStorageUint256(newStakingModuleIndex + 1); emit StakingModuleAdded(newStakingModuleId, _stakingModuleAddress, _name, msg.sender); - emit StakingModuleShareLimitSet(newStakingModuleId, _stakeShareLimit, _priorityExitShareThreshold, msg.sender); - emit StakingModuleFeesSet(newStakingModuleId, _stakingModuleFee, _treasuryFee, msg.sender); + _updateStakingModule( + newStakingModule, + newStakingModuleId, + _stakeShareLimit, + _priorityExitShareThreshold, + _stakingModuleFee, + _treasuryFee, + _maxDepositsPerBlock, + _minDepositBlockDistance + ); } /** @@ -243,28 +260,66 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * @param _priorityExitShareThreshold module's proirity exit share threshold * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards * @param _treasuryFee treasury fee + * @param _maxDepositsPerBlock the maximum number of validators that can be deposited in a single block + * @param _minDepositBlockDistance the minimum distance between deposits in blocks */ function updateStakingModule( uint256 _stakingModuleId, uint256 _stakeShareLimit, uint256 _priorityExitShareThreshold, uint256 _stakingModuleFee, - uint256 _treasuryFee + uint256 _treasuryFee, + uint256 _maxDepositsPerBlock, + uint256 _minDepositBlockDistance ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { - if (_stakeShareLimit > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakeShareLimit"); - if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_priorityExitShareThreshold"); - if (_stakeShareLimit > _priorityExitShareThreshold) revert InvalidPriorityExitShareThreshold(); - if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) revert ValueOver100Percent("_stakingModuleFee + _treasuryFee"); - StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); + _updateStakingModule( + stakingModule, + _stakingModuleId, + _stakeShareLimit, + _priorityExitShareThreshold, + _stakingModuleFee, + _treasuryFee, + _maxDepositsPerBlock, + _minDepositBlockDistance + ); + } + + function _updateStakingModule( + StakingModule storage stakingModule, + uint256 _stakingModuleId, + uint256 _stakeShareLimit, + uint256 _priorityExitShareThreshold, + uint256 _stakingModuleFee, + uint256 _treasuryFee, + uint256 _maxDepositsPerBlock, + uint256 _minDepositBlockDistance + ) internal { + if (_stakeShareLimit > TOTAL_BASIS_POINTS) { + revert ValueOver100Percent("_stakeShareLimit"); + } + if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) { + revert ValueOver100Percent("_priorityExitShareThreshold"); + } + if (_stakeShareLimit > _priorityExitShareThreshold) { + revert InvalidPriorityExitShareThreshold(); + } + if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) { + revert ValueOver100Percent("_stakingModuleFee + _treasuryFee"); + } + if (_minDepositBlockDistance == 0) revert InvalidMinDepositBlockDistance(); stakingModule.stakeShareLimit = uint16(_stakeShareLimit); stakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThreshold); stakingModule.treasuryFee = uint16(_treasuryFee); stakingModule.stakingModuleFee = uint16(_stakingModuleFee); + stakingModule.maxDepositsPerBlock = uint64(_maxDepositsPerBlock); + stakingModule.minDepositBlockDistance = uint64(_minDepositBlockDistance); emit StakingModuleShareLimitSet(_stakingModuleId, _stakeShareLimit, _priorityExitShareThreshold, msg.sender); emit StakingModuleFeesSet(_stakingModuleId, _stakingModuleFee, _treasuryFee, msg.sender); + emit StakingModuleMaxDepositsPerBlockSet(_stakingModuleId, _maxDepositsPerBlock, msg.sender); + emit StakingModuleMinDepositBlockDistanceSet(_stakingModuleId, _minDepositBlockDistance, msg.sender); } /// @notice Updates the limit of the validators that can be used for deposit @@ -278,9 +333,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 _targetLimitMode, uint256 _targetLimit ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { - address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; - IStakingModule(moduleAddr) - .updateTargetValidatorsLimits(_nodeOperatorId, _targetLimitMode, _targetLimit); + _getIStakingModuleById(_stakingModuleId).updateTargetValidatorsLimits( + _nodeOperatorId, _targetLimitMode, _targetLimit + ); } /// @notice Updates the number of the refunded validators in the staking module with the given @@ -293,9 +348,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 _nodeOperatorId, uint256 _refundedValidatorsCount ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { - address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; - IStakingModule(moduleAddr) - .updateRefundedValidatorsCount(_nodeOperatorId, _refundedValidatorsCount); + _getIStakingModuleById(_stakingModuleId).updateRefundedValidatorsCount( + _nodeOperatorId, _refundedValidatorsCount + ); } function reportRewardsMinted(uint256[] calldata _stakingModuleIds, uint256[] calldata _totalShares) @@ -308,8 +363,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version for (uint256 i = 0; i < _stakingModuleIds.length; ) { if (_totalShares[i] > 0) { - address moduleAddr = _getStakingModuleById(_stakingModuleIds[i]).stakingModuleAddress; - try IStakingModule(moduleAddr).onRewardsMinted(_totalShares[i]) {} + try _getIStakingModuleById(_stakingModuleIds[i]).onRewardsMinted(_totalShares[i]) {} catch (bytes memory lowLevelRevertData) { /// @dev This check is required to prevent incorrect gas estimation of the method. /// Without it, Ethereum nodes that use binary search for gas estimation may @@ -439,12 +493,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) { - address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _exitedValidatorsCounts); - IStakingModule(moduleAddr).updateExitedValidatorsCount( - _nodeOperatorIds, - _exitedValidatorsCounts - ); + _getIStakingModuleById(_stakingModuleId).updateExitedValidatorsCount(_nodeOperatorIds, _exitedValidatorsCounts); } struct ValidatorsCountsCorrection { @@ -494,8 +544,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version external onlyRole(UNSAFE_SET_EXITED_VALIDATORS_ROLE) { - StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); - address moduleAddr = stakingModule.stakingModuleAddress; + StakingModule storage stakingModuleState = _getStakingModuleById(_stakingModuleId); + IStakingModule stakingModule = IStakingModule(stakingModuleState.stakingModuleAddress); ( /* uint156 targetLimitMode */, @@ -506,29 +556,29 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 totalExitedValidators, /* uint256 totalDepositedValidators */, /* uint256 depositableValidatorsCount */ - ) = IStakingModule(moduleAddr).getNodeOperatorSummary(_nodeOperatorId); + ) = stakingModule.getNodeOperatorSummary(_nodeOperatorId); - if (_correction.currentModuleExitedValidatorsCount != stakingModule.exitedValidatorsCount || + if (_correction.currentModuleExitedValidatorsCount != stakingModuleState.exitedValidatorsCount || _correction.currentNodeOperatorExitedValidatorsCount != totalExitedValidators || _correction.currentNodeOperatorStuckValidatorsCount != stuckValidatorsCount ) { revert UnexpectedCurrentValidatorsCount( - stakingModule.exitedValidatorsCount, + stakingModuleState.exitedValidatorsCount, totalExitedValidators, stuckValidatorsCount ); } - stakingModule.exitedValidatorsCount = _correction.newModuleExitedValidatorsCount; + stakingModuleState.exitedValidatorsCount = _correction.newModuleExitedValidatorsCount; - IStakingModule(moduleAddr).unsafeUpdateValidatorsCount( + stakingModule.unsafeUpdateValidatorsCount( _nodeOperatorId, _correction.newNodeOperatorExitedValidatorsCount, _correction.newNodeOperatorStuckValidatorsCount ); if (_triggerUpdateFinish) { - IStakingModule(moduleAddr).onExitedAndStuckValidatorsCountsUpdated(); + stakingModule.onExitedAndStuckValidatorsCountsUpdated(); } } @@ -550,9 +600,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) { - address moduleAddr = _getStakingModuleById(_stakingModuleId).stakingModuleAddress; _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _stuckValidatorsCounts); - IStakingModule(moduleAddr).updateStuckValidatorsCount(_nodeOperatorIds, _stuckValidatorsCounts); + _getIStakingModuleById(_stakingModuleId).updateStuckValidatorsCount(_nodeOperatorIds, _stuckValidatorsCounts); } /// @notice Called by the oracle when the second phase of data reporting finishes, i.e. when the @@ -595,6 +644,24 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } + /// @notice decrese vetted signing keys counts per node operator for the staking module with + /// the specified id. + /// + /// @param _stakingModuleId The id of the staking modules to be updated. + /// @param _nodeOperatorIds Ids of the node operators to be updated. + /// @param _vettedSigningKeysCounts New counts of vetted signing keys for the specified node operators. + /// + function decreaseStakingModuleVettedKeysCountByNodeOperator( + uint256 _stakingModuleId, + bytes calldata _nodeOperatorIds, + bytes calldata _vettedSigningKeysCounts + ) external onlyRole(STAKING_MODULE_VETTING_ROLE) { + _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _vettedSigningKeysCounts); + _getIStakingModuleById(_stakingModuleId).decreaseVettedSigningKeysCount( + _nodeOperatorIds, _vettedSigningKeysCounts + ); + } + /** * @notice Returns all registered staking modules */ @@ -815,9 +882,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs /// for data aggregation function getAllNodeOperatorDigests(uint256 _stakingModuleId) external view returns (NodeOperatorDigest[] memory) { - IStakingModule stakingModule = IStakingModule(_getStakingModuleAddressById(_stakingModuleId)); - uint256 nodeOperatorsCount = stakingModule.getNodeOperatorsCount(); - return getNodeOperatorDigests(_stakingModuleId, 0, nodeOperatorsCount); + return getNodeOperatorDigests( + _stakingModuleId, 0, _getIStakingModuleById(_stakingModuleId).getNodeOperatorsCount() + ); } /// @notice Returns node operator digest for passed node operator ids in the given staking module @@ -831,9 +898,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 _offset, uint256 _limit ) public view returns (NodeOperatorDigest[] memory) { - IStakingModule stakingModule = IStakingModule(_getStakingModuleAddressById(_stakingModuleId)); - uint256[] memory nodeOperatorIds = stakingModule.getNodeOperatorIds(_offset, _limit); - return getNodeOperatorDigests(_stakingModuleId, nodeOperatorIds); + return getNodeOperatorDigests( + _stakingModuleId, _getIStakingModuleById(_stakingModuleId).getNodeOperatorIds(_offset, _limit) + ); } /// @notice Returns node operator digest for a slice of node operators registered in the given @@ -847,7 +914,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version view returns (NodeOperatorDigest[] memory digests) { - IStakingModule stakingModule = IStakingModule(_getStakingModuleAddressById(_stakingModuleId)); + IStakingModule stakingModule = _getIStakingModuleById(_stakingModuleId); digests = new NodeOperatorDigest[](_nodeOperatorIds.length); for (uint256 i = 0; i < _nodeOperatorIds.length; ++i) { digests[i] = NodeOperatorDigest({ @@ -914,7 +981,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } function getStakingModuleNonce(uint256 _stakingModuleId) external view returns (uint256) { - return IStakingModule(_getStakingModuleAddressById(_stakingModuleId)).getNonce(); + return _getIStakingModuleById(_stakingModuleId).getNonce(); } function getStakingModuleLastDepositBlock(uint256 _stakingModuleId) @@ -922,8 +989,15 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version view returns (uint256) { - StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); - return stakingModule.lastDepositBlock; + return _getStakingModuleById(_stakingModuleId).lastDepositBlock; + } + + function getStakingModuleMinDepositBlockDistance(uint256 _stakingModuleId) external view returns (uint256) { + return _getStakingModuleById(_stakingModuleId).minDepositBlockDistance; + } + + function getStakingModuleMaxDepositsPerBlock(uint256 _stakingModuleId) external view returns (uint256) { + return _getStakingModuleById(_stakingModuleId).maxDepositsPerBlock; } function getStakingModuleActiveValidatorsCount(uint256 _stakingModuleId) @@ -1300,6 +1374,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return _getStakingModuleByIndex(_getStakingModuleIndexById(_stakingModuleId)); } + function _getIStakingModuleById(uint256 _stakingModuleId) internal view returns (IStakingModule) { + return IStakingModule(_getStakingModuleAddressById(_stakingModuleId)); + } + function _getStakingModuleByIndex(uint256 _stakingModuleIndex) internal view returns (StakingModule storage) { mapping(uint256 => StakingModule) storage _stakingModules = _getStorageStakingModulesMapping(); return _stakingModules[_stakingModuleIndex]; diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index 66040d535..ece7ee34b 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -86,6 +86,14 @@ interface IStakingModule { /// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions function onRewardsMinted(uint256 _totalShares) external; + /// @notice Called by StakingRouter to decrease the number of vetted keys for node operator with given id + /// @param _nodeOperatorIds bytes packed array of the node operators id + /// @param _vettedSigningKeysCounts bytes packed array of the new number of vetted keys for the node operators + function decreaseVettedSigningKeysCount( + bytes calldata _nodeOperatorIds, + bytes calldata _vettedSigningKeysCounts + ) external; + /// @notice Updates the number of the validators of the given node operator that were requested /// to exit but failed to do so in the max allowed time /// @param _nodeOperatorIds bytes packed array of the node operators id @@ -97,10 +105,10 @@ interface IStakingModule { /// @notice Updates the number of the validators in the EXITED state for node operator with given id /// @param _nodeOperatorIds bytes packed array of the node operators id - /// @param _stuckValidatorsCounts bytes packed array of the new number of EXITED validators for the node operators + /// @param _exitedValidatorsCounts bytes packed array of the new number of EXITED validators for the node operators function updateExitedValidatorsCount( bytes calldata _nodeOperatorIds, - bytes calldata _stuckValidatorsCounts + bytes calldata _exitedValidatorsCounts ) external; /// @notice Updates the number of the refunded validators for node operator with the given id diff --git a/contracts/0.8.9/test_helpers/ModuleSolo.sol b/contracts/0.8.9/test_helpers/ModuleSolo.sol index 800b23c34..ee0a4ac10 100644 --- a/contracts/0.8.9/test_helpers/ModuleSolo.sol +++ b/contracts/0.8.9/test_helpers/ModuleSolo.sol @@ -78,6 +78,11 @@ contract ModuleSolo is IStakingModule { function onRewardsMinted(uint256 _totalShares) external {} + function decreaseVettedSigningKeysCount( + bytes calldata _nodeOperatorIds, + bytes calldata _vettedSigningKeysCounts + ) external {} + function updateStuckValidatorsCount( bytes calldata _nodeOperatorIds, bytes calldata _stuckValidatorsCounts diff --git a/contracts/0.8.9/test_helpers/StakingModuleMock.sol b/contracts/0.8.9/test_helpers/StakingModuleMock.sol index b8beca59c..40768dd0a 100644 --- a/contracts/0.8.9/test_helpers/StakingModuleMock.sol +++ b/contracts/0.8.9/test_helpers/StakingModuleMock.sol @@ -121,6 +121,24 @@ contract StakingModuleMock is IStakingModule { ++lastCall_onRewardsMinted.callCount; } + // solhint-disable-next-line + struct Call_decreaseVettedSigningKeysCount { + bytes nodeOperatorIds; + bytes vettedSigningKeys; + uint256 callCount; + } + + Call_decreaseVettedSigningKeysCount public lastCall_decreaseVettedSiginingKeysCount; + + function decreaseVettedSigningKeysCount( + bytes calldata _nodeOperatorIds, + bytes calldata _vettedSigningKeysCounts + ) external { + lastCall_decreaseVettedSiginingKeysCount.nodeOperatorIds = _nodeOperatorIds; + lastCall_decreaseVettedSiginingKeysCount.vettedSigningKeys = _vettedSigningKeysCounts; + ++lastCall_decreaseVettedSiginingKeysCount.callCount; + } + // solhint-disable-next-line struct Call_updateValidatorsCount { bytes nodeOperatorIds; diff --git a/lib/dsm.ts b/lib/dsm.ts index f09898088..b48854880 100644 --- a/lib/dsm.ts +++ b/lib/dsm.ts @@ -33,39 +33,75 @@ export class DSMAttestMessage extends DSMMessage { blockHash: string; depositRoot: string; stakingModule: number; - keysOpIndex: number; + nonce: number; - constructor(blockNumber: number, blockHash: string, depositRoot: string, stakingModule: number, keysOpIndex: number) { + constructor(blockNumber: number, blockHash: string, depositRoot: string, stakingModule: number, nonce: number) { super(); this.blockNumber = blockNumber; this.blockHash = blockHash; this.depositRoot = depositRoot; this.stakingModule = stakingModule; - this.keysOpIndex = keysOpIndex; + this.nonce = nonce; } get hash() { return solidityPackedKeccak256( ["bytes32", "uint256", "bytes32", "bytes32", "uint256", "uint256"], - [this.messagePrefix, this.blockNumber, this.blockHash, this.depositRoot, this.stakingModule, this.keysOpIndex], + [this.messagePrefix, this.blockNumber, this.blockHash, this.depositRoot, this.stakingModule, this.nonce], ); } } export class DSMPauseMessage extends DSMMessage { blockNumber: number; + + constructor(blockNumber: number) { + super(); + this.blockNumber = blockNumber; + } + + get hash() { + return solidityPackedKeccak256(["bytes32", "uint256"], [this.messagePrefix, this.blockNumber]); + } +} + +export class DSMUnvetMessage extends DSMMessage { + blockNumber: number; + blockHash: string; stakingModule: number; + nonce: number; + nodeOperatorIds: string; + vettedSigningKeysCounts: string; - constructor(blockNumber: number, stakingModule: number) { + constructor( + blockNumber: number, + blockHash: string, + stakingModule: number, + nonce: number, + nodeOperatorIds: string, + vettedSigningKeysCounts: string, + ) { super(); this.blockNumber = blockNumber; + this.blockHash = blockHash; this.stakingModule = stakingModule; + this.nonce = nonce; + this.nodeOperatorIds = nodeOperatorIds; + this.vettedSigningKeysCounts = vettedSigningKeysCounts; } get hash() { return solidityPackedKeccak256( - ["bytes32", "uint256", "uint256"], - [this.messagePrefix, this.blockNumber, this.stakingModule], + ["bytes32", "uint256", "bytes32", "uint256", "uint256", "bytes", "bytes"], + [ + this.messagePrefix, + this.blockNumber, + this.blockHash, + this.stakingModule, + this.nonce, + this.nodeOperatorIds, + this.vettedSigningKeysCounts, + ], ); } } diff --git a/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol b/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol index f1d1b7466..3a8a88cdf 100644 --- a/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol +++ b/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol @@ -11,12 +11,15 @@ import {StakingRouter} from "contracts/0.8.9/StakingRouter.sol"; contract StakingRouterMockForDepositSecurityModule is IStakingRouter { error StakingModuleUnregistered(); + event StakingModuleVettedKeysDecreased(uint24 stakingModuleId, bytes nodeOperatorIds, bytes vettedSigningKeysCounts); event StakingModuleDeposited(uint256 maxDepositsCount, uint24 stakingModuleId, bytes depositCalldata); event StakingModuleStatusSet(uint24 indexed stakingModuleId, StakingRouter.StakingModuleStatus status, address setBy); StakingRouter.StakingModuleStatus private status; uint256 private stakingModuleNonce; uint256 private stakingModuleLastDepositBlock; + uint256 private stakingModuleMaxDepositsPerBlock; + uint256 private stakingModuleMinDepositBlockDistance; uint256 private registeredStakingModuleId; constructor(uint256 stakingModuleId) { @@ -32,6 +35,14 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { return maxDepositsCount; } + function decreaseStakingModuleVettedKeysCountByNodeOperator( + uint256 stakingModuleId, + bytes calldata _nodeOperatorIds, + bytes calldata _vettedSigningKeysCounts + ) external whenModuleIsRegistered(stakingModuleId) { + emit StakingModuleVettedKeysDecreased(uint24(stakingModuleId), _nodeOperatorIds, _vettedSigningKeysCounts); + } + function hasStakingModule(uint256 _stakingModuleId) public view returns (bool) { return _stakingModuleId == registeredStakingModuleId; } @@ -95,6 +106,26 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { stakingModuleLastDepositBlock = value; } + function getStakingModuleMaxDepositsPerBlock( + uint256 stakingModuleId + ) external view whenModuleIsRegistered(stakingModuleId) returns (uint256) { + return stakingModuleMaxDepositsPerBlock; + } + + function setStakingModuleMaxDepositsPerBlock(uint256 value) external { + stakingModuleMaxDepositsPerBlock = value; + } + + function getStakingModuleMinDepositBlockDistance( + uint256 stakingModuleId + ) external view whenModuleIsRegistered(stakingModuleId) returns (uint256) { + return stakingModuleMinDepositBlockDistance; + } + + function setStakingModuleMinDepositBlockDistance(uint256 value) external { + stakingModuleMinDepositBlockDistance = value; + } + modifier whenModuleIsRegistered(uint256 _stakingModuleId) { if (!hasStakingModule(_stakingModuleId)) revert StakingModuleUnregistered(); _; diff --git a/test/0.8.9/depositSecurityModule.test.ts b/test/0.8.9/depositSecurityModule.test.ts index 4ed6ef1df..2b1eb26b8 100644 --- a/test/0.8.9/depositSecurityModule.test.ts +++ b/test/0.8.9/depositSecurityModule.test.ts @@ -1,5 +1,14 @@ import { expect } from "chai"; -import { encodeBytes32String, keccak256, solidityPacked, Wallet, ZeroAddress } from "ethers"; +import { + concat, + ContractTransactionResponse, + encodeBytes32String, + keccak256, + solidityPacked, + Wallet, + ZeroAddress, + ZeroHash, +} from "ethers"; import { ethers, network } from "hardhat"; import { describe } from "mocha"; @@ -23,7 +32,9 @@ const STAKING_MODULE_ID = 100; const MAX_DEPOSITS_PER_BLOCK = 100; const MIN_DEPOSIT_BLOCK_DISTANCE = 14; const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 10; -const DEPOSIT_NONCE = 12; +const UNVET_INTENT_VALIDITY_PERIOD_BLOCKS = 10; +const MAX_OPERATORS_PER_UNVETTING = 20; +const MODULE_NONCE = 12; const DEPOSIT_ROOT = "0xd151867719c94ad8458feaf491809f9bc8096c702a72747403ecaac30c179137"; // status enum @@ -37,9 +48,9 @@ type Params = { lido: string; depositContract: string; stakingRouter: string; - maxDepositsPerBlock: number; - minDepositBlockDistance: number; pauseIntentValidityPeriodBlocks: number; + unvetIntentValidityPeriodBlocks: number; + maxOperatorsPerUnvetting: number; }; type Block = { @@ -52,9 +63,9 @@ function initialParams(): Params { lido: "", depositContract: "", stakingRouter: "", - maxDepositsPerBlock: MAX_DEPOSITS_PER_BLOCK, - minDepositBlockDistance: MIN_DEPOSIT_BLOCK_DISTANCE, pauseIntentValidityPeriodBlocks: PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS, + unvetIntentValidityPeriodBlocks: UNVET_INTENT_VALIDITY_PERIOD_BLOCKS, + maxOperatorsPerUnvetting: MAX_OPERATORS_PER_UNVETTING, } as Params; } @@ -84,6 +95,41 @@ describe("DepositSecurityModule.sol", () => { return block as Block; } + async function deposit( + sortedGuardianWallets: Wallet[], + args?: { + blockNumber?: number; + blockHash?: string; + depositRoot?: string; + nonce?: number; + depositCalldata?: string; + }, + ): Promise { + const stakingModuleId = STAKING_MODULE_ID; + + const latestBlock = await getLatestBlock(); + const blockNumber = args?.blockNumber ?? latestBlock.number; + const blockHash = args?.blockHash ?? latestBlock.hash; + const depositRoot = args?.depositRoot ?? (await depositContract.get_deposit_root()); + const nonce = args?.nonce ?? Number(await stakingRouter.getStakingModuleNonce(stakingModuleId)); + const depositCalldata = args?.depositCalldata ?? encodeBytes32String(""); + + const sortedGuardianSignatures = sortedGuardianWallets.map((guardian) => { + const validAttestMessage = new DSMAttestMessage(blockNumber, blockHash, depositRoot, stakingModuleId, nonce); + return validAttestMessage.sign(guardian.privateKey); + }); + + return await dsm.depositBufferedEther( + blockNumber, + blockHash, + depositRoot, + stakingModuleId, + nonce, + depositCalldata, + sortedGuardianSignatures, + ); + } + before(async () => { ({ provider } = ethers); [admin, stranger] = await ethers.getSigners(); @@ -112,6 +158,15 @@ describe("DepositSecurityModule.sol", () => { DSMAttestMessage.setMessagePrefix(await dsm.ATTEST_MESSAGE_PREFIX()); DSMPauseMessage.setMessagePrefix(await dsm.PAUSE_MESSAGE_PREFIX()); + DSMUnvetMessage.setMessagePrefix(await dsm.UNVET_MESSAGE_PREFIX()); + + await stakingRouter.setStakingModuleMinDepositBlockDistance(MIN_DEPOSIT_BLOCK_DISTANCE); + const minDepositBlockDistance = await stakingRouter.getStakingModuleMinDepositBlockDistance(STAKING_MODULE_ID); + expect(minDepositBlockDistance).to.equal(MIN_DEPOSIT_BLOCK_DISTANCE); + + await stakingRouter.setStakingModuleMaxDepositsPerBlock(MAX_DEPOSITS_PER_BLOCK); + const maxDepositsPerBlock = await stakingRouter.getStakingModuleMaxDepositsPerBlock(STAKING_MODULE_ID); + expect(maxDepositsPerBlock).to.equal(MAX_DEPOSITS_PER_BLOCK); await depositContract.set_deposit_root(DEPOSIT_ROOT); expect(await depositContract.get_deposit_root()).to.equal(DEPOSIT_ROOT); @@ -130,6 +185,7 @@ describe("DepositSecurityModule.sol", () => { beforeEach(async () => { originalState = await Snapshot.take(); }); + afterEach(async () => { await Snapshot.restore(originalState); }); @@ -160,6 +216,15 @@ describe("DepositSecurityModule.sol", () => { "ZeroAddress", ); }); + + it("Sets `lastDepositBlock` to deployment block", async () => { + const tx = await dsm.deploymentTransaction(); + const deploymentBlock = tx?.blockNumber; + + expect(deploymentBlock).to.be.an("number"); + expect(await dsm.getLastDepositBlock()).to.equal(deploymentBlock); + await expect(tx).to.emit(dsm, "LastDepositBlockChanged").withArgs(deploymentBlock); + }); }); context("Constants", () => { @@ -176,6 +241,7 @@ describe("DepositSecurityModule.sol", () => { expect(await dsm.ATTEST_MESSAGE_PREFIX()).to.equal(encodedAttestMessagePrefix); }); + it("Returns the PAUSE_MESSAGE_PREFIX variable", async () => { const dsmPauseMessagePrefix = streccak("lido.DepositSecurityModule.PAUSE_MESSAGE"); expect(dsmPauseMessagePrefix).to.equal("0x9c4c40205558f12027f21204d6218b8006985b7a6359bcab15404bcc3e3fa122"); @@ -189,12 +255,29 @@ describe("DepositSecurityModule.sol", () => { expect(await dsm.PAUSE_MESSAGE_PREFIX()).to.equal(encodedPauseMessagePrefix); }); + + it("Returns the UNVET_MESSAGE_PREFIX variable", async () => { + const dsmUnvetMessagePrefix = streccak("lido.DepositSecurityModule.UNVET_MESSAGE"); + expect(dsmUnvetMessagePrefix).to.equal("0x2dd9727393562ed11c29080a884630e2d3a7078e71b313e713a8a1ef68948f6a"); + + const encodedPauseMessagePrefix = keccak256( + solidityPacked( + ["bytes32", "uint256", "address"], + [dsmUnvetMessagePrefix, network.config.chainId, await dsm.getAddress()], + ), + ); + + expect(await dsm.UNVET_MESSAGE_PREFIX()).to.equal(encodedPauseMessagePrefix); + }); + it("Returns the LIDO address", async () => { expect(await dsm.LIDO()).to.equal(config.lido); }); + it("Returns the STAKING_ROUTER address", async () => { expect(await dsm.STAKING_ROUTER()).to.equal(config.stakingRouter); }); + it("Returns the DEPOSIT_CONTRACT address", async () => { expect(await dsm.DEPOSIT_CONTRACT()).to.equal(config.depositContract); }); @@ -281,14 +364,14 @@ describe("DepositSecurityModule.sol", () => { }); }); - context("Max deposits", () => { - context("Function `getMaxDeposits`", () => { - it("Returns `maxDepositsPerBlock`", async () => { - expect(await dsm.getMaxDeposits()).to.equal(config.maxDepositsPerBlock); + context("Unvet intent validity period blocks", () => { + context("Function `getUnvetIntentValidityPeriodBlocks`", () => { + it("Returns current `unvetIntentValidityPeriodBlocks` contract parameter", async () => { + expect(await dsm.getUnvetIntentValidityPeriodBlocks()).to.equal(config.unvetIntentValidityPeriodBlocks); }); }); - context("Function `setMaxDeposits`", () => { + context("Function `setUnvetIntentValidityPeriodBlocks`", () => { let originalState: string; before(async () => { @@ -299,32 +382,36 @@ describe("DepositSecurityModule.sol", () => { await Snapshot.restore(originalState); }); - it("Reverts if the `setMaxDeposits` called by not an owner", async () => { + it("Reverts if the `newValue` is zero parameter", async () => { + await expect(dsm.setUnvetIntentValidityPeriodBlocks(0)).to.be.revertedWithCustomError(dsm, "ZeroParameter"); + }); + + it("Reverts if the `setUnvetIntentValidityPeriodBlocks` called by not an owner", async () => { await expect( - dsm.connect(stranger).setMaxDeposits(config.maxDepositsPerBlock + 1), + dsm.connect(stranger).setUnvetIntentValidityPeriodBlocks(config.unvetIntentValidityPeriodBlocks), ).to.be.revertedWithCustomError(dsm, "NotAnOwner"); }); - it("Sets `setMaxDeposits` and fires `MaxDepositsChanged` event", async () => { - const valueBefore = await dsm.getMaxDeposits(); + it("Sets `unvetIntentValidityPeriodBlocks` and fires `UnvetIntentValidityPeriodBlocksChanged` event", async () => { + const newValue = config.unvetIntentValidityPeriodBlocks + 1; - const newValue = config.maxDepositsPerBlock + 1; - await expect(dsm.setMaxDeposits(newValue)).to.emit(dsm, "MaxDepositsChanged").withArgs(newValue); + await expect(dsm.setUnvetIntentValidityPeriodBlocks(newValue)) + .to.emit(dsm, "UnvetIntentValidityPeriodBlocksChanged") + .withArgs(newValue); - expect(await dsm.getMaxDeposits()).to.equal(newValue); - expect(await dsm.getMaxDeposits()).to.not.equal(valueBefore); + expect(await dsm.getUnvetIntentValidityPeriodBlocks()).to.equal(newValue); }); }); }); - context("Min deposit block distance", () => { - context("Function `getMinDepositBlockDistance`", () => { - it("Returns `getMinDepositBlockDistance`", async () => { - expect(await dsm.getMinDepositBlockDistance()).to.equal(config.minDepositBlockDistance); + context("Max operators per unvetting", () => { + context("Function `getMaxOperatorsPerUnvetting`", () => { + it("Returns `maxDepositsPerBlock`", async () => { + expect(await dsm.getMaxOperatorsPerUnvetting()).to.equal(config.maxOperatorsPerUnvetting); }); }); - context("Function `setMinDepositBlockDistance`", () => { + context("Function `setMaxOperatorsPerUnvetting`", () => { let originalState: string; before(async () => { @@ -335,33 +422,26 @@ describe("DepositSecurityModule.sol", () => { await Snapshot.restore(originalState); }); - it("Reverts if the `setMinDepositBlockDistance` called by not an owner", async () => { - await expect( - dsm.connect(stranger).setMinDepositBlockDistance(config.minDepositBlockDistance + 1), - ).to.be.revertedWithCustomError(dsm, "NotAnOwner"); - }); - it("Reverts if the `newValue` is zero parameter", async () => { - await expect(dsm.setMinDepositBlockDistance(0)).to.be.revertedWithCustomError(dsm, "ZeroParameter"); + await expect(dsm.setMaxOperatorsPerUnvetting(0)).to.be.revertedWithCustomError(dsm, "ZeroParameter"); }); - it("Sets the equal `newValue` as previous one and NOT fires `MinDepositBlockDistanceChanged` event", async () => { - await expect(dsm.setMinDepositBlockDistance(config.minDepositBlockDistance)).to.not.emit( - dsm, - "MinDepositBlockDistanceChanged", - ); - - expect(await dsm.getMinDepositBlockDistance()).to.equal(config.minDepositBlockDistance); + it("Reverts if the `setMaxOperatorsPerUnvetting` called by not an owner", async () => { + await expect( + dsm.connect(stranger).setMaxOperatorsPerUnvetting(config.maxOperatorsPerUnvetting + 1), + ).to.be.revertedWithCustomError(dsm, "NotAnOwner"); }); - it("Sets the `newValue` and fires `MinDepositBlockDistanceChanged` event", async () => { - const newValue = config.minDepositBlockDistance + 1; + it("Sets `setMaxOperatorsPerUnvetting` and fires `MaxOperatorsPerUnvettingChanged` event", async () => { + const valueBefore = await dsm.getMaxOperatorsPerUnvetting(); - await expect(dsm.setMinDepositBlockDistance(newValue)) - .to.emit(dsm, "MinDepositBlockDistanceChanged") + const newValue = config.maxOperatorsPerUnvetting + 1; + await expect(dsm.setMaxOperatorsPerUnvetting(newValue)) + .to.emit(dsm, "MaxOperatorsPerUnvettingChanged") .withArgs(newValue); - expect(await dsm.getMinDepositBlockDistance()).to.equal(newValue); + expect(await dsm.getMaxOperatorsPerUnvetting()).to.equal(newValue); + expect(await dsm.getMaxOperatorsPerUnvetting()).to.not.equal(valueBefore); }); }); }); @@ -422,7 +502,7 @@ describe("DepositSecurityModule.sol", () => { context("Function `getGuardians`", () => { it("Returns empty list of guardians initially", async () => { - expect((await dsm.getGuardians()).length).to.equal(0); + expect(await dsm.getGuardians()).to.have.length(0); }); }); @@ -445,7 +525,7 @@ describe("DepositSecurityModule.sol", () => { await dsm.addGuardian(guardian1, 0); await dsm.addGuardian(guardian2, 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); expect(await dsm.isGuardian(guardian3)).to.equal(false); }); @@ -453,7 +533,7 @@ describe("DepositSecurityModule.sol", () => { await dsm.addGuardian(guardian1, 0); await dsm.addGuardian(guardian2, 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); expect(await dsm.isGuardian(guardian1)).to.equal(true); expect(await dsm.isGuardian(guardian2)).to.equal(true); }); @@ -478,7 +558,7 @@ describe("DepositSecurityModule.sol", () => { await dsm.addGuardian(guardian1, 0); await dsm.addGuardian(guardian2, 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); expect(await dsm.getGuardianIndex(guardian3)).to.equal(-1); }); @@ -486,7 +566,7 @@ describe("DepositSecurityModule.sol", () => { await dsm.addGuardian(guardian1, 0); await dsm.addGuardian(guardian2, 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); expect(await dsm.getGuardianIndex(guardian2)).to.equal(1); }); }); @@ -519,7 +599,7 @@ describe("DepositSecurityModule.sol", () => { it("Adds a guardian address sets a new quorum value, fires `GuardianAdded` and `GuardianQuorumChanged` events", async () => { const newQuorum = 1; const tx1 = await dsm.addGuardian(guardian1, newQuorum); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(newQuorum); await expect(tx1).to.emit(dsm, "GuardianAdded").withArgs(guardian1.address); @@ -530,7 +610,7 @@ describe("DepositSecurityModule.sol", () => { it("Adds a guardian address sets the same quorum value, fires `GuardianAdded` and NOT `GuardianQuorumChanged` events", async () => { const newQuorum = 0; const tx1 = await dsm.addGuardian(guardian1, newQuorum); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(newQuorum); await expect(tx1).to.emit(dsm, "GuardianAdded").withArgs(guardian1.address); @@ -541,14 +621,14 @@ describe("DepositSecurityModule.sol", () => { it("Re-adds deleted guardian", async () => { await dsm.addGuardian(guardian1, 0); await dsm.addGuardian(guardian2, 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); await dsm.removeGuardian(guardian1, 0); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); await dsm.addGuardian(guardian1, 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); expect(await dsm.isGuardian(guardian1)).to.equal(true); expect(await dsm.getGuardians()).to.include(guardian1.address); }); @@ -590,14 +670,14 @@ describe("DepositSecurityModule.sol", () => { it("Re-adds deleted guardian", async () => { await dsm.addGuardians([guardian1, guardian2], 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); await dsm.removeGuardian(guardian1, 0); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); await dsm.addGuardians([guardian1], 0); - expect((await dsm.getGuardians()).length).to.equal(2); + expect(await dsm.getGuardians()).to.have.length(2); expect(await dsm.isGuardian(guardian1)).to.equal(true); expect(await dsm.getGuardians()).to.include(guardian1.address); }); @@ -707,20 +787,6 @@ describe("DepositSecurityModule.sol", () => { await Snapshot.restore(originalState); }); - it("Reverts if staking module is unregistered and fires `StakingModuleUnregistered` event on StakingRouter contract", async () => { - const blockNumber = 1; - - const sig: DepositSecurityModule.SignatureStruct = { - r: encodeBytes32String(""), - vs: encodeBytes32String(""), - }; - - await expect(dsm.pauseDeposits(blockNumber, UNREGISTERED_STAKING_MODULE_ID, sig)).to.be.revertedWithCustomError( - stakingRouter, - "StakingModuleUnregistered", - ); - }); - it("Reverts if signature is invalid", async () => { const blockNumber = 1; @@ -729,56 +795,54 @@ describe("DepositSecurityModule.sol", () => { vs: encodeBytes32String(""), }; - await expect(dsm.pauseDeposits(blockNumber, STAKING_MODULE_ID, sig)).to.be.revertedWith( - "ECDSA: invalid signature", - ); + await expect(dsm.pauseDeposits(blockNumber, sig)).to.be.revertedWith("ECDSA: invalid signature"); }); it("Reverts if signature is not guardian", async () => { const blockNumber = await time.latestBlock(); - const validPauseMessage = new DSMPauseMessage(blockNumber, STAKING_MODULE_ID); + const validPauseMessage = new DSMPauseMessage(blockNumber); const sig = validPauseMessage.sign(guardian3.privateKey); - await expect(dsm.pauseDeposits(blockNumber, STAKING_MODULE_ID, sig)).to.be.revertedWithCustomError( - dsm, - "InvalidSignature", - ); + await expect(dsm.pauseDeposits(blockNumber, sig)).to.be.revertedWithCustomError(dsm, "InvalidSignature"); }); it("Reverts if called by an anon submitting an unrelated sig", async () => { const blockNumber = await time.latestBlock(); - const validPauseMessage = new DSMPauseMessage(blockNumber, STAKING_MODULE_ID); + const validPauseMessage = new DSMPauseMessage(blockNumber); const sig = validPauseMessage.sign(guardian3.privateKey); - await expect( - dsm.connect(stranger).pauseDeposits(blockNumber, STAKING_MODULE_ID, sig), - ).to.be.revertedWithCustomError(dsm, "InvalidSignature"); + await expect(dsm.connect(stranger).pauseDeposits(blockNumber, sig)).to.be.revertedWithCustomError( + dsm, + "InvalidSignature", + ); }); it("Reverts if called with an expired `blockNumber` by a guardian", async () => { const blockNumber = await time.latestBlock(); const staleBlockNumber = blockNumber - PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS; - const validPauseMessage = new DSMPauseMessage(blockNumber, STAKING_MODULE_ID); + const validPauseMessage = new DSMPauseMessage(blockNumber); const sig = validPauseMessage.sign(guardian1.privateKey); - await expect( - dsm.connect(guardian1).pauseDeposits(staleBlockNumber, STAKING_MODULE_ID, sig), - ).to.be.revertedWithCustomError(dsm, "PauseIntentExpired"); + await expect(dsm.connect(guardian1).pauseDeposits(staleBlockNumber, sig)).to.be.revertedWithCustomError( + dsm, + "PauseIntentExpired", + ); }); it("Reverts if called with an expired `blockNumber` by an anon submitting a guardian's sig", async () => { const blockNumber = await time.latestBlock(); const staleBlockNumber = blockNumber - PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS; - const stalePauseMessage = new DSMPauseMessage(staleBlockNumber, STAKING_MODULE_ID); + const stalePauseMessage = new DSMPauseMessage(staleBlockNumber); const sig = stalePauseMessage.sign(guardian1.privateKey); - await expect( - dsm.connect(stranger).pauseDeposits(staleBlockNumber, STAKING_MODULE_ID, sig), - ).to.be.revertedWithCustomError(dsm, "PauseIntentExpired"); + await expect(dsm.connect(stranger).pauseDeposits(staleBlockNumber, sig)).to.be.revertedWithCustomError( + dsm, + "PauseIntentExpired", + ); }); it("Reverts if called with a future `blockNumber` by a guardian", async () => { @@ -789,67 +853,56 @@ describe("DepositSecurityModule.sol", () => { vs: encodeBytes32String(""), }; - await expect( - dsm.connect(guardian1).pauseDeposits(futureBlockNumber, STAKING_MODULE_ID, sig), - ).to.be.revertedWithPanic(PANIC_CODES.ARITHMETIC_OVERFLOW); + await expect(dsm.connect(guardian1).pauseDeposits(futureBlockNumber, sig)).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); }); it("Reverts if called with a future `blockNumber` by an anon submitting a guardian's sig", async () => { const futureBlockNumber = (await time.latestBlock()) + 100; - const futurePauseMessage = new DSMPauseMessage(futureBlockNumber, STAKING_MODULE_ID); + const futurePauseMessage = new DSMPauseMessage(futureBlockNumber); const sig = futurePauseMessage.sign(guardian1.privateKey); - await expect( - dsm.connect(stranger).pauseDeposits(futureBlockNumber, STAKING_MODULE_ID, sig), - ).to.be.revertedWithPanic(PANIC_CODES.ARITHMETIC_OVERFLOW); + await expect(dsm.connect(stranger).pauseDeposits(futureBlockNumber, sig)).to.be.revertedWithPanic( + PANIC_CODES.ARITHMETIC_OVERFLOW, + ); }); - it("Pause if called by guardian and fires `DepositsPaused` and `StakingModuleStatusSet` events", async () => { + it("Pause if called by guardian and fires `DepositsPaused` event", async () => { const blockNumber = await time.latestBlock(); const sig: DepositSecurityModule.SignatureStruct = { r: encodeBytes32String(""), vs: encodeBytes32String(""), }; - const tx = await dsm.connect(guardian1).pauseDeposits(blockNumber, STAKING_MODULE_ID, sig); + const tx = await dsm.connect(guardian1).pauseDeposits(blockNumber, sig); - await expect(tx).to.emit(dsm, "DepositsPaused").withArgs(guardian1.address, STAKING_MODULE_ID); - await expect(tx) - .to.emit(stakingRouter, "StakingModuleStatusSet") - .withArgs(STAKING_MODULE_ID, StakingModuleStatus.DepositsPaused, await dsm.getAddress()); + await expect(tx).to.emit(dsm, "DepositsPaused").withArgs(guardian1.address); }); it("Pause if called by anon submitting sig of guardian", async () => { const blockNumber = await time.latestBlock(); - const validPauseMessage = new DSMPauseMessage(blockNumber, STAKING_MODULE_ID); + const validPauseMessage = new DSMPauseMessage(blockNumber); const sig = validPauseMessage.sign(guardian2.privateKey); - const tx = await dsm.connect(stranger).pauseDeposits(blockNumber, STAKING_MODULE_ID, sig); + const tx = await dsm.connect(stranger).pauseDeposits(blockNumber, sig); - await expect(tx).to.emit(dsm, "DepositsPaused").withArgs(guardian2.address, STAKING_MODULE_ID); - await expect(tx) - .to.emit(stakingRouter, "StakingModuleStatusSet") - .withArgs(STAKING_MODULE_ID, StakingModuleStatus.DepositsPaused, await dsm.getAddress()); + await expect(tx).to.emit(dsm, "DepositsPaused").withArgs(guardian2.address); }); it("Do not pause and emits events if was paused before", async () => { const blockNumber = await time.latestBlock(); - const validPauseMessage = new DSMPauseMessage(blockNumber, STAKING_MODULE_ID); + const validPauseMessage = new DSMPauseMessage(blockNumber); const sig = validPauseMessage.sign(guardian2.privateKey); - const tx1 = await dsm.connect(stranger).pauseDeposits(blockNumber, STAKING_MODULE_ID, sig); - - await expect(tx1).to.emit(dsm, "DepositsPaused").withArgs(guardian2.address, STAKING_MODULE_ID); - await expect(tx1) - .to.emit(stakingRouter, "StakingModuleStatusSet") - .withArgs(STAKING_MODULE_ID, StakingModuleStatus.DepositsPaused, await dsm.getAddress()); + const tx1 = await dsm.connect(stranger).pauseDeposits(blockNumber, sig); + await expect(tx1).to.emit(dsm, "DepositsPaused").withArgs(guardian2.address); - const tx2 = await dsm.connect(stranger).pauseDeposits(blockNumber, STAKING_MODULE_ID, sig); + const tx2 = await dsm.connect(stranger).pauseDeposits(blockNumber, sig); await expect(tx2).to.not.emit(dsm, "DepositsPaused"); - await expect(tx2).to.not.emit(stakingRouter, "StakingModuleStatusSet"); }); }); @@ -863,15 +916,11 @@ describe("DepositSecurityModule.sol", () => { const blockNumber = await time.latestBlock(); - const validPauseMessage = new DSMPauseMessage(blockNumber, STAKING_MODULE_ID); + const validPauseMessage = new DSMPauseMessage(blockNumber); const sig = validPauseMessage.sign(guardian2.privateKey); - const tx = await dsm.connect(stranger).pauseDeposits(blockNumber, STAKING_MODULE_ID, sig); - - await expect(tx).to.emit(dsm, "DepositsPaused").withArgs(guardian2.address, STAKING_MODULE_ID); - await expect(tx) - .to.emit(stakingRouter, "StakingModuleStatusSet") - .withArgs(STAKING_MODULE_ID, StakingModuleStatus.DepositsPaused, await dsm.getAddress()); + const tx = await dsm.connect(stranger).pauseDeposits(blockNumber, sig); + await expect(tx).to.emit(dsm, "DepositsPaused").withArgs(guardian2.address); }); afterEach(async () => { @@ -879,50 +928,18 @@ describe("DepositSecurityModule.sol", () => { }); it("Reverts if called by not an owner", async () => { - await expect(dsm.connect(stranger).unpauseDeposits(UNREGISTERED_STAKING_MODULE_ID)).to.be.revertedWithCustomError( - dsm, - "NotAnOwner", - ); - }); - - it("Reverts if staking module is unregistered and fires `StakingModuleUnregistered` event on StakingRouter contract", async () => { - await expect(dsm.unpauseDeposits(UNREGISTERED_STAKING_MODULE_ID)).to.be.revertedWithCustomError( - stakingRouter, - "StakingModuleUnregistered", - ); - }); - - it("No events on active module", async () => { - expect(await stakingRouter.getStakingModuleStatus(STAKING_MODULE_ID)).to.equal( - StakingModuleStatus.DepositsPaused, - ); - await stakingRouter.setStakingModuleStatus(STAKING_MODULE_ID, StakingModuleStatus.Active); - expect(await stakingRouter.getStakingModuleStatus(STAKING_MODULE_ID)).to.equal(StakingModuleStatus.Active); - - await expect(dsm.unpauseDeposits(STAKING_MODULE_ID)).to.not.emit(dsm, "DepositsUnpaused"); - }); - - it("No events on stopped module", async () => { - expect(await stakingRouter.getStakingModuleStatus(STAKING_MODULE_ID)).to.equal( - StakingModuleStatus.DepositsPaused, - ); - await stakingRouter.setStakingModuleStatus(STAKING_MODULE_ID, StakingModuleStatus.Stopped); - expect(await stakingRouter.getStakingModuleStatus(STAKING_MODULE_ID)).to.equal(StakingModuleStatus.Stopped); - - await expect(dsm.unpauseDeposits(STAKING_MODULE_ID)).to.not.emit(dsm, "DepositsUnpaused"); + await expect(dsm.connect(stranger).unpauseDeposits()).to.be.revertedWithCustomError(dsm, "NotAnOwner"); }); it("Unpause if called by owner and module status is `DepositsPaused` and fires events", async () => { - expect(await stakingRouter.getStakingModuleStatus(STAKING_MODULE_ID)).to.equal( - StakingModuleStatus.DepositsPaused, - ); - - const tx = await dsm.unpauseDeposits(STAKING_MODULE_ID); + const tx = await dsm.unpauseDeposits(); + await expect(tx).to.emit(dsm, "DepositsUnpaused").withArgs(); + }); - await expect(tx).to.emit(dsm, "DepositsUnpaused").withArgs(STAKING_MODULE_ID); - await expect(tx) - .to.emit(stakingRouter, "StakingModuleStatusSet") - .withArgs(STAKING_MODULE_ID, StakingModuleStatus.Active, await dsm.getAddress()); + it("Reverts if already paused", async () => { + const tx = await dsm.unpauseDeposits(); + await expect(tx).to.emit(dsm, "DepositsUnpaused").withArgs(); + await expect(dsm.unpauseDeposits()).to.be.revertedWithCustomError(dsm, "DepositsNotPaused"); }); }); @@ -931,6 +948,11 @@ describe("DepositSecurityModule.sol", () => { beforeEach(async () => { originalState = await Snapshot.take(); + + await dsm.addGuardian(guardian1, 1); + const lastDepositBlockNumber = await time.latestBlock(); + await stakingRouter.setStakingModuleLastDepositBlock(lastDepositBlockNumber); + await mineUpTo((await time.latestBlock()) + MIN_DEPOSIT_BLOCK_DISTANCE); }); afterEach(async () => { @@ -941,121 +963,180 @@ describe("DepositSecurityModule.sol", () => { expect(await dsm.canDeposit(UNREGISTERED_STAKING_MODULE_ID)).to.equal(false); }); - it("Returns `true` if: \n\t\t1) StakingModule is not paused \n\t\t2) DSN quorum > 0 \n\t\t3) block.number - lastDepositBlock >= minDepositBlockDistance \n\t\t4) Lido.canDeposit() is true", async () => { - expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(true); + it("Returns `true` if: \n\t\t1) Deposits is not paused \n\t\t2) Module is active \n\t\t3) DSM quorum > 0 \n\t\t4) Min deposit block distance is passed \n\t\t5) Lido.canDeposit() is true", async () => { + const dsmLastDepositBlock = await dsm.getLastDepositBlock(); + const moduleLastDepositBlock = await stakingRouter.getStakingModuleLastDepositBlock(STAKING_MODULE_ID); + const minDepositBlockDistance = await stakingRouter.getStakingModuleMinDepositBlockDistance(STAKING_MODULE_ID); + const currentBlockNumber = await time.latestBlock(); + const maxLastDepositBlock = Math.max(Number(dsmLastDepositBlock), Number(moduleLastDepositBlock)); - await dsm.addGuardian(guardian1, 1); + expect(await dsm.isDepositsPaused()).to.equal(false); + expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(true); expect(await dsm.getGuardianQuorum()).to.equal(1); + expect(currentBlockNumber - maxLastDepositBlock >= minDepositBlockDistance).to.equal(true); + expect(await lido.canDeposit()).to.equal(true); - const lastDepositBlockNumber = await time.latestBlock(); - await stakingRouter.setStakingModuleLastDepositBlock(lastDepositBlockNumber); - await mineUpTo((await time.latestBlock()) + MIN_DEPOSIT_BLOCK_DISTANCE); + expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(true); + }); - const currentBlockNumber = await time.latestBlock(); - const minDepositBlockDistance = await dsm.getMinDepositBlockDistance(); + it("Returns `false` if deposits paused", async () => { + const blockNumber = await time.latestBlock(); + const sig: DepositSecurityModule.SignatureStruct = { + r: encodeBytes32String(""), + vs: encodeBytes32String(""), + }; - expect(currentBlockNumber - lastDepositBlockNumber >= minDepositBlockDistance).to.equal(true); - expect(await lido.canDeposit()).to.equal(true); - expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(true); + await dsm.connect(guardian1).pauseDeposits(blockNumber, sig); + expect(await dsm.isDepositsPaused()).to.equal(true); + expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); }); - it("Returns `false` if: \n\t\t1) StakingModule is paused \n\t\t2) DSN quorum > 0 \n\t\t3) block.number - lastDepositBlock >= minDepositBlockDistance \n\t\t4) Lido.canDeposit() is true", async () => { - expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(true); + it("Returns `false` if module is paused", async () => { await stakingRouter.setStakingModuleStatus(STAKING_MODULE_ID, StakingModuleStatus.DepositsPaused); - expect(await stakingRouter.getStakingModuleIsDepositsPaused(STAKING_MODULE_ID)).to.equal(true); + expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(false); + expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); + }); - await dsm.addGuardian(guardian1, 1); - expect(await dsm.getGuardianQuorum()).to.equal(1); + it("Returns `false` if module is stopped", async () => { + await stakingRouter.setStakingModuleStatus(STAKING_MODULE_ID, StakingModuleStatus.Stopped); + expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(false); + expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); + }); - const lastDepositBlockNumber = await time.latestBlock(); - await stakingRouter.setStakingModuleLastDepositBlock(lastDepositBlockNumber); - await mineUpTo((await time.latestBlock()) + MIN_DEPOSIT_BLOCK_DISTANCE); + it("Returns `false` if quorum is 0", async () => { + await dsm.setGuardianQuorum(0); + expect(await dsm.getGuardianQuorum()).to.equal(0); + expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); + }); + + it("Returns `false` if min deposit block distance is not passed and dsm.lastDepositBlock < module.lastDepositBlock", async () => { + const moduleLastDepositBlock = await time.latestBlock(); + const dsmLastDepositBlock = Number(await dsm.getLastDepositBlock()); + + await stakingRouter.setStakingModuleLastDepositBlock(moduleLastDepositBlock); + await mineUpTo((await time.latestBlock()) + MIN_DEPOSIT_BLOCK_DISTANCE / 2); + const minDepositBlockDistance = await stakingRouter.getStakingModuleMinDepositBlockDistance(STAKING_MODULE_ID); const currentBlockNumber = await time.latestBlock(); - const minDepositBlockDistance = await dsm.getMinDepositBlockDistance(); - expect(currentBlockNumber - lastDepositBlockNumber >= minDepositBlockDistance).to.equal(true); - expect(await lido.canDeposit()).to.equal(true); + expect(dsmLastDepositBlock < moduleLastDepositBlock).to.equal(true); + expect(currentBlockNumber - dsmLastDepositBlock >= minDepositBlockDistance).to.equal(true); + expect(currentBlockNumber - moduleLastDepositBlock < minDepositBlockDistance).to.equal(true); expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); }); - it("Returns `false` if: \n\t\t1) StakingModule is not paused \n\t\t2) DSN quorum = 0 \n\t\t3) block.number - lastDepositBlock >= minDepositBlockDistance \n\t\t4) Lido.canDeposit() is true", async () => { - expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(true); - - await dsm.addGuardian(guardian1, 0); - expect(await dsm.getGuardianQuorum()).to.equal(0); - - const lastDepositBlockNumber = await time.latestBlock(); - await stakingRouter.setStakingModuleLastDepositBlock(lastDepositBlockNumber); + it("Returns `false` if min deposit block distance is not passed and dsm.lastDepositBlock > module.lastDepositBlock", async () => { await mineUpTo((await time.latestBlock()) + MIN_DEPOSIT_BLOCK_DISTANCE); + await deposit([guardian1]); + + const dsmLastDepositBlock = Number(await dsm.getLastDepositBlock()); + const moduleLastDepositBlock = dsmLastDepositBlock - MIN_DEPOSIT_BLOCK_DISTANCE; + await stakingRouter.setStakingModuleLastDepositBlock(moduleLastDepositBlock); + const minDepositBlockDistance = await stakingRouter.getStakingModuleMinDepositBlockDistance(STAKING_MODULE_ID); const currentBlockNumber = await time.latestBlock(); - const minDepositBlockDistance = await dsm.getMinDepositBlockDistance(); - expect(currentBlockNumber - lastDepositBlockNumber >= minDepositBlockDistance).to.equal(true); - expect(await lido.canDeposit()).to.equal(true); + expect(dsmLastDepositBlock > moduleLastDepositBlock).to.equal(true); + expect(currentBlockNumber - dsmLastDepositBlock < minDepositBlockDistance).to.equal(true); + expect(currentBlockNumber - moduleLastDepositBlock >= minDepositBlockDistance).to.equal(true); expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); }); - it("Returns `false` if: \n\t\t1) StakingModule is not paused \n\t\t2) DSN quorum > 0 \n\t\t3) block.number - lastDepositBlock < minDepositBlockDistance \n\t\t4) Lido.canDeposit() is true", async () => { - expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(true); + it("Returns `false` if Lido.canDeposit() is false", async () => { + await lido.setCanDeposit(false); - await dsm.addGuardian(guardian1, 1); - expect(await dsm.getGuardianQuorum()).to.equal(1); + expect(await lido.canDeposit()).to.equal(false); + expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); + }); + }); - const lastDepositBlockNumber = await time.latestBlock(); - await stakingRouter.setStakingModuleLastDepositBlock(lastDepositBlockNumber); - await mineUpTo((await time.latestBlock()) + MIN_DEPOSIT_BLOCK_DISTANCE / 2); + context("Function `getLastDepositBlock`", () => { + let originalState: string; - const currentBlockNumber = await time.latestBlock(); - const minDepositBlockDistance = await dsm.getMinDepositBlockDistance(); + beforeEach(async () => { + originalState = await Snapshot.take(); + }); - expect(currentBlockNumber - lastDepositBlockNumber < minDepositBlockDistance).to.equal(true); - expect(await lido.canDeposit()).to.equal(true); - expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); + afterEach(async () => { + await Snapshot.restore(originalState); }); - it("Returns `false` if: \n\t\t1) StakingModule is not paused \n\t\t2) DSN quorum > 0 \n\t\t3) block.number - lastDepositBlock >= minDepositBlockDistance \n\t\t4) Lido.canDeposit() is false", async () => { - expect(await stakingRouter.getStakingModuleIsActive(STAKING_MODULE_ID)).to.equal(true); + it("Returns deployment block before any deposits", async () => { + const tx = await dsm.deploymentTransaction(); + const deploymentBlock = tx?.blockNumber; + expect(deploymentBlock).to.be.an("number"); + expect(await dsm.getLastDepositBlock()).to.equal(deploymentBlock); + await expect(tx).to.emit(dsm, "LastDepositBlockChanged").withArgs(deploymentBlock); + }); + + it("Returns last deposit block", async () => { await dsm.addGuardian(guardian1, 1); - expect(await dsm.getGuardianQuorum()).to.equal(1); + const tx = await await deposit([guardian1]); + const depositBlock = tx.blockNumber; - const lastDepositBlockNumber = await time.latestBlock(); - await stakingRouter.setStakingModuleLastDepositBlock(lastDepositBlockNumber); - await mineUpTo((await time.latestBlock()) + 2 * MIN_DEPOSIT_BLOCK_DISTANCE); + expect(await dsm.getLastDepositBlock()).to.equal(depositBlock); + await expect(tx).to.emit(dsm, "LastDepositBlockChanged").withArgs(depositBlock); + }); + }); + + context("Function `isMinDepositDistancePassed`", () => { + let originalState: string; + + beforeEach(async () => { + originalState = await Snapshot.take(); + await dsm.addGuardian(guardian1, 1); + await mineUpTo((await time.latestBlock()) + MIN_DEPOSIT_BLOCK_DISTANCE); + }); + + afterEach(async () => { + await Snapshot.restore(originalState); + }); + + it("Returns true if min deposit distance is passed", async () => { + expect(await dsm.isMinDepositDistancePassed(STAKING_MODULE_ID)).to.equal(true); + }); + it("Returns false if distance is not passed for both dsm.lastDepositBlock and module.lastDepositBlock", async () => { + await deposit([guardian1]); + const dsmLastDepositBlock = await dsm.getLastDepositBlock(); + await stakingRouter.setStakingModuleLastDepositBlock(dsmLastDepositBlock); + + const moduleLastDepositBlock = await stakingRouter.getStakingModuleLastDepositBlock(STAKING_MODULE_ID); + const minDepositBlockDistance = await stakingRouter.getStakingModuleMinDepositBlockDistance(STAKING_MODULE_ID); const currentBlockNumber = await time.latestBlock(); - const minDepositBlockDistance = await dsm.getMinDepositBlockDistance(); - expect(currentBlockNumber - lastDepositBlockNumber >= minDepositBlockDistance).to.equal(true); + expect(dsmLastDepositBlock).to.equal(moduleLastDepositBlock); + expect(currentBlockNumber - Number(dsmLastDepositBlock) < minDepositBlockDistance).to.equal(true); + expect(currentBlockNumber - Number(moduleLastDepositBlock) < minDepositBlockDistance).to.equal(true); - await lido.setCanDeposit(false); - expect(await lido.canDeposit()).to.equal(false); + expect(await dsm.isMinDepositDistancePassed(STAKING_MODULE_ID)).to.equal(false); + }); - expect(await dsm.canDeposit(STAKING_MODULE_ID)).to.equal(false); + it("Returns false if distance is not passed for dsm.lastDepositBlock but passed for module.lastDepositBlock", async () => { + await deposit([guardian1]); + const currentBlockNumber = await time.latestBlock(); + const minDepositBlockDistance = await stakingRouter.getStakingModuleMinDepositBlockDistance(STAKING_MODULE_ID); + await stakingRouter.setStakingModuleLastDepositBlock(currentBlockNumber - Number(minDepositBlockDistance)); + + const dsmLastDepositBlock = await dsm.getLastDepositBlock(); + const moduleLastDepositBlock = await stakingRouter.getStakingModuleLastDepositBlock(STAKING_MODULE_ID); + + expect(currentBlockNumber - Number(dsmLastDepositBlock) < minDepositBlockDistance).to.equal(true); + expect(currentBlockNumber - Number(moduleLastDepositBlock) >= minDepositBlockDistance).to.equal(true); + + expect(await dsm.isMinDepositDistancePassed(STAKING_MODULE_ID)).to.equal(false); }); }); context("Function `depositBufferedEther`", () => { let originalState: string; - let validAttestMessage: DSMAttestMessage; - let block: Block; beforeEach(async () => { originalState = await Snapshot.take(); - block = await getLatestBlock(); - - await stakingRouter.setStakingModuleNonce(DEPOSIT_NONCE); - expect(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)).to.equal(DEPOSIT_NONCE); - validAttestMessage = new DSMAttestMessage( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - ); + await stakingRouter.setStakingModuleNonce(MODULE_NONCE); + expect(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)).to.equal(MODULE_NONCE); }); afterEach(async () => { @@ -1065,20 +1146,7 @@ describe("DepositSecurityModule.sol", () => { context("Total guardians: 0, quorum: 0", () => { it("Reverts if no quorum", async () => { expect(await dsm.getGuardianQuorum()).to.equal(0); - - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = []; - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); + await expect(deposit([])).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); }); }); @@ -1086,22 +1154,10 @@ describe("DepositSecurityModule.sol", () => { it("Reverts if no quorum", async () => { await dsm.addGuardian(guardian1, 0); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(0); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = []; - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); + await expect(deposit([])).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); }); }); @@ -1109,46 +1165,19 @@ describe("DepositSecurityModule.sol", () => { it("Reverts if no guardian signatures", async () => { await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = []; - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); + await expect(deposit([])).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); }); it("Reverts if deposit with an unrelated sig", async () => { await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian2.privateKey), - ]; - - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "InvalidSignature"); + await expect(deposit([guardian2])).to.be.revertedWithCustomError(dsm, "InvalidSignature"); }); it("Reverts if deposit contract root changed", async () => { @@ -1160,61 +1189,39 @@ describe("DepositSecurityModule.sol", () => { await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian1.privateKey), - ]; - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), + deposit([guardian1], { + depositRoot: depositRootBefore, + }), ).to.be.revertedWithCustomError(dsm, "DepositRootChanged"); }); it("Reverts if nonce changed", async () => { - const nonceBefore = await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID); - const newNonce = 11; + const nonceBefore = Number(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)); + const newNonce = nonceBefore + 1; await stakingRouter.setStakingModuleNonce(newNonce); expect(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)).to.equal(newNonce); expect(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)).to.not.equal(nonceBefore); await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian1.privateKey), - ]; - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositNonceChanged"); + deposit([guardian1], { + nonce: nonceBefore, + }), + ).to.be.revertedWithCustomError(dsm, "ModuleNonceChanged"); }); it("Reverts if deposit too frequent", async () => { - await stakingRouter.setStakingModuleLastDepositBlock(block.number - 1); - const latestBlock = await getLatestBlock(); + await stakingRouter.setStakingModuleLastDepositBlock(latestBlock.number - 1); + const lastDepositBlock = await stakingRouter.getStakingModuleLastDepositBlock(STAKING_MODULE_ID); expect(BigInt(latestBlock.number) - BigInt(lastDepositBlock) < BigInt(MIN_DEPOSIT_BLOCK_DISTANCE)).to.equal( true, @@ -1222,25 +1229,10 @@ describe("DepositSecurityModule.sol", () => { await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian1.privateKey), - ]; - - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositTooFrequent"); + await expect(deposit([guardian1])).to.be.revertedWithCustomError(dsm, "DepositTooFrequent"); }); it("Reverts if module is inactive", async () => { @@ -1248,105 +1240,69 @@ describe("DepositSecurityModule.sol", () => { await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian1.privateKey), - ]; - - await expect( - dsm.depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositInactiveModule"); + await expect(deposit([guardian1])).to.be.revertedWithCustomError(dsm, "DepositInactiveModule"); }); it("Reverts if `block.hash` and `block.number` from different blocks", async () => { + const previousBlockNumber = await time.latestBlock(); await mineUpTo((await time.latestBlock()) + 1); - const latestBlock = await getLatestBlock(); - expect(latestBlock.number > block.number).to.equal(true); + const latestBlockNumber = await time.latestBlock(); + expect(latestBlockNumber > previousBlockNumber).to.equal(true); await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian1.privateKey), - ]; - await expect( - dsm.depositBufferedEther( - latestBlock.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), + deposit([guardian1], { + blockNumber: previousBlockNumber, + }), ).to.be.revertedWithCustomError(dsm, "DepositUnexpectedBlockHash"); }); - it("Reverts if deposit with zero `block.hash`", async () => { + it("Reverts if called with zero `block.hash`", async () => { + await dsm.addGuardian(guardian1, 1); + expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); + expect(await dsm.getGuardians()).to.have.length(1); + expect(await dsm.getGuardianQuorum()).to.equal(1); + + await expect(deposit([guardian1], { blockHash: ZeroHash })).to.be.revertedWithCustomError( + dsm, + "DepositUnexpectedBlockHash", + ); + }); + + it("Reverts if called for block with unrecoverable `block.hash`", async () => { + const tooOldBlock = await getLatestBlock(); await mineUpTo((await time.latestBlock()) + 255); const latestBlock = await getLatestBlock(); - expect(latestBlock.number > block.number).to.equal(true); + expect(latestBlock.number > tooOldBlock.number).to.equal(true); await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian1.privateKey), - ]; - await expect( - dsm.depositBufferedEther( - latestBlock.number, - encodeBytes32String(""), - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), + deposit([guardian1], { + blockNumber: tooOldBlock.number, + blockHash: tooOldBlock.hash, + }), ).to.be.revertedWithCustomError(dsm, "DepositUnexpectedBlockHash"); }); it("Deposit with the guardian's sig", async () => { await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); - expect((await dsm.getGuardians()).length).to.equal(1); + expect(await dsm.getGuardians()).to.have.length(1); expect(await dsm.getGuardianQuorum()).to.equal(1); const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = [ - validAttestMessage.sign(guardian1.privateKey), - ]; - - const tx = await dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ); + const tx = await deposit([guardian1], { depositCalldata }); await expect(tx) .to.emit(lido, "StakingModuleDeposited") @@ -1358,158 +1314,62 @@ describe("DepositSecurityModule.sol", () => { it("Reverts if no signatures", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures: DepositSecurityModule.SignatureStruct[] = []; - - await expect( - dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); + await expect(deposit([])).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); }); it("Reverts if signatures < quorum", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [validAttestMessage.sign(guardian1.privateKey)]; - - await expect( - dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); + await expect(deposit([guardian1])).to.be.revertedWithCustomError(dsm, "DepositNoQuorum"); }); it("Reverts if deposit with guardian's sigs (1,0) with `SignaturesNotSorted` exception", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [ - validAttestMessage.sign(guardian2.privateKey), - validAttestMessage.sign(guardian1.privateKey), - ]; - - await expect( - dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "SignaturesNotSorted"); + await expect(deposit([guardian2, guardian1])).to.be.revertedWithCustomError(dsm, "SignaturesNotSorted"); }); it("Reverts if deposit with guardian's sigs (0,0,1) with `SignaturesNotSorted` exception", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [ - validAttestMessage.sign(guardian1.privateKey), - validAttestMessage.sign(guardian1.privateKey), - validAttestMessage.sign(guardian2.privateKey), - ]; + await expect(deposit([guardian1, guardian1, guardian2])).to.be.revertedWithCustomError( + dsm, + "SignaturesNotSorted", + ); + }); - await expect( - dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "SignaturesNotSorted"); - }); - - it("Reverts if deposit with guardian's sigs (0,0,1) with `InvalidSignature` exception", async () => { + it("Reverts if deposit with guardian's sigs (0,x,x) with `InvalidSignature` exception", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); - const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [ - validAttestMessage.sign(guardian1.privateKey), - validAttestMessage.sign(unrelatedGuardian1.privateKey), - validAttestMessage.sign(unrelatedGuardian2.privateKey), - ]; - - await expect( - dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ), - ).to.be.revertedWithCustomError(dsm, "InvalidSignature"); + await expect(deposit([guardian1, unrelatedGuardian1, unrelatedGuardian2])).to.be.revertedWithCustomError( + dsm, + "InvalidSignature", + ); }); it("Allow deposit if deposit with guardian's sigs (0,1,2)", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [ - validAttestMessage.sign(guardian1.privateKey), - validAttestMessage.sign(guardian2.privateKey), - validAttestMessage.sign(guardian3.privateKey), - ]; - - const tx = await dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ); + const tx = await deposit([guardian1, guardian2, guardian3], { depositCalldata }); await expect(tx) .to.emit(lido, "StakingModuleDeposited") @@ -1519,26 +1379,11 @@ describe("DepositSecurityModule.sol", () => { it("Allow deposit if deposit with guardian's sigs (0,1)", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [ - validAttestMessage.sign(guardian1.privateKey), - validAttestMessage.sign(guardian2.privateKey), - ]; - - const tx = await dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ); + const tx = await deposit([guardian1, guardian2], { depositCalldata }); await expect(tx) .to.emit(lido, "StakingModuleDeposited") @@ -1548,26 +1393,11 @@ describe("DepositSecurityModule.sol", () => { it("Allow deposit if deposit with guardian's sigs (0,2)", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [ - validAttestMessage.sign(guardian1.privateKey), - validAttestMessage.sign(guardian3.privateKey), - ]; - - const tx = await dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ); + const tx = await deposit([guardian1, guardian3], { depositCalldata }); await expect(tx) .to.emit(lido, "StakingModuleDeposited") @@ -1577,26 +1407,11 @@ describe("DepositSecurityModule.sol", () => { it("Allow deposit if deposit with guardian's sigs (1,2)", async () => { await dsm.addGuardians([guardian1, guardian2, guardian3], 2); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address, guardian2.address, guardian3.address]); - expect((await dsm.getGuardians()).length).to.equal(3); + expect(await dsm.getGuardians()).to.have.length(3); expect(await dsm.getGuardianQuorum()).to.equal(2); const depositCalldata = encodeBytes32String(""); - const sortedGuardianSignatures = [ - validAttestMessage.sign(guardian2.privateKey), - validAttestMessage.sign(guardian3.privateKey), - ]; - - const tx = await dsm - .connect(stranger) - .depositBufferedEther( - block.number, - block.hash, - DEPOSIT_ROOT, - STAKING_MODULE_ID, - DEPOSIT_NONCE, - depositCalldata, - sortedGuardianSignatures, - ); + const tx = await deposit([guardian2, guardian3], { depositCalldata }); await expect(tx) .to.emit(lido, "StakingModuleDeposited") @@ -1604,4 +1419,204 @@ describe("DepositSecurityModule.sol", () => { }); }); }); + + context("Function `unvetSigningKeys`", () => { + const operatorId1 = "0x0000000000000001"; + const operatorId2 = "0x0000000000000002"; + const vettedSigningKeysCount1 = "0x00000000000000000000000000000001"; + const vettedSigningKeysCount2 = "0x00000000000000000000000000000002"; + const defaultNodeOperatorIds = concat([operatorId1, operatorId2]); + const defaultVettedSigningKeysCounts = concat([vettedSigningKeysCount1, vettedSigningKeysCount2]); + + const invalidSig: DepositSecurityModule.SignatureStruct = { + r: encodeBytes32String(""), + vs: encodeBytes32String(""), + }; + + interface UnvetArgs { + blockNumber?: number; + blockHash?: string; + stakingModuleId?: number; + nonce?: number; + nodeOperatorIds?: string; + vettedSigningKeysCounts?: string; + sig?: DepositSecurityModule.SignatureStruct; + } + + interface UnvetSignedArgs extends UnvetArgs { + sig?: DepositSecurityModule.SignatureStruct; + } + + async function getUnvetArgs(overridingArgs?: UnvetArgs) { + const latestBlock = await getLatestBlock(); + const blockNumber = overridingArgs?.blockNumber ?? latestBlock.number; + const blockHash = overridingArgs?.blockHash ?? latestBlock.hash; + const stakingModuleId = overridingArgs?.stakingModuleId ?? STAKING_MODULE_ID; + const nonce = overridingArgs?.nonce ?? MODULE_NONCE; + + const nodeOperatorIds = overridingArgs?.nodeOperatorIds ?? defaultNodeOperatorIds; + const vettedSigningKeysCounts = overridingArgs?.vettedSigningKeysCounts ?? defaultVettedSigningKeysCounts; + + return [blockNumber, blockHash, stakingModuleId, nonce, nodeOperatorIds, vettedSigningKeysCounts] as const; + } + + async function getUnvetSignature(from: Wallet, overridingArgs?: UnvetArgs) { + const args = await getUnvetArgs(overridingArgs); + const validUnvetMessage = new DSMUnvetMessage(...args); + return validUnvetMessage.sign(from.privateKey); + } + + async function unvetSigningKeys(from: Wallet, overridingArgs?: UnvetSignedArgs) { + const unvetArgs = await getUnvetArgs(overridingArgs); + const sig = overridingArgs?.sig ?? (await getUnvetSignature(from, overridingArgs)); + return await dsm.connect(from).unvetSigningKeys(...unvetArgs, sig); + } + + let originalState: string; + + beforeEach(async () => { + originalState = await Snapshot.take(); + + await dsm.addGuardians([guardian1, guardian2], 0); + expect(await dsm.getGuardians()).to.have.length(2); + + await stakingRouter.setStakingModuleNonce(MODULE_NONCE); + expect(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)).to.equal(MODULE_NONCE); + }); + + afterEach(async () => { + await Snapshot.restore(originalState); + }); + + it("Reverts if module nonce changed", async () => { + const nonceBefore = Number(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)); + const newNonce = nonceBefore + 1; + await stakingRouter.setStakingModuleNonce(newNonce); + expect(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)).to.equal(newNonce); + expect(await stakingRouter.getStakingModuleNonce(STAKING_MODULE_ID)).to.not.equal(nonceBefore); + + await expect(unvetSigningKeys(guardian1, { nonce: nonceBefore })).to.be.revertedWithCustomError( + dsm, + "ModuleNonceChanged", + ); + }); + + it("Reverts if `nodeOperatorIds` is not a multiple of 8 bytes", async () => { + await expect(unvetSigningKeys(guardian1, { nodeOperatorIds: "0x000001" })).to.be.revertedWithCustomError( + dsm, + "UnvetPayloadInvalid", + ); + }); + + it("Reverts if `vettedSigningKeysCounts` is not a multiple of 16 bytes", async () => { + await expect(unvetSigningKeys(guardian1, { vettedSigningKeysCounts: "0x000001" })).to.be.revertedWithCustomError( + dsm, + "UnvetPayloadInvalid", + ); + }); + + it("Reverts if the number of operator ids is not equal to the number of keys count", async () => { + await expect( + unvetSigningKeys(guardian1, { + nodeOperatorIds: concat([operatorId1, operatorId2]), + vettedSigningKeysCounts: vettedSigningKeysCount1, + }), + ).to.be.revertedWithCustomError(dsm, "UnvetPayloadInvalid"); + }); + + it("Reverts if the number of operator ids is not equal to the number of keys count", async () => { + const overlimitedPayloadSize = MAX_OPERATORS_PER_UNVETTING + 1; + const nodeOperatorIds = concat(Array(overlimitedPayloadSize).fill(operatorId1)); + const vettedSigningKeysCounts = concat(Array(overlimitedPayloadSize).fill(vettedSigningKeysCount1)); + + await expect( + unvetSigningKeys(guardian1, { nodeOperatorIds, vettedSigningKeysCounts }), + ).to.be.revertedWithCustomError(dsm, "UnvetPayloadInvalid"); + }); + + it("Reverts if it's called by stranger with invalid signature", async () => { + await expect(unvetSigningKeys(unrelatedGuardian1, { sig: invalidSig })).to.be.revertedWith( + "ECDSA: invalid signature", + ); + }); + + it("Reverts if called with zero `block.hash`", async () => { + await expect(unvetSigningKeys(guardian1, { blockHash: ZeroHash })).to.be.revertedWithCustomError( + dsm, + "UnvetUnexpectedBlockHash", + ); + }); + + it("Reverts if called for block with unrecoverable `block.hash`", async () => { + const tooOldBlock = await getLatestBlock(); + await mineUpTo((await time.latestBlock()) + 255); + const latestBlock = await getLatestBlock(); + expect(latestBlock.number > tooOldBlock.number).to.equal(true); + + await expect(unvetSigningKeys(guardian1, { blockHash: ZeroHash })).to.be.revertedWithCustomError( + dsm, + "UnvetUnexpectedBlockHash", + ); + }); + + it("Reverts if `block.hash` and `block.number` from different blocks", async () => { + const previousBlockNumber = await time.latestBlock(); + await mineUpTo((await time.latestBlock()) + 1); + const latestBlockNumber = await time.latestBlock(); + expect(latestBlockNumber > previousBlockNumber).to.equal(true); + + await expect(unvetSigningKeys(guardian1, { blockNumber: previousBlockNumber })).to.be.revertedWithCustomError( + dsm, + "UnvetUnexpectedBlockHash", + ); + }); + + it("Reverts if called with an expired `blockNumber` and `blockHash` by a guardian", async () => { + const block = await getLatestBlock(); + await mineUpTo((await time.latestBlock()) + UNVET_INTENT_VALIDITY_PERIOD_BLOCKS); + + await expect( + unvetSigningKeys(guardian1, { blockNumber: block.number, blockHash: block.hash }), + ).to.be.revertedWithCustomError(dsm, "UnvetIntentExpired"); + }); + + it("Reverts if signature is not guardian", async () => { + await expect(unvetSigningKeys(unrelatedGuardian1)).to.be.revertedWithCustomError(dsm, "InvalidSignature"); + }); + + it("Unvets keys if it's called by stranger with valid signature", async () => { + const sig = await getUnvetSignature(guardian1); + const tx = await unvetSigningKeys(unrelatedGuardian1, { sig }); + + await expect(tx) + .to.emit(stakingRouter, "StakingModuleVettedKeysDecreased") + .withArgs(STAKING_MODULE_ID, defaultNodeOperatorIds, defaultVettedSigningKeysCounts); + }); + + it("Unvets keys if it's called by guardian", async () => { + const tx = await unvetSigningKeys(guardian1); + + await expect(tx) + .to.emit(stakingRouter, "StakingModuleVettedKeysDecreased") + .withArgs(STAKING_MODULE_ID, defaultNodeOperatorIds, defaultVettedSigningKeysCounts); + }); + + it("Unvets keys if it's called by guardian with valid signature", async () => { + const sig = await getUnvetSignature(guardian1); + const tx = await unvetSigningKeys(guardian1, { sig }); + + await expect(tx) + .to.emit(stakingRouter, "StakingModuleVettedKeysDecreased") + .withArgs(STAKING_MODULE_ID, defaultNodeOperatorIds, defaultVettedSigningKeysCounts); + }); + + it("Unvets keys if it's called by guardian with invalid signature", async () => { + const sig = await getUnvetSignature(unrelatedGuardian1); + const tx = await unvetSigningKeys(guardian1, { sig }); + + await expect(tx) + .to.emit(stakingRouter, "StakingModuleVettedKeysDecreased") + .withArgs(STAKING_MODULE_ID, defaultNodeOperatorIds, defaultVettedSigningKeysCounts); + }); + }); }); From 58a967fa3fbced6fab1eb85c307881fc18cd0107 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 15:06:24 +0300 Subject: [PATCH 037/177] fix: add decreaseVettedSigningKeysCount to the staking module mock contract --- test/0.8.9/contracts/StakingModule__Mock.sol | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/0.8.9/contracts/StakingModule__Mock.sol b/test/0.8.9/contracts/StakingModule__Mock.sol index 253198a1e..d7578ec7b 100644 --- a/test/0.8.9/contracts/StakingModule__Mock.sol +++ b/test/0.8.9/contracts/StakingModule__Mock.sol @@ -155,6 +155,15 @@ contract StakingModule__Mock is IStakingModule { onRewardsMintedShouldRunOutGas = shoudRunOutOfGas; } + event Mock__VettedSigningKeysCountDecreased(bytes _nodeOperatorIds, bytes _stuckValidatorsCounts); + + function decreaseVettedSigningKeysCount( + bytes calldata _nodeOperatorIds, + bytes calldata _vettedSigningKeysCounts + ) external { + emit Mock__VettedSigningKeysCountDecreased(_nodeOperatorIds, _vettedSigningKeysCounts); + } + event Mock__StuckValidatorsCountUpdated(bytes _nodeOperatorIds, bytes _stuckValidatorsCounts); function updateStuckValidatorsCount(bytes calldata _nodeOperatorIds, bytes calldata _stuckValidatorsCounts) external { From 3761f331f1bb2af0e6746ddf00a0d7451e5a7148 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 16:46:44 +0300 Subject: [PATCH 038/177] docs: add natspec to dsm contract --- contracts/0.8.9/DepositSecurityModule.sol | 279 ++++++++++++++-------- 1 file changed, 180 insertions(+), 99 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 5e7652580..67ebd2b27 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -18,7 +18,6 @@ interface IDepositContract { interface IStakingRouter { function getStakingModuleMinDepositBlockDistance(uint256 _stakingModuleId) external view returns (uint256); function getStakingModuleMaxDepositsPerBlock(uint256 _stakingModuleId) external view returns (uint256); - function getStakingModuleIsDepositsPaused(uint256 _stakingModuleId) external view returns (bool); function getStakingModuleIsActive(uint256 _stakingModuleId) external view returns (bool); function getStakingModuleNonce(uint256 _stakingModuleId) external view returns (uint256); function getStakingModuleLastDepositBlock(uint256 _stakingModuleId) external view returns (uint256); @@ -30,11 +29,13 @@ interface IStakingRouter { ) external; } - - +/** + * @title DepositSecurityModule + * @dev The contract represents a security module for handling deposits. + */ contract DepositSecurityModule { /** - * Short ECDSA signature as defined in https://eips.ethereum.org/EIPS/eip-2098. + * @dev Short ECDSA signature as defined in https://eips.ethereum.org/EIPS/eip-2098. */ struct Signature { bytes32 r; @@ -71,14 +72,21 @@ contract DepositSecurityModule { error NotAGuardian(address addr); error ZeroParameter(string parameter); + /// @notice Represents the code version to help distinguish contract interfaces. + uint256 public constant VERSION = 3; + + /// @notice Prefix for the message signed by guardians to attest a deposit. bytes32 public immutable ATTEST_MESSAGE_PREFIX; + /// @notice Prefix for the message signed by guardians to pause deposits. bytes32 public immutable PAUSE_MESSAGE_PREFIX; + /// @notice Prefix for the message signed by guardians to unvet signing keys. bytes32 public immutable UNVET_MESSAGE_PREFIX; ILido public immutable LIDO; IStakingRouter public immutable STAKING_ROUTER; IDepositContract public immutable DEPOSIT_CONTRACT; + /// @notice Flag indicating whether deposits are paused. bool public isDepositsPaused; uint256 internal lastDepositBlock; @@ -93,6 +101,12 @@ contract DepositSecurityModule { address[] internal guardians; mapping(address => uint256) internal guardianIndicesOneBased; // 1-based + /** + * @notice Initializes the contract with the given parameters. + * @dev Reverts if any of the addresses is zero. + * + * Sets the last deposit block to the current block number. + */ constructor( address _lido, address _depositContract, @@ -144,7 +158,8 @@ contract DepositSecurityModule { } /** - * Returns the owner address. + * @notice Returns the owner of the contract. + * @return owner The address of the owner. */ function getOwner() external view returns (address) { return owner; @@ -156,7 +171,9 @@ contract DepositSecurityModule { } /** - * Sets new owner. Only callable by the current owner. + * @notice Sets the owner of the contract. + * @param newValue The address of the new owner. + * @dev Only callable by the current owner. */ function setOwner(address newValue) external onlyOwner { _setOwner(newValue); @@ -169,14 +186,17 @@ contract DepositSecurityModule { } /** - * Returns current `pauseIntentValidityPeriodBlocks` contract parameter (see `pauseDeposits`). + * @notice Returns the number of blocks during which the pause intent is valid. + * @return pauseIntentValidityPeriodBlocks The number of blocks during which the pause intent is valid. */ function getPauseIntentValidityPeriodBlocks() external view returns (uint256) { return pauseIntentValidityPeriodBlocks; } /** - * Sets `pauseIntentValidityPeriodBlocks`. Only callable by the owner. + * @notice Sets the number of blocks during which the pause intent is valid. + * @param newValue The new number of blocks during which the pause intent is valid. + * @dev Only callable by the owner. */ function setPauseIntentValidityPeriodBlocks(uint256 newValue) external onlyOwner { _setPauseIntentValidityPeriodBlocks(newValue); @@ -189,14 +209,17 @@ contract DepositSecurityModule { } /** - * Returns current `unvetIntentValidityPeriodBlocks` contract parameter (see `unvetSigningKeys`). + * @notice Returns the number of blocks during which the unvet intent is valid. + * @return unvetIntentValidityPeriodBlocks The number of blocks during which the unvet intent is valid. */ function getUnvetIntentValidityPeriodBlocks() external view returns (uint256) { return unvetIntentValidityPeriodBlocks; } /** - * Sets `unvetIntentValidityPeriodBlocks`. Only callable by the owner. + * @notice Sets the number of blocks during which the unvet intent is valid. + * @param newValue The new number of blocks during which the unvet intent is valid. + * @dev Only callable by the owner. */ function setUnvetIntentValidityPeriodBlocks(uint256 newValue) external onlyOwner { _setUnvetIntentValidityPeriodBlocks(newValue); @@ -208,16 +231,18 @@ contract DepositSecurityModule { emit UnvetIntentValidityPeriodBlocksChanged(newValue); } - /** - * Returns current `maxOperatorsPerUnvetting` contract parameter (see `unvetSigningKeys`). + * @notice Returns the maximum number of operators per unvetting. + * @return maxOperatorsPerUnvetting The maximum number of operators per unvetting. */ function getMaxOperatorsPerUnvetting() external view returns (uint256) { return maxOperatorsPerUnvetting; } /** - * Sets `maxOperatorsPerUnvetting`. Only callable by the owner. + * @notice Sets the maximum number of operators per unvetting. + * @param newValue The new maximum number of operators per unvetting. + * @dev Only callable by the owner. */ function setMaxOperatorsPerUnvetting(uint256 newValue) external onlyOwner { _setMaxOperatorsPerUnvetting(newValue); @@ -230,18 +255,25 @@ contract DepositSecurityModule { } /** - * Returns number of valid guardian signatures required to attest (depositRoot, nonce) pair. + * @notice Returns the number of guardians required to perform a deposit. + * @return quorum The guardian quorum value. */ function getGuardianQuorum() external view returns (uint256) { return quorum; } + /** + * @notice Sets the number of guardians required to perform a deposit. + * @param newValue The new guardian quorum value. + * @dev Only callable by the owner. + */ function setGuardianQuorum(uint256 newValue) external onlyOwner { _setGuardianQuorum(newValue); } function _setGuardianQuorum(uint256 newValue) internal { - // we're intentionally allowing setting quorum value higher than the number of guardians + /// @dev This intentionally allows the quorum value to be set higher + /// than the number of guardians. if (quorum != newValue) { quorum = newValue; emit GuardianQuorumChanged(newValue); @@ -249,14 +281,17 @@ contract DepositSecurityModule { } /** - * Returns guardian committee member list. + * @notice Returns the list of guardian addresses. + * @return guardians The list of guardian addresses. */ function getGuardians() external view returns (address[] memory) { return guardians; } /** - * Checks whether the given address is a guardian. + * @notice Returns whether the given address is a guardian. + * @param addr The address to check. + * @return isGuardian Whether the address is a guardian. */ function isGuardian(address addr) external view returns (bool) { return _isGuardian(addr); @@ -267,7 +302,10 @@ contract DepositSecurityModule { } /** - * Returns index of the guardian, or -1 if the address is not a guardian. + * @notice Returns the index of the guardian with the given address. + * @param addr The address of the guardian. + * @return guardianIndex The index of the guardian. + * @dev Returns -1 if the address is not a guardian. */ function getGuardianIndex(address addr) external view returns (int256) { return _getGuardianIndex(addr); @@ -278,10 +316,11 @@ contract DepositSecurityModule { } /** - * Adds a guardian address and sets a new quorum value. - * Reverts if the address is already a guardian. - * - * Only callable by the owner. + * @notice Adds a guardian address and sets a new quorum value. + * @param addr The address of the guardian. + * @param newQuorum The new guardian quorum value. + * @dev Only callable by the owner. + * Reverts if the address is already a guardian or is zero. */ function addGuardian(address addr, uint256 newQuorum) external onlyOwner { _addGuardian(addr); @@ -289,10 +328,11 @@ contract DepositSecurityModule { } /** - * Adds a set of guardian addresses and sets a new quorum value. - * Reverts any of them is already a guardian. - * - * Only callable by the owner. + * @notice Adds multiple guardian addresses and sets a new quorum value. + * @param addresses The list of guardian addresses. + * @param newQuorum The new guardian quorum value. + * @dev Only callable by the owner. + * Reverts if any of the addresses is already a guardian or is zero. */ function addGuardians(address[] memory addresses, uint256 newQuorum) external onlyOwner { for (uint256 i = 0; i < addresses.length; ++i) { @@ -310,9 +350,11 @@ contract DepositSecurityModule { } /** - * Removes a guardian with the given address and sets a new quorum value. - * - * Only callable by the owner. + * @notice Removes a guardian address and sets a new quorum value. + * @param addr The address of the guardian. + * @param newQuorum The new guardian quorum value. + * @dev Only callable by the owner. + * Reverts if the address is not a guardian. */ function removeGuardian(address addr, uint256 newQuorum) external onlyOwner { uint256 indexOneBased = guardianIndicesOneBased[addr]; @@ -336,25 +378,25 @@ contract DepositSecurityModule { } /** - * Pauses deposits if both conditions are satisfied (reverts otherwise): - * - * 1. The function is called by the guardian with index guardianIndex OR sig - * is a valid signature by the guardian with index guardianIndex of the data - * defined below. + * @notice Pauses deposits if both conditions are satisfied. + * @param blockNumber The block number at which the pause intent was created. + * @param sig The signature of the guardian. + * @dev Does nothing if deposits are already paused. * - * 2. block.number - blockNumber <= pauseIntentValidityPeriodBlocks + * Reverts if: + * - the pause intent is expired; + * - the caller is not a guardian and signature is invalid or not from a guardian. * - * The signature, if present, must be produced for keccak256 hash of the following + * The signature, if present, must be produced for the keccak256 hash of the following * message (each component taking 32 bytes): * * | PAUSE_MESSAGE_PREFIX | blockNumber | */ function pauseDeposits(uint256 blockNumber, Signature memory sig) external { - // In case of an emergency function `pauseDeposits` is supposed to be called - // by all guardians. Thus only the first call will do the actual change. But - // the other calls would be OK operations from the point of view of protocol’s logic. - // Thus we prefer not to use “error” semantics which is implied by `require`. - + /// @dev In case of an emergency function `pauseDeposits` is supposed to be called + /// by all guardians. Thus only the first call will do the actual change. But + /// the other calls would be OK operations from the point of view of protocol’s logic. + /// Thus we prefer not to use “error” semantics which is implied by `require`. if (isDepositsPaused) return; address guardianAddr = msg.sender; @@ -374,9 +416,9 @@ contract DepositSecurityModule { } /** - * Unpauses deposits - * - * Only callable by the owner. + * @notice Unpauses deposits. + * @dev Only callable by the owner. + * Reverts if deposits are not paused. */ function unpauseDeposits() external onlyOwner { if (!isDepositsPaused) revert DepositsNotPaused(); @@ -384,10 +426,20 @@ contract DepositSecurityModule { emit DepositsUnpaused(); } + /** - * Returns whether LIDO.deposit() can be called, given that the caller will provide - * guardian attestations of non-stale deposit root and `nonce`, and the number of - * such attestations will be enough to reach quorum. + * @notice Returns whether LIDO.deposit() can be called, given that the caller + * will provide guardian attestations of non-stale deposit root and nonce, + * and the number of such attestations will be enough to reach the quorum. + * + * @param stakingModuleId The ID of the staking module. + * @return canDeposit Whether a deposit can be made. + * @dev Returns true if all of the following conditions are met: + * - deposits are not paused; + * - the staking module is active; + * - the guardian quorum is not set to zero; + * - the deposit distance is greater than the minimum required; + * - LIDO.canDeposit() returns true. */ function canDeposit(uint256 stakingModuleId) external view returns (bool) { if (!STAKING_ROUTER.hasStakingModule(stakingModuleId)) return false; @@ -406,7 +458,8 @@ contract DepositSecurityModule { } /** - * Returns the last block number when a deposit was made. + * @notice Returns the block number of the last deposit. + * @return lastDepositBlock The block number of the last deposit. */ function getLastDepositBlock() external view returns (uint256) { return lastDepositBlock; @@ -418,7 +471,10 @@ contract DepositSecurityModule { } /** - * Returns whether the deposit distance is greater than the minimum required. + * @notice Returns whether the deposit distance is greater than the minimum required. + * @param stakingModuleId The ID of the staking module. + * @return isMinDepositDistancePassed Whether the deposit distance is greater than the minimum required. + * @dev Checks the distance for the last deposit to any staking module. */ function isMinDepositDistancePassed(uint256 stakingModuleId) external view returns (bool) { return _isMinDepositDistancePassed(stakingModuleId); @@ -432,15 +488,23 @@ contract DepositSecurityModule { } /** - * Calls LIDO.deposit(maxDepositsPerBlock, stakingModuleId, depositCalldata). - * - * Reverts if any of the following is true: - * 1. IDepositContract.get_deposit_root() != depositRoot. - * 2. StakingModule.getNonce() != nonce. - * 3. The number of guardian signatures is less than getGuardianQuorum(). - * 4. An invalid or non-guardian signature received. - * 5. block.number - StakingModule.getLastDepositBlock() < minDepositBlockDistance. - * 6. blockhash(blockNumber) != blockHash. + * @notice Calls LIDO.deposit(maxDepositsPerBlock, stakingModuleId, depositCalldata). + * @param blockNumber The block number at which the deposit intent was created. + * @param blockHash The block hash at which the deposit intent was created. + * @param depositRoot The deposit root hash. + * @param stakingModuleId The ID of the staking module. + * @param nonce The nonce of the staking module. + * @param depositCalldata The calldata for the deposit. + * @param sortedGuardianSignatures The list of guardian signatures ascendingly sorted by address. + * @dev Reverts if any of the following is true: + * - quorum is zero; + * - the number of guardian signatures is less than the quorum; + * - onchain deposit root is different from the provided one; + * - module is not active; + * - min deposit distance is not passed; + * - blockHash is zero or not equal to the blockhash(blockNumber); + * - onchain module nonce is different from the provided one; + * - invalid or non-guardian signature received; * * Signatures must be sorted in ascending order by address of the guardian. Each signature must * be produced for the keccak256 hash of the following message (each component taking 32 bytes): @@ -456,39 +520,77 @@ contract DepositSecurityModule { bytes calldata depositCalldata, Signature[] calldata sortedGuardianSignatures ) external { - if (quorum == 0 || sortedGuardianSignatures.length < quorum) revert DepositNoQuorum(); - + /// @dev The first most likely reason for the signature to go stale bytes32 onchainDepositRoot = IDepositContract(DEPOSIT_CONTRACT).get_deposit_root(); if (depositRoot != onchainDepositRoot) revert DepositRootChanged(); - if (!STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId)) revert DepositInactiveModule(); - - uint256 maxDepositsPerBlock = STAKING_ROUTER.getStakingModuleMaxDepositsPerBlock(stakingModuleId); + /// @dev The second most likely reason for the signature to go stale + uint256 onchainNonce = STAKING_ROUTER.getStakingModuleNonce(stakingModuleId); + if (nonce != onchainNonce) revert ModuleNonceChanged(); + if (quorum == 0 || sortedGuardianSignatures.length < quorum) revert DepositNoQuorum(); + if (!STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId)) revert DepositInactiveModule(); if (!_isMinDepositDistancePassed(stakingModuleId)) revert DepositTooFrequent(); if (blockHash == bytes32(0) || blockhash(blockNumber) != blockHash) revert DepositUnexpectedBlockHash(); - uint256 onchainNonce = STAKING_ROUTER.getStakingModuleNonce(stakingModuleId); - if (nonce != onchainNonce) revert ModuleNonceChanged(); - - _verifySignatures(depositRoot, blockNumber, blockHash, stakingModuleId, nonce, sortedGuardianSignatures); + _verifyAttestSignatures( + depositRoot, + blockNumber, + blockHash, + stakingModuleId, + nonce, + sortedGuardianSignatures + ); + uint256 maxDepositsPerBlock = STAKING_ROUTER.getStakingModuleMaxDepositsPerBlock(stakingModuleId); LIDO.deposit(maxDepositsPerBlock, stakingModuleId, depositCalldata); + _setLastDepositBlock(block.number); } + function _verifyAttestSignatures( + bytes32 depositRoot, + uint256 blockNumber, + bytes32 blockHash, + uint256 stakingModuleId, + uint256 nonce, + Signature[] memory sigs + ) internal view { + bytes32 msgHash = keccak256( + abi.encodePacked(ATTEST_MESSAGE_PREFIX, blockNumber, blockHash, depositRoot, stakingModuleId, nonce) + ); + + address prevSignerAddr = address(0); + + for (uint256 i = 0; i < sigs.length;) { + address signerAddr = ECDSA.recover(msgHash, sigs[i].r, sigs[i].vs); + if (!_isGuardian(signerAddr)) revert InvalidSignature(); + if (signerAddr <= prevSignerAddr) revert SignaturesNotSorted(); + prevSignerAddr = signerAddr; + + unchecked { ++i; } + } + } + /** - * Unvetting signing keys for the given node operators. - * - * Reverts if any of the following is true: - * 1. nodeOperatorIds is not packed with 8 bytes per id. - * 2. vettedSigningKeysCounts is not packed with 16 bytes per count. - * 3. The number of node operators is greater than maxOperatorsPerUnvetting. - * 4. The nonce is not equal to the on-chain nonce of the staking module. - * 5. The signature is invalid or the signer is not a guardian. - * 6. block.number - blockNumber > unvetIntentValidityPeriodBlocks. + * @notice Unvets signing keys for the given node operators. + * @param blockNumber The block number at which the unvet intent was created. + * @param blockHash The block hash at which the unvet intent was created. + * @param stakingModuleId The ID of the staking module. + * @param nonce The nonce of the staking module. + * @param nodeOperatorIds The list of node operator IDs. + * @param vettedSigningKeysCounts The list of vetted signing keys counts. + * @param sig The signature of the guardian. + * @dev Reverts if any of the following is true: + * - The nonce is not equal to the on-chain nonce of the staking module; + * - nodeOperatorIds is not packed with 8 bytes per id; + * - vettedSigningKeysCounts is not packed with 16 bytes per count; + * - the number of node operators is greater than maxOperatorsPerUnvetting; + * - the signature is invalid or the signer is not a guardian; + * - blockHash is zero or not equal to the blockhash(blockNumber); + * - the unvet intent is expired. * - * The signature, if present, must be produced for keccak256 hash of the following message: + * The signature, if present, must be produced for the keccak256 hash of the following message: * * | UNVET_MESSAGE_PREFIX | blockNumber | blockHash | stakingModuleId | nonce | nodeOperatorIds | vettedSigningKeysCounts | */ @@ -501,6 +603,7 @@ contract DepositSecurityModule { bytes calldata vettedSigningKeysCounts, Signature calldata sig ) external { + /// @dev The most likely reason for the signature to go stale uint256 onchainNonce = STAKING_ROUTER.getStakingModuleNonce(stakingModuleId); if (nonce != onchainNonce) revert ModuleNonceChanged(); @@ -540,26 +643,4 @@ contract DepositSecurityModule { stakingModuleId, nodeOperatorIds, vettedSigningKeysCounts ); } - - function _verifySignatures( - bytes32 depositRoot, - uint256 blockNumber, - bytes32 blockHash, - uint256 stakingModuleId, - uint256 nonce, - Signature[] memory sigs - ) internal view { - bytes32 msgHash = keccak256( - abi.encodePacked(ATTEST_MESSAGE_PREFIX, blockNumber, blockHash, depositRoot, stakingModuleId, nonce) - ); - - address prevSignerAddr = address(0); - - for (uint256 i = 0; i < sigs.length; ++i) { - address signerAddr = ECDSA.recover(msgHash, sigs[i].r, sigs[i].vs); - if (!_isGuardian(signerAddr)) revert InvalidSignature(); - if (signerAddr <= prevSignerAddr) revert SignaturesNotSorted(); - prevSignerAddr = signerAddr; - } - } } From 6d803c8612b8975602652c3a7276b3fb076f1d7d Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 16:48:48 +0300 Subject: [PATCH 039/177] refactor: dsm test types --- test/0.8.9/depositSecurityModule.test.ts | 66 +++++++++++++----------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/test/0.8.9/depositSecurityModule.test.ts b/test/0.8.9/depositSecurityModule.test.ts index 2b1eb26b8..8c196803e 100644 --- a/test/0.8.9/depositSecurityModule.test.ts +++ b/test/0.8.9/depositSecurityModule.test.ts @@ -95,39 +95,45 @@ describe("DepositSecurityModule.sol", () => { return block as Block; } + type DepositArgs = { + blockNumber?: number; + blockHash?: string; + depositRoot?: string; + stakingModuleId?: number; + nonce?: number; + depositCalldata?: string; + }; + + async function getDepositArgs(overridingArgs?: DepositArgs) { + const stakingModuleId = overridingArgs?.stakingModuleId ?? STAKING_MODULE_ID; + + const [latestBlock, defaultDepositRoot, defaultModuleNonce] = await Promise.all([ + getLatestBlock(), + depositContract.get_deposit_root(), + stakingRouter.getStakingModuleNonce(stakingModuleId), + ]); + + const blockNumber = overridingArgs?.blockNumber ?? latestBlock.number; + const blockHash = overridingArgs?.blockHash ?? latestBlock.hash; + const depositRoot = overridingArgs?.depositRoot ?? defaultDepositRoot; + const nonce = overridingArgs?.nonce ?? Number(defaultModuleNonce); + const depositCalldata = overridingArgs?.depositCalldata ?? encodeBytes32String(""); + + return [depositCalldata, blockNumber, blockHash, depositRoot, stakingModuleId, nonce] as const; + } + async function deposit( sortedGuardianWallets: Wallet[], - args?: { - blockNumber?: number; - blockHash?: string; - depositRoot?: string; - nonce?: number; - depositCalldata?: string; - }, + overridingArgs?: DepositArgs, ): Promise { - const stakingModuleId = STAKING_MODULE_ID; - - const latestBlock = await getLatestBlock(); - const blockNumber = args?.blockNumber ?? latestBlock.number; - const blockHash = args?.blockHash ?? latestBlock.hash; - const depositRoot = args?.depositRoot ?? (await depositContract.get_deposit_root()); - const nonce = args?.nonce ?? Number(await stakingRouter.getStakingModuleNonce(stakingModuleId)); - const depositCalldata = args?.depositCalldata ?? encodeBytes32String(""); + const [depositCalldata, ...signingArgs] = await getDepositArgs(overridingArgs); const sortedGuardianSignatures = sortedGuardianWallets.map((guardian) => { - const validAttestMessage = new DSMAttestMessage(blockNumber, blockHash, depositRoot, stakingModuleId, nonce); + const validAttestMessage = new DSMAttestMessage(...signingArgs); return validAttestMessage.sign(guardian.privateKey); }); - return await dsm.depositBufferedEther( - blockNumber, - blockHash, - depositRoot, - stakingModuleId, - nonce, - depositCalldata, - sortedGuardianSignatures, - ); + return await dsm.depositBufferedEther(...signingArgs, depositCalldata, sortedGuardianSignatures); } before(async () => { @@ -432,7 +438,7 @@ describe("DepositSecurityModule.sol", () => { ).to.be.revertedWithCustomError(dsm, "NotAnOwner"); }); - it("Sets `setMaxOperatorsPerUnvetting` and fires `MaxOperatorsPerUnvettingChanged` event", async () => { + it("Sets `maxOperatorsPerUnvetting` and fires `MaxOperatorsPerUnvettingChanged` event", async () => { const valueBefore = await dsm.getMaxOperatorsPerUnvetting(); const newValue = config.maxOperatorsPerUnvetting + 1; @@ -1433,7 +1439,7 @@ describe("DepositSecurityModule.sol", () => { vs: encodeBytes32String(""), }; - interface UnvetArgs { + type UnvetArgs = { blockNumber?: number; blockHash?: string; stakingModuleId?: number; @@ -1441,11 +1447,9 @@ describe("DepositSecurityModule.sol", () => { nodeOperatorIds?: string; vettedSigningKeysCounts?: string; sig?: DepositSecurityModule.SignatureStruct; - } + }; - interface UnvetSignedArgs extends UnvetArgs { - sig?: DepositSecurityModule.SignatureStruct; - } + type UnvetSignedArgs = UnvetArgs & { sig?: DepositSecurityModule.SignatureStruct }; async function getUnvetArgs(overridingArgs?: UnvetArgs) { const latestBlock = await getLatestBlock(); From 1c3a0918c76988b5c32d40743df4ae85f1cd3d9d Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 16:49:13 +0300 Subject: [PATCH 040/177] test: dsm contract version --- test/0.8.9/depositSecurityModule.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/0.8.9/depositSecurityModule.test.ts b/test/0.8.9/depositSecurityModule.test.ts index 8c196803e..c16a4f297 100644 --- a/test/0.8.9/depositSecurityModule.test.ts +++ b/test/0.8.9/depositSecurityModule.test.ts @@ -234,6 +234,10 @@ describe("DepositSecurityModule.sol", () => { }); context("Constants", () => { + it("Returns the VERSION variable", async () => { + expect(await dsm.VERSION()).to.equal(3); + }); + it("Returns the ATTEST_MESSAGE_PREFIX variable", async () => { const dsmAttestMessagePrefix = streccak("lido.DepositSecurityModule.ATTEST_MESSAGE"); expect(dsmAttestMessagePrefix).to.equal("0x1085395a994e25b1b3d0ea7937b7395495fb405b31c7d22dbc3976a6bd01f2bf"); From 6009f711be173f82cff1b866ab9b9c65360878c9 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 17:12:23 +0300 Subject: [PATCH 041/177] fix: move priorityExitShareThreshold to the end of struct for backward compatibility --- contracts/0.8.9/StakingRouter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 4494d5fdd..fef425253 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -88,8 +88,6 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint16 stakeShareLimit; // formerly known as `targetShare` /// @notice staking module status if staking module can not accept the deposits or can participate in further reward distribution uint8 status; - /// @notice module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP - uint16 priorityExitShareThreshold; /// @notice name of staking module string name; /// @notice block.timestamp of the last deposit of the staking module @@ -100,6 +98,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 lastDepositBlock; /// @notice number of exited validators uint256 exitedValidatorsCount; + /// @notice module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP + uint16 priorityExitShareThreshold; /// @notice the maximum number of validators that can be deposited in a single block uint64 maxDepositsPerBlock; /// @notice the minimum distance between deposits in blocks From df47bd168c1bf51d07dbb9dc7ad27cc428841b58 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 17:12:54 +0300 Subject: [PATCH 042/177] test: add module deposit params to staking router tests --- .../stakingRouter.module-management.test.ts | 62 ++++++++++++++++++- .../stakingRouter.module-sync.test.ts | 26 +++++++- .../stakingRouter.rewards.test.ts | 8 +++ .../stakingRouter.status-control.test.ts | 2 + 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts index a638b045e..7383822c8 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts @@ -52,12 +52,23 @@ describe("StakingRouter:module-management", () => { const PRIORITY_EXIT_SHARE_THRESHOLD = STAKE_SHARE_LIMIT; const MODULE_FEE = 5_00n; const TREASURY_FEE = 5_00n; + const MAX_DEPOSITS_PER_BLOCK = 150n; + const MIN_DEPOSIT_BLOCK_DISTANCE = 25n; it("Reverts if the caller does not have the role", async () => { await expect( stakingRouter .connect(user) - .addStakingModule(NAME, ADDRESS, STAKE_SHARE_LIMIT, PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE), + .addStakingModule( + NAME, + ADDRESS, + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, + ), ).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.STAKING_MODULE_MANAGE_ROLE()); }); //todo priority < share @@ -73,6 +84,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") @@ -90,6 +103,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE_INVALID, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") @@ -105,6 +120,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE_INVALID, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") @@ -120,6 +137,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ZeroAddress") @@ -137,6 +156,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModuleWrongName"); }); @@ -153,6 +174,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModuleWrongName"); }); @@ -168,6 +191,8 @@ describe("StakingRouter:module-management", () => { 1_00, 1_00, 1_00, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ); } @@ -181,6 +206,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModulesLimitExceeded"); }); @@ -193,6 +220,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ); await expect( @@ -203,6 +232,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ).to.be.revertedWithCustomError(stakingRouter, "StakingModuleAddressExists"); }); @@ -219,6 +250,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.emit(stakingRouter, "StakingRouterETHDeposited") @@ -237,11 +270,13 @@ describe("StakingRouter:module-management", () => { TREASURY_FEE, STAKE_SHARE_LIMIT, 0n, // status active - PRIORITY_EXIT_SHARE_THRESHOLD, NAME, moduleAddedBlock.timestamp, moduleAddedBlock.number, - 0n, // exited validators + 0n, // exited validators, + PRIORITY_EXIT_SHARE_THRESHOLD, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ]); }); }); @@ -253,6 +288,8 @@ describe("StakingRouter:module-management", () => { const PRIORITY_EXIT_SHARE_THRESHOLD = STAKE_SHARE_LIMIT; const MODULE_FEE = 5_00n; const TREASURY_FEE = 5_00n; + const MAX_DEPOSITS_PER_BLOCK = 150n; + const MIN_DEPOSIT_BLOCK_DISTANCE = 25n; let ID: bigint; @@ -262,6 +299,9 @@ describe("StakingRouter:module-management", () => { const NEW_MODULE_FEE = 6_00n; const NEW_TREASURY_FEE = 4_00n; + const NEW_MAX_DEPOSITS_PER_BLOCK = 100n; + const NEW_MIN_DEPOSIT_BLOCK_DISTANCE = 20n; + beforeEach(async () => { await stakingRouter.addStakingModule( NAME, @@ -270,6 +310,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ); ID = await stakingRouter.getStakingModulesCount(); }); @@ -284,6 +326,8 @@ describe("StakingRouter:module-management", () => { NEW_PRIORITY_EXIT_SHARE_THRESHOLD, NEW_MODULE_FEE, NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + NEW_MIN_DEPOSIT_BLOCK_DISTANCE, ), ).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.STAKING_MODULE_MANAGE_ROLE()); }); @@ -297,6 +341,8 @@ describe("StakingRouter:module-management", () => { NEW_PRIORITY_EXIT_SHARE_THRESHOLD, NEW_MODULE_FEE, NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + NEW_MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") @@ -312,6 +358,8 @@ describe("StakingRouter:module-management", () => { NEW_PRIORITY_EXIT_SHARE_THRESHOLD_OVER_100, NEW_MODULE_FEE, NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + NEW_MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") @@ -328,6 +376,8 @@ describe("StakingRouter:module-management", () => { NEW_PRIORITY_EXIT_SHARE_THRESHOLD, NEW_MODULE_FEE, NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + NEW_MIN_DEPOSIT_BLOCK_DISTANCE, ), ).to.be.revertedWithCustomError(stakingRouter, "InvalidPriorityExitShareThreshold"); }); @@ -342,6 +392,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, NEW_MODULE_FEE_INVALID, TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") @@ -355,6 +407,8 @@ describe("StakingRouter:module-management", () => { PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, NEW_TREASURY_FEE_INVALID, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") @@ -369,6 +423,8 @@ describe("StakingRouter:module-management", () => { NEW_PRIORITY_EXIT_SHARE_THRESHOLD, NEW_MODULE_FEE, NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + NEW_MIN_DEPOSIT_BLOCK_DISTANCE, ), ) .to.be.emit(stakingRouter, "StakingModuleShareLimitSet") diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts index 61539cb2c..487412ad3 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts @@ -39,6 +39,8 @@ describe("StakingRouter:module-sync", () => { const treasuryFee = 5_00n; const stakeShareLimit = 1_00n; const priorityExitShareThreshold = 2_00n; + const maxDepositsPerBlock = 150n; + const minDepositBlockDistance = 25n; beforeEach(async () => { [deployer, admin, user, lido] = await ethers.getSigners(); @@ -86,13 +88,29 @@ describe("StakingRouter:module-sync", () => { priorityExitShareThreshold, stakingModuleFee, treasuryFee, + maxDepositsPerBlock, + minDepositBlockDistance, ); moduleId = await stakingRouter.getStakingModulesCount(); }); context("Getters", () => { - let stakingModuleInfo: [bigint, string, bigint, bigint, bigint, bigint, bigint, string, bigint, bigint, bigint]; + let stakingModuleInfo: [ + bigint, + string, + bigint, + bigint, + bigint, + bigint, + string, + bigint, + bigint, + bigint, + bigint, + bigint, + bigint, + ]; // module mock state const stakingModuleSummary: Parameters = [ @@ -127,11 +145,13 @@ describe("StakingRouter:module-sync", () => { treasuryFee, stakeShareLimit, 0n, // status - priorityExitShareThreshold, name, lastDepositAt, lastDepositBlock, - 0n, // exitedValidatorsCount + 0n, // exitedValidatorsCount, + priorityExitShareThreshold, + maxDepositsPerBlock, + minDepositBlockDistance, ]; // mocking module state diff --git a/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts b/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts index 2d64a1921..385a06653 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.rewards.test.ts @@ -28,6 +28,8 @@ describe("StakingRouter:deposits", () => { priorityExitShareThreshold: 100_00n, moduleFee: 5_00n, treasuryFee: 5_00n, + maxDepositsPerBlock: 150n, + minDepositBlockDistance: 25n, }; beforeEach(async () => { @@ -447,6 +449,8 @@ describe("StakingRouter:deposits", () => { priorityExitShareThreshold, moduleFee, treasuryFee, + maxDepositsPerBlock, + minDepositBlockDistance, exited = 0n, deposited = 0n, depositable = 0n, @@ -464,6 +468,8 @@ describe("StakingRouter:deposits", () => { priorityExitShareThreshold, moduleFee, treasuryFee, + maxDepositsPerBlock, + minDepositBlockDistance, ); const moduleId = modulesCount + 1n; @@ -490,6 +496,8 @@ interface ModuleConfig { priorityExitShareThreshold: bigint; moduleFee: bigint; treasuryFee: bigint; + maxDepositsPerBlock: bigint; + minDepositBlockDistance: bigint; exited?: bigint; deposited?: bigint; depositable?: bigint; diff --git a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts index d0bcd1f44..c5ca2dbf2 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts @@ -64,6 +64,8 @@ context("StakingRouter:status-control", () => { 1_00, // target share 5_00, // module fee 5_00, // treasury fee + 150, // max deposits per block + 25, // min deposit block distance ); moduleId = await stakingRouter.getStakingModulesCount(); From b5b0b40ad19ef65c8820148476f2448179414dc0 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 17:28:00 +0300 Subject: [PATCH 043/177] refactor: format dsm contract --- contracts/0.8.9/DepositSecurityModule.sol | 40 +++++++++++------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 67ebd2b27..3d90df01f 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -426,7 +426,6 @@ contract DepositSecurityModule { emit DepositsUnpaused(); } - /** * @notice Returns whether LIDO.deposit() can be called, given that the caller * will provide guardian attestations of non-stale deposit root and nonce, @@ -533,14 +532,7 @@ contract DepositSecurityModule { if (!_isMinDepositDistancePassed(stakingModuleId)) revert DepositTooFrequent(); if (blockHash == bytes32(0) || blockhash(blockNumber) != blockHash) revert DepositUnexpectedBlockHash(); - _verifyAttestSignatures( - depositRoot, - blockNumber, - blockHash, - stakingModuleId, - nonce, - sortedGuardianSignatures - ); + _verifyAttestSignatures(depositRoot, blockNumber, blockHash, stakingModuleId, nonce, sortedGuardianSignatures); uint256 maxDepositsPerBlock = STAKING_ROUTER.getStakingModuleMaxDepositsPerBlock(stakingModuleId); LIDO.deposit(maxDepositsPerBlock, stakingModuleId, depositCalldata); @@ -562,13 +554,15 @@ contract DepositSecurityModule { address prevSignerAddr = address(0); - for (uint256 i = 0; i < sigs.length;) { + for (uint256 i = 0; i < sigs.length; ) { address signerAddr = ECDSA.recover(msgHash, sigs[i].r, sigs[i].vs); if (!_isGuardian(signerAddr)) revert InvalidSignature(); if (signerAddr <= prevSignerAddr) revert SignaturesNotSorted(); prevSignerAddr = signerAddr; - unchecked { ++i; } + unchecked { + ++i; + } } } @@ -622,15 +616,17 @@ contract DepositSecurityModule { int256 guardianIndex = _getGuardianIndex(msg.sender); if (guardianIndex == -1) { - bytes32 msgHash = keccak256(abi.encodePacked( - UNVET_MESSAGE_PREFIX, - blockNumber, - blockHash, - stakingModuleId, - nonce, - nodeOperatorIds, - vettedSigningKeysCounts - )); + bytes32 msgHash = keccak256( + abi.encodePacked( + UNVET_MESSAGE_PREFIX, + blockNumber, + blockHash, + stakingModuleId, + nonce, + nodeOperatorIds, + vettedSigningKeysCounts + ) + ); guardianAddr = ECDSA.recover(msgHash, sig.r, sig.vs); guardianIndex = _getGuardianIndex(guardianAddr); if (guardianIndex == -1) revert InvalidSignature(); @@ -640,7 +636,9 @@ contract DepositSecurityModule { if (block.number - blockNumber > unvetIntentValidityPeriodBlocks) revert UnvetIntentExpired(); STAKING_ROUTER.decreaseStakingModuleVettedKeysCountByNodeOperator( - stakingModuleId, nodeOperatorIds, vettedSigningKeysCounts + stakingModuleId, + nodeOperatorIds, + vettedSigningKeysCounts ); } } From 8147c700968509c094f65578114d624300255826 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 17:28:30 +0300 Subject: [PATCH 044/177] docs: add module deposit params note --- contracts/0.8.9/StakingRouter.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index fef425253..8042b0907 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -101,8 +101,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @notice module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP uint16 priorityExitShareThreshold; /// @notice the maximum number of validators that can be deposited in a single block + /// @dev must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit` + /// (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function) uint64 maxDepositsPerBlock; /// @notice the minimum distance between deposits in blocks + /// @dev must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit` + /// (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function) uint64 minDepositBlockDistance; } From b56788f8d8a0b0eb2f1399194bbe4928dad69107 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 18:53:11 +0300 Subject: [PATCH 045/177] feat: remove pause and resume methods for staking module from SR --- contracts/0.8.9/StakingRouter.sol | 51 ++------- scripts/scratch/steps/13-grant-roles.ts | 8 +- ...kingRouterMockForDepositSecurityModule.sol | 10 -- test/0.8.9/depositSecurityModule.test.ts | 8 +- .../stakingRouter.module-sync.test.ts | 101 +++++++++++++++++- .../stakingRouter.status-control.test.ts | 80 +------------- 6 files changed, 118 insertions(+), 140 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 8042b0907..e51272628 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -42,7 +42,6 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error ZeroAddress(string field); error ValueOver100Percent(string field); error StakingModuleNotActive(); - error StakingModuleNotPaused(); error EmptyWithdrawalsCredentials(); error DirectETHTransfer(); error InvalidReportData(uint256 code); @@ -122,10 +121,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } bytes32 public constant MANAGE_WITHDRAWAL_CREDENTIALS_ROLE = keccak256("MANAGE_WITHDRAWAL_CREDENTIALS_ROLE"); - bytes32 public constant STAKING_MODULE_PAUSE_ROLE = keccak256("STAKING_MODULE_PAUSE_ROLE"); - bytes32 public constant STAKING_MODULE_RESUME_ROLE = keccak256("STAKING_MODULE_RESUME_ROLE"); bytes32 public constant STAKING_MODULE_MANAGE_ROLE = keccak256("STAKING_MODULE_MANAGE_ROLE"); - bytes32 public constant STAKING_MODULE_VETTING_ROLE = keccak256("STAKING_MODULE_VETTING_ROLE"); + bytes32 public constant STAKING_MODULE_UNVETTING_ROLE = keccak256("STAKING_MODULE_UNVETTING_ROLE"); bytes32 public constant REPORT_EXITED_VALIDATORS_ROLE = keccak256("REPORT_EXITED_VALIDATORS_ROLE"); bytes32 public constant UNSAFE_SET_EXITED_VALIDATORS_ROLE = keccak256("UNSAFE_SET_EXITED_VALIDATORS_ROLE"); bytes32 public constant REPORT_REWARDS_MINTED_ROLE = keccak256("REPORT_REWARDS_MINTED_ROLE"); @@ -659,11 +656,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, bytes calldata _vettedSigningKeysCounts - ) external onlyRole(STAKING_MODULE_VETTING_ROLE) { + ) external onlyRole(STAKING_MODULE_UNVETTING_ROLE) { _checkValidatorsByNodeOperatorReportData(_nodeOperatorIds, _vettedSigningKeysCounts); - _getIStakingModuleById(_stakingModuleId).decreaseVettedSigningKeysCount( - _nodeOperatorIds, _vettedSigningKeysCounts - ); + _getIStakingModuleById(_stakingModuleId).decreaseVettedSigningKeysCount(_nodeOperatorIds, _vettedSigningKeysCounts); } /** @@ -930,43 +925,17 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } /** - * @notice set the staking module status flag for participation in further deposits and/or reward distribution - */ - function setStakingModuleStatus(uint256 _stakingModuleId, StakingModuleStatus _status) external - onlyRole(STAKING_MODULE_MANAGE_ROLE) - { + * @notice set the staking module status flag for participation in further deposits and/or reward distribution + */ + function setStakingModuleStatus( + uint256 _stakingModuleId, + StakingModuleStatus _status + ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); - if (StakingModuleStatus(stakingModule.status) == _status) - revert StakingModuleStatusTheSame(); + if (StakingModuleStatus(stakingModule.status) == _status) revert StakingModuleStatusTheSame(); _setStakingModuleStatus(stakingModule, _status); } - /** - * @notice pause deposits for staking module - * @param _stakingModuleId id of the staking module to be paused - */ - function pauseStakingModule(uint256 _stakingModuleId) external - onlyRole(STAKING_MODULE_PAUSE_ROLE) - { - StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); - if (StakingModuleStatus(stakingModule.status) != StakingModuleStatus.Active) - revert StakingModuleNotActive(); - _setStakingModuleStatus(stakingModule, StakingModuleStatus.DepositsPaused); - } - - /** - * @notice resume deposits for staking module - * @param _stakingModuleId id of the staking module to be unpaused - */ - function resumeStakingModule(uint256 _stakingModuleId) external - onlyRole(STAKING_MODULE_RESUME_ROLE) - { - StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); - if (StakingModuleStatus(stakingModule.status) != StakingModuleStatus.DepositsPaused) - revert StakingModuleNotPaused(); - _setStakingModuleStatus(stakingModule, StakingModuleStatus.Active); - } - function getStakingModuleIsStopped(uint256 _stakingModuleId) external view returns (bool) { return getStakingModuleStatus(_stakingModuleId) == StakingModuleStatus.Stopped; diff --git a/scripts/scratch/steps/13-grant-roles.ts b/scripts/scratch/steps/13-grant-roles.ts index b9c489973..568765fbb 100644 --- a/scripts/scratch/steps/13-grant-roles.ts +++ b/scripts/scratch/steps/13-grant-roles.ts @@ -28,13 +28,7 @@ async function main() { await makeTx( stakingRouter, "grantRole", - [await stakingRouter.getFunction("STAKING_MODULE_PAUSE_ROLE")(), depositSecurityModuleAddress], - { from: deployer }, - ); - await makeTx( - stakingRouter, - "grantRole", - [await stakingRouter.getFunction("STAKING_MODULE_RESUME_ROLE")(), depositSecurityModuleAddress], + [await stakingRouter.getFunction("STAKING_MODULE_UNVETTING_ROLE")(), depositSecurityModuleAddress], { from: deployer }, ); await makeTx( diff --git a/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol b/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol index 3a8a88cdf..6616a7aca 100644 --- a/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol +++ b/test/0.8.9/contracts/StakingRouterMockForDepositSecurityModule.sol @@ -58,16 +58,6 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { status = _status; } - function pauseStakingModule(uint256 stakingModuleId) external whenModuleIsRegistered(stakingModuleId) { - emit StakingModuleStatusSet(uint24(stakingModuleId), StakingRouter.StakingModuleStatus.DepositsPaused, msg.sender); - status = StakingRouter.StakingModuleStatus.DepositsPaused; - } - - function resumeStakingModule(uint256 stakingModuleId) external whenModuleIsRegistered(stakingModuleId) { - emit StakingModuleStatusSet(uint24(stakingModuleId), StakingRouter.StakingModuleStatus.Active, msg.sender); - status = StakingRouter.StakingModuleStatus.Active; - } - function getStakingModuleIsStopped( uint256 stakingModuleId ) external view whenModuleIsRegistered(stakingModuleId) returns (bool) { diff --git a/test/0.8.9/depositSecurityModule.test.ts b/test/0.8.9/depositSecurityModule.test.ts index c16a4f297..1218f9139 100644 --- a/test/0.8.9/depositSecurityModule.test.ts +++ b/test/0.8.9/depositSecurityModule.test.ts @@ -1246,7 +1246,7 @@ describe("DepositSecurityModule.sol", () => { }); it("Reverts if module is inactive", async () => { - await stakingRouter.pauseStakingModule(STAKING_MODULE_ID); + await stakingRouter.setStakingModuleStatus(STAKING_MODULE_ID, Status.DepositsPaused); await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); @@ -1628,3 +1628,9 @@ describe("DepositSecurityModule.sol", () => { }); }); }); + +enum Status { + Active, + DepositsPaused, + Stopped, +} diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts index 487412ad3..a971b0291 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts @@ -68,8 +68,8 @@ describe("StakingRouter:module-sync", () => { await Promise.all([ stakingRouter.grantRole(await stakingRouter.MANAGE_WITHDRAWAL_CREDENTIALS_ROLE(), admin), stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_MANAGE_ROLE(), admin), - stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_PAUSE_ROLE(), admin), stakingRouter.grantRole(await stakingRouter.REPORT_EXITED_VALIDATORS_ROLE(), admin), + stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_UNVETTING_ROLE(), admin), stakingRouter.grantRole(await stakingRouter.UNSAFE_SET_EXITED_VALIDATORS_ROLE(), admin), stakingRouter.grantRole(await stakingRouter.REPORT_REWARDS_MINTED_ROLE(), admin), ]); @@ -102,7 +102,7 @@ describe("StakingRouter:module-sync", () => { bigint, bigint, bigint, - bigint, + number, string, bigint, bigint, @@ -144,7 +144,7 @@ describe("StakingRouter:module-sync", () => { stakingModuleFee, treasuryFee, stakeShareLimit, - 0n, // status + Status.Active, name, lastDepositAt, lastDepositBlock, @@ -821,6 +821,93 @@ describe("StakingRouter:module-sync", () => { }); }); + context("decreaseStakingModuleVettedKeysCountByNodeOperator", () => { + const NODE_OPERATOR_IDS = bigintToHex(1n, true, 8); + const VETTED_KEYS_COUNTS = bigintToHex(100n, true, 16); + + it("Reverts if the caller does not have the role", async () => { + await expect( + stakingRouter + .connect(user) + .decreaseStakingModuleVettedKeysCountByNodeOperator(moduleId, NODE_OPERATOR_IDS, VETTED_KEYS_COUNTS), + ).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.STAKING_MODULE_UNVETTING_ROLE()); + }); + + it("Reverts if the node operators ids are packed incorrectly", async () => { + const incorrectlyPackedNodeOperatorIds = bufToHex(new Uint8Array([1]), true, 7); + + await expect( + stakingRouter.decreaseStakingModuleVettedKeysCountByNodeOperator( + moduleId, + incorrectlyPackedNodeOperatorIds, + VETTED_KEYS_COUNTS, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "InvalidReportData") + .withArgs(3n); + }); + + it("Reverts if the validator counts are packed incorrectly", async () => { + const incorrectlyPackedValidatorCounts = bufToHex(new Uint8Array([100]), true, 15); + + await expect( + stakingRouter.decreaseStakingModuleVettedKeysCountByNodeOperator( + moduleId, + NODE_OPERATOR_IDS, + incorrectlyPackedValidatorCounts, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "InvalidReportData") + .withArgs(3n); + }); + + it("Reverts if the number of node operators does not match validator counts", async () => { + const tooManyValidatorCounts = VETTED_KEYS_COUNTS + bigintToHex(101n, false, 16); + + await expect( + stakingRouter.decreaseStakingModuleVettedKeysCountByNodeOperator( + moduleId, + NODE_OPERATOR_IDS, + tooManyValidatorCounts, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "InvalidReportData") + .withArgs(2n); + }); + + it("Reverts if the number of node operators does not match validator counts", async () => { + const tooManyValidatorCounts = VETTED_KEYS_COUNTS + bigintToHex(101n, false, 16); + + await expect( + stakingRouter.decreaseStakingModuleVettedKeysCountByNodeOperator( + moduleId, + NODE_OPERATOR_IDS, + tooManyValidatorCounts, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "InvalidReportData") + .withArgs(2n); + }); + + it("Reverts if the node operators ids is empty", async () => { + await expect(stakingRouter.decreaseStakingModuleVettedKeysCountByNodeOperator(moduleId, "0x", "0x")) + .to.be.revertedWithCustomError(stakingRouter, "InvalidReportData") + .withArgs(1n); + }); + + it("Updates stuck validators count on the module", async () => { + await expect( + stakingRouter.decreaseStakingModuleVettedKeysCountByNodeOperator( + moduleId, + NODE_OPERATOR_IDS, + VETTED_KEYS_COUNTS, + ), + ) + .to.emit(stakingModule, "Mock__VettedSigningKeysCountDecreased") + .withArgs(NODE_OPERATOR_IDS, VETTED_KEYS_COUNTS); + }); + }); + context("deposit", () => { beforeEach(async () => { stakingRouter = stakingRouter.connect(lido); @@ -843,7 +930,7 @@ describe("StakingRouter:module-sync", () => { }); it("Reverts if the staking module is not active", async () => { - await stakingRouter.connect(admin).pauseStakingModule(moduleId); + await stakingRouter.connect(admin).setStakingModuleStatus(moduleId, Status.DepositsPaused); await expect(stakingRouter.deposit(100n, moduleId, "0x")).to.be.revertedWithCustomError( stakingRouter, @@ -883,3 +970,9 @@ describe("StakingRouter:module-sync", () => { }); }); }); + +enum Status { + Active, + DepositsPaused, + Stopped, +} diff --git a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts index c5ca2dbf2..482ab6c05 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts @@ -49,12 +49,8 @@ context("StakingRouter:status-control", () => { hexlify(randomBytes(32)), // mock withdrawal credentials ); - // give the necessary roles to the admin - await Promise.all([ - stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_MANAGE_ROLE(), admin), - stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_PAUSE_ROLE(), admin), - stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_RESUME_ROLE(), admin), - ]); + // give the necessary role to the admin + await stakingRouter.grantRole(await stakingRouter.STAKING_MODULE_MANAGE_ROLE(), admin); // add staking module await stakingRouter.addStakingModule( @@ -91,76 +87,6 @@ context("StakingRouter:status-control", () => { }); }); - context("pauseStakingModule", () => { - it("Reverts if the caller does not have the role", async () => { - await expect(stakingRouter.connect(user).pauseStakingModule(moduleId)).to.be.revertedWithOZAccessControlError( - user.address, - await stakingRouter.STAKING_MODULE_PAUSE_ROLE(), - ); - }); - - it("Reverts if the status is stopped", async () => { - await stakingRouter.setStakingModuleStatus(moduleId, Status.Stopped); - - await expect(stakingRouter.pauseStakingModule(moduleId)).to.be.revertedWithCustomError( - stakingRouter, - "StakingModuleNotActive", - ); - }); - - it("Reverts if the status is deposits paused", async () => { - await stakingRouter.setStakingModuleStatus(moduleId, Status.DepositsPaused); - - await expect(stakingRouter.pauseStakingModule(moduleId)).to.be.revertedWithCustomError( - stakingRouter, - "StakingModuleNotActive", - ); - }); - - it("Pauses the staking module", async () => { - await expect(stakingRouter.pauseStakingModule(moduleId)) - .to.emit(stakingRouter, "StakingModuleStatusSet") - .withArgs(moduleId, Status.DepositsPaused, admin.address); - }); - }); - - context("resumeStakingModule", () => { - beforeEach(async () => { - await stakingRouter.pauseStakingModule(moduleId); - }); - - it("Reverts if the caller does not have the role", async () => { - await expect(stakingRouter.connect(user).resumeStakingModule(moduleId)).to.be.revertedWithOZAccessControlError( - user.address, - await stakingRouter.STAKING_MODULE_RESUME_ROLE(), - ); - }); - - it("Reverts if the module is already active", async () => { - await stakingRouter.resumeStakingModule(moduleId); - - await expect(stakingRouter.resumeStakingModule(moduleId)).to.be.revertedWithCustomError( - stakingRouter, - "StakingModuleNotPaused", - ); - }); - - it("Reverts if the module is stopped", async () => { - await stakingRouter.setStakingModuleStatus(moduleId, Status.Stopped); - - await expect(stakingRouter.resumeStakingModule(moduleId)).to.be.revertedWithCustomError( - stakingRouter, - "StakingModuleNotPaused", - ); - }); - - it("Resumes the staking module", async () => { - await expect(stakingRouter.resumeStakingModule(moduleId)) - .to.emit(stakingRouter, "StakingModuleStatusSet") - .withArgs(moduleId, Status.Active, admin.address); - }); - }); - context("getStakingModuleIsStopped", () => { it("Returns false if the module is active", async () => { expect(await stakingRouter.getStakingModuleStatus(moduleId)).to.equal(Status.Active); @@ -168,7 +94,7 @@ context("StakingRouter:status-control", () => { }); it("Returns false if the module is paused", async () => { - await stakingRouter.pauseStakingModule(moduleId); + await stakingRouter.setStakingModuleStatus(moduleId, Status.DepositsPaused); expect(await stakingRouter.getStakingModuleIsStopped(moduleId)).to.be.false; }); From d22aac4782e68d46a8b17f1b8afb29ca8a577369 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 18 Apr 2024 19:26:24 +0300 Subject: [PATCH 046/177] docs: fix lastDepositAt comment --- contracts/0.8.9/StakingRouter.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index e51272628..d8a1ac2c8 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -232,7 +232,6 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @dev Simulate zero value deposit to prevent real deposits into the new StakingModule via /// DepositSecurityModule just after the addition. - /// See DepositSecurityModule.getMaxDeposits() for details newStakingModule.lastDepositAt = uint64(block.timestamp); newStakingModule.lastDepositBlock = block.number; emit StakingRouterETHDeposited(newStakingModuleId, 0); From 07e52b3d8a16d02c6d78ef4263f4a81851f6ce1d Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Fri, 19 Apr 2024 10:37:40 +0300 Subject: [PATCH 047/177] feat: support new addStakingModule interface in scratch deploy --- docs/scratch-deploy.md | 5 ++++- .../scratch/steps/14-plug-curated-staking-module.ts | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/scratch-deploy.md b/docs/scratch-deploy.md index a07f39734..ad954c0c4 100644 --- a/docs/scratch-deploy.md +++ b/docs/scratch-deploy.md @@ -170,9 +170,12 @@ await stakingRouter.grantRole(STAKING_MODULE_MANAGE_ROLE, agent.address, { from: await stakingRouter.addStakingModule( state.nodeOperatorsRegistry.deployParameters.stakingModuleTypeId, nodeOperatorsRegistry.address, - NOR_STAKING_MODULE_TARGET_SHARE_BP, + NOR_STAKING_MODULE_STAKE_SHARE_LIMIT_BP, + NOR_STAKING_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD_BP, NOR_STAKING_MODULE_MODULE_FEE_BP, NOR_STAKING_MODULE_TREASURY_FEE_BP, + NOR_STAKING_MODULE_MAX_DEPOSITS_PER_BLOCK, + NOR_STAKING_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, { from: agent.address }, ); await stakingRouter.renounceRole(STAKING_MODULE_MANAGE_ROLE, agent.address, { from: agent.address }); diff --git a/scripts/scratch/steps/14-plug-curated-staking-module.ts b/scripts/scratch/steps/14-plug-curated-staking-module.ts index 38150fb38..da9649bb9 100644 --- a/scripts/scratch/steps/14-plug-curated-staking-module.ts +++ b/scripts/scratch/steps/14-plug-curated-staking-module.ts @@ -6,9 +6,12 @@ import { streccak } from "lib/keccak"; import { log } from "lib/log"; import { readNetworkState, Sk } from "lib/state-file"; -const NOR_STAKING_MODULE_TARGET_SHARE_BP = 10000; // 100% +const NOR_STAKING_MODULE_STAKE_SHARE_LIMIT_BP = 10000; // 100% +const NOR_STAKING_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD_BP = 10000; // 100% const NOR_STAKING_MODULE_MODULE_FEE_BP = 500; // 5% const NOR_STAKING_MODULE_TREASURY_FEE_BP = 500; // 5% +const NOR_STAKING_MODULE_MAX_DEPOSITS_PER_BLOCK = 150; +const NOR_STAKING_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25; const STAKING_MODULE_MANAGE_ROLE = streccak("STAKING_MODULE_MANAGE_ROLE"); async function main() { @@ -30,9 +33,12 @@ async function main() { [ state.nodeOperatorsRegistry.deployParameters.stakingModuleTypeId, nodeOperatorsRegistry.address, - NOR_STAKING_MODULE_TARGET_SHARE_BP, + NOR_STAKING_MODULE_STAKE_SHARE_LIMIT_BP, + NOR_STAKING_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD_BP, NOR_STAKING_MODULE_MODULE_FEE_BP, NOR_STAKING_MODULE_TREASURY_FEE_BP, + NOR_STAKING_MODULE_MAX_DEPOSITS_PER_BLOCK, + NOR_STAKING_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, ], { from: deployer }, ); From ed3ef93c1e78eead978fab4657cbc90b3d8a44f6 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Fri, 19 Apr 2024 10:38:17 +0300 Subject: [PATCH 048/177] fix: rename target limit to stake share limit in tests --- .../stakingRouter/stakingRouter.module-management.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts index 7383822c8..87a57dd29 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts @@ -74,13 +74,13 @@ describe("StakingRouter:module-management", () => { //todo priority < share //todo priority > 100 it("Reverts if the target share is greater than 100%", async () => { - const TARGET_SHARE_OVER_100 = 100_01; + const STAKE_SHARE_LIMIT_OVER_100 = 100_01; await expect( stakingRouter.addStakingModule( NAME, ADDRESS, - TARGET_SHARE_OVER_100, + STAKE_SHARE_LIMIT_OVER_100, PRIORITY_EXIT_SHARE_THRESHOLD, MODULE_FEE, TREASURY_FEE, @@ -333,11 +333,11 @@ describe("StakingRouter:module-management", () => { }); it("Reverts if the new target share is greater than 100%", async () => { - const NEW_TARGET_SHARE_OVER_100 = 100_01; + const NEW_STAKE_SHARE_LIMIT_OVER_100 = 100_01; await expect( stakingRouter.updateStakingModule( ID, - NEW_TARGET_SHARE_OVER_100, + NEW_STAKE_SHARE_LIMIT_OVER_100, NEW_PRIORITY_EXIT_SHARE_THRESHOLD, NEW_MODULE_FEE, NEW_TREASURY_FEE, From d820f9a8861179d95af98b8bd87f4c46a695fded Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 22 Apr 2024 11:22:17 +0300 Subject: [PATCH 049/177] feat: finalizeUpgrade_v2 for staking router --- contracts/0.8.9/StakingRouter.sol | 31 ++++++++++++++++++- .../stakingRouter/stakingRouter.misc.test.ts | 4 +-- .../stakingRouter.versioned.test.ts | 2 +- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index d8a1ac2c8..09b09c363 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -160,7 +160,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version if (_admin == address(0)) revert ZeroAddress("_admin"); if (_lido == address(0)) revert ZeroAddress("_lido"); - _initializeContractVersionTo(1); + _initializeContractVersionTo(2); _setupRole(DEFAULT_ADMIN_ROLE, _admin); @@ -174,6 +174,35 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version revert DirectETHTransfer(); } + + /** + * @notice A function to finalize upgrade to v2 (from v1). Can be called only once + */ + function finalizeUpgrade_v2(uint256[] memory _priorityExitShareThresholds) external { + _checkContractVersion(1); + + uint256 stakingModulesCount = getStakingModulesCount(); + + if (stakingModulesCount != _priorityExitShareThresholds.length) { + revert ArraysLengthMismatch(stakingModulesCount, _priorityExitShareThresholds.length); + } + + for (uint256 i; i < stakingModulesCount; ) { + StakingModule storage stakingModule = _getStakingModuleByIndex(i); + + if (stakingModule.stakeShareLimit > _priorityExitShareThresholds[i]) { + revert InvalidPriorityExitShareThreshold(); + } + stakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThresholds[i]); + + unchecked { + ++i; + } + } + + _updateContractVersion(2); + } + /** * @notice Return the Lido contract address */ diff --git a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts index cea631156..c4ce836dd 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts @@ -57,13 +57,13 @@ describe("StakingRouter", () => { it("Initializes the contract version, sets up roles and variables", async () => { await expect(stakingRouter.initialize(stakingRouterAdmin.address, lido, withdrawalCredentials)) .to.emit(stakingRouter, "ContractVersionSet") - .withArgs(1) + .withArgs(2) .and.to.emit(stakingRouter, "RoleGranted") .withArgs(await stakingRouter.DEFAULT_ADMIN_ROLE(), stakingRouterAdmin.address, user.address) .and.to.emit(stakingRouter, "WithdrawalCredentialsSet") .withArgs(withdrawalCredentials, user.address); - expect(await stakingRouter.getContractVersion()).to.equal(1); + expect(await stakingRouter.getContractVersion()).to.equal(2); expect(await stakingRouter.getLido()).to.equal(lido); expect(await stakingRouter.getWithdrawalCredentials()).to.equal(withdrawalCredentials); }); diff --git a/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts b/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts index 2f90c3b0d..c190c5052 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.versioned.test.ts @@ -48,7 +48,7 @@ describe("StakingRouter:Versioned", () => { it("Increments version", async () => { await versioned.initialize(randomAddress(), randomAddress(), randomBytes(32)); - expect(await versioned.getContractVersion()).to.equal(1n); + expect(await versioned.getContractVersion()).to.equal(2n); }); }); }); From cebbb54ae3e148d016713c8519f64bd6ee5ba6c5 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 8 May 2024 15:22:29 +0200 Subject: [PATCH 050/177] fix: forge-lib --- foundry/lib/forge-std | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry/lib/forge-std b/foundry/lib/forge-std index 5dd1c6813..ffa2ee0d9 160000 --- a/foundry/lib/forge-std +++ b/foundry/lib/forge-std @@ -1 +1 @@ -Subproject commit 5dd1c68131ddd3c89ef169666eb262b92e90507c +Subproject commit ffa2ee0d921b4163b7abd0f1122df93ead205805 From afff5e37bdcb4ff5df610e43dcd4c71a3a4a6508 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 10:04:35 +0300 Subject: [PATCH 051/177] feat: optimize errors to reduce staking router bytecode size --- contracts/0.8.9/StakingRouter.sol | 31 ++++++++---------- .../stakingRouter/stakingRouter.misc.test.ts | 13 ++++---- .../stakingRouter.module-management.test.ts | 32 +++++-------------- 3 files changed, 28 insertions(+), 48 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 09b09c363..ea1115b4f 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -39,8 +39,11 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version event StakingRouterETHDeposited(uint256 indexed stakingModuleId, uint256 amount); /// @dev errors - error ZeroAddress(string field); - error ValueOver100Percent(string field); + error ZeroAddressLido(); + error ZeroAddressAdmin(); + error ZeroAddressStakingModule(); + error InvalidStakeShareLimit(); + error InvalidFeeSum(); error StakingModuleNotActive(); error EmptyWithdrawalsCredentials(); error DirectETHTransfer(); @@ -157,8 +160,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * @param _withdrawalCredentials Lido withdrawal vault contract address */ function initialize(address _admin, address _lido, bytes32 _withdrawalCredentials) external { - if (_admin == address(0)) revert ZeroAddress("_admin"); - if (_lido == address(0)) revert ZeroAddress("_lido"); + if (_admin == address(0)) revert ZeroAddressAdmin(); + if (_lido == address(0)) revert ZeroAddressLido(); _initializeContractVersionTo(2); @@ -231,7 +234,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 _maxDepositsPerBlock, uint256 _minDepositBlockDistance ) external onlyRole(STAKING_MODULE_MANAGE_ROLE) { - if (_stakingModuleAddress == address(0)) revert ZeroAddress("_stakingModuleAddress"); + if (_stakingModuleAddress == address(0)) revert ZeroAddressStakingModule(); if (bytes(_name).length == 0 || bytes(_name).length > MAX_STAKING_MODULE_NAME_LENGTH) revert StakingModuleWrongName(); uint256 newStakingModuleIndex = getStakingModulesCount(); @@ -324,18 +327,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 _maxDepositsPerBlock, uint256 _minDepositBlockDistance ) internal { - if (_stakeShareLimit > TOTAL_BASIS_POINTS) { - revert ValueOver100Percent("_stakeShareLimit"); - } - if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) { - revert ValueOver100Percent("_priorityExitShareThreshold"); - } - if (_stakeShareLimit > _priorityExitShareThreshold) { - revert InvalidPriorityExitShareThreshold(); - } - if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) { - revert ValueOver100Percent("_stakingModuleFee + _treasuryFee"); - } + if (_stakeShareLimit > TOTAL_BASIS_POINTS) revert InvalidStakeShareLimit(); + if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) revert InvalidPriorityExitShareThreshold(); + if (_stakeShareLimit > _priorityExitShareThreshold) revert InvalidPriorityExitShareThreshold(); + if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) revert InvalidFeeSum(); if (_minDepositBlockDistance == 0) revert InvalidMinDepositBlockDistance(); stakingModule.stakeShareLimit = uint16(_stakeShareLimit); @@ -718,7 +713,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } /** - * @dev Returns staking module by id + * @dev Returns staking module by id */ function getStakingModule(uint256 _stakingModuleId) public diff --git a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts index c4ce836dd..000e56ff2 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts @@ -43,15 +43,16 @@ describe("StakingRouter", () => { context("initialize", () => { it("Reverts if admin is zero address", async () => { - await expect(stakingRouter.initialize(ZeroAddress, lido, withdrawalCredentials)) - .to.be.revertedWithCustomError(stakingRouter, "ZeroAddress") - .withArgs("_admin"); + await expect(stakingRouter.initialize(ZeroAddress, lido, withdrawalCredentials)).to.be.revertedWithCustomError( + stakingRouter, + "ZeroAddressAdmin", + ); }); it("Reverts if lido is zero address", async () => { - await expect(stakingRouter.initialize(stakingRouterAdmin.address, ZeroAddress, withdrawalCredentials)) - .to.be.revertedWithCustomError(stakingRouter, "ZeroAddress") - .withArgs("_lido"); + await expect( + stakingRouter.initialize(stakingRouterAdmin.address, ZeroAddress, withdrawalCredentials), + ).to.be.revertedWithCustomError(stakingRouter, "ZeroAddressLido"); }); it("Initializes the contract version, sets up roles and variables", async () => { diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts index 87a57dd29..573e6319b 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts @@ -87,9 +87,7 @@ describe("StakingRouter:module-management", () => { MAX_DEPOSITS_PER_BLOCK, MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_stakeShareLimit"); + ).to.be.revertedWithCustomError(stakingRouter, "InvalidStakeShareLimit"); }); it("Reverts if the sum of module and treasury fees is greater than 100%", async () => { @@ -106,9 +104,7 @@ describe("StakingRouter:module-management", () => { MAX_DEPOSITS_PER_BLOCK, MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_stakingModuleFee + _treasuryFee"); + ).to.be.revertedWithCustomError(stakingRouter, "InvalidFeeSum"); const TREASURY_FEE_INVALID = 100_01n - MODULE_FEE; @@ -123,9 +119,7 @@ describe("StakingRouter:module-management", () => { MAX_DEPOSITS_PER_BLOCK, MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_stakingModuleFee + _treasuryFee"); + ).to.be.revertedWithCustomError(stakingRouter, "InvalidFeeSum"); }); it("Reverts if the staking module address is zero address", async () => { @@ -140,9 +134,7 @@ describe("StakingRouter:module-management", () => { MAX_DEPOSITS_PER_BLOCK, MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ZeroAddress") - .withArgs("_stakingModuleAddress"); + ).to.be.revertedWithCustomError(stakingRouter, "ZeroAddressStakingModule"); }); it("Reverts if the staking module name is empty string", async () => { @@ -344,9 +336,7 @@ describe("StakingRouter:module-management", () => { NEW_MAX_DEPOSITS_PER_BLOCK, NEW_MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_stakeShareLimit"); + ).to.be.revertedWithCustomError(stakingRouter, "InvalidStakeShareLimit"); }); it("Reverts if the new priority exit share is greater than 100%", async () => { @@ -361,9 +351,7 @@ describe("StakingRouter:module-management", () => { NEW_MAX_DEPOSITS_PER_BLOCK, NEW_MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_priorityExitShareThreshold"); + ).to.be.revertedWithCustomError(stakingRouter, "InvalidPriorityExitShareThreshold"); }); it("Reverts if the new priority exit share is less than stake share limit", async () => { @@ -395,9 +383,7 @@ describe("StakingRouter:module-management", () => { MAX_DEPOSITS_PER_BLOCK, MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_stakingModuleFee + _treasuryFee"); + ).to.be.revertedWithCustomError(stakingRouter, "InvalidFeeSum"); const NEW_TREASURY_FEE_INVALID = 100_01n - MODULE_FEE; await expect( @@ -410,9 +396,7 @@ describe("StakingRouter:module-management", () => { MAX_DEPOSITS_PER_BLOCK, MIN_DEPOSIT_BLOCK_DISTANCE, ), - ) - .to.be.revertedWithCustomError(stakingRouter, "ValueOver100Percent") - .withArgs("_stakingModuleFee + _treasuryFee"); + ).to.be.revertedWithCustomError(stakingRouter, "InvalidFeeSum"); }); it("Update target share, module and treasury fees and emits events", async () => { From f3fff9b7dfebd7aff32fc82d0be77c1f9d416128 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 10:07:14 +0300 Subject: [PATCH 052/177] fix: add missed module params to stakingRouter:finalizeUpgrade_v2 --- contracts/0.8.9/StakingRouter.sol | 40 +++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index ea1115b4f..7bdfaf27b 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -180,27 +180,47 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /** * @notice A function to finalize upgrade to v2 (from v1). Can be called only once + * @param _priorityExitShareThresholds array of priority exit share thresholds + * @param _maxDepositsPerBlock array of max deposits per block + * @param _minDepositBlockDistances array of min deposit block distances */ - function finalizeUpgrade_v2(uint256[] memory _priorityExitShareThresholds) external { + function finalizeUpgrade_v2( + uint256[] memory _priorityExitShareThresholds, + uint256[] memory _maxDepositsPerBlock, + uint256[] memory _minDepositBlockDistances + ) external { _checkContractVersion(1); uint256 stakingModulesCount = getStakingModulesCount(); if (stakingModulesCount != _priorityExitShareThresholds.length) { - revert ArraysLengthMismatch(stakingModulesCount, _priorityExitShareThresholds.length); + revert ArraysLengthMismatch(stakingModulesCount, _priorityExitShareThresholds.length); } - for (uint256 i; i < stakingModulesCount; ) { - StakingModule storage stakingModule = _getStakingModuleByIndex(i); - - if (stakingModule.stakeShareLimit > _priorityExitShareThresholds[i]) { - revert InvalidPriorityExitShareThreshold(); + if (stakingModulesCount != _maxDepositsPerBlock.length) { + revert ArraysLengthMismatch(stakingModulesCount, _maxDepositsPerBlock.length); } - stakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThresholds[i]); - unchecked { - ++i; + if (stakingModulesCount != _minDepositBlockDistances.length) { + revert ArraysLengthMismatch(stakingModulesCount, _minDepositBlockDistances.length); } + + for (uint256 i; i < stakingModulesCount; ) { + StakingModule storage stakingModule = _getStakingModuleByIndex(i); + _updateStakingModule( + stakingModule, + stakingModule.id, + stakingModule.stakeShareLimit, + _priorityExitShareThresholds[i], + stakingModule.stakingModuleFee, + stakingModule.treasuryFee, + _maxDepositsPerBlock[i], + _minDepositBlockDistances[i] + ); + + unchecked { + ++i; + } } _updateContractVersion(2); From ea1c2b07a61cff9933f5f8162cd80ded5f430435 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 10:08:05 +0300 Subject: [PATCH 053/177] docs: targetLimitMode description in IStakingModule --- contracts/0.8.9/interfaces/IStakingModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index ece7ee34b..97ac07d4c 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -23,7 +23,7 @@ interface IStakingModule { /// @notice Returns all-validators summary belonging to the node operator with the given id /// @param _nodeOperatorId id of the operator to return report for - /// @return targetLimitMode shows whether the current target limit applied to the node operator (1 = soft mode, 2 = forced mode) + /// @return targetLimitMode shows whether the current target limit applied to the node operator (0 = disabled, 1 = soft mode, 2 = forced mode) /// @return targetValidatorsCount relative target active validators limit for operator /// @return stuckValidatorsCount number of validators with an expired request to exit time /// @return refundedValidatorsCount number of validators that can't be withdrawn, but deposit From 2dd4c1dc189657ec56781206ae21f09c15600186 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 10:10:58 +0300 Subject: [PATCH 054/177] docs: target limit mode description in NOR --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 3d21456f9..018243a15 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -101,7 +101,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint8 internal constant TOTAL_DEPOSITED_KEYS_COUNT_OFFSET = 3; // TargetValidatorsStats - /// @dev Target limit mode, allows limiting target active validators count for operator, >1 means forced target limit enabled + /// @dev Target limit mode, allows limiting target active validators count for operator (0 = disabled, 1 = soft mode, 2 = forced mode) uint8 internal constant TARGET_LIMIT_MODE_OFFSET = 0; /// @dev relative target active validators limit for operator, set by DAO /// @notice used to check how many keys should go to exit, 0 - means all deposited keys would be exited @@ -688,7 +688,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// @notice Updates the limit of the validators that can be used for deposit by DAO /// @param _nodeOperatorId Id of the node operator /// @param _targetLimit Target limit of the node operator - /// @param _targetLimitMode target limit mode, >1 means forced target limit + /// @param _targetLimitMode target limit mode (0 = disabled, 1 = soft mode, 2 = forced mode) function updateTargetValidatorsLimits(uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit) public { _onlyExistedNodeOperator(_nodeOperatorId); _auth(STAKING_ROUTER_ROLE); From 62dd75805cde4cde030b5e6a4814d7c3143f94c1 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 10:19:16 +0300 Subject: [PATCH 055/177] fix: remove unvet intent period --- contracts/0.8.9/DepositSecurityModule.sol | 32 +------------- test/0.8.9/depositSecurityModule.test.ts | 53 +---------------------- 2 files changed, 2 insertions(+), 83 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 3d90df01f..4898d61b0 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -44,7 +44,6 @@ contract DepositSecurityModule { event OwnerChanged(address newValue); event PauseIntentValidityPeriodBlocksChanged(uint256 newValue); - event UnvetIntentValidityPeriodBlocksChanged(uint256 newValue); event MaxOperatorsPerUnvettingChanged(uint256 newValue); event GuardianQuorumChanged(uint256 newValue); event GuardianAdded(address guardian); @@ -66,7 +65,6 @@ contract DepositSecurityModule { error DepositsNotPaused(); error ModuleNonceChanged(); error PauseIntentExpired(); - error UnvetIntentExpired(); error UnvetPayloadInvalid(); error UnvetUnexpectedBlockHash(); error NotAGuardian(address addr); @@ -92,7 +90,6 @@ contract DepositSecurityModule { uint256 internal lastDepositBlock; uint256 internal pauseIntentValidityPeriodBlocks; - uint256 internal unvetIntentValidityPeriodBlocks; uint256 internal maxOperatorsPerUnvetting; address internal owner; @@ -112,7 +109,6 @@ contract DepositSecurityModule { address _depositContract, address _stakingRouter, uint256 _pauseIntentValidityPeriodBlocks, - uint256 _unvetIntentValidityPeriodBlocks, uint256 _maxOperatorsPerUnvetting ) { if (_lido == address(0)) revert ZeroAddress("_lido"); @@ -153,7 +149,6 @@ contract DepositSecurityModule { _setOwner(msg.sender); _setLastDepositBlock(block.number); _setPauseIntentValidityPeriodBlocks(_pauseIntentValidityPeriodBlocks); - _setUnvetIntentValidityPeriodBlocks(_unvetIntentValidityPeriodBlocks); _setMaxOperatorsPerUnvetting(_maxOperatorsPerUnvetting); } @@ -208,29 +203,6 @@ contract DepositSecurityModule { emit PauseIntentValidityPeriodBlocksChanged(newValue); } - /** - * @notice Returns the number of blocks during which the unvet intent is valid. - * @return unvetIntentValidityPeriodBlocks The number of blocks during which the unvet intent is valid. - */ - function getUnvetIntentValidityPeriodBlocks() external view returns (uint256) { - return unvetIntentValidityPeriodBlocks; - } - - /** - * @notice Sets the number of blocks during which the unvet intent is valid. - * @param newValue The new number of blocks during which the unvet intent is valid. - * @dev Only callable by the owner. - */ - function setUnvetIntentValidityPeriodBlocks(uint256 newValue) external onlyOwner { - _setUnvetIntentValidityPeriodBlocks(newValue); - } - - function _setUnvetIntentValidityPeriodBlocks(uint256 newValue) internal { - if (newValue == 0) revert ZeroParameter("unvetIntentValidityPeriodBlocks"); - unvetIntentValidityPeriodBlocks = newValue; - emit UnvetIntentValidityPeriodBlocksChanged(newValue); - } - /** * @notice Returns the maximum number of operators per unvetting. * @return maxOperatorsPerUnvetting The maximum number of operators per unvetting. @@ -581,8 +553,7 @@ contract DepositSecurityModule { * - vettedSigningKeysCounts is not packed with 16 bytes per count; * - the number of node operators is greater than maxOperatorsPerUnvetting; * - the signature is invalid or the signer is not a guardian; - * - blockHash is zero or not equal to the blockhash(blockNumber); - * - the unvet intent is expired. + * - blockHash is zero or not equal to the blockhash(blockNumber). * * The signature, if present, must be produced for the keccak256 hash of the following message: * @@ -633,7 +604,6 @@ contract DepositSecurityModule { } if (blockHash == bytes32(0) || blockhash(blockNumber) != blockHash) revert UnvetUnexpectedBlockHash(); - if (block.number - blockNumber > unvetIntentValidityPeriodBlocks) revert UnvetIntentExpired(); STAKING_ROUTER.decreaseStakingModuleVettedKeysCountByNodeOperator( stakingModuleId, diff --git a/test/0.8.9/depositSecurityModule.test.ts b/test/0.8.9/depositSecurityModule.test.ts index 1218f9139..af41f5dee 100644 --- a/test/0.8.9/depositSecurityModule.test.ts +++ b/test/0.8.9/depositSecurityModule.test.ts @@ -32,7 +32,6 @@ const STAKING_MODULE_ID = 100; const MAX_DEPOSITS_PER_BLOCK = 100; const MIN_DEPOSIT_BLOCK_DISTANCE = 14; const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 10; -const UNVET_INTENT_VALIDITY_PERIOD_BLOCKS = 10; const MAX_OPERATORS_PER_UNVETTING = 20; const MODULE_NONCE = 12; const DEPOSIT_ROOT = "0xd151867719c94ad8458feaf491809f9bc8096c702a72747403ecaac30c179137"; @@ -64,7 +63,6 @@ function initialParams(): Params { depositContract: "", stakingRouter: "", pauseIntentValidityPeriodBlocks: PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS, - unvetIntentValidityPeriodBlocks: UNVET_INTENT_VALIDITY_PERIOD_BLOCKS, maxOperatorsPerUnvetting: MAX_OPERATORS_PER_UNVETTING, } as Params; } @@ -374,46 +372,6 @@ describe("DepositSecurityModule.sol", () => { }); }); - context("Unvet intent validity period blocks", () => { - context("Function `getUnvetIntentValidityPeriodBlocks`", () => { - it("Returns current `unvetIntentValidityPeriodBlocks` contract parameter", async () => { - expect(await dsm.getUnvetIntentValidityPeriodBlocks()).to.equal(config.unvetIntentValidityPeriodBlocks); - }); - }); - - context("Function `setUnvetIntentValidityPeriodBlocks`", () => { - let originalState: string; - - before(async () => { - originalState = await Snapshot.take(); - }); - - after(async () => { - await Snapshot.restore(originalState); - }); - - it("Reverts if the `newValue` is zero parameter", async () => { - await expect(dsm.setUnvetIntentValidityPeriodBlocks(0)).to.be.revertedWithCustomError(dsm, "ZeroParameter"); - }); - - it("Reverts if the `setUnvetIntentValidityPeriodBlocks` called by not an owner", async () => { - await expect( - dsm.connect(stranger).setUnvetIntentValidityPeriodBlocks(config.unvetIntentValidityPeriodBlocks), - ).to.be.revertedWithCustomError(dsm, "NotAnOwner"); - }); - - it("Sets `unvetIntentValidityPeriodBlocks` and fires `UnvetIntentValidityPeriodBlocksChanged` event", async () => { - const newValue = config.unvetIntentValidityPeriodBlocks + 1; - - await expect(dsm.setUnvetIntentValidityPeriodBlocks(newValue)) - .to.emit(dsm, "UnvetIntentValidityPeriodBlocksChanged") - .withArgs(newValue); - - expect(await dsm.getUnvetIntentValidityPeriodBlocks()).to.equal(newValue); - }); - }); - }); - context("Max operators per unvetting", () => { context("Function `getMaxOperatorsPerUnvetting`", () => { it("Returns `maxDepositsPerBlock`", async () => { @@ -1532,7 +1490,7 @@ describe("DepositSecurityModule.sol", () => { ).to.be.revertedWithCustomError(dsm, "UnvetPayloadInvalid"); }); - it("Reverts if the number of operator ids is not equal to the number of keys count", async () => { + it("Reverts if the number of operators is greater than the limit", async () => { const overlimitedPayloadSize = MAX_OPERATORS_PER_UNVETTING + 1; const nodeOperatorIds = concat(Array(overlimitedPayloadSize).fill(operatorId1)); const vettedSigningKeysCounts = concat(Array(overlimitedPayloadSize).fill(vettedSigningKeysCount1)); @@ -1579,15 +1537,6 @@ describe("DepositSecurityModule.sol", () => { ); }); - it("Reverts if called with an expired `blockNumber` and `blockHash` by a guardian", async () => { - const block = await getLatestBlock(); - await mineUpTo((await time.latestBlock()) + UNVET_INTENT_VALIDITY_PERIOD_BLOCKS); - - await expect( - unvetSigningKeys(guardian1, { blockNumber: block.number, blockHash: block.hash }), - ).to.be.revertedWithCustomError(dsm, "UnvetIntentExpired"); - }); - it("Reverts if signature is not guardian", async () => { await expect(unvetSigningKeys(unrelatedGuardian1)).to.be.revertedWithCustomError(dsm, "InvalidSignature"); }); From 524a63db7fcc762965edc3eae7227befc1098615 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 10:30:11 +0300 Subject: [PATCH 056/177] fix: add deposit pause check --- contracts/0.8.9/DepositSecurityModule.sol | 2 ++ test/0.8.9/depositSecurityModule.test.ts | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 4898d61b0..5abf07be4 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -62,6 +62,7 @@ contract DepositSecurityModule { error DepositInactiveModule(); error DepositTooFrequent(); error DepositUnexpectedBlockHash(); + error DepositsArePaused(); error DepositsNotPaused(); error ModuleNonceChanged(); error PauseIntentExpired(); @@ -503,6 +504,7 @@ contract DepositSecurityModule { if (!STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId)) revert DepositInactiveModule(); if (!_isMinDepositDistancePassed(stakingModuleId)) revert DepositTooFrequent(); if (blockHash == bytes32(0) || blockhash(blockNumber) != blockHash) revert DepositUnexpectedBlockHash(); + if (isDepositsPaused) revert DepositsArePaused(); _verifyAttestSignatures(depositRoot, blockNumber, blockHash, stakingModuleId, nonce, sortedGuardianSignatures); diff --git a/test/0.8.9/depositSecurityModule.test.ts b/test/0.8.9/depositSecurityModule.test.ts index af41f5dee..a3d27a03a 100644 --- a/test/0.8.9/depositSecurityModule.test.ts +++ b/test/0.8.9/depositSecurityModule.test.ts @@ -1263,6 +1263,23 @@ describe("DepositSecurityModule.sol", () => { ).to.be.revertedWithCustomError(dsm, "DepositUnexpectedBlockHash"); }); + it("Reverts if deposits are paused", async () => { + const blockNumber = await time.latestBlock(); + + await dsm.addGuardian(guardian1, 1); + expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); + expect(await dsm.getGuardians()).to.have.length(1); + expect(await dsm.getGuardianQuorum()).to.equal(1); + + await dsm.connect(guardian1).pauseDeposits(blockNumber, { + r: encodeBytes32String(""), + vs: encodeBytes32String(""), + }); + expect(await dsm.isDepositsPaused()).to.equal(true); + + await expect(deposit([guardian1])).to.be.revertedWithCustomError(dsm, "DepositsArePaused"); + }); + it("Deposit with the guardian's sig", async () => { await dsm.addGuardian(guardian1, 1); expect(await dsm.getGuardians()).to.deep.equal([guardian1.address]); From 28540da86272e2c751ab9ac78f117428d0afada6 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 13:14:00 +0300 Subject: [PATCH 057/177] docs: disable slither for encoding in unvetSigningKeys --- contracts/0.8.9/DepositSecurityModule.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 5abf07be4..9ad8b49f2 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -590,6 +590,8 @@ contract DepositSecurityModule { if (guardianIndex == -1) { bytes32 msgHash = keccak256( + // slither-disable-start encode-packed-collision + // values with a dynamic type checked earlier abi.encodePacked( UNVET_MESSAGE_PREFIX, blockNumber, @@ -599,6 +601,7 @@ contract DepositSecurityModule { nodeOperatorIds, vettedSigningKeysCounts ) + // slither-disable-end encode-packed-collision ); guardianAddr = ECDSA.recover(msgHash, sig.r, sig.vs); guardianIndex = _getGuardianIndex(guardianAddr); From bc00b386c231a50a0ab8bcbdf6ed213e091efb99 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 22 May 2024 15:08:17 +0300 Subject: [PATCH 058/177] fix: merge conflicts --- test/0.4.24/nor/node-operators-registry.test.ts | 2 +- test/0.8.9/depositSecurityModule.test.ts | 2 +- test/deploy/dao.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index a4c842700..ca1d66102 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -18,7 +18,7 @@ import { } from "typechain-types"; import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; -import { addAragonApp, deployLidoDao, hasPermission } from "lib"; +import { addAragonApp, deployLidoDao, hasPermission } from "test/deploy"; const CURATED_TYPE = "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000"; // "curated-onchain-v1" const PENALTY_DELAY = 2 * 24 * 60 * 60; // 2 days diff --git a/test/0.8.9/depositSecurityModule.test.ts b/test/0.8.9/depositSecurityModule.test.ts index a3d27a03a..678d0f930 100644 --- a/test/0.8.9/depositSecurityModule.test.ts +++ b/test/0.8.9/depositSecurityModule.test.ts @@ -23,7 +23,7 @@ import { StakingRouterMockForDepositSecurityModule, } from "typechain-types"; -import { certainAddress, DSMAttestMessage, DSMPauseMessage, ether, streccak } from "lib"; +import { certainAddress, DSMAttestMessage, DSMPauseMessage, DSMUnvetMessage, ether, streccak } from "lib"; import { Snapshot } from "test/suite"; diff --git a/test/deploy/dao.ts b/test/deploy/dao.ts index 469e4edaf..bb3857cab 100644 --- a/test/deploy/dao.ts +++ b/test/deploy/dao.ts @@ -48,7 +48,7 @@ async function createAragonDao(rootAccount: HardhatEthersSigner) { return { dao, acl }; } -async function addAragonApp({ dao, name, impl, rootAccount }: CreateAddAppArgs): Promise { +export async function addAragonApp({ dao, name, impl, rootAccount }: CreateAddAppArgs): Promise { const tx = await dao["newAppInstance(bytes32,address,bytes,bool)"]( streccak(`${name}.aragonpm.test`), await impl.getAddress(), From a4a69218e556c4d632e61ba3b02493aa6bca678e Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Wed, 22 May 2024 15:23:33 +0200 Subject: [PATCH 059/177] chore: try to use latest foundry build --- .github/workflows/tests.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 34b8f3980..b8c7e1ebd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,10 +36,6 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - # Use a specific version of Foundry in case nightly is broken - # https://github.com/foundry-rs/foundry/releases - # with: - # version: nightly-54d8510c0f2b0f791f4c5ef99866c6af99b7606a - run: corepack enable From 593740dd5f506b61b28a8963a561f96b48e44fb2 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Wed, 22 May 2024 15:27:43 +0200 Subject: [PATCH 060/177] chore: add comments about unstable foundry nightly builds --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b8c7e1ebd..34b8f3980 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,6 +36,10 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + # Use a specific version of Foundry in case nightly is broken + # https://github.com/foundry-rs/foundry/releases + # with: + # version: nightly-54d8510c0f2b0f791f4c5ef99866c6af99b7606a - run: corepack enable From 20751a71995cd5c62725e4583f07b07f3319a663 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 30 May 2024 12:27:57 +0300 Subject: [PATCH 061/177] test: sr distance and max deposits --- .../stakingRouter/stakingRouter.module-sync.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts index a971b0291..ba4878ad8 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts @@ -271,6 +271,18 @@ describe("StakingRouter:module-sync", () => { }); }); + context("getStakingModuleMinDepositBlockDistance", () => { + it("Returns the minimum deposit block distance", async () => { + expect(await stakingRouter.getStakingModuleMinDepositBlockDistance(moduleId)).to.equal(minDepositBlockDistance); + }); + }); + + context("getStakingModuleMaxDepositsPerBlock", () => { + it("Returns the maximum deposits per block", async () => { + expect(await stakingRouter.getStakingModuleMaxDepositsPerBlock(moduleId)).to.equal(maxDepositsPerBlock); + }); + }); + context("getStakingModuleActiveValidatorsCount", () => { it("Returns the number of active validators in the module", async () => { const [exitedValidators, depositedValidators] = stakingModuleSummary; From 1ab7272c37ee012e25fe44e9379f2910c882ff8f Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 4 Jun 2024 07:17:55 +0200 Subject: [PATCH 062/177] feat: added events to the staking module --- contracts/0.8.9/interfaces/IStakingModule.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index 97ac07d4c..61c5daad0 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -171,4 +171,10 @@ interface IStakingModule { /// @dev Event to be emitted on StakingModule's nonce change event NonceChanged(uint256 nonce); + + /// @dev Event to be emitted when a signing key is added to the StakingModule + event SigningKeyAdded(uint256 indexed nodeOperatorId, bytes pubkey); + + /// @dev Event to be emitted when a signing key is removed from the StakingModule + event SigningKeyRemoved(uint256 indexed nodeOperatorId, bytes pubkey); } From de149887761d550e73c91a6e1206333698818fab Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 4 Jun 2024 14:19:16 +0200 Subject: [PATCH 063/177] test: fix nor test after contract version update --- test/0.4.24/nor/node-operators-registry.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index ca1d66102..5f8e667e4 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -141,7 +141,7 @@ describe("NodeOperatorsRegistry", () => { }); it("sets contract version correctly", async () => { - expect(await nor.getContractVersion()).to.be.equal(2); + expect(await nor.getContractVersion()).to.be.equal(3); }); it("sets hasInitialized() to true", async () => { From 4106101a1f5fe43419eceaeca2a56a11265548a2 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 6 Jun 2024 13:14:06 +0200 Subject: [PATCH 064/177] test: nor finalizeUpgrade_v3 --- .../NodeOperatorsRegistryMock.sol | 4 ++ .../nor/node-operators-registry.test.ts | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol index 68df9e5f0..0c93e04c2 100644 --- a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol +++ b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol @@ -143,6 +143,10 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { _setContractVersion(_newBaseVersion); } + function testing_setRewardDistributionStatus(RewardDistributionState _state) external { + _updateRewardDistributionState(_state); + } + function testing_resetRegistry() external { uint256 totalOperatorsCount = TOTAL_OPERATORS_COUNT_POSITION.getStorageUint256(); TOTAL_OPERATORS_COUNT_POSITION.setStorageUint256(0); diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index 5f8e667e4..b12e5fe3b 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -64,6 +64,12 @@ const NODE_OPERATORS: NodeOperatorConfig[] = [ }, ]; +enum RewardDistributionState { + TransferredToModule, // New reward portion minted and transferred to the module + ReadyForDistribution, // Operators' statistics updated, reward ready for distribution + Distributed, // Reward distributed among operators +} + describe("NodeOperatorsRegistry", () => { let deployer: HardhatEthersSigner; let user: HardhatEthersSigner; @@ -144,6 +150,10 @@ describe("NodeOperatorsRegistry", () => { expect(await nor.getContractVersion()).to.be.equal(3); }); + it("sets reward distribution state correctly", async () => { + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); + }); + it("sets hasInitialized() to true", async () => { expect(await nor.hasInitialized()).to.be.true; }); @@ -211,6 +221,41 @@ describe("NodeOperatorsRegistry", () => { }); }); + context("finalizeUpgrade_v3()", () => { + beforeEach(async () => { + // reset version there to test upgrade finalization + await nor.testing_setBaseVersion(2); + await nor.testing_setRewardDistributionStatus(0); + }); + + it("fails with CONTRACT_NOT_INITIALIZED error when called on implementation", async () => { + await expect(impl.finalizeUpgrade_v3()).to.be.revertedWith("CONTRACT_NOT_INITIALIZED"); + }); + + it("fails with CONTRACT_NOT_INITIALIZED error when nor instance not initialized yet", async () => { + const appProxy = await addAragonApp({ + dao, + name: "new-node-operators-registry", + impl, + rootAccount: deployer, + }); + const registry = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); + await expect(registry.finalizeUpgrade_v3()).to.be.revertedWith("CONTRACT_NOT_INITIALIZED"); + }); + + it("sets correct contract version", async () => { + await nor.finalizeUpgrade_v3(); + expect(await nor.getContractVersion()).to.be.equal(3); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); + }); + + it("reverts with error UNEXPECTED_CONTRACT_VERSION when called on already initialized contract", async () => { + await nor.finalizeUpgrade_v3(); + expect(await nor.getContractVersion()).to.be.equal(3); + await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); + }); + }); + context("setNodeOperatorName()", async () => { const firstNodeOperatorId = 0; const secondNodeOperatorId = 1; From 77013cb0fe2a77b29ba85a00ddb3a588e1504a38 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 6 Jun 2024 15:03:15 +0200 Subject: [PATCH 065/177] test: nor reward distribution status --- .../nor/node-operators-registry.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index b12e5fe3b..64fab4e38 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -487,6 +487,81 @@ describe("NodeOperatorsRegistry", () => { expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); }); }); + + context("getRewardDistributionState()", () => { + it("returns correct reward distribution state", async () => { + await nor.testing_setRewardDistributionStatus(RewardDistributionState.ReadyForDistribution); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); + + await nor.testing_setRewardDistributionStatus(RewardDistributionState.TransferredToModule); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.TransferredToModule); + + await nor.testing_setRewardDistributionStatus(RewardDistributionState.Distributed); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); + }); + }); + + context("distributeReward()", () => { + it('distribute reward when module not in "ReadyForDistribution" status', async () => { + await nor.testing_setRewardDistributionStatus(RewardDistributionState.ReadyForDistribution); + + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); + await expect(nor.distributeReward()) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); + }); + + it('reverts with "DISTRIBUTION_NOT_READY" error when module not in "ReadyForDistribution" status', async () => { + await nor.testing_setRewardDistributionStatus(RewardDistributionState.TransferredToModule); + await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); + + await nor.testing_setRewardDistributionStatus(RewardDistributionState.Distributed); + await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); + }); + }); + + describe("onRewardsMinted()", () => { + it("reverts with no STAKING_ROUTER_ROLE", async () => { + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; + await expect(nor.connect(stranger).onRewardsMinted(123)).to.be.revertedWith("APP_AUTH_FAILED"); + }); + + it("no reverts with STAKING_ROUTER_ROLE", async () => { + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; + await nor.connect(stakingRouter).onRewardsMinted(123); + }); + + it("emits RewardDistributionStateChanged event", async () => { + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; + await expect(nor.connect(stakingRouter).onRewardsMinted(123)) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.TransferredToModule); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.TransferredToModule); + }); + }); + + describe("onExitedAndStuckValidatorsCountsUpdated()", () => { + it("reverts with no STAKING_ROUTER_ROLE", async () => { + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; + await expect(nor.connect(stranger).onExitedAndStuckValidatorsCountsUpdated()).to.be.revertedWith( + "APP_AUTH_FAILED", + ); + }); + + it("no reverts with STAKING_ROUTER_ROLE", async () => { + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; + await nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated(); + }); + + it("emits ExitedAndStuckValidatorsCountsUpdated event", async () => { + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; + await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.ReadyForDistribution); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); + }); + }); }); interface NodeOperatorConfig { From bd5b8473b72345843860bb0c32b928d4431b7920 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 12 Jun 2024 15:10:32 +0200 Subject: [PATCH 066/177] feat: update dsm contract scratch deploy --- scripts/scratch/deployed-testnet-defaults.json | 3 +-- scripts/scratch/steps/09-deploy-non-aragon-contracts.ts | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/scripts/scratch/deployed-testnet-defaults.json b/scripts/scratch/deployed-testnet-defaults.json index 4c0ec821c..f3b2b6d25 100644 --- a/scripts/scratch/deployed-testnet-defaults.json +++ b/scripts/scratch/deployed-testnet-defaults.json @@ -94,8 +94,7 @@ }, "depositSecurityModule": { "deployParameters": { - "maxDepositsPerBlock": 150, - "minDepositBlockDistance": 5, + "maxOperatorsPerUnvetting": 200, "pauseIntentValidityPeriodBlocks": 6646, "usePredefinedAddressInstead": null } diff --git a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts index cfa4cd9b9..9b5b4e6fb 100644 --- a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts +++ b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts @@ -178,15 +178,13 @@ async function main() { // let depositSecurityModuleAddress = depositSecurityModuleParams.usePredefinedAddressInstead; if (depositSecurityModuleAddress === null) { - const { maxDepositsPerBlock, minDepositBlockDistance, pauseIntentValidityPeriodBlocks } = - depositSecurityModuleParams; + const { maxOperatorsPerUnvetting, pauseIntentValidityPeriodBlocks } = depositSecurityModuleParams; const depositSecurityModuleArgs = [ lidoAddress, depositContract, stakingRouter.address, - maxDepositsPerBlock, - minDepositBlockDistance, pauseIntentValidityPeriodBlocks, + maxOperatorsPerUnvetting, ]; depositSecurityModuleAddress = ( await deployWithoutProxy(Sk.depositSecurityModule, "DepositSecurityModule", deployer, depositSecurityModuleArgs) From 9978dc649ae7c68ae0b2b899bb291178e90883fb Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 12 Jun 2024 21:04:17 +0200 Subject: [PATCH 067/177] feat: update stakingRouter and nor contract scratch deploy Deploy MinFirstAllocationStrategy library and link it to the contracts --- lib/deploy.ts | 15 ++++++++++----- lib/state-file.ts | 1 + .../steps/03-deploy-template-and-app-bases.ts | 10 +++++++++- .../steps/09-deploy-non-aragon-contracts.ts | 6 ++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/deploy.ts b/lib/deploy.ts index 8e76c3270..0cc502d40 100644 --- a/lib/deploy.ts +++ b/lib/deploy.ts @@ -1,5 +1,6 @@ -import { ContractFactory, ContractTransactionReceipt } from "ethers"; +import { ContractFactory, ContractTransactionReceipt, Signer } from "ethers"; import { ethers } from "hardhat"; +import { FactoryOptions } from "hardhat/types"; import { addContractHelperFields, @@ -62,9 +63,10 @@ async function deployContractType2( artifactName: string, constructorArgs: unknown[], deployer: string, + signerOrOptions?: Signer | FactoryOptions, ): Promise { const txParams = await getDeployTxParams(deployer); - const factory = (await ethers.getContractFactory(artifactName)) as ContractFactory; + const factory = (await ethers.getContractFactory(artifactName, signerOrOptions)) as ContractFactory; const contract = await factory.deploy(...constructorArgs, txParams); const tx = contract.deploymentTransaction(); if (!tx) { @@ -90,10 +92,11 @@ export async function deployContract( artifactName: string, constructorArgs: unknown[], deployer: string, + signerOrOptions?: Signer | FactoryOptions, ): Promise { const txParams = await getDeployTxParams(deployer); if (txParams.type === 2) { - return await deployContractType2(artifactName, constructorArgs, deployer); + return await deployContractType2(artifactName, constructorArgs, deployer, signerOrOptions); } else { throw Error("Tx type 1 is not supported"); } @@ -126,12 +129,13 @@ export async function deployImplementation( artifactName: string, deployer: string, constructorArgs: ConvertibleToString[] = [], + signerOrOptions?: Signer | FactoryOptions, ): Promise { log.lineWithArguments( `Deploying implementation for proxy of ${artifactName} with constructor args: `, constructorArgs, ); - const contract = await deployContract(artifactName, constructorArgs, deployer); + const contract = await deployContract(artifactName, constructorArgs, deployer, signerOrOptions); updateObjectInState(nameInState, { implementation: { @@ -150,13 +154,14 @@ export async function deployBehindOssifiableProxy( deployer: string, constructorArgs: ConvertibleToString[] = [], implementation: null | string = null, + signerOrOptions?: Signer | FactoryOptions, ) { if (implementation === null) { log.lineWithArguments( `Deploying implementation for proxy of ${artifactName} with constructor args: `, constructorArgs, ); - const contract = await deployContract(artifactName, constructorArgs, deployer); + const contract = await deployContract(artifactName, constructorArgs, deployer, signerOrOptions); implementation = contract.address; } else { log(`Using pre-deployed implementation of ${artifactName}: ${implementation}`); diff --git a/lib/state-file.ts b/lib/state-file.ts index fa5e7aae9..877416578 100644 --- a/lib/state-file.ts +++ b/lib/state-file.ts @@ -79,6 +79,7 @@ export enum Sk { lidoLocator = "lidoLocator", chainSpec = "chainSpec", scratchDeployGasUsed = "scratchDeployGasUsed", + minFirstAllocationStrategy = "minFirstAllocationStrategy", } export function getAddress(contractKey: Sk, state: DeploymentState): string { diff --git a/scripts/scratch/steps/03-deploy-template-and-app-bases.ts b/scripts/scratch/steps/03-deploy-template-and-app-bases.ts index 11cd0993f..e33c26e3a 100644 --- a/scripts/scratch/steps/03-deploy-template-and-app-bases.ts +++ b/scripts/scratch/steps/03-deploy-template-and-app-bases.ts @@ -25,7 +25,15 @@ async function main() { await deployImplementation(Sk.appLido, "Lido", deployer); await deployImplementation(Sk.appOracle, "LegacyOracle", deployer); - await deployImplementation(Sk.appNodeOperatorsRegistry, "NodeOperatorsRegistry", deployer); + + const minFirstAllocationStrategy = await deployWithoutProxy( + Sk.minFirstAllocationStrategy, + "MinFirstAllocationStrategy", + deployer, + ); + await deployImplementation(Sk.appNodeOperatorsRegistry, "NodeOperatorsRegistry", deployer, [], { + libraries: { MinFirstAllocationStrategy: minFirstAllocationStrategy.address }, + }); const template = await deployWithoutProxy(Sk.lidoTemplate, "LidoTemplate", state.deployer, templateConstructorArgs); const receipt = await ethers.provider.getTransactionReceipt(template.deploymentTx); diff --git a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts index 9b5b4e6fb..bcd4d8975 100644 --- a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts +++ b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts @@ -27,6 +27,7 @@ async function main() { const hashConsensusForAccountingParams = state[Sk.hashConsensusForAccountingOracle].deployParameters; const hashConsensusForExitBusParams = state[Sk.hashConsensusForValidatorsExitBusOracle].deployParameters; const withdrawalQueueERC721Params = state[Sk.withdrawalQueueERC721].deployParameters; + const minFirstAllocationStrategyAddress = state[Sk.minFirstAllocationStrategy].address; const proxyContractsOwner = deployer; const admin = deployer; @@ -165,12 +166,17 @@ async function main() { // // === StakingRouter === // + const stakingRouter = await deployBehindOssifiableProxy( Sk.stakingRouter, "StakingRouter", proxyContractsOwner, deployer, [depositContract], + null, + { + libraries: { MinFirstAllocationStrategy: minFirstAllocationStrategyAddress }, + }, ); // From aa51127d65b981d8ad32a75d6ce44ec79a365eb7 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 13 Jun 2024 13:19:27 +0200 Subject: [PATCH 068/177] feat: update scratch deploy for sanity checker --- scripts/scratch/steps/09-deploy-non-aragon-contracts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts index bcd4d8975..31b77baf5 100644 --- a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts +++ b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts @@ -78,7 +78,6 @@ async function main() { admin, [ sanityChecks.exitedValidatorsPerDayLimit, - sanityChecks.appearedValidatorsPerDayLimit, sanityChecks.oneOffCLBalanceDecreaseBPLimit, sanityChecks.annualBalanceIncreaseBPLimit, sanityChecks.simulatedShareRateDeviationBPLimit, @@ -87,6 +86,7 @@ async function main() { sanityChecks.maxNodeOperatorsPerExtraDataItemCount, sanityChecks.requestTimestampMargin, sanityChecks.maxPositiveTokenRebase, + sanityChecks.appearedValidatorsPerDayLimit, ], [[], [], [], [], [], [], [], [], [], [], []], ]; From 2ef7140aadc6b77d130f45e4d0c80a9cad3559b5 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 13 Jun 2024 17:25:30 +0200 Subject: [PATCH 069/177] feat: accounting oracle scratch deploy update consensus version --- scripts/scratch/deployed-testnet-defaults.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/scratch/deployed-testnet-defaults.json b/scripts/scratch/deployed-testnet-defaults.json index f3b2b6d25..579f33810 100644 --- a/scripts/scratch/deployed-testnet-defaults.json +++ b/scripts/scratch/deployed-testnet-defaults.json @@ -78,7 +78,7 @@ }, "accountingOracle": { "deployParameters": { - "consensusVersion": 1 + "consensusVersion": 2 } }, "hashConsensusForValidatorsExitBusOracle": { From 69621e23e1ce94dd0ef6d38253c8bb994f33d7aa Mon Sep 17 00:00:00 2001 From: maxim Date: Mon, 17 Jun 2024 17:53:19 +0200 Subject: [PATCH 070/177] feat: update comment --- contracts/0.8.9/StakingRouter.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 7bdfaf27b..98051a1cc 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -103,12 +103,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @notice module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP uint16 priorityExitShareThreshold; /// @notice the maximum number of validators that can be deposited in a single block - /// @dev must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit` - /// (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function) + /// @dev must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit` + /// (see docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function) uint64 maxDepositsPerBlock; /// @notice the minimum distance between deposits in blocks - /// @dev must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit` - /// (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function) + /// @dev must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit` + /// (see docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function) uint64 minDepositBlockDistance; } From 5b848eae228e15d717201adedfbbd4c45d50d7b0 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 11 Jun 2024 17:56:52 +0400 Subject: [PATCH 071/177] fix: script for deploy sr v2 --- lib/deploy.ts | 2 + lib/state-file.ts | 2 + scripts/staking-router-v2/deploy.ts | 166 ++++++++++++++++++ .../staking-router-v2/sr-v2-holesky-deploy.sh | 37 ++++ .../staking-router-v2/sr-v2-mainnet-deploy.sh | 37 ++++ 5 files changed, 244 insertions(+) create mode 100644 scripts/staking-router-v2/deploy.ts create mode 100755 scripts/staking-router-v2/sr-v2-holesky-deploy.sh create mode 100755 scripts/staking-router-v2/sr-v2-mainnet-deploy.sh diff --git a/lib/deploy.ts b/lib/deploy.ts index 0cc502d40..8c8e69069 100644 --- a/lib/deploy.ts +++ b/lib/deploy.ts @@ -147,6 +147,8 @@ export async function deployImplementation( return contract; } +export async function deployLibraru() {} + export async function deployBehindOssifiableProxy( nameInState: Sk | null, artifactName: string, diff --git a/lib/state-file.ts b/lib/state-file.ts index 877416578..729e85f72 100644 --- a/lib/state-file.ts +++ b/lib/state-file.ts @@ -30,6 +30,8 @@ export enum Sk { appLido = "app:lido", appOracle = `app:oracle`, appNodeOperatorsRegistry = "app:node-operators-registry", + appSDVT = "app:sdvt", + appSandbox = "app:sandbox", aragonAcl = "aragon-acl", aragonEvmScriptRegistry = "aragon-evm-script-registry", aragonApmRegistry = "aragon-apm-registry", diff --git a/scripts/staking-router-v2/deploy.ts b/scripts/staking-router-v2/deploy.ts new file mode 100644 index 000000000..3dedd7c97 --- /dev/null +++ b/scripts/staking-router-v2/deploy.ts @@ -0,0 +1,166 @@ +import { ethers } from "hardhat"; + +import { deployImplementation, deployWithoutProxy, log, persistNetworkState, readNetworkState, Sk } from "lib"; + +function getEnvVariable(name: string, defaultValue?: string) { + const value = process.env[name]; + if (value === undefined) { + if (defaultValue === undefined) { + throw new Error(`Env variable ${name} must be set`); + } + return defaultValue; + } else { + log(`Using env variable ${name}=${value}`); + return value; + } +} + +async function main() { + const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); + const chainId = (await ethers.provider.getNetwork()).chainId; + const balance = await ethers.provider.getBalance(deployer); + log(`Deployer ${deployer} on network ${chainId} has balance: ${ethers.formatEther(balance)} ETH`); + + const state = readNetworkState(); + state[Sk.scratchDeployGasUsed] = 0n.toString(); + persistNetworkState(state); + + // Read all the constants from environment variables + const LIDO = getEnvVariable("LIDO"); + const DEPOSIT_CONTRACT = getEnvVariable("DEPOSIT_CONTRACT"); + const STAKING_ROUTER = getEnvVariable("STAKING_ROUTER"); + const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = parseInt(getEnvVariable("PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS")); + const MAX_OPERATORS_PER_UNVETTING = parseInt(getEnvVariable("MAX_OPERATORS_PER_UNVETTING")); + + const LOCATOR = getEnvVariable("LOCATOR"); + const LEGACY_ORACLE = getEnvVariable("LEGACY_ORACLE"); + const SECONDS_PER_SLOT = parseInt(getEnvVariable("SECONDS_PER_SLOT")); + const GENESIS_TIME = parseInt(getEnvVariable("GENESIS_TIME")); + + const ACCOUNTING_ORACLE_PROXY = getEnvVariable("ACCOUNTING_ORACLE_PROXY"); + const EL_REWARDS_VAULT = getEnvVariable("EL_REWARDS_VAULT"); + const BURNER = getEnvVariable("BURNER"); + const TREASURY_ADDRESS = getEnvVariable("TREASURY_ADDRESS"); + const VEBO = getEnvVariable("VEBO"); + const WQ = getEnvVariable("WITHDRAWAL_QUEUE_ERC721"); + const WITHDRAWAL_VAULT = getEnvVariable("WITHDRAWAL_VAULT_ADDRESS"); + const ORACLE_DAEMON_CONFIG = getEnvVariable("ORACLE_DAEMON_CONFIG"); + + // StakingRouter deploy + + // Deploy MinFirstAllocationStrategy + const minFirstAllocationStrategyAddress = ( + await deployWithoutProxy(Sk.minFirstAllocationStrategy, "MinFirstAllocationStrategy", deployer) + ).address; + + log(`MinFirstAllocationStrategy address: ${minFirstAllocationStrategyAddress}`); + + const libraries = { + MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, + }; + + const stakingRouterAddress = ( + await deployImplementation(Sk.stakingRouter, "StakingRouter", deployer, [DEPOSIT_CONTRACT], { libraries }) + ).address; + + log(`StakingRouter implementation address: ${stakingRouterAddress}`); + + const appNodeOperatorsRegistry = ( + await deployImplementation(Sk.appNodeOperatorsRegistry, "NodeOperatorsRegistry", deployer, [], { libraries }) + ).address; + + log(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`); + + const appSDVT = (await deployImplementation(Sk.appSDVT, "NodeOperatorsRegistry", deployer, [], { libraries })) + .address; + + log(`SimpleDvt address implementation: ${appSDVT}`); + + if (Number(chainId) == 17000) { + const appSandbox = ( + await deployImplementationWithLinkedLibrary(Sk.appSandbox, "NodeOperatorsRegistry", deployer, [], libraries) + ).address; + + log(`Sandbox address implementation: ${appSandbox}`); + } + + const depositSecurityModuleParams = [ + LIDO, + DEPOSIT_CONTRACT, + STAKING_ROUTER, + PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS, + MAX_OPERATORS_PER_UNVETTING, + ]; + + const depositSecurityModuleAddress = ( + await deployWithoutProxy(Sk.depositSecurityModule, "DepositSecurityModule", deployer, depositSecurityModuleParams) + ).address; + + log(`New DSM address: ${depositSecurityModuleAddress}`); + + const accountingOracleArgs = [LOCATOR, LIDO, LEGACY_ORACLE, SECONDS_PER_SLOT, GENESIS_TIME]; + + const accountingOracleAddress = ( + await deployImplementation(Sk.accountingOracle, "AccountingOracle", deployer, accountingOracleArgs) + ).address; + + log(`AO implementation address: ${accountingOracleAddress}`); + + const SC_ADMIN = deployer; + const LIMITS_LIST = [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000, 1500]; + const MANAGERS_ROSTER = [ + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + [SC_ADMIN], + ]; + const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS_LIST, MANAGERS_ROSTER]; + + const oracleReportSanityCheckerAddress = ( + await deployWithoutProxy( + Sk.oracleReportSanityChecker, + "OracleReportSanityChecker", + deployer, + oracleReportSanityCheckerArgs, + ) + ).address; + + log(`OracleReportSanityChecker new address ${oracleReportSanityCheckerAddress}`); + + const locatorConfig = [ + [ + ACCOUNTING_ORACLE_PROXY, + depositSecurityModuleAddress, + EL_REWARDS_VAULT, + LEGACY_ORACLE, + LIDO, + oracleReportSanityCheckerAddress, + LEGACY_ORACLE, + BURNER, + STAKING_ROUTER, + TREASURY_ADDRESS, + VEBO, + WQ, + WITHDRAWAL_VAULT, + ORACLE_DAEMON_CONFIG, + ], + ]; + + const locatorAddress = (await deployImplementation(Sk.lidoLocator, "LidoLocator", deployer, locatorConfig)).address; + + log(`Locator implementation address ${locatorAddress}`); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + log.error(error); + process.exit(1); + }); diff --git a/scripts/staking-router-v2/sr-v2-holesky-deploy.sh b/scripts/staking-router-v2/sr-v2-holesky-deploy.sh new file mode 100755 index 000000000..0231e9044 --- /dev/null +++ b/scripts/staking-router-v2/sr-v2-holesky-deploy.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e +u +set -o pipefail + +# export NETWORK=testnet +export DEPLOYER=${DEPLOYER} +export NETWORK=local +export RPC_URL=${RPC_URL:="http://127.0.0.1:8555"} # if defined use the value set to default otherwise + +export PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS=7200 +export MAX_OPERATORS_PER_UNVETTING=20 + +export SECONDS_PER_SLOT=12 +export GENESIS_TIME=1695902400 + +export GAS_PRIORITY_FEE=${GAS_PRIORITY_FEE:=2} +export GAS_MAX_FEE=${GAS_MAX_FEE:=100} + + +# contracts addresses on mainnet +export LIDO="0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034" +export DEPOSIT_CONTRACT="0x4242424242424242424242424242424242424242" +export STAKING_ROUTER="0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229" +export ACCOUNTING_ORACLE_PROXY="0x4E97A3972ce8511D87F334dA17a2C332542a5246" +export EL_REWARDS_VAULT="0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8" +export POST_TOKEN_REBASE_RECEIVER="0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019" +export BURNER="0x4E46BD7147ccf666E1d73A3A456fC7a68de82eCA" +export TREASURY_ADDRESS="0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" +export VEBO="0xffDDF7025410412deaa05E3E1cE68FE53208afcb" +export WITHDRAWAL_QUEUE_ERC721="0xc7cc160b58F8Bb0baC94b80847E2CF2800565C50" +export WITHDRAWAL_VAULT_ADDRESS="0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9" +export ORACLE_DAEMON_CONFIG="0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7" +export LOCATOR="0x28fab2059c713a7f9d8c86db49f9bb0e96af1ef8" +export LEGACY_ORACLE="0x072f72be3acfe2c52715829f2cd9061a6c8ff019" + +# Run the deployment script with the environment variables +yarn hardhat --network $NETWORK run scripts/staking-router-v2/deploy.ts diff --git a/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh b/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh new file mode 100755 index 000000000..58bdde783 --- /dev/null +++ b/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e +u +set -o pipefail + +# export NETWORK=mainnet +export DEPLOYER=${DEPLOYER} +export NETWORK=local +export RPC_URL=${RPC_URL:="http://127.0.0.1:8555"} # if defined use the value set to default otherwise + +export PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS=7200 +export MAX_OPERATORS_PER_UNVETTING=20 + +export SECONDS_PER_SLOT=12 +export GENESIS_TIME=1606824023 + +export GAS_PRIORITY_FEE=${GAS_PRIORITY_FEE:=2} +export GAS_MAX_FEE=${GAS_MAX_FEE:=100} + + +# contracts addresses on mainnet +export LIDO="0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +export DEPOSIT_CONTRACT="0x00000000219ab540356cBB839Cbe05303d7705Fa" +export STAKING_ROUTER="0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +export ACCOUNTING_ORACLE_PROXY="0x852deD011285fe67063a08005c71a85690503Cee" +export EL_REWARDS_VAULT="0x388C818CA8B9251b393131C08a736A67ccB19297" +export POST_TOKEN_REBASE_RECEIVER="0x442af784A788A5bd6F42A01Ebe9F287a871243fb" +export BURNER="0xD15a672319Cf0352560eE76d9e89eAB0889046D3" +export TREASURY_ADDRESS="0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +export WITHDRAWAL_QUEUE_ERC721="0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1" +export WITHDRAWAL_VAULT_ADDRESS="0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f" +export ORACLE_DAEMON_CONFIG="0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" +export LOCATOR="0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" +export LEGACY_ORACLE="0x442af784A788A5bd6F42A01Ebe9F287a871243fb" +export VEBO="0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e" + +# Run the deployment script with the environment variables +yarn hardhat --network $NETWORK run scripts/staking-router-v2/deploy.ts From 548cedc41a0d011ee7e895ae9abe276c5a01eddb Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 19 Jun 2024 10:47:53 +0400 Subject: [PATCH 072/177] fix: removed empty func --- lib/deploy.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/deploy.ts b/lib/deploy.ts index 8c8e69069..0cc502d40 100644 --- a/lib/deploy.ts +++ b/lib/deploy.ts @@ -147,8 +147,6 @@ export async function deployImplementation( return contract; } -export async function deployLibraru() {} - export async function deployBehindOssifiableProxy( nameInState: Sk | null, artifactName: string, From 79711fe411c3c3f52caf1c154f4d39d5533f5f99 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 19 Jun 2024 11:00:33 +0400 Subject: [PATCH 073/177] fix: deploy on holesky --- scripts/staking-router-v2/deploy.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/staking-router-v2/deploy.ts b/scripts/staking-router-v2/deploy.ts index 3dedd7c97..5a87e7f51 100644 --- a/scripts/staking-router-v2/deploy.ts +++ b/scripts/staking-router-v2/deploy.ts @@ -77,9 +77,8 @@ async function main() { log(`SimpleDvt address implementation: ${appSDVT}`); if (Number(chainId) == 17000) { - const appSandbox = ( - await deployImplementationWithLinkedLibrary(Sk.appSandbox, "NodeOperatorsRegistry", deployer, [], libraries) - ).address; + const appSandbox = (await deployImplementation(Sk.appSandbox, "NodeOperatorsRegistry", deployer, [], { libraries })) + .address; log(`Sandbox address implementation: ${appSandbox}`); } From 2538d7919045557f04c6cea7ff4be64c068cf1c1 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 19 Jun 2024 11:53:14 +0400 Subject: [PATCH 074/177] fix: deploy NOR impl only once --- lib/state-file.ts | 2 -- scripts/staking-router-v2/deploy.ts | 31 +++---------------- .../staking-router-v2/sr-v2-holesky-deploy.sh | 1 + .../staking-router-v2/sr-v2-mainnet-deploy.sh | 1 + 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/lib/state-file.ts b/lib/state-file.ts index 729e85f72..877416578 100644 --- a/lib/state-file.ts +++ b/lib/state-file.ts @@ -30,8 +30,6 @@ export enum Sk { appLido = "app:lido", appOracle = `app:oracle`, appNodeOperatorsRegistry = "app:node-operators-registry", - appSDVT = "app:sdvt", - appSandbox = "app:sandbox", aragonAcl = "aragon-acl", aragonEvmScriptRegistry = "aragon-evm-script-registry", aragonApmRegistry = "aragon-apm-registry", diff --git a/scripts/staking-router-v2/deploy.ts b/scripts/staking-router-v2/deploy.ts index 5a87e7f51..7a4156351 100644 --- a/scripts/staking-router-v2/deploy.ts +++ b/scripts/staking-router-v2/deploy.ts @@ -25,6 +25,10 @@ async function main() { state[Sk.scratchDeployGasUsed] = 0n.toString(); persistNetworkState(state); + const SC_ADMIN = getEnvVariable("ARAGON_AGENT"); + const LIMITS_LIST = [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000, 1500]; + const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; + // Read all the constants from environment variables const LIDO = getEnvVariable("LIDO"); const DEPOSIT_CONTRACT = getEnvVariable("DEPOSIT_CONTRACT"); @@ -71,18 +75,6 @@ async function main() { log(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`); - const appSDVT = (await deployImplementation(Sk.appSDVT, "NodeOperatorsRegistry", deployer, [], { libraries })) - .address; - - log(`SimpleDvt address implementation: ${appSDVT}`); - - if (Number(chainId) == 17000) { - const appSandbox = (await deployImplementation(Sk.appSandbox, "NodeOperatorsRegistry", deployer, [], { libraries })) - .address; - - log(`Sandbox address implementation: ${appSandbox}`); - } - const depositSecurityModuleParams = [ LIDO, DEPOSIT_CONTRACT, @@ -105,21 +97,6 @@ async function main() { log(`AO implementation address: ${accountingOracleAddress}`); - const SC_ADMIN = deployer; - const LIMITS_LIST = [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000, 1500]; - const MANAGERS_ROSTER = [ - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - [SC_ADMIN], - ]; const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS_LIST, MANAGERS_ROSTER]; const oracleReportSanityCheckerAddress = ( diff --git a/scripts/staking-router-v2/sr-v2-holesky-deploy.sh b/scripts/staking-router-v2/sr-v2-holesky-deploy.sh index 0231e9044..602019a89 100755 --- a/scripts/staking-router-v2/sr-v2-holesky-deploy.sh +++ b/scripts/staking-router-v2/sr-v2-holesky-deploy.sh @@ -32,6 +32,7 @@ export WITHDRAWAL_VAULT_ADDRESS="0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9" export ORACLE_DAEMON_CONFIG="0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7" export LOCATOR="0x28fab2059c713a7f9d8c86db49f9bb0e96af1ef8" export LEGACY_ORACLE="0x072f72be3acfe2c52715829f2cd9061a6c8ff019" +export ARAGON_AGENT="0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" # Run the deployment script with the environment variables yarn hardhat --network $NETWORK run scripts/staking-router-v2/deploy.ts diff --git a/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh b/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh index 58bdde783..f3dd44858 100755 --- a/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh +++ b/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh @@ -32,6 +32,7 @@ export ORACLE_DAEMON_CONFIG="0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" export LOCATOR="0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" export LEGACY_ORACLE="0x442af784A788A5bd6F42A01Ebe9F287a871243fb" export VEBO="0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e" +export ARAGON_AGENT="0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" # Run the deployment script with the environment variables yarn hardhat --network $NETWORK run scripts/staking-router-v2/deploy.ts From 60f037275587cb671e7a477c5af0dc96a70b8335 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 19 Jun 2024 16:06:59 +0500 Subject: [PATCH 075/177] fix: gas cost optim --- contracts/0.8.9/DepositSecurityModule.sol | 5 +++-- contracts/0.8.9/StakingRouter.sol | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 9ad8b49f2..d1439d2d8 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -526,10 +526,11 @@ contract DepositSecurityModule { abi.encodePacked(ATTEST_MESSAGE_PREFIX, blockNumber, blockHash, depositRoot, stakingModuleId, nonce) ); - address prevSignerAddr = address(0); + address prevSignerAddr; + address signerAddr; for (uint256 i = 0; i < sigs.length; ) { - address signerAddr = ECDSA.recover(msgHash, sigs[i].r, sigs[i].vs); + signerAddr = ECDSA.recover(msgHash, sigs[i].r, sigs[i].vs); if (!_isGuardian(signerAddr)) revert InvalidSignature(); if (signerAddr <= prevSignerAddr) revert SignaturesNotSorted(); prevSignerAddr = signerAddr; diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 98051a1cc..685169c48 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -660,10 +660,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version onlyRole(REPORT_EXITED_VALIDATORS_ROLE) { uint256 stakingModulesCount = getStakingModulesCount(); + StakingModule storage stakingModule; + IStakingModule moduleContract; for (uint256 i; i < stakingModulesCount; ) { - StakingModule storage stakingModule = _getStakingModuleByIndex(i); - IStakingModule moduleContract = IStakingModule(stakingModule.stakingModuleAddress); + stakingModule = _getStakingModuleByIndex(i); + moduleContract = IStakingModule(stakingModule.stakingModuleAddress); (uint256 exitedValidatorsCount, , ) = moduleContract.getStakingModuleSummary(); if (exitedValidatorsCount == stakingModule.exitedValidatorsCount) { From 23e2c0752570a4a122f0cb50063a7d825c32eea7 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 19 Jun 2024 14:14:47 +0300 Subject: [PATCH 076/177] docs: add explainer for _isMinDepositDistancePassed --- contracts/0.8.9/DepositSecurityModule.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index d1439d2d8..e432b8ab8 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -453,6 +453,9 @@ contract DepositSecurityModule { } function _isMinDepositDistancePassed(uint256 stakingModuleId) internal view returns (bool) { + /// @dev The distance is reset when a deposit is made to any module. This prevents a front-run attack + /// by colluding guardians on several modules at once, providing the necessary window for an honest + /// guardian to react and pause deposits to all modules. uint256 lastDepositToModuleBlock = STAKING_ROUTER.getStakingModuleLastDepositBlock(stakingModuleId); uint256 minDepositBlockDistance = STAKING_ROUTER.getStakingModuleMinDepositBlockDistance(stakingModuleId); uint256 maxLastDepositBlock = lastDepositToModuleBlock >= lastDepositBlock ? lastDepositToModuleBlock : lastDepositBlock; From 3dabb5ae5254488641379f6993d07a80d97ad9ae Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 19 Jun 2024 14:36:55 +0300 Subject: [PATCH 077/177] chore: unification of iterators in dsm --- contracts/0.8.9/DepositSecurityModule.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index e432b8ab8..bf6b0eca1 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -308,8 +308,12 @@ contract DepositSecurityModule { * Reverts if any of the addresses is already a guardian or is zero. */ function addGuardians(address[] memory addresses, uint256 newQuorum) external onlyOwner { - for (uint256 i = 0; i < addresses.length; ++i) { + for (uint256 i = 0; i < addresses.length; ) { _addGuardian(addresses[i]); + + unchecked { + ++i; + } } _setGuardianQuorum(newQuorum); } From 806715b65d600075529df91ec0ccb324d6bd778d Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 20 Jun 2024 12:02:30 +0300 Subject: [PATCH 078/177] fix: targetLimitMode type in comments --- contracts/0.8.9/StakingRouter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 685169c48..529454bcb 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -592,7 +592,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version IStakingModule stakingModule = IStakingModule(stakingModuleState.stakingModuleAddress); ( - /* uint156 targetLimitMode */, + /* uint256 targetLimitMode */, /* uint256 targetValidatorsCount */, uint256 stuckValidatorsCount, /* uint256 refundedValidatorsCount */, From ab0fff98d4c15a3c920f845a7154dfd1459e8cc6 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 20 Jun 2024 20:37:36 +0400 Subject: [PATCH 079/177] fix: reading contract addresses from json --- .gitignore | 3 + .../deployed-holesky.json.template | 776 ++++++++++++++++++ .../deployed-mainnet.json.template | 567 +++++++++++++ .../{deploy.ts => sr-v2-deploy.ts} | 51 +- .../staking-router-v2/sr-v2-holesky-deploy.sh | 38 - .../staking-router-v2/sr-v2-mainnet-deploy.sh | 38 - 6 files changed, 1372 insertions(+), 101 deletions(-) create mode 100644 scripts/staking-router-v2/deployed-holesky.json.template create mode 100644 scripts/staking-router-v2/deployed-mainnet.json.template rename scripts/staking-router-v2/{deploy.ts => sr-v2-deploy.ts} (78%) delete mode 100755 scripts/staking-router-v2/sr-v2-holesky-deploy.sh delete mode 100755 scripts/staking-router-v2/sr-v2-mainnet-deploy.sh diff --git a/.gitignore b/.gitignore index be3682bb4..10b013cbb 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,8 @@ foundry/out/ .env deployed-local.json +deployed-mainnet.json +deployed-holesky.json + # MacOS .DS_Store diff --git a/scripts/staking-router-v2/deployed-holesky.json.template b/scripts/staking-router-v2/deployed-holesky.json.template new file mode 100644 index 000000000..b0a79583a --- /dev/null +++ b/scripts/staking-router-v2/deployed-holesky.json.template @@ -0,0 +1,776 @@ +{ + "accountingOracle": { + "deployParameters": { + "consensusVersion": 1 + }, + "proxy": { + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "address": "0x4E97A3972ce8511D87F334dA17a2C332542a5246", + "constructorArgs": [ + "0x6AcA050709469F1f98d8f40f68b1C83B533cd2b2", + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0x" + ] + }, + "implementation": { + "contract": "contracts/0.8.9/oracle/AccountingOracle.sol", + "address": "0x6AcA050709469F1f98d8f40f68b1C83B533cd2b2", + "constructorArgs": [ + "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8", + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", + "0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019", + 12, + 1695902400 + ] + } + }, + "apmRepoBaseAddress": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "app:aragon-agent": { + "implementation": { + "contract": "@aragon/apps-agent/contracts/Agent.sol", + "address": "0xF4aDA7Ff34c508B9Af2dE4160B6078D2b58FD46B", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-agent", + "fullName": "aragon-agent.lidopm.eth", + "id": "0x701a4fd1f5174d12a0f1d9ad2c88d0ad11ab6aad0ac72b7d9ce621815f8016a9" + }, + "proxy": { + "address": "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0x701a4fd1f5174d12a0f1d9ad2c88d0ad11ab6aad0ac72b7d9ce621815f8016a9", + "0x8129fc1c" + ] + } + }, + "app:aragon-finance": { + "implementation": { + "contract": "@aragon/apps-finance/contracts/Finance.sol", + "address": "0x1a76ED38B14C768e02b96A879d89Db18AC83EC53", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-finance", + "fullName": "aragon-finance.lidopm.eth", + "id": "0x5c9918c99c4081ca9459c178381be71d9da40e49e151687da55099c49a4237f1" + }, + "proxy": { + "address": "0xf0F281E5d7FBc54EAFcE0dA225CDbde04173AB16", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0x5c9918c99c4081ca9459c178381be71d9da40e49e151687da55099c49a4237f1", + "0x1798de81000000000000000000000000e92329ec7ddb11d25e25b3c21eebf11f15eb325d0000000000000000000000000000000000000000000000000000000000278d00" + ] + } + }, + "app:aragon-token-manager": { + "implementation": { + "contract": "@aragon/apps-lido/apps/token-manager/contracts/TokenManager.sol", + "address": "0x6f0b994E6827faC1fDb58AF66f365676247bAD71", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-token-manager", + "fullName": "aragon-token-manager.lidopm.eth", + "id": "0xcd567bdf93dd0f6acc3bc7f2155f83244d56a65abbfbefb763e015420102c67b" + }, + "proxy": { + "address": "0xFaa1692c6eea8eeF534e7819749aD93a1420379A", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0xcd567bdf93dd0f6acc3bc7f2155f83244d56a65abbfbefb763e015420102c67b", + "0x" + ] + } + }, + "app:aragon-voting": { + "implementation": { + "contract": "@aragon/apps-lido/apps/voting/contracts/Voting.sol", + "address": "0x994c92228803e8b2D0fb8a610AbCB47412EeF8eF", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-voting", + "fullName": "aragon-voting.lidopm.eth", + "id": "0x0abcd104777321a82b010357f20887d61247493d89d2e987ff57bcecbde00e1e" + }, + "proxy": { + "address": "0xdA7d2573Df555002503F29aA4003e398d28cc00f", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0x0abcd104777321a82b010357f20887d61247493d89d2e987ff57bcecbde00e1e", + "0x13e0945300000000000000000000000014ae7daeecdf57034f3e9db8564e46dba8d9734400000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000384000000000000000000000000000000000000000000000000000000000000012c" + ] + } + }, + "app:lido": { + "implementation": { + "contract": "contracts/0.4.24/Lido.sol", + "address": "0x59034815464d18134A55EED3702b535D8A32c52b", + "constructorArgs": [] + }, + "aragonApp": { + "name": "lido", + "fullName": "lido.lidopm.eth", + "id": "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" + }, + "proxy": { + "address": "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320", + "0x" + ] + } + }, + "app:node-operators-registry": { + "implementation": { + "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", + "address": "0xE0270CF2564d81E02284e16539F59C1B5a4718fE", + "constructorArgs": [] + }, + "aragonApp": { + "name": "node-operators-registry", + "fullName": "node-operators-registry.lidopm.eth", + "id": "0x7071f283424072341f856ac9e947e7ec0eb68719f757a7e785979b6b8717579d" + }, + "proxy": { + "address": "0x595F64Ddc3856a3b5Ff4f4CC1d1fb4B46cFd2bAC", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0x7071f283424072341f856ac9e947e7ec0eb68719f757a7e785979b6b8717579d", + "0x" + ] + } + }, + "app:oracle": { + "implementation": { + "contract": "contracts/0.4.24/oracle/LegacyOracle.sol", + "address": "0xcE4B3D5bd6259F5dD73253c51b17e5a87bb9Ee64", + "constructorArgs": [] + }, + "aragonApp": { + "name": "oracle", + "fullName": "oracle.lidopm.eth", + "id": "0x8b47ba2a8454ec799cd91646e7ec47168e91fd139b23f017455f3e5898aaba93" + }, + "proxy": { + "address": "0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0x8b47ba2a8454ec799cd91646e7ec47168e91fd139b23f017455f3e5898aaba93", + "0x" + ] + } + }, + "app:simple-dvt": { + "stakingRouterModuleParams": { + "moduleName": "SimpleDVT", + "moduleType": "simple-dvt-onchain-v1", + "targetShare": 50, + "moduleFee": 800, + "treasuryFee": 200, + "penaltyDelay": 86400, + "easyTrackAddress": "0x1763b9ED3586B08AE796c7787811a2E1bc16163a", + "easyTrackEVMScriptExecutor": "0x2819B65021E13CEEB9AC33E77DB32c7e64e7520D", + "easyTrackFactories": { + "AddNodeOperators": "0xC20129f1dd4DFeD023a6d6A8de9d54A7b61af5CC", + "ActivateNodeOperators": "0x08c48Fef9Cadca882E27d2325D1785858D5c1aE3", + "DeactivateNodeOperators": "0xf5436129Cf9d8fa2a1cb6e591347155276550635", + "SetNodeOperatorNames": "0xb6a31141A579FCB540E3BB3504C58F1e6F5f543a", + "SetNodeOperatorRewardAddresses": "0x7F9c5b838510e06b85DD146e71553EB7890fAf2e", + "UpdateTargetValidatorLimits": "0x6e570D487aE5729Bd982A7bb3a7bfA5213AeAEdE", + "SetVettedValidatorsLimits": "0xD420d6C8aA81c087829A64Ce59936b7C1176A81a", + "TransferNodeOperatorManager": "0xaa49cF620e3f80Ce72D3A7668b1b4f3dF370D2C7" + } + }, + "aragonApp": { + "name": "simple-dvt", + "fullName": "simple-dvt.lidopm.eth", + "id": "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4" + }, + "proxy": { + "address": "0x11a93807078f8BB880c1BD0ee4C387537de4b4b6", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4", + "0x" + ] + } + }, + "aragon-acl": { + "implementation": { + "contract": "@aragon/os/contracts/acl/ACL.sol", + "address": "0xF1A087E055EA1C11ec3B540795Bd1A544e6dcbe9", + "constructorArgs": [] + }, + "proxy": { + "address": "0xfd1E42595CeC3E83239bf8dFc535250e7F48E0bC", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0xe3262375f45a6e2026b7e7b18c2b807434f2508fe1a2a3dfb493c7df8f4aad6a", + "0x00" + ], + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol" + }, + "aragonApp": { + "name": "aragon-acl", + "id": "0xe3262375f45a6e2026b7e7b18c2b807434f2508fe1a2a3dfb493c7df8f4aad6a" + } + }, + "aragon-apm-registry": { + "implementation": { + "contract": "@aragon/os/contracts/apm/APMRegistry.sol", + "address": "0x3EcF7190312F50043DB0494bA0389135Fc3833F3", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0x9089af016eb74d66811e1c39c1eef86fdcdb84b5665a4884ebf62339c2613991", + "0x00" + ] + }, + "proxy": { + "address": "0xB576A85c310CC7Af5C106ab26d2942fA3a5ea94A", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol" + }, + "factory": { + "address": "0x54eF0022cc769344D0cBCeF12e051281cCBb9fad", + "contract": "@aragon/os/contracts/factory/APMRegistryFactory.sol", + "constructorArgs": [ + "0xB33f9AE6C34D8cC59A48fd9973C64488f00fa64F", + "0x3EcF7190312F50043DB0494bA0389135Fc3833F3", + "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "0x7B133ACab5Cec7B90FB13CCf68d6568f8A051EcE", + "0x4327d1Fc6E5fa0326CCAE737F67C066c50BcC258", + "0x0000000000000000000000000000000000000000" + ] + } + }, + "aragon-app-repo-agent": { + "implementation": { + "contract": "@aragon/os/contracts/apm/Repo.sol", + "address": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "constructorArgs": [] + }, + "proxy": { + "address": "0xe7b4567913AaF2bD54A26E742cec22727D8109eA", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [] + } + }, + "aragon-app-repo-finance": { + "implementation": { + "contract": "@aragon/os/contracts/apm/Repo.sol", + "address": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "constructorArgs": [] + }, + "proxy": { + "address": "0x0df65b7c78Dc42a872010d031D3601C284D8fE71", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [] + } + }, + "aragon-app-repo-lido": { + "implementation": { + "contract": "@aragon/os/contracts/apm/Repo.sol", + "address": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "constructorArgs": [] + }, + "proxy": { + "address": "0xA37fb4C41e7D30af5172618a863BBB0f9042c604", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [] + } + }, + "aragon-app-repo-node-operators-registry": { + "implementation": { + "contract": "@aragon/os/contracts/apm/Repo.sol", + "address": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "constructorArgs": [] + }, + "proxy": { + "address": "0x4E8970d148CB38460bE9b6ddaab20aE2A74879AF", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [] + } + }, + "aragon-app-repo-oracle": { + "implementation": { + "contract": "@aragon/os/contracts/apm/Repo.sol", + "address": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "constructorArgs": [] + }, + "proxy": { + "address": "0xB3d74c319C0C792522705fFD3097f873eEc71764", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [] + } + }, + "aragon-app-repo-token-manager": { + "implementation": { + "contract": "@aragon/os/contracts/apm/Repo.sol", + "address": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "constructorArgs": [] + }, + "proxy": { + "address": "0xD327b4Fb87fa01599DaD491Aa63B333c44C74472", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [] + } + }, + "aragon-app-repo-voting": { + "implementation": { + "contract": "@aragon/os/contracts/apm/Repo.sol", + "address": "0x8959360c48D601a6817BAf2449E5D00cC543FA3A", + "constructorArgs": [] + }, + "proxy": { + "address": "0x2997EA0D07D79038D83Cb04b3BB9A2Bc512E3fDA", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [] + } + }, + "aragon-evm-script-registry": { + "proxy": { + "address": "0xE1200ae048163B67D69Bc0492bF5FddC3a2899C0", + "constructorArgs": [ + "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "0xddbcfd564f642ab5627cf68b9b7d374fb4f8a36e941a75d89c87998cef03bd61", + "0x8129fc1c" + ], + "contract": "@aragon/os/contracts/apps/AppProxyPinned.sol" + }, + "aragonApp": { + "name": "aragon-evm-script-registry", + "id": "0xddbcfd564f642ab5627cf68b9b7d374fb4f8a36e941a75d89c87998cef03bd61" + }, + "implementation": { + "address": "0x923B9Cab88E4a1d3de7EE921dEFBF9e2AC6e0791", + "contract": "@aragon/os/contracts/evmscript/EVMScriptRegistry.sol", + "constructorArgs": [] + } + }, + "aragon-kernel": { + "implementation": { + "contract": "@aragon/os/contracts/kernel/Kernel.sol", + "address": "0x34c0cbf9836FD945423bD3d2d72880da9d068E5F", + "constructorArgs": [ + true + ] + }, + "proxy": { + "address": "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", + "contract": "@aragon/os/contracts/kernel/KernelProxy.sol", + "constructorArgs": [ + "0x34c0cbf9836FD945423bD3d2d72880da9d068E5F" + ] + } + }, + "aragonEnsLabelName": "aragonpm", + "aragonEnsNode": "0x9065c3e7f7b7ef1ef4e53d2d0b8e0cef02874ab020c1ece79d5f0d3d0111c0ba", + "aragonEnsNodeName": "aragonpm.eth", + "aragonIDAddress": "0xCA01225e211AB0c6EFCD3aCc64D85465e4D8ab53", + "aragonIDConstructorArgs": [ + "0x4327d1Fc6E5fa0326CCAE737F67C066c50BcC258", + "0x2B725cBA5F75c3B61bb5E37454a7090fb11c757E", + "0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86" + ], + "aragonIDEnsNode": "0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86", + "aragonIDEnsNodeName": "aragonid.eth", + "burner": { + "deployParameters": { + "totalCoverSharesBurnt": "0", + "totalNonCoverSharesBurnt": "0" + }, + "contract": "contracts/0.8.9/Burner.sol", + "address": "0x4E46BD7147ccf666E1d73A3A456fC7a68de82eCA", + "constructorArgs": [ + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d", + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", + "0", + "0" + ] + }, + "callsScript": { + "address": "0xAa8B4F258a4817bfb0058b861447878168ddf7B0", + "contract": "@aragon/os/contracts/evmscript/executors/CallsScript.sol", + "constructorArgs": [] + }, + "chainId": 17000, + "chainSpec": { + "slotsPerEpoch": 32, + "secondsPerSlot": 12, + "genesisTime": 1695902400, + "depositContractAddress": "0x4242424242424242424242424242424242424242" + }, + "createAppReposTx": "0xd8a9b10e16b5e75b984c90154a9cb51fbb06bf560a3c424e2e7ad81951008502", + "daoAragonId": "lido-dao", + "daoFactoryAddress": "0xB33f9AE6C34D8cC59A48fd9973C64488f00fa64F", + "daoFactoryConstructorArgs": [ + "0x34c0cbf9836FD945423bD3d2d72880da9d068E5F", + "0xF1A087E055EA1C11ec3B540795Bd1A544e6dcbe9", + "0x11E7591F83360d0Bc238c8AB9e50B6D2B7566aDc" + ], + "daoInitialSettings": { + "voting": { + "minSupportRequired": "500000000000000000", + "minAcceptanceQuorum": "50000000000000000", + "voteDuration": 900, + "objectionPhaseDuration": 300 + }, + "fee": { + "totalPercent": 10, + "treasuryPercent": 50, + "nodeOperatorsPercent": 50 + }, + "token": { + "name": "TEST Lido DAO Token", + "symbol": "TLDO" + } + }, + "deployCommit": "eda16728a7c80f1bb55c3b91c668aae190a1efb0", + "deployer": "0x22896Bfc68814BFD855b1a167255eE497006e730", + "depositSecurityModule": { + "deployParameters": { + "maxDepositsPerBlock": 150, + "minDepositBlockDistance": 5, + "pauseIntentValidityPeriodBlocks": 6646 + }, + "contract": "contracts/0.8.9/DepositSecurityModule.sol", + "address": "0x045dd46212A178428c088573A7d102B9d89a022A", + "constructorArgs": [ + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", + "0x4242424242424242424242424242424242424242", + "0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229", + 150, + 5, + 6646 + ] + }, + "dummyEmptyContract": { + "contract": "contracts/0.8.9/test_helpers/DummyEmptyContract.sol", + "address": "0x5F4FEf09Cbd5ad743632Fb869E80294933473f0B", + "constructorArgs": [] + }, + "eip712StETH": { + "contract": "contracts/0.8.9/EIP712StETH.sol", + "address": "0xE154732c5Eab277fd88a9fF6Bdff7805eD97BCB1", + "constructorArgs": [ + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034" + ] + }, + "ensAddress": "0x4327d1Fc6E5fa0326CCAE737F67C066c50BcC258", + "ensFactoryAddress": "0xADba3e3122F2Da8F7B07723a3e1F1cEDe3fe8d7d", + "ensFactoryConstructorArgs": [], + "ensSubdomainRegistrarBaseAddress": "0x7B133ACab5Cec7B90FB13CCf68d6568f8A051EcE", + "evmScriptRegistryFactoryAddress": "0x11E7591F83360d0Bc238c8AB9e50B6D2B7566aDc", + "evmScriptRegistryFactoryConstructorArgs": [], + "executionLayerRewardsVault": { + "contract": "contracts/0.8.9/LidoExecutionLayerRewardsVault.sol", + "address": "0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8", + "constructorArgs": [ + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", + "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" + ] + }, + "gateSeal": { + "factoryAddress": "0x1134F7077055b0B3559BE52AfeF9aA22A0E1eEC2", + "sealDuration": 518400, + "expiryTimestamp": 1714521600, + "sealingCommittee": "0xCD1f9954330AF39a74Fd6e7B25781B4c24ee373f", + "address": "0x7f6FA688d4C12a2d51936680b241f3B0F0F9ca60" + }, + "hashConsensusForAccountingOracle": { + "deployParameters": { + "fastLaneLengthSlots": 10, + "epochsPerFrame": 12 + }, + "contract": "contracts/0.8.9/oracle/HashConsensus.sol", + "address": "0xa067FC95c22D51c3bC35fd4BE37414Ee8cc890d2", + "constructorArgs": [ + 32, + 12, + 1695902400, + 12, + 10, + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0x4E97A3972ce8511D87F334dA17a2C332542a5246" + ] + }, + "hashConsensusForValidatorsExitBusOracle": { + "deployParameters": { + "fastLaneLengthSlots": 10, + "epochsPerFrame": 4 + }, + "contract": "contracts/0.8.9/oracle/HashConsensus.sol", + "address": "0xe77Cf1A027d7C10Ee6bb7Ede5E922a181FF40E8f", + "constructorArgs": [ + 32, + 12, + 1695902400, + 4, + 10, + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0xffDDF7025410412deaa05E3E1cE68FE53208afcb" + ] + }, + "ldo": { + "address": "0x14ae7daeecdf57034f3E9db8564e46Dba8D97344", + "contract": "@aragon/minime/contracts/MiniMeToken.sol", + "constructorArgs": [ + "0x15ef666c9620C0f606Ba35De2aF668fe987E26ae", + "0x0000000000000000000000000000000000000000", + 0, + "TEST Lido DAO Token", + 18, + "TLDO", + true + ] + }, + "legacyOracle": { + "deployParameters": { + "lastCompletedEpochId": 0 + } + }, + "lidoApm": { + "deployArguments": [ + "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae", + "0x90a9580abeb24937fc658e497221c81ce8553b560304f9525821f32b17dbdaec" + ], + "deployTx": "0x2fac1c172a250736c34d16d3a721d2916abac0de0dea67d79955346a1f4345a2", + "address": "0x4605Dc9dC4BD0442F850eB8226B94Dd0e27C3Ce7" + }, + "lidoApmEnsName": "lidopm.eth", + "lidoApmEnsRegDurationSec": 94608000, + "lidoLocator": { + "proxy": { + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "address": "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8", + "constructorArgs": [ + "0x5F4FEf09Cbd5ad743632Fb869E80294933473f0B", + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0x" + ] + }, + "implementation": { + "contract": "contracts/0.8.9/LidoLocator.sol", + "address": "0xDba5Ad530425bb1b14EECD76F1b4a517780de537", + "constructorArgs": [ + [ + "0x4E97A3972ce8511D87F334dA17a2C332542a5246", + "0x045dd46212A178428c088573A7d102B9d89a022A", + "0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8", + "0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019", + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", + "0xF0d576c7d934bBeCc68FE15F1c5DAF98ea2B78bb", + "0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019", + "0x4E46BD7147ccf666E1d73A3A456fC7a68de82eCA", + "0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229", + "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d", + "0xffDDF7025410412deaa05E3E1cE68FE53208afcb", + "0xc7cc160b58F8Bb0baC94b80847E2CF2800565C50", + "0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9", + "0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7" + ] + ] + } + }, + "lidoTemplate": { + "contract": "contracts/0.4.24/template/LidoTemplate.sol", + "address": "0x0e065Dd0Bc85Ca53cfDAf8D9ed905e692260De2E", + "constructorArgs": [ + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0xB33f9AE6C34D8cC59A48fd9973C64488f00fa64F", + "0x4327d1Fc6E5fa0326CCAE737F67C066c50BcC258", + "0x15ef666c9620C0f606Ba35De2aF668fe987E26ae", + "0xCA01225e211AB0c6EFCD3aCc64D85465e4D8ab53", + "0x54eF0022cc769344D0cBCeF12e051281cCBb9fad" + ], + "deployBlock": 30581 + }, + "lidoTemplateCreateStdAppReposTx": "0x3f5b8918667bd3e971606a54a907798720158587df355a54ce07c0d0f9750d3c", + "lidoTemplateNewDaoTx": "0x3346246f09f91ffbc260b6c300b11ababce9f5ca54d7880a277860961f343112", + "miniMeTokenFactoryAddress": "0x15ef666c9620C0f606Ba35De2aF668fe987E26ae", + "miniMeTokenFactoryConstructorArgs": [], + "networkId": 17000, + "newDaoTx": "0x3346246f09f91ffbc260b6c300b11ababce9f5ca54d7880a277860961f343112", + "nodeOperatorsRegistry": { + "deployParameters": { + "stakingModuleTypeId": "curated-onchain-v1", + "stuckPenaltyDelay": 172800 + } + }, + "oracleDaemonConfig": { + "contract": "contracts/0.8.9/OracleDaemonConfig.sol", + "address": "0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7", + "constructorArgs": [ + "0x22896Bfc68814BFD855b1a167255eE497006e730", + [] + ], + "deployParameters": { + "NORMALIZED_CL_REWARD_PER_EPOCH": 64, + "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, + "REBASE_CHECK_NEAREST_EPOCH_DISTANCE": 1, + "REBASE_CHECK_DISTANT_EPOCH_DISTANCE": 23, + "VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS": 7200, + "VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS": 28800, + "NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP": 100, + "PREDICTION_DURATION_IN_SLOTS": 50400, + "FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT": 1350 + } + }, + "oracleReportSanityChecker": { + "deployParameters": { + "churnValidatorsPerDayLimit": 1500, + "oneOffCLBalanceDecreaseBPLimit": 500, + "annualBalanceIncreaseBPLimit": 1000, + "simulatedShareRateDeviationBPLimit": 250, + "maxValidatorExitRequestsPerReport": 2000, + "maxAccountingExtraDataListItemsCount": 100, + "maxNodeOperatorsPerExtraDataItemCount": 100, + "requestTimestampMargin": 128, + "maxPositiveTokenRebase": 5000000 + }, + "contract": "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol", + "address": "0xF0d576c7d934bBeCc68FE15F1c5DAF98ea2B78bb", + "constructorArgs": [ + "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8", + "0x22896Bfc68814BFD855b1a167255eE497006e730", + [ + 1500, + 500, + 1000, + 250, + 2000, + 100, + 100, + 128, + 5000000 + ], + [ + [], + [], + [], + [], + [], + [], + [], + [], + [], + [] + ] + ] + }, + "stakingRouter": { + "proxy": { + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "address": "0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229", + "constructorArgs": [ + "0x32f236423928c2c138F46351D9E5FD26331B1aa4", + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0x" + ] + }, + "implementation": { + "contract": "contracts/0.8.9/StakingRouter.sol", + "address": "0x32f236423928c2c138F46351D9E5FD26331B1aa4", + "constructorArgs": [ + "0x4242424242424242424242424242424242424242" + ] + } + }, + "validatorsExitBusOracle": { + "deployParameters": { + "consensusVersion": 1 + }, + "proxy": { + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "address": "0xffDDF7025410412deaa05E3E1cE68FE53208afcb", + "constructorArgs": [ + "0x210f60EC8A4D020b3e22f15fee2d2364e9b22357", + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0x" + ] + }, + "implementation": { + "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", + "address": "0x210f60EC8A4D020b3e22f15fee2d2364e9b22357", + "constructorArgs": [ + 12, + 1695902400, + "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8" + ] + } + }, + "vestingParams": { + "unvestedTokensAmount": "0", + "holders": { + "0xCD1f9954330AF39a74Fd6e7B25781B4c24ee373f": "880000000000000000000000", + "0xaa6bfBCD634EE744CB8FE522b29ADD23124593D3": "60000000000000000000000", + "0xBA59A84C6440E8cccfdb5448877E26F1A431Fc8B": "60000000000000000000000" + }, + "start": 0, + "cliff": 0, + "end": 0, + "revokable": false + }, + "withdrawalQueueERC721": { + "deployParameters": { + "name": "stETH Withdrawal NFT", + "symbol": "unstETH" + }, + "proxy": { + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "address": "0xc7cc160b58F8Bb0baC94b80847E2CF2800565C50", + "constructorArgs": [ + "0xFF72B5cdc701E9eE677966B2702c766c38F412a4", + "0x22896Bfc68814BFD855b1a167255eE497006e730", + "0x" + ] + }, + "implementation": { + "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", + "address": "0xFF72B5cdc701E9eE677966B2702c766c38F412a4", + "constructorArgs": [ + "0x8d09a4502Cc8Cf1547aD300E066060D043f6982D", + "stETH Withdrawal NFT", + "unstETH" + ] + } + }, + "withdrawalVault": { + "implementation": { + "contract": "contracts/0.8.9/WithdrawalVault.sol", + "address": "0xd517d9d04DA9B47dA23df91261bd3bF435BE964A", + "constructorArgs": [ + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", + "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" + ] + }, + "proxy": { + "contract": "contracts/0.8.4/WithdrawalsManagerProxy.sol", + "address": "0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9", + "constructorArgs": [ + "0xdA7d2573Df555002503F29aA4003e398d28cc00f", + "0xd517d9d04DA9B47dA23df91261bd3bF435BE964A" + ] + } + }, + "wstETH": { + "contract": "contracts/0.6.12/WstETH.sol", + "address": "0x8d09a4502Cc8Cf1547aD300E066060D043f6982D", + "constructorArgs": [ + "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034" + ] + } +} diff --git a/scripts/staking-router-v2/deployed-mainnet.json.template b/scripts/staking-router-v2/deployed-mainnet.json.template new file mode 100644 index 000000000..5087872ed --- /dev/null +++ b/scripts/staking-router-v2/deployed-mainnet.json.template @@ -0,0 +1,567 @@ +{ + "accountingOracle": { + "deployParameters": { + "consensusVersion": 1 + }, + "proxy": { + "address": "0x852deD011285fe67063a08005c71a85690503Cee", + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "deployTx": "0x3def88f27741216b131de2861cf89af2ca2ac4242b384ee33dca8cc70c51c8dd", + "constructorArgs": [ + "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x" + ] + }, + "implementation": { + "address": "0xF3c5E0A67f32CF1dc07a8817590efa102079a1aF", + "contract": "contracts/0.8.9/oracle/AccountingOracle.sol", + "deployTx": "0x3e27627d3ed236aff8901df187196e9682187dfd0d259c5d5811a6e923436083", + "constructorArgs": [ + "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", + 12, + 1606824023 + ] + } + }, + "apmRegistryFactoryAddress": "0xa0BC4B67F5FacDE4E50EAFF48691Cfc43F4E280A", + "app:aragon-agent": { + "implementation": { + "contract": "@aragon/apps-agent/contracts/Agent.sol", + "address": "0x3A93C17FC82CC33420d1809dDA9Fb715cc89dd37", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-agent", + "fullName": "aragon-agent.lidopm.eth", + "id": "0x701a4fd1f5174d12a0f1d9ad2c88d0ad11ab6aad0ac72b7d9ce621815f8016a9" + }, + "proxy": { + "address": "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol" + } + }, + "app:aragon-finance": { + "implementation": { + "contract": "@aragon/apps-finance/contracts/Finance.sol", + "address": "0x836835289A2E81B66AE5d95b7c8dBC0480dCf9da", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-finance", + "fullName": "aragon-finance.lidopm.eth", + "id": "0x5c9918c99c4081ca9459c178381be71d9da40e49e151687da55099c49a4237f1" + }, + "proxy": { + "address": "0xB9E5CBB9CA5b0d659238807E84D0176930753d86", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol" + } + }, + "app:aragon-token-manager": { + "implementation": { + "contract": "@aragon/apps-lido/apps/token-manager/contracts/TokenManager.sol", + "address": "0xde3A93028F2283cc28756B3674BD657eaFB992f4", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-token-manager", + "fullName": "aragon-token-manager.lidopm.eth", + "id": "0xcd567bdf93dd0f6acc3bc7f2155f83244d56a65abbfbefb763e015420102c67b" + }, + "proxy": { + "address": "0xf73a1260d222f447210581DDf212D915c09a3249", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol" + } + }, + "app:aragon-voting": { + "implementation": { + "contract": "@aragon/apps-lido/apps/voting/contracts/Voting.sol", + "address": "0x72fb5253AD16307B9E773d2A78CaC58E309d5Ba4", + "constructorArgs": [] + }, + "aragonApp": { + "name": "aragon-voting", + "fullName": "aragon-voting.lidopm.eth", + "id": "0x0abcd104777321a82b010357f20887d61247493d89d2e987ff57bcecbde00e1e" + }, + "proxy": { + "address": "0x2e59A20f205bB85a89C53f1936454680651E618e", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol" + } + }, + "app:lido": { + "proxy": { + "address": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + }, + "implementation": { + "address": "0x17144556fd3424EDC8Fc8A4C940B2D04936d17eb", + "contract": "contracts/0.4.24/Lido.sol", + "deployTx": "0xb4b5e02643c9802fd0f7c73c4854c4f1b83497aca13f8297ba67207b71c4dcd9", + "constructorArgs": [] + }, + "aragonApp": { + "fullName": "lido.lidopm.eth", + "name": "lido", + "id": "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320", + "ipfsCid": "QmQkJMtvu4tyJvWrPXJfjLfyTWn959iayyNjp7YqNzX7pS", + "contentURI": "0x697066733a516d516b4a4d7476753474794a76577250584a666a4c667954576e393539696179794e6a703759714e7a58377053" + } + }, + "app:node-operators-registry": { + "proxy": { + "address": "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" + }, + "implementation": { + "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", + "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", + "deployTx": "0xbec5b6ffb8fbc775a183e25cd285367993d7201752e1638a07abec8962bab750", + "constructorArgs": [], + "deployParameters": { + "stakingModuleTypeId": "curated-onchain-v1", + "stuckPenaltyDelay": "432000" + } + }, + "aragonApp": { + "fullName": "node-operators-registry.lidopm.eth", + "name": "node-operators-registry", + "id": "0x7071f283424072341f856ac9e947e7ec0eb68719f757a7e785979b6b8717579d", + "ipfsCid": "Qma7PXHmEj4js2gjM9vtHPtqvuK82iS5EYPiJmzKLzU58G", + "contentURI": "0x697066733a516d61375058486d456a346a7332676a4d3976744850747176754b3832695335455950694a6d7a4b4c7a55353847" + } + }, + "app:oracle": { + "proxy": { + "address": "0x442af784A788A5bd6F42A01Ebe9F287a871243fb" + }, + "implementation": { + "address": "0xa29b819654cE6224A222bb5f586920105E2D7E0E", + "contract": "contracts/0.4.24/oracle/LegacyOracle.sol", + "deployTx": "0xe666e3ce409bb4c18e1016af0b9ed3495b20361a69f2856bccb9e67599795b6f", + "constructorArgs": [] + }, + "aragonApp": { + "fullName": "oracle.lidopm.eth", + "name": "oracle", + "id": "0x8b47ba2a8454ec799cd91646e7ec47168e91fd139b23f017455f3e5898aaba93", + "ipfsCid": "QmUMPfiEKq5Mxm8y2GYQPLujGaJiWz1tvep5W7EdAGgCR8", + "contentURI": "0x697066733a516d656138394d5533504852503763513157616b3672327355654d554146324c39727132624c6d5963644b764c57" + } + }, + "app:simple-dvt": { + "stakingRouterModuleParams": { + "moduleName": "SimpleDVT", + "moduleType": "curated-onchain-v1", + "targetShare": 50, + "moduleFee": 800, + "treasuryFee": 200, + "penaltyDelay": 432000, + "easyTrackTrustedCaller": "0x08637515E85A4633E23dfc7861e2A9f53af640f7", + "easyTrackAddress": "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea", + "easyTrackFactories": { + "AddNodeOperators": "0xcAa3AF7460E83E665EEFeC73a7a542E5005C9639", + "ActivateNodeOperators": "0xCBb418F6f9BFd3525CE6aADe8F74ECFEfe2DB5C8", + "DeactivateNodeOperators": "0x8B82C1546D47330335a48406cc3a50Da732672E7", + "SetVettedValidatorsLimits": "0xD75778b855886Fc5e1eA7D6bFADA9EB68b35C19D", + "SetNodeOperatorNames": "0x7d509BFF310d9460b1F613e4e40d342201a83Ae4", + "SetNodeOperatorRewardAddresses": "0x589e298964b9181D9938B84bB034C3BB9024E2C0", + "UpdateTargetValidatorLimits": "0x41CF3DbDc939c5115823Fba1432c4EC5E7bD226C", + "ChangeNodeOperatorManagers": "0xE31A0599A6772BCf9b2bFc9e25cf941e793c9a7D" + } + }, + "aragonApp": { + "name": "simple-dvt", + "fullName": "simple-dvt.lidopm.eth", + "id": "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4", + "ipfsCid": "QmaSSujHCGcnFuetAPGwVW5BegaMBvn5SCsgi3LSfvraSo", + "contentURI": "0x697066733a516d615353756a484347636e4675657441504777565735426567614d42766e355343736769334c5366767261536f" + }, + "proxy": { + "address": "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433", + "contract": "@aragon/os/contracts/apps/AppProxyUpgradeable.sol", + "constructorArgs": [ + "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", + "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4", + "0x" + ] + }, + "implementation": { + "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", + "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol" + } + }, + "aragon-kernel": { + "implementation": { + "contract": "@aragon/os/contracts/kernel/Kernel.sol", + "address": "0x2b33CF282f867A7FF693A66e11B0FcC5552e4425", + "constructorArgs": [true] + }, + "proxy": { + "address": "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", + "contract": "@aragon/os/contracts/kernel/KernelProxy.sol" + } + }, + "aragonIDAddress": "0x546aa2eae2514494eeadb7bbb35243348983c59d", + "burner": { + "address": "0xD15a672319Cf0352560eE76d9e89eAB0889046D3", + "contract": "contracts/0.8.9/Burner.sol", + "deployTx": "0xbebf5c85404a0d8e36b859046c984fdf6dd764b5d317feb7eb3525016005b1d9", + "constructorArgs": [ + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0", + "32145684728326685744" + ], + "deployParameters": { + "totalCoverSharesBurnt": "0", + "totalNonCoverSharesBurnt": "32145684728326685744" + } + }, + "chainSpec": { + "depositContractAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa", + "slotsPerEpoch": 32, + "secondsPerSlot": 12, + "genesisTime": 1606824023 + }, + "createAppReposTx": "0xf48cb21c6be021dd18bd8e02ce89ac7b924245b859f0a8b7c47e88a39016ed41", + "daoAragonId": "lido-dao", + "daoFactoryAddress": "0x7378ad1ba8f3c8e64bbb2a04473edd35846360f1", + "daoInitialSettings": { + "token": { + "name": "Lido DAO Token", + "symbol": "LDO" + }, + "voting": { + "minSupportRequired": "500000000000000000", + "minAcceptanceQuorum": "50000000000000000", + "voteDuration": 86400 + }, + "fee": { + "totalPercent": 10, + "treasuryPercent": 0, + "insurancePercent": 50, + "nodeOperatorsPercent": 50 + } + }, + "daoTokenAddress": "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32", + "deployCommit": "e45c4d6fb8120fd29426b8d969c19d8a798ca974", + "deployer": "0x55Bc991b2edF3DDb4c520B222bE4F378418ff0fA", + "depositSecurityModule": { + "address": "0xC77F8768774E1c9244BEed705C4354f2113CFc09", + "contract": "contracts/0.8.9/DepositSecurityModule.sol", + "deployTx": "0x21307a2321f167f99de11ccec86d7bdd8233481bbffa493e15c519ca8d662c4f", + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0x00000000219ab540356cBB839Cbe05303d7705Fa", + "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", + 150, + 25, + 6646 + ], + "deployParameters": { + "maxDepositsPerBlock": 150, + "minDepositBlockDistance": 25, + "pauseIntentValidityPeriodBlocks": 6646 + } + }, + "dummyEmptyContract": { + "address": "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", + "contract": "contracts/0.8.9/test_helpers/DummyEmptyContract.sol", + "deployTx": "0x9d76786f639bd18365f10c087444761db5dafd0edc85c5c1a3e90219f2d1331d", + "constructorArgs": [] + }, + "eip712StETH": { + "address": "0x8F73e4C2A6D852bb4ab2A45E6a9CF5715b3228B7", + "contract": "contracts/0.8.9/EIP712StETH.sol", + "deployTx": "0xecb5010620fb13b0e2bbc98b8a0c82de0d7385491452cd36cf303cd74216ed91", + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + }, + "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "executionLayerRewardsVault": { + "address": "0x388C818CA8B9251b393131C08a736A67ccB19297", + "contract": "contracts/0.8.9/LidoExecutionLayerRewardsVault.sol", + "deployTx": "0xd72cf25e4a5fe3677b6f9b2ae13771e02ad66f8d2419f333bb8bde3147bd4294" + }, + "hashConsensusForAccountingOracle": { + "address": "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288", + "contract": "contracts/0.8.9/oracle/HashConsensus.sol", + "deployTx": "0xd74dcca9bacede9f332d70562f49808254061853937ffbbfc7397ab5d017041a", + "constructorArgs": [ + 32, + 12, + 1606824023, + 225, + 100, + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x852deD011285fe67063a08005c71a85690503Cee" + ], + "deployParameters": { + "fastLaneLengthSlots": 100, + "epochsPerFrame": 225 + } + }, + "hashConsensusForValidatorsExitBusOracle": { + "address": "0x7FaDB6358950c5fAA66Cb5EB8eE5147De3df355a", + "contract": "contracts/0.8.9/oracle/HashConsensus.sol", + "deployTx": "0xed1ab73dd5458b5ec0b174508318d2f39a31029112af21f87d09106933bd3a9e", + "constructorArgs": [ + 32, + 12, + 1606824023, + 75, + 100, + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e" + ], + "deployParameters": { + "fastLaneLengthSlots": 100, + "epochsPerFrame": 75 + } + }, + "ipfsAPI": "https://ipfs.infura.io:5001/api/v0", + "lidoApm": { + "deployTx": "0xfa66476569ecef5790f2d0634997b952862bbca56aa088f151b8049421eeb87b", + "address": "0x0cb113890b04B49455DfE06554e2D784598A29C9" + }, + "lidoApmEnsName": "lidopm.eth", + "lidoApmEnsRegDurationSec": 94608000, + "lidoLocator": { + "proxy": { + "address": "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "deployTx": "0x3a2910624533935cc8c21837b1705bcb159a760796930097016186be705cc455", + "constructorArgs": [ + "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x" + ] + }, + "implementation": { + "address": "0x1D920cc5bACf7eE506a271a5259f2417CaDeCE1d", + "contract": "contracts/0.8.9/LidoLocator.sol", + "deployTx": "0xf90012ef0a40e47c909ab3a5b3503ecee78f6a9be134d1349a742e500d37ae33", + "constructorArgs": [ + [ + "0x852deD011285fe67063a08005c71a85690503Cee", + "0xC77F8768774E1c9244BEed705C4354f2113CFc09", + "0x388C818CA8B9251b393131C08a736A67ccB19297", + "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", + "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", + "0xD15a672319Cf0352560eE76d9e89eAB0889046D3", + "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", + "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + "0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e", + "0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1", + "0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f", + "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" + ] + ] + } + }, + "lidoTemplate": { + "contract": "contracts/0.4.24/template/LidoTemplate.sol", + "address": "0x752350797CB92Ad3BF1295Faf904B27585e66BF5", + "deployTx": "0xdcd4ebe028aa3663a1fe8bbc92ae8489045e29d2a6ef5284083d9be5c3fa5f19", + "constructorArgs": [ + "0x55Bc991b2edF3DDb4c520B222bE4F378418ff0fA", + "0x7378ad1ba8f3c8e64bbb2a04473edd35846360f1", + "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", + "0x909d05f384d0663ed4be59863815ab43b4f347ec", + "0x546aa2eae2514494eeadb7bbb35243348983c59d", + "0xa0BC4B67F5FacDE4E50EAFF48691Cfc43F4E280A" + ] + }, + "miniMeTokenFactoryAddress": "0x909d05f384d0663ed4be59863815ab43b4f347ec", + "networkId": 1, + "newDaoTx": "0x3feabd79e8549ad68d1827c074fa7123815c80206498946293d5373a160fd866", + "oracleDaemonConfig": { + "address": "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09", + "contract": "contracts/0.8.9/OracleDaemonConfig.sol", + "deployTx": "0xa4f380b8806f5a504ef67fce62989e09be5a48bf114af63483c01c22f0c9a36f", + "constructorArgs": ["0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", []], + "deployParameters": { + "NORMALIZED_CL_REWARD_PER_EPOCH": 64, + "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, + "REBASE_CHECK_NEAREST_EPOCH_DISTANCE": 1, + "REBASE_CHECK_DISTANT_EPOCH_DISTANCE": 23, + "VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS": 7200, + "VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS": 28800, + "PREDICTION_DURATION_IN_SLOTS": 50400, + "FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT": 1350, + "NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP": 100 + } + }, + "oracleReportSanityChecker": { + "address": "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", + "contract": "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol", + "deployTx": "0x700c83996ad7deefda286044280ad86108dfef9c880909bd8e75a3746f7d631c", + "constructorArgs": [ + "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", + "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", + [20000, 500, 1000, 50, 600, 2, 100, 7680, 750000], + [[], [], [], [], [], [], [], [], [], []] + ], + "deployParameters": { + "churnValidatorsPerDayLimit": 20000, + "oneOffCLBalanceDecreaseBPLimit": 500, + "annualBalanceIncreaseBPLimit": 1000, + "simulatedShareRateDeviationBPLimit": 50, + "maxValidatorExitRequestsPerReport": 600, + "maxAccountingExtraDataListItemsCount": 2, + "maxNodeOperatorsPerExtraDataItemCount": 100, + "requestTimestampMargin": 7680, + "maxPositiveTokenRebase": 750000 + } + }, + "stakingRouter": { + "proxy": { + "address": "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "deployTx": "0xb8620f04a8db6bb52cfd0978c6677a5f16011e03d4622e5d660ea6ba34c2b122", + "constructorArgs": [ + "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x" + ] + }, + "implementation": { + "address": "0xD8784e748f59Ba711fB5643191Ec3fAdD50Fb6df", + "contract": "contracts/0.8.9/StakingRouter.sol", + "deployTx": "0xd6d489f22203c835da6027ff0e532a01a08f36f0fda6c7c0a42e471ae3b3c461", + "constructorArgs": ["0x00000000219ab540356cBB839Cbe05303d7705Fa"] + } + }, + "validatorsExitBusOracle": { + "proxy": { + "address": "0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e", + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "deployTx": "0xef3eea1523d2161c2f36ba61e327e3520231614c055b8a88c7f5928d18e423ea", + "constructorArgs": [ + "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x" + ] + }, + "implementation": { + "address": "0xA89Ea51FddE660f67d1850e03C9c9862d33Bc42c", + "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", + "deployTx": "0x5ab545276f78a72a432c3e971c96384973abfab6394e08cb077a006c25aef7a7", + "constructorArgs": [12, 1606824023, "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb"], + "deployParameters": { + "consensusVersion": 1 + } + } + }, + "vestingParams": { + "unvestedTokensAmount": "363197500000000000000000000", + "holders": { + "0x9Bb75183646e2A0DC855498bacD72b769AE6ceD3": "20000000000000000000000000", + "0x0f89D54B02ca570dE82F770D33c7B7Cf7b3C3394": "25000000000000000000000000", + "0xe49f68B9A01d437B0b7ea416376a7AB21532624e": "2282000000000000000000000", + "0xb842aFD82d940fF5D8F6EF3399572592EBF182B0": "17718000000000000000000000", + "0x9849c2C1B73B41AEE843A002C332a2d16aaaB611": "10000000000000000000000000", + "0x96481cb0fcd7673254ebccc42dce9b92da10ea04": "5000000000000000000000000", + "0xB3DFe140A77eC43006499CB8c2E5e31975caD909": "7500000000000000000000000", + "0x61C808D82A3Ac53231750daDc13c777b59310bD9": "20000000000000000000000000", + "0x447f95026107aaed7472A0470931e689f51e0e42": "20000000000000000000000000", + "0x6ae83EAB68b7112BaD5AfD72d6B24546AbFF137D": "2222222220000000000000000", + "0xC24da173A250e9Ca5c54870639EbE5f88be5102d": "17777777780000000000000000", + "0x1f3813fE7ace2a33585F1438215C7F42832FB7B3": "20000000000000000000000000", + "0x82a8439BA037f88bC73c4CCF55292e158A67f125": "7000000000000000000000000", + "0x91715128a71c9C734CDC20E5EdEEeA02E72e428E": "15000000000000000000000000", + "0xB5587A54fF7022AC218438720BDCD840a32f0481": "5000000000000000000000000", + "0xf5fb27b912d987b5b6e02a1b1be0c1f0740e2c6f": "2000000000000000000000000", + "0x8b1674a617F103897Fb82eC6b8EB749BA0b9765B": "15000000000000000000000000", + "0x48Acf41D10a063f9A6B718B9AAd2e2fF5B319Ca2": "5000000000000000000000000", + "0x7eE09c11D6Dc9684D6D5a4C6d333e5b9e336bb6C": "10000000000000000000000000", + "0x11099aC9Cc097d0C9759635b8e16c6a91ECC43dA": "2000000000000000000000000", + "0x3d4AD2333629eE478E4f522d60A56Ae1Db5D3Cdb": "5000000000000000000000000", + "0xd5eCB56c6ca8f8f52D2DB4dC1257d6161cf3Da29": "100000000000000000000000", + "0x7F5e13a815EC9b4466d283CD521eE9829e7F6f0e": "200000000000000000000000", + "0x2057cbf2332ab2697a52B8DbC85756535d577e32": "500000000000000000000000", + "0x537dfB5f599A3d15C50E2d9270e46b808A52559D": "1000000000000000000000000", + "0x33c4c38e96337172d3de39df82060de26b638c4b": "550000000000000000000000", + "0x6094E1Dd925caCe56Fa501dAEc02b01a49E55770": "300000000000000000000000", + "0x977911f476B28f9F5332fA500387deE81e480a44": "40000000000000000000000", + "0x66d3FdA643320c6DddFBba39e635288A5dF75FB9": "200000000000000000000000", + "0xDFC0ae54af992217100845597982274A26d8CB28": "12500000000000000000000", + "0x32254b28F793CC18B3575C86c61fE3D7421cbbef": "500000000000000000000000", + "0x0Bf5566fB5F1f9934a3944AEF128a1b1a8cF3f17": "50000000000000000000000", + "0x1d3Fa8bf35870271115B997b8eCFe18529422a16": "50000000000000000000000", + "0x366B9729C5A89EC4618A0AB95F832E411eaE8237": "200000000000000000000000", + "0x20921142A35c89bE5D002973d2D6B72d9a625FB0": "200000000000000000000000", + "0x663b91628674846e8D1CBB779EFc8202d86284E2": "7500000000000000000000000", + "0xa6829908f728C6bC5627E2aFe93a0B71E978892D": "300000000000000000000000", + "0x9575B7859DF77F2A0EF034339b80e24dE44AB3F6": "200000000000000000000000", + "0xEe217c23131C6F055F7943Ef1f80Bec99dF35244": "400000000000000000000000", + "0xadde043f556d1083f060A7298E79eaBa08A3a077": "400000000000000000000000", + "0xaFBEfC8401c885A0bb6Ea6Af43f592A015433C65": "200000000000000000000000", + "0x8a62A63b877877bd5B1209B9b67F3d2685284268": "200000000000000000000000", + "0x62Ac238Ac055017DEcAb645E7E56176749f316d0": "200000000000000000000000", + "0x55Bc991b2edF3DDb4c520B222bE4F378418ff0fA": "5000000000000000000000000", + "0x8D689476EB446a1FB0065bFFAc32398Ed7F89165": "10000000000000000000000000", + "0x083fc10cE7e97CaFBaE0fE332a9c4384c5f54E45": "5000000000000000000000000", + "0x0028E24e4Fe5184792Bd0Cf498C11AE5b76185f5": "5000000000000000000000000", + "0xFe45baf0F18c207152A807c1b05926583CFE2e4b": "5000000000000000000000000", + "0x4a7C6899cdcB379e284fBFD045462e751DA4C7cE": "5000000000000000000000000", + "0xD7f0dDcBb0117A53e9ff2cad814B8B810a314f59": "5000000000000000000000000", + "0xb8d83908AAB38a159F3dA47a59d84dB8e1838712": "50000000000000000000000000", + "0xA2dfC431297aee387C05bEEf507E5335E684FbCD": "50000000000000000000000000", + "0x1597D19659F3DE52ABd475F7D2314DCca29359BD": "50000000000000000000000000", + "0x695C388153bEa0fbE3e1C049c149bAD3bc917740": "50000000000000000000000000", + "0x945755dE7EAc99008c8C57bdA96772d50872168b": "50000000000000000000000000", + "0xFea88380bafF95e85305419eB97247981b1a8eEE": "30000000000000000000000000", + "0xAD4f7415407B83a081A0Bee22D05A8FDC18B42da": "50000000000000000000000000", + "0x68335B3ac272C8238b722963368F87dE736b64D6": "5000000000000000000000000", + "0xfA2Ab7C161Ef7F83194498f36ca7aFba90FD08d4": "5000000000000000000000000", + "0x58A764028350aB15899fDCcAFFfd3940e602CEEA": "10000000000000000000000000" + }, + "start": 1639785600, + "cliff": 1639785600, + "end": 1671321600, + "revokable": false + }, + "withdrawalQueueERC721": { + "proxy": { + "address": "0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1", + "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", + "deployTx": "0x98c2170be034f750f5006cb69ea0aeeaf0858b11f6324ee53d582fa4dd49bc1a", + "constructorArgs": [ + "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + "0x" + ] + }, + "implementation": { + "address": "0xE42C659Dc09109566720EA8b2De186c2Be7D94D9", + "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", + "deployTx": "0x6ab0151735c01acdef518421358d41a08752169bc383c57d57f5bfa135ac6eb1", + "constructorArgs": ["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "Lido: stETH Withdrawal NFT", "unstETH"], + "deployParameters": { + "name": "Lido: stETH Withdrawal NFT", + "symbol": "unstETH" + } + } + }, + "withdrawalVault": { + "proxy": { + "address": "0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f" + }, + "implementation": { + "address": "0xCC52f17756C04bBa7E377716d7062fC36D7f69Fd", + "contract": "contracts/0.8.9/WithdrawalVault.sol", + "deployTx": "0xd9eb2eca684770e4d2b192709b6071875f75072a0ce794a582824ee907a704f3", + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] + } + }, + "wstETH": { + "address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "contract": "contracts/0.6.12/WstETH.sol", + "deployTx": "0xaf2c1a501d2b290ef1e84ddcfc7beb3406f8ece2c46dee14e212e8233654ff05", + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + } +} diff --git a/scripts/staking-router-v2/deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts similarity index 78% rename from scripts/staking-router-v2/deploy.ts rename to scripts/staking-router-v2/sr-v2-deploy.ts index 7a4156351..f767b55b7 100644 --- a/scripts/staking-router-v2/deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -15,42 +15,43 @@ function getEnvVariable(name: string, defaultValue?: string) { } } +// TODO: add guardians async function main() { const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); const chainId = (await ethers.provider.getNetwork()).chainId; const balance = await ethers.provider.getBalance(deployer); log(`Deployer ${deployer} on network ${chainId} has balance: ${ethers.formatEther(balance)} ETH`); - const state = readNetworkState(); - state[Sk.scratchDeployGasUsed] = 0n.toString(); - persistNetworkState(state); + // parameters from env variables - const SC_ADMIN = getEnvVariable("ARAGON_AGENT"); - const LIMITS_LIST = [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000, 1500]; - const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; - - // Read all the constants from environment variables - const LIDO = getEnvVariable("LIDO"); - const DEPOSIT_CONTRACT = getEnvVariable("DEPOSIT_CONTRACT"); - const STAKING_ROUTER = getEnvVariable("STAKING_ROUTER"); const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = parseInt(getEnvVariable("PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS")); const MAX_OPERATORS_PER_UNVETTING = parseInt(getEnvVariable("MAX_OPERATORS_PER_UNVETTING")); - - const LOCATOR = getEnvVariable("LOCATOR"); - const LEGACY_ORACLE = getEnvVariable("LEGACY_ORACLE"); const SECONDS_PER_SLOT = parseInt(getEnvVariable("SECONDS_PER_SLOT")); const GENESIS_TIME = parseInt(getEnvVariable("GENESIS_TIME")); - const ACCOUNTING_ORACLE_PROXY = getEnvVariable("ACCOUNTING_ORACLE_PROXY"); - const EL_REWARDS_VAULT = getEnvVariable("EL_REWARDS_VAULT"); - const BURNER = getEnvVariable("BURNER"); - const TREASURY_ADDRESS = getEnvVariable("TREASURY_ADDRESS"); - const VEBO = getEnvVariable("VEBO"); - const WQ = getEnvVariable("WITHDRAWAL_QUEUE_ERC721"); - const WITHDRAWAL_VAULT = getEnvVariable("WITHDRAWAL_VAULT_ADDRESS"); - const ORACLE_DAEMON_CONFIG = getEnvVariable("ORACLE_DAEMON_CONFIG"); + const LIMITS_LIST = [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000, 1500]; + const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; + + const state = readNetworkState(); + state[Sk.scratchDeployGasUsed] = 0n.toString(); + persistNetworkState(state); - // StakingRouter deploy + // Read contracts addresses from config + const DEPOSIT_CONTRACT_ADDRESS = state[Sk.chainSpec].depositContractAddress; + const APP_AGENT_ADDRESS = state[Sk.appAgent].proxy.address; + const SC_ADMIN = APP_AGENT_ADDRESS; + const LIDO = state[Sk.appLido].proxy.address; + const STAKING_ROUTER = state[Sk.stakingRouter].proxy.address; + const LOCATOR = state[Sk.lidoLocator].proxy.address; + const LEGACY_ORACLE = state[Sk.appOracle].proxy.address; + const ACCOUNTING_ORACLE_PROXY = state[Sk.accountingOracle].proxy.address; + const EL_REWARDS_VAULT = state[Sk.executionLayerRewardsVault].address; + const BURNER = state[Sk.burner].address; + const TREASURY_ADDRESS = APP_AGENT_ADDRESS; + const VEBO = state[Sk.validatorsExitBusOracle].proxy.address; + const WQ = state[Sk.withdrawalQueueERC721].proxy.address; + const WITHDRAWAL_VAULT = state[Sk.withdrawalVault].proxy.address; + const ORACLE_DAEMON_CONFIG = state[Sk.oracleDaemonConfig].address; // Deploy MinFirstAllocationStrategy const minFirstAllocationStrategyAddress = ( @@ -64,7 +65,7 @@ async function main() { }; const stakingRouterAddress = ( - await deployImplementation(Sk.stakingRouter, "StakingRouter", deployer, [DEPOSIT_CONTRACT], { libraries }) + await deployImplementation(Sk.stakingRouter, "StakingRouter", deployer, [DEPOSIT_CONTRACT_ADDRESS], { libraries }) ).address; log(`StakingRouter implementation address: ${stakingRouterAddress}`); @@ -77,7 +78,7 @@ async function main() { const depositSecurityModuleParams = [ LIDO, - DEPOSIT_CONTRACT, + DEPOSIT_CONTRACT_ADDRESS, STAKING_ROUTER, PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS, MAX_OPERATORS_PER_UNVETTING, diff --git a/scripts/staking-router-v2/sr-v2-holesky-deploy.sh b/scripts/staking-router-v2/sr-v2-holesky-deploy.sh deleted file mode 100755 index 602019a89..000000000 --- a/scripts/staking-router-v2/sr-v2-holesky-deploy.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -set -e +u -set -o pipefail - -# export NETWORK=testnet -export DEPLOYER=${DEPLOYER} -export NETWORK=local -export RPC_URL=${RPC_URL:="http://127.0.0.1:8555"} # if defined use the value set to default otherwise - -export PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS=7200 -export MAX_OPERATORS_PER_UNVETTING=20 - -export SECONDS_PER_SLOT=12 -export GENESIS_TIME=1695902400 - -export GAS_PRIORITY_FEE=${GAS_PRIORITY_FEE:=2} -export GAS_MAX_FEE=${GAS_MAX_FEE:=100} - - -# contracts addresses on mainnet -export LIDO="0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034" -export DEPOSIT_CONTRACT="0x4242424242424242424242424242424242424242" -export STAKING_ROUTER="0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229" -export ACCOUNTING_ORACLE_PROXY="0x4E97A3972ce8511D87F334dA17a2C332542a5246" -export EL_REWARDS_VAULT="0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8" -export POST_TOKEN_REBASE_RECEIVER="0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019" -export BURNER="0x4E46BD7147ccf666E1d73A3A456fC7a68de82eCA" -export TREASURY_ADDRESS="0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" -export VEBO="0xffDDF7025410412deaa05E3E1cE68FE53208afcb" -export WITHDRAWAL_QUEUE_ERC721="0xc7cc160b58F8Bb0baC94b80847E2CF2800565C50" -export WITHDRAWAL_VAULT_ADDRESS="0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9" -export ORACLE_DAEMON_CONFIG="0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7" -export LOCATOR="0x28fab2059c713a7f9d8c86db49f9bb0e96af1ef8" -export LEGACY_ORACLE="0x072f72be3acfe2c52715829f2cd9061a6c8ff019" -export ARAGON_AGENT="0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" - -# Run the deployment script with the environment variables -yarn hardhat --network $NETWORK run scripts/staking-router-v2/deploy.ts diff --git a/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh b/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh deleted file mode 100755 index f3dd44858..000000000 --- a/scripts/staking-router-v2/sr-v2-mainnet-deploy.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -set -e +u -set -o pipefail - -# export NETWORK=mainnet -export DEPLOYER=${DEPLOYER} -export NETWORK=local -export RPC_URL=${RPC_URL:="http://127.0.0.1:8555"} # if defined use the value set to default otherwise - -export PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS=7200 -export MAX_OPERATORS_PER_UNVETTING=20 - -export SECONDS_PER_SLOT=12 -export GENESIS_TIME=1606824023 - -export GAS_PRIORITY_FEE=${GAS_PRIORITY_FEE:=2} -export GAS_MAX_FEE=${GAS_MAX_FEE:=100} - - -# contracts addresses on mainnet -export LIDO="0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" -export DEPOSIT_CONTRACT="0x00000000219ab540356cBB839Cbe05303d7705Fa" -export STAKING_ROUTER="0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -export ACCOUNTING_ORACLE_PROXY="0x852deD011285fe67063a08005c71a85690503Cee" -export EL_REWARDS_VAULT="0x388C818CA8B9251b393131C08a736A67ccB19297" -export POST_TOKEN_REBASE_RECEIVER="0x442af784A788A5bd6F42A01Ebe9F287a871243fb" -export BURNER="0xD15a672319Cf0352560eE76d9e89eAB0889046D3" -export TREASURY_ADDRESS="0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" -export WITHDRAWAL_QUEUE_ERC721="0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1" -export WITHDRAWAL_VAULT_ADDRESS="0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f" -export ORACLE_DAEMON_CONFIG="0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" -export LOCATOR="0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" -export LEGACY_ORACLE="0x442af784A788A5bd6F42A01Ebe9F287a871243fb" -export VEBO="0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e" -export ARAGON_AGENT="0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" - -# Run the deployment script with the environment variables -yarn hardhat --network $NETWORK run scripts/staking-router-v2/deploy.ts From 09fe5aec94c0fb624d865e7e78781c6ac7bf8a4c Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Fri, 21 Jun 2024 00:58:54 +0400 Subject: [PATCH 080/177] fix: add guardians --- .../deployed-holesky.json.template | 14 +++- .../deployed-mainnet.json.template | 74 ++++++++++++++++--- scripts/staking-router-v2/sr-v2-deploy.ts | 43 ++++++++--- 3 files changed, 110 insertions(+), 21 deletions(-) diff --git a/scripts/staking-router-v2/deployed-holesky.json.template b/scripts/staking-router-v2/deployed-holesky.json.template index b0a79583a..2974128af 100644 --- a/scripts/staking-router-v2/deployed-holesky.json.template +++ b/scripts/staking-router-v2/deployed-holesky.json.template @@ -455,7 +455,19 @@ 150, 5, 6646 - ] + ], + "guardians": { + "addresses": [ + "0x711B5fCfeD5A30CA78e0CAC321B060dE9D6f8979", + "0xDAaE8C017f1E2a9bEC6111d288f9ebB165e0E163", + "0x31fa51343297FFce0CC1E67a50B2D3428057D1b1", + "0x43464Fe06c18848a2E2e913194D64c1970f4326a", + "0x79A132BE0c25cED09e745629D47cf05e531bb2bb", + "0x0bf1B3d1e6f78b12f26204348ABfCA9310259FfA", + "0xf060ab3d5dCfdC6a0DFd5ca0645ac569b8f105CA" + ], + "quorum": 3 + } }, "dummyEmptyContract": { "contract": "contracts/0.8.9/test_helpers/DummyEmptyContract.sol", diff --git a/scripts/staking-router-v2/deployed-mainnet.json.template b/scripts/staking-router-v2/deployed-mainnet.json.template index 5087872ed..d78fe8fdd 100644 --- a/scripts/staking-router-v2/deployed-mainnet.json.template +++ b/scripts/staking-router-v2/deployed-mainnet.json.template @@ -195,7 +195,9 @@ "implementation": { "contract": "@aragon/os/contracts/kernel/Kernel.sol", "address": "0x2b33CF282f867A7FF693A66e11B0FcC5552e4425", - "constructorArgs": [true] + "constructorArgs": [ + true + ] }, "proxy": { "address": "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", @@ -264,6 +266,17 @@ "maxDepositsPerBlock": 150, "minDepositBlockDistance": 25, "pauseIntentValidityPeriodBlocks": 6646 + }, + "guardians": { + "addresses": [ + "0x5fd0dDbC3351d009eb3f88DE7Cd081a614C519F1", + "0x7912Fa976BcDe9c2cf728e213e892AD7588E6AaF", + "0x14D5d5B71E048d2D75a39FfC5B407e3a3AB6F314", + "0xf82D88217C249297C6037BA77CE34b3d8a90ab43", + "0xa56b128Ea2Ea237052b0fA2a96a387C0E43157d8", + "0xd4EF84b638B334699bcf5AF4B0410B8CCD71943f" + ], + "quorum": 4 } }, "dummyEmptyContract": { @@ -276,7 +289,9 @@ "address": "0x8F73e4C2A6D852bb4ab2A45E6a9CF5715b3228B7", "contract": "contracts/0.8.9/EIP712StETH.sol", "deployTx": "0xecb5010620fb13b0e2bbc98b8a0c82de0d7385491452cd36cf303cd74216ed91", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + ] }, "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "executionLayerRewardsVault": { @@ -382,7 +397,10 @@ "address": "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09", "contract": "contracts/0.8.9/OracleDaemonConfig.sol", "deployTx": "0xa4f380b8806f5a504ef67fce62989e09be5a48bf114af63483c01c22f0c9a36f", - "constructorArgs": ["0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", []], + "constructorArgs": [ + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + [] + ], "deployParameters": { "NORMALIZED_CL_REWARD_PER_EPOCH": 64, "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, @@ -402,8 +420,29 @@ "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - [20000, 500, 1000, 50, 600, 2, 100, 7680, 750000], - [[], [], [], [], [], [], [], [], [], []] + [ + 20000, + 500, + 1000, + 50, + 600, + 2, + 100, + 7680, + 750000 + ], + [ + [], + [], + [], + [], + [], + [], + [], + [], + [], + [] + ] ], "deployParameters": { "churnValidatorsPerDayLimit": 20000, @@ -432,7 +471,9 @@ "address": "0xD8784e748f59Ba711fB5643191Ec3fAdD50Fb6df", "contract": "contracts/0.8.9/StakingRouter.sol", "deployTx": "0xd6d489f22203c835da6027ff0e532a01a08f36f0fda6c7c0a42e471ae3b3c461", - "constructorArgs": ["0x00000000219ab540356cBB839Cbe05303d7705Fa"] + "constructorArgs": [ + "0x00000000219ab540356cBB839Cbe05303d7705Fa" + ] } }, "validatorsExitBusOracle": { @@ -450,7 +491,11 @@ "address": "0xA89Ea51FddE660f67d1850e03C9c9862d33Bc42c", "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", "deployTx": "0x5ab545276f78a72a432c3e971c96384973abfab6394e08cb077a006c25aef7a7", - "constructorArgs": [12, 1606824023, "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb"], + "constructorArgs": [ + 12, + 1606824023, + "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" + ], "deployParameters": { "consensusVersion": 1 } @@ -540,7 +585,11 @@ "address": "0xE42C659Dc09109566720EA8b2De186c2Be7D94D9", "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", "deployTx": "0x6ab0151735c01acdef518421358d41a08752169bc383c57d57f5bfa135ac6eb1", - "constructorArgs": ["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "Lido: stETH Withdrawal NFT", "unstETH"], + "constructorArgs": [ + "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "Lido: stETH Withdrawal NFT", + "unstETH" + ], "deployParameters": { "name": "Lido: stETH Withdrawal NFT", "symbol": "unstETH" @@ -555,13 +604,18 @@ "address": "0xCC52f17756C04bBa7E377716d7062fC36D7f69Fd", "contract": "contracts/0.8.9/WithdrawalVault.sol", "deployTx": "0xd9eb2eca684770e4d2b192709b6071875f75072a0ce794a582824ee907a704f3", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" + ] } }, "wstETH": { "address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "contract": "contracts/0.6.12/WstETH.sol", "deployTx": "0xaf2c1a501d2b290ef1e84ddcfc7beb3406f8ece2c46dee14e212e8233654ff05", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + ] } } diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index f767b55b7..2f5f47af4 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -1,6 +1,16 @@ import { ethers } from "hardhat"; -import { deployImplementation, deployWithoutProxy, log, persistNetworkState, readNetworkState, Sk } from "lib"; +import { DepositSecurityModule, DepositSecurityModule__factory } from "typechain-types"; + +import { + deployImplementation, + deployWithoutProxy, + loadContract, + log, + persistNetworkState, + readNetworkState, + Sk, +} from "lib"; function getEnvVariable(name: string, defaultValue?: string) { const value = process.env[name]; @@ -15,27 +25,32 @@ function getEnvVariable(name: string, defaultValue?: string) { } } -// TODO: add guardians async function main() { - const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); - const chainId = (await ethers.provider.getNetwork()).chainId; - const balance = await ethers.provider.getBalance(deployer); - log(`Deployer ${deployer} on network ${chainId} has balance: ${ethers.formatEther(balance)} ETH`); - // parameters from env variables - const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = parseInt(getEnvVariable("PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS")); const MAX_OPERATORS_PER_UNVETTING = parseInt(getEnvVariable("MAX_OPERATORS_PER_UNVETTING")); const SECONDS_PER_SLOT = parseInt(getEnvVariable("SECONDS_PER_SLOT")); const GENESIS_TIME = parseInt(getEnvVariable("GENESIS_TIME")); + const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); + const chainId = (await ethers.provider.getNetwork()).chainId; - const LIMITS_LIST = [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000, 1500]; - const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; + const balance = await ethers.provider.getBalance(deployer); + log(`Deployer ${deployer} on network ${chainId} has balance: ${ethers.formatEther(balance)} ETH`); const state = readNetworkState(); state[Sk.scratchDeployGasUsed] = 0n.toString(); persistNetworkState(state); + const appearedValidatorsPerDayLimit = 1500; + const maxPositiveTokenRebaseManagers: string[] = []; + const currentLimits = state[Sk.oracleReportSanityChecker].constructorArgs[2]; + const managersRoster = state[Sk.oracleReportSanityChecker].constructorArgs[3]; + const LIMITS_LIST = [...currentLimits, appearedValidatorsPerDayLimit]; + const MANAGERS_ROSTER = [...managersRoster, maxPositiveTokenRebaseManagers]; + + const guardians = state[Sk.depositSecurityModule].guardians.addresses; + const quorum = state[Sk.depositSecurityModule].guardians.quorum; + // Read contracts addresses from config const DEPOSIT_CONTRACT_ADDRESS = state[Sk.chainSpec].depositContractAddress; const APP_AGENT_ADDRESS = state[Sk.appAgent].proxy.address; @@ -90,6 +105,14 @@ async function main() { log(`New DSM address: ${depositSecurityModuleAddress}`); + const dsmContract = await loadContract( + DepositSecurityModule__factory, + depositSecurityModuleAddress, + ); + await dsmContract.addGuardians(guardians, quorum); + + log(`Guardians list: ${await dsmContract.getGuardians()}, quorum ${await dsmContract.getGuardianQuorum()}`); + const accountingOracleArgs = [LOCATOR, LIDO, LEGACY_ORACLE, SECONDS_PER_SLOT, GENESIS_TIME]; const accountingOracleAddress = ( From 37fa449fd09bc49dd2a8e89392de7c471912a5a7 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Fri, 21 Jun 2024 10:11:12 +0400 Subject: [PATCH 081/177] fix: moved APPEARED_VALIDATORS_PER_DAY_LIMIT in env --- scripts/staking-router-v2/sr-v2-deploy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 2f5f47af4..6abc9bf46 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -32,6 +32,7 @@ async function main() { const SECONDS_PER_SLOT = parseInt(getEnvVariable("SECONDS_PER_SLOT")); const GENESIS_TIME = parseInt(getEnvVariable("GENESIS_TIME")); const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); + const APPEARED_VALIDATORS_PER_DAY_LIMIT = parseInt(getEnvVariable("APPEARED_VALIDATORS_PER_DAY_LIMIT")); const chainId = (await ethers.provider.getNetwork()).chainId; const balance = await ethers.provider.getBalance(deployer); @@ -41,11 +42,10 @@ async function main() { state[Sk.scratchDeployGasUsed] = 0n.toString(); persistNetworkState(state); - const appearedValidatorsPerDayLimit = 1500; const maxPositiveTokenRebaseManagers: string[] = []; const currentLimits = state[Sk.oracleReportSanityChecker].constructorArgs[2]; const managersRoster = state[Sk.oracleReportSanityChecker].constructorArgs[3]; - const LIMITS_LIST = [...currentLimits, appearedValidatorsPerDayLimit]; + const LIMITS_LIST = [...currentLimits, APPEARED_VALIDATORS_PER_DAY_LIMIT]; const MANAGERS_ROSTER = [...managersRoster, maxPositiveTokenRebaseManagers]; const guardians = state[Sk.depositSecurityModule].guardians.addresses; From e312c0b19504da9598cd361f693e74b4b879aade Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Fri, 21 Jun 2024 09:20:25 +0300 Subject: [PATCH 082/177] docs: fix depositBufferedEther natspec --- contracts/0.8.9/DepositSecurityModule.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index bf6b0eca1..b39ef28bb 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -476,14 +476,15 @@ contract DepositSecurityModule { * @param depositCalldata The calldata for the deposit. * @param sortedGuardianSignatures The list of guardian signatures ascendingly sorted by address. * @dev Reverts if any of the following is true: + * - onchain deposit root is different from the provided one; + * - onchain module nonce is different from the provided one; * - quorum is zero; * - the number of guardian signatures is less than the quorum; - * - onchain deposit root is different from the provided one; * - module is not active; * - min deposit distance is not passed; * - blockHash is zero or not equal to the blockhash(blockNumber); - * - onchain module nonce is different from the provided one; - * - invalid or non-guardian signature received; + * - deposits are paused; + * - invalid or non-guardian signature received. * * Signatures must be sorted in ascending order by address of the guardian. Each signature must * be produced for the keccak256 hash of the following message (each component taking 32 bytes): From da253401e7d77bb0de93678985999c46ca180b68 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Fri, 21 Jun 2024 10:28:22 +0300 Subject: [PATCH 083/177] docs: staking router natspec fixes --- contracts/0.8.9/StakingRouter.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 529454bcb..9274a6926 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -74,7 +74,6 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version Active, // deposits and rewards allowed DepositsPaused, // deposits NOT allowed, rewards allowed Stopped // deposits and rewards NOT allowed - } struct StakingModule { @@ -238,7 +237,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * @param _name name of staking module * @param _stakingModuleAddress address of staking module * @param _stakeShareLimit maximum share that can be allocated to a module - * @param _priorityExitShareThreshold module's proirity exit share threshold + * @param _priorityExitShareThreshold module's priority exit share threshold * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards * @param _treasuryFee treasury fee * @param _maxDepositsPerBlock the maximum number of validators that can be deposited in a single block @@ -309,7 +308,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * @notice Update staking module params * @param _stakingModuleId staking module id * @param _stakeShareLimit target total stake share - * @param _priorityExitShareThreshold module's proirity exit share threshold + * @param _priorityExitShareThreshold module's priority exit share threshold * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards * @param _treasuryFee treasury fee * @param _maxDepositsPerBlock the maximum number of validators that can be deposited in a single block @@ -690,10 +689,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /// @notice decrese vetted signing keys counts per node operator for the staking module with + /// @notice decrease vetted signing keys counts per node operator for the staking module with /// the specified id. /// - /// @param _stakingModuleId The id of the staking modules to be updated. + /// @param _stakingModuleId The id of the staking module to be updated. /// @param _nodeOperatorIds Ids of the node operators to be updated. /// @param _vettedSigningKeysCounts New counts of vetted signing keys for the specified node operators. /// From 1a2326aaefa0255492a303cf1547446d5b82a4f6 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Fri, 21 Jun 2024 10:35:13 +0300 Subject: [PATCH 084/177] chore: sr iterators unification --- contracts/0.8.9/StakingRouter.sol | 43 ++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 9274a6926..3f74c67ea 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -264,6 +264,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version for (uint256 i; i < newStakingModuleIndex; ) { if (_stakingModuleAddress == _getStakingModuleByIndex(i).stakingModuleAddress) revert StakingModuleAddressExists(); + unchecked { ++i; } @@ -420,7 +421,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version ); } } - unchecked { ++i; } + + unchecked { + ++i; + } } } @@ -512,7 +516,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } stakingModule.exitedValidatorsCount = _exitedValidatorsCounts[i]; - unchecked { ++i; } + + unchecked { + ++i; + } } return newlyExitedValidatorsCount; @@ -685,7 +692,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - unchecked { ++i; } + unchecked { + ++i; + } } } @@ -713,6 +722,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version res = new StakingModule[](stakingModulesCount); for (uint256 i; i < stakingModulesCount; ) { res[i] = _getStakingModuleByIndex(i); + unchecked { ++i; } @@ -727,6 +737,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version stakingModuleIds = new uint256[](stakingModulesCount); for (uint256 i; i < stakingModulesCount; ) { stakingModuleIds[i] = _getStakingModuleByIndex(i).id; + unchecked { ++i; } @@ -908,7 +919,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version returns (StakingModuleDigest[] memory digests) { digests = new StakingModuleDigest[](_stakingModuleIds.length); - for (uint256 i = 0; i < _stakingModuleIds.length; ++i) { + for (uint256 i = 0; i < _stakingModuleIds.length; ) { StakingModule memory stakingModuleState = getStakingModule(_stakingModuleIds[i]); IStakingModule stakingModule = IStakingModule(stakingModuleState.stakingModuleAddress); digests[i] = StakingModuleDigest({ @@ -917,6 +928,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version state: stakingModuleState, summary: getStakingModuleSummary(_stakingModuleIds[i]) }); + + unchecked { + ++i; + } } } @@ -959,12 +974,16 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version { IStakingModule stakingModule = _getIStakingModuleById(_stakingModuleId); digests = new NodeOperatorDigest[](_nodeOperatorIds.length); - for (uint256 i = 0; i < _nodeOperatorIds.length; ++i) { + for (uint256 i = 0; i < _nodeOperatorIds.length; ) { digests[i] = NodeOperatorDigest({ id: _nodeOperatorIds[i], isActive: stakingModule.getNodeOperatorIsActive(_nodeOperatorIds[i]), summary: getNodeOperatorSummary(_stakingModuleId, _nodeOperatorIds[i]) }); + + unchecked { + ++i; + } } } @@ -1068,8 +1087,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint96[] memory moduleFees; uint96 totalFee; (, , moduleFees, totalFee, basePrecision) = getStakingRewardsDistribution(); - for (uint256 i; i < moduleFees.length; ++i) { + for (uint256 i; i < moduleFees.length; ) { modulesFee += moduleFees[i]; + + unchecked { + ++i; + } } treasuryFee = totalFee - modulesFee; } @@ -1133,6 +1156,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version rewardedStakingModulesCount++; } } + unchecked { ++i; } @@ -1245,7 +1269,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 stakingModulesCount = getStakingModulesCount(); for (uint256 i; i < stakingModulesCount; ) { StakingModule storage stakingModule = _getStakingModuleByIndex(i); - unchecked { ++i; } + + unchecked { + ++i; + } try IStakingModule(stakingModule.stakingModuleAddress) .onWithdrawalCredentialsChanged() {} @@ -1301,6 +1328,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version for (uint256 i; i < stakingModulesCount; ) { stakingModulesCache[i] = _loadStakingModulesCacheItem(i); totalActiveValidators += stakingModulesCache[i].activeValidatorsCount; + unchecked { ++i; } @@ -1366,6 +1394,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version allocations[i] = stakingModulesCache[i].activeValidatorsCount; targetValidators = (stakingModulesCache[i].stakeShareLimit * totalActiveValidators) / TOTAL_BASIS_POINTS; capacities[i] = Math256.min(targetValidators, stakingModulesCache[i].activeValidatorsCount + stakingModulesCache[i].availableValidatorsCount); + unchecked { ++i; } From 5b71ed305984f7be6186056a9e4fc6db4c30b0eb Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 24 Jun 2024 16:49:02 +0300 Subject: [PATCH 085/177] docs: natspec harmonization for SR contract --- contracts/0.8.9/StakingRouter.sol | 526 ++++++++++++++++-------------- 1 file changed, 273 insertions(+), 253 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 3f74c67ea..60dbcc797 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -18,7 +18,7 @@ import {Versioned} from "./utils/Versioned.sol"; contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Versioned { using UnstructuredStorage for bytes32; - /// @dev events + /// @dev Events event StakingModuleAdded(uint256 indexed stakingModuleId, address stakingModule, string name, address createdBy); event StakingModuleShareLimitSet(uint256 indexed stakingModuleId, uint256 stakeShareLimit, uint256 priorityExitShareThreshold, address setBy); event StakingModuleFeesSet(uint256 indexed stakingModuleId, uint256 stakingModuleFee, uint256 treasuryFee, address setBy); @@ -38,7 +38,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// Emitted when the StakingRouter received ETH event StakingRouterETHDeposited(uint256 indexed stakingModuleId, uint256 amount); - /// @dev errors + /// @dev Errors error ZeroAddressLido(); error ZeroAddressAdmin(); error ZeroAddressStakingModule(); @@ -77,37 +77,39 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } struct StakingModule { - /// @notice unique id of the staking module + /// @notice Unique id of the staking module. uint24 id; - /// @notice address of staking module + /// @notice Address of the staking module. address stakingModuleAddress; - /// @notice part of the fee taken from staking rewards that goes to the staking module + /// @notice Part of the fee taken from staking rewards that goes to the staking module. uint16 stakingModuleFee; - /// @notice part of the fee taken from staking rewards that goes to the treasury + /// @notice Part of the fee taken from staking rewards that goes to the treasury. uint16 treasuryFee; - /// @notice maximum stake share that can be allocated to a module, in BP - uint16 stakeShareLimit; // formerly known as `targetShare` - /// @notice staking module status if staking module can not accept the deposits or can participate in further reward distribution + /// @notice Maximum stake share that can be allocated to a module, in BP. + /// @dev Formerly known as `targetShare`. + uint16 stakeShareLimit; + /// @notice Staking module status if staking module can not accept the deposits or can + /// participate in further reward distribution. uint8 status; - /// @notice name of staking module + /// @notice Name of the staking module. string name; - /// @notice block.timestamp of the last deposit of the staking module - /// @dev NB: lastDepositAt gets updated even if the deposit value was 0 and no actual deposit happened + /// @notice block.timestamp of the last deposit of the staking module. + /// @dev NB: lastDepositAt gets updated even if the deposit value was 0 and no actual deposit happened. uint64 lastDepositAt; - /// @notice block.number of the last deposit of the staking module - /// @dev NB: lastDepositBlock gets updated even if the deposit value was 0 and no actual deposit happened + /// @notice block.number of the last deposit of the staking module. + /// @dev NB: lastDepositBlock gets updated even if the deposit value was 0 and no actual deposit happened. uint256 lastDepositBlock; - /// @notice number of exited validators + /// @notice Number of exited validators. uint256 exitedValidatorsCount; - /// @notice module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP + /// @notice Module's share threshold, upon crossing which, exits of validators from the module will be prioritized, in BP. uint16 priorityExitShareThreshold; - /// @notice the maximum number of validators that can be deposited in a single block - /// @dev must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit` - /// (see docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function) + /// @notice The maximum number of validators that can be deposited in a single block. + /// @dev Must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit`. + /// See docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function. uint64 maxDepositsPerBlock; - /// @notice the minimum distance between deposits in blocks - /// @dev must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit` - /// (see docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function) + /// @notice The minimum distance between deposits in blocks. + /// @dev Must be harmonized with `OracleReportSanityChecker.appearedValidatorsPerDayLimit`. + /// See docs for the `OracleReportSanityChecker.setAppearedValidatorsPerDayLimit` function). uint64 minDepositBlockDistance; } @@ -131,33 +133,32 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version bytes32 internal constant LIDO_POSITION = keccak256("lido.StakingRouter.lido"); - /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side + /// @dev Credentials to withdraw ETH on Consensus Layer side. bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.StakingRouter.withdrawalCredentials"); - /// @dev total count of staking modules + /// @dev Total count of staking modules. bytes32 internal constant STAKING_MODULES_COUNT_POSITION = keccak256("lido.StakingRouter.stakingModulesCount"); - /// @dev id of the last added staking module. This counter grow on staking modules adding + /// @dev Id of the last added staking module. This counter grow on staking modules adding. bytes32 internal constant LAST_STAKING_MODULE_ID_POSITION = keccak256("lido.StakingRouter.lastStakingModuleId"); - /// @dev mapping is used instead of array to allow to extend the StakingModule + /// @dev Mapping is used instead of array to allow to extend the StakingModule. bytes32 internal constant STAKING_MODULES_MAPPING_POSITION = keccak256("lido.StakingRouter.stakingModules"); /// @dev Position of the staking modules in the `_stakingModules` map, plus 1 because - /// index 0 means a value is not in the set. + /// index 0 means a value is not in the set. bytes32 internal constant STAKING_MODULE_INDICES_MAPPING_POSITION = keccak256("lido.StakingRouter.stakingModuleIndicesOneBased"); uint256 public constant FEE_PRECISION_POINTS = 10 ** 20; // 100 * 10 ** 18 uint256 public constant TOTAL_BASIS_POINTS = 10000; uint256 public constant MAX_STAKING_MODULES_COUNT = 32; - /// @dev restrict the name size with 31 bytes to storage in a single slot + /// @dev Restrict the name size with 31 bytes to storage in a single slot. uint256 public constant MAX_STAKING_MODULE_NAME_LENGTH = 31; constructor(address _depositContract) BeaconChainDepositor(_depositContract) {} - /** - * @dev proxy initialization - * @param _admin Lido DAO Aragon agent contract address - * @param _lido Lido address - * @param _withdrawalCredentials Lido withdrawal vault contract address - */ + /// @notice Initializes the contract. + /// @param _admin Lido DAO Aragon agent contract address. + /// @param _lido Lido address. + /// @param _withdrawalCredentials Credentials to withdraw ETH on Consensus Layer side. + /// @dev Proxy initialization method. function initialize(address _admin, address _lido, bytes32 _withdrawalCredentials) external { if (_admin == address(0)) revert ZeroAddressAdmin(); if (_lido == address(0)) revert ZeroAddressLido(); @@ -171,18 +172,16 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version emit WithdrawalCredentialsSet(_withdrawalCredentials, msg.sender); } - /// @dev prohibit direct transfer to contract + /// @dev Prohibit direct transfer to contract. receive() external payable { revert DirectETHTransfer(); } - - /** - * @notice A function to finalize upgrade to v2 (from v1). Can be called only once - * @param _priorityExitShareThresholds array of priority exit share thresholds - * @param _maxDepositsPerBlock array of max deposits per block - * @param _minDepositBlockDistances array of min deposit block distances - */ + /// @notice Finalizes upgrade to v2 (from v1). Can be called only once. + /// @param _priorityExitShareThresholds Array of priority exit share thresholds. + /// @param _maxDepositsPerBlock Array of max deposits per block. + /// @param _minDepositBlockDistances Array of min deposit block distances. + /// @dev https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md function finalizeUpgrade_v2( uint256[] memory _priorityExitShareThresholds, uint256[] memory _maxDepositsPerBlock, @@ -225,24 +224,22 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version _updateContractVersion(2); } - /** - * @notice Return the Lido contract address - */ + /// @notice Returns Lido contract address. + /// @return Lido contract address. function getLido() public view returns (address) { return LIDO_POSITION.getStorageAddress(); } - /** - * @notice register a new staking module - * @param _name name of staking module - * @param _stakingModuleAddress address of staking module - * @param _stakeShareLimit maximum share that can be allocated to a module - * @param _priorityExitShareThreshold module's priority exit share threshold - * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards - * @param _treasuryFee treasury fee - * @param _maxDepositsPerBlock the maximum number of validators that can be deposited in a single block - * @param _minDepositBlockDistance the minimum distance between deposits in blocks - */ + /// @notice Registers a new staking module. + /// @param _name Name of staking module. + /// @param _stakingModuleAddress Address of staking module. + /// @param _stakeShareLimit Maximum share that can be allocated to a module. + /// @param _priorityExitShareThreshold Module's priority exit share threshold. + /// @param _stakingModuleFee Fee of the staking module taken from the staking rewards. + /// @param _treasuryFee Treasury fee. + /// @param _maxDepositsPerBlock The maximum number of validators that can be deposited in a single block. + /// @param _minDepositBlockDistance The minimum distance between deposits in blocks. + /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function addStakingModule( string calldata _name, address _stakingModuleAddress, @@ -276,14 +273,14 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version newStakingModule.id = newStakingModuleId; newStakingModule.name = _name; newStakingModule.stakingModuleAddress = _stakingModuleAddress; - /// @dev since `enum` is `uint8` by nature, so the `status` is stored as `uint8` to avoid + /// @dev Since `enum` is `uint8` by nature, so the `status` is stored as `uint8` to avoid /// possible problems when upgrading. But for human readability, we use `enum` as - /// function parameter type. More about conversion in the docs + /// function parameter type. More about conversion in the docs: /// https://docs.soliditylang.org/en/v0.8.17/types.html#enums newStakingModule.status = uint8(StakingModuleStatus.Active); - /// @dev Simulate zero value deposit to prevent real deposits into the new StakingModule via - /// DepositSecurityModule just after the addition. + /// @dev Simulate zero value deposit to prevent real deposits into the new StakingModule via + /// DepositSecurityModule just after the addition. newStakingModule.lastDepositAt = uint64(block.timestamp); newStakingModule.lastDepositBlock = block.number; emit StakingRouterETHDeposited(newStakingModuleId, 0); @@ -305,16 +302,15 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version ); } - /** - * @notice Update staking module params - * @param _stakingModuleId staking module id - * @param _stakeShareLimit target total stake share - * @param _priorityExitShareThreshold module's priority exit share threshold - * @param _stakingModuleFee fee of the staking module taken from the consensus layer rewards - * @param _treasuryFee treasury fee - * @param _maxDepositsPerBlock the maximum number of validators that can be deposited in a single block - * @param _minDepositBlockDistance the minimum distance between deposits in blocks - */ + /// @notice Updates staking module params. + /// @param _stakingModuleId Staking module id. + /// @param _stakeShareLimit Target total stake share. + /// @param _priorityExitShareThreshold Module's priority exit share threshold. + /// @param _stakingModuleFee Fee of the staking module taken from the staking rewards. + /// @param _treasuryFee Treasury fee. + /// @param _maxDepositsPerBlock The maximum number of validators that can be deposited in a single block. + /// @param _minDepositBlockDistance The minimum distance between deposits in blocks. + /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function updateStakingModule( uint256 _stakingModuleId, uint256 _stakeShareLimit, @@ -366,11 +362,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version emit StakingModuleMinDepositBlockDistanceSet(_stakingModuleId, _minDepositBlockDistance, msg.sender); } - /// @notice Updates the limit of the validators that can be used for deposit - /// @param _stakingModuleId Id of the staking module - /// @param _nodeOperatorId Id of the node operator - /// @param _targetLimitMode Target limit mode - /// @param _targetLimit Target limit of the node operator + /// @notice Updates the limit of the validators that can be used for deposit. + /// @param _stakingModuleId Id of the staking module. + /// @param _nodeOperatorId Id of the node operator. + /// @param _targetLimitMode Target limit mode. + /// @param _targetLimit Target limit of the node operator. + /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function updateTargetValidatorsLimits( uint256 _stakingModuleId, uint256 _nodeOperatorId, @@ -383,10 +380,11 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } /// @notice Updates the number of the refunded validators in the staking module with the given - /// node operator id - /// @param _stakingModuleId Id of the staking module - /// @param _nodeOperatorId Id of the node operator - /// @param _refundedValidatorsCount New number of refunded validators of the node operator + /// node operator id. + /// @param _stakingModuleId Id of the staking module. + /// @param _nodeOperatorId Id of the node operator. + /// @param _refundedValidatorsCount New number of refunded validators of the node operator. + /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function updateRefundedValidatorsCount( uint256 _stakingModuleId, uint256 _nodeOperatorId, @@ -397,6 +395,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version ); } + /// @notice Reports the minted rewards to the staking modules with the specified ids. + /// @param _stakingModuleIds Ids of the staking modules. + /// @param _totalShares Total shares minted for the staking modules. + /// @dev The function is restricted to the `REPORT_REWARDS_MINTED_ROLE` role. function reportRewardsMinted(uint256[] calldata _stakingModuleIds, uint256[] calldata _totalShares) external onlyRole(REPORT_REWARDS_MINTED_ROLE) @@ -428,15 +430,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /// @notice Updates total numbers of exited validators for staking modules with the specified - /// module ids. - /// + /// @notice Updates total numbers of exited validators for staking modules with the specified module ids. /// @param _stakingModuleIds Ids of the staking modules to be updated. /// @param _exitedValidatorsCounts New counts of exited validators for the specified staking modules. - /// /// @return The total increase in the aggregate number of exited validators across all updated modules. /// - /// The total numbers are stored in the staking router and can differ from the totals obtained by calling + /// @dev The total numbers are stored in the staking router and can differ from the totals obtained by calling /// `IStakingModule.getStakingModuleSummary()`. The overall process of updating validator counts is the following: /// /// 1. In the first data submission phase, the oracle calls `updateExitedValidatorsCountByStakingModule` on the @@ -469,6 +468,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// `StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished` which, in turn, calls /// `IStakingModule.onExitedAndStuckValidatorsCountsUpdated` on all modules. /// + /// @dev The function is restricted to the `REPORT_EXITED_VALIDATORS_ROLE` role. function updateExitedValidatorsCountByStakingModule( uint256[] calldata _stakingModuleIds, uint256[] calldata _exitedValidatorsCounts @@ -526,15 +526,14 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } /// @notice Updates exited validators counts per node operator for the staking module with - /// the specified id. - /// - /// See the docs for `updateExitedValidatorsCountByStakingModule` for the description of the - /// overall update process. + /// the specified id. See the docs for `updateExitedValidatorsCountByStakingModule` for the + /// description of the overall update process. /// /// @param _stakingModuleId The id of the staking modules to be updated. /// @param _nodeOperatorIds Ids of the node operators to be updated. /// @param _exitedValidatorsCounts New counts of exited validators for the specified node operators. /// + /// @dev The function is restricted to the `REPORT_EXITED_VALIDATORS_ROLE` role. function reportStakingModuleExitedValidatorsCountByNodeOperator( uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, @@ -565,26 +564,22 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 newNodeOperatorStuckValidatorsCount; } - /** - * @notice Sets exited validators count for the given module and given node operator in that - * module without performing critical safety checks, e.g. that exited validators count cannot - * decrease. - * - * Should only be used by the DAO in extreme cases and with sufficient precautions to correct - * invalid data reported by the oracle committee due to a bug in the oracle daemon. - * - * @param _stakingModuleId ID of the staking module. - * - * @param _nodeOperatorId ID of the node operator. - * - * @param _triggerUpdateFinish Whether to call `onExitedAndStuckValidatorsCountsUpdated` on - * the module after applying the corrections. - * - * @param _correction See the docs for the `ValidatorsCountsCorrection` struct. - * - * Reverts if the current numbers of exited and stuck validators of the module and node operator - * don't match the supplied expected current values. - */ + /// @notice Sets exited validators count for the given module and given node operator in that module + /// without performing critical safety checks, e.g. that exited validators count cannot decrease. + /// + /// Should only be used by the DAO in extreme cases and with sufficient precautions to correct invalid + /// data reported by the oracle committee due to a bug in the oracle daemon. + /// + /// @param _stakingModuleId Id of the staking module. + /// @param _nodeOperatorId Id of the node operator. + /// @param _triggerUpdateFinish Whether to call `onExitedAndStuckValidatorsCountsUpdated` on the module + /// after applying the corrections. + /// @param _correction See the docs for the `ValidatorsCountsCorrection` struct. + /// + /// @dev Reverts if the current numbers of exited and stuck validators of the module and node operator + /// don't match the supplied expected current values. + /// + /// @dev The function is restricted to the `UNSAFE_SET_EXITED_VALIDATORS_ROLE` role. function unsafeSetExitedValidatorsCount( uint256 _stakingModuleId, uint256 _nodeOperatorId, @@ -633,15 +628,14 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } /// @notice Updates stuck validators counts per node operator for the staking module with - /// the specified id. - /// - /// See the docs for `updateExitedValidatorsCountByStakingModule` for the description of the - /// overall update process. + /// the specified id. See the docs for `updateExitedValidatorsCountByStakingModule` for the + /// description of the overall update process. /// /// @param _stakingModuleId The id of the staking modules to be updated. /// @param _nodeOperatorIds Ids of the node operators to be updated. /// @param _stuckValidatorsCounts New counts of stuck validators for the specified node operators. /// + /// @dev The function is restricted to the `REPORT_EXITED_VALIDATORS_ROLE` role. function reportStakingModuleStuckValidatorsCountByNodeOperator( uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, @@ -654,13 +648,15 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version _getIStakingModuleById(_stakingModuleId).updateStuckValidatorsCount(_nodeOperatorIds, _stuckValidatorsCounts); } - /// @notice Called by the oracle when the second phase of data reporting finishes, i.e. when the - /// oracle submitted the complete data on the stuck and exited validator counts per node operator - /// for the current reporting frame. + /// @notice Finalizes the reporting of the exited and stuck validators counts for the current + /// reporting frame. /// - /// See the docs for `updateExitedValidatorsCountByStakingModule` for the description of the - /// overall update process. + /// @dev Called by the oracle when the second phase of data reporting finishes, i.e. when the + /// oracle submitted the complete data on the stuck and exited validator counts per node operator + /// for the current reporting frame. See the docs for `updateExitedValidatorsCountByStakingModule` + /// for the description of the overall update process. /// + /// @dev The function is restricted to the `REPORT_EXITED_VALIDATORS_ROLE` role. function onValidatorsCountsByNodeOperatorReportingFinished() external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) @@ -698,13 +694,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /// @notice decrease vetted signing keys counts per node operator for the staking module with + /// @notice Decreases vetted signing keys counts per node operator for the staking module with /// the specified id. - /// /// @param _stakingModuleId The id of the staking module to be updated. /// @param _nodeOperatorIds Ids of the node operators to be updated. /// @param _vettedSigningKeysCounts New counts of vetted signing keys for the specified node operators. - /// + /// @dev The function is restricted to the `STAKING_MODULE_UNVETTING_ROLE` role. function decreaseStakingModuleVettedKeysCountByNodeOperator( uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, @@ -714,9 +709,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version _getIStakingModuleById(_stakingModuleId).decreaseVettedSigningKeysCount(_nodeOperatorIds, _vettedSigningKeysCounts); } - /** - * @notice Returns all registered staking modules - */ + /// @notice Returns all registered staking modules. + /// @return res Array of staking modules. function getStakingModules() external view returns (StakingModule[] memory res) { uint256 stakingModulesCount = getStakingModulesCount(); res = new StakingModule[](stakingModulesCount); @@ -729,9 +723,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /** - * @notice Returns the ids of all registered staking modules - */ + /// @notice Returns the ids of all registered staking modules. + /// @return stakingModuleIds Array of staking module ids. function getStakingModuleIds() public view returns (uint256[] memory stakingModuleIds) { uint256 stakingModulesCount = getStakingModulesCount(); stakingModuleIds = new uint256[](stakingModulesCount); @@ -744,9 +737,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /** - * @dev Returns staking module by id - */ + /// @notice Returns the staking module by its id. + /// @param _stakingModuleId Id of the staking module. + /// @return Staking module data. function getStakingModule(uint256 _stakingModuleId) public view @@ -755,23 +748,22 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return _getStakingModuleById(_stakingModuleId); } - /** - * @dev Returns total number of staking modules - */ + /// @notice Returns total number of staking modules. + /// @return Total number of staking modules. function getStakingModulesCount() public view returns (uint256) { return STAKING_MODULES_COUNT_POSITION.getStorageUint256(); } - /** - * @dev Returns true if staking module with the given id was registered via `addStakingModule`, false otherwise - */ + /// @notice Returns true if staking module with the given id was registered via `addStakingModule`, false otherwise. + /// @param _stakingModuleId Id of the staking module. + /// @return True if staking module with the given id was registered, false otherwise. function hasStakingModule(uint256 _stakingModuleId) external view returns (bool) { return _getStorageStakingIndicesMapping()[_stakingModuleId] != 0; } - /** - * @dev Returns status of staking module - */ + /// @notice Returns status of staking module. + /// @param _stakingModuleId Id of the staking module. + /// @return Status of the staking module. function getStakingModuleStatus(uint256 _stakingModuleId) public view @@ -780,54 +772,55 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return StakingModuleStatus(_getStakingModuleById(_stakingModuleId).status); } - /// @notice A summary of the staking module's validators + /// @notice A summary of the staking module's validators. struct StakingModuleSummary { - /// @notice The total number of validators in the EXITED state on the Consensus Layer - /// @dev This value can't decrease in normal conditions + /// @notice The total number of validators in the EXITED state on the Consensus Layer. + /// @dev This value can't decrease in normal conditions. uint256 totalExitedValidators; - /// @notice The total number of validators deposited via the official Deposit Contract + /// @notice The total number of validators deposited via the official Deposit Contract. /// @dev This value is a cumulative counter: even when the validator goes into EXITED state this - /// counter is not decreasing + /// counter is not decreasing. uint256 totalDepositedValidators; /// @notice The number of validators in the set available for deposit uint256 depositableValidatorsCount; } - /// @notice A summary of node operator and its validators + /// @notice A summary of node operator and its validators. struct NodeOperatorSummary { - /// @notice Shows whether the current target limit applied to the node operator + /// @notice Shows whether the current target limit applied to the node operator. uint256 targetLimitMode; - /// @notice Relative target active validators limit for operator + /// @notice Relative target active validators limit for operator. uint256 targetValidatorsCount; - /// @notice The number of validators with an expired request to exit time + /// @notice The number of validators with an expired request to exit time. uint256 stuckValidatorsCount; /// @notice The number of validators that can't be withdrawn, but deposit costs were - /// compensated to the Lido by the node operator + /// compensated to the Lido by the node operator. uint256 refundedValidatorsCount; - /// @notice A time when the penalty for stuck validators stops applying to node operator rewards + /// @notice A time when the penalty for stuck validators stops applying to node operator rewards. uint256 stuckPenaltyEndTimestamp; - /// @notice The total number of validators in the EXITED state on the Consensus Layer - /// @dev This value can't decrease in normal conditions + /// @notice The total number of validators in the EXITED state on the Consensus Layer. + /// @dev This value can't decrease in normal conditions. uint256 totalExitedValidators; - /// @notice The total number of validators deposited via the official Deposit Contract + /// @notice The total number of validators deposited via the official Deposit Contract. /// @dev This value is a cumulative counter: even when the validator goes into EXITED state this - /// counter is not decreasing + /// counter is not decreasing. uint256 totalDepositedValidators; - /// @notice The number of validators in the set available for deposit + /// @notice The number of validators in the set available for deposit. uint256 depositableValidatorsCount; } - /// @notice Returns all-validators summary in the staking module - /// @param _stakingModuleId id of the staking module to return summary for + /// @notice Returns all-validators summary in the staking module. + /// @param _stakingModuleId Id of the staking module to return summary for. + /// @return summary Staking module summary. function getStakingModuleSummary(uint256 _stakingModuleId) public view @@ -843,9 +836,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } - /// @notice Returns node operator summary from the staking module - /// @param _stakingModuleId id of the staking module where node operator is onboarded - /// @param _nodeOperatorId id of the node operator to return summary for + /// @notice Returns node operator summary from the staking module. + /// @param _stakingModuleId Id of the staking module where node operator is onboarded. + /// @param _nodeOperatorId Id of the node operator to return summary for. + /// @return summary Node operator summary. function getNodeOperatorSummary(uint256 _stakingModuleId, uint256 _nodeOperatorId) public view @@ -854,7 +848,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version StakingModule memory stakingModuleState = getStakingModule(_stakingModuleId); IStakingModule stakingModule = IStakingModule(stakingModuleState.stakingModuleAddress); /// @dev using intermediate variables below due to "Stack too deep" error in case of - /// assigning directly into the NodeOperatorSummary struct + /// assigning directly into the NodeOperatorSummary struct ( uint256 targetLimitMode, uint256 targetValidatorsCount, @@ -876,43 +870,46 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } /// @notice A collection of the staking module data stored across the StakingRouter and the - /// staking module contract + /// staking module contract. + /// /// @dev This data, first of all, is designed for off-chain usage and might be redundant for - /// on-chain calls. Give preference for dedicated methods for gas-efficient on-chain calls + /// on-chain calls. Give preference for dedicated methods for gas-efficient on-chain calls. struct StakingModuleDigest { - /// @notice The number of node operators registered in the staking module + /// @notice The number of node operators registered in the staking module. uint256 nodeOperatorsCount; - /// @notice The number of node operators registered in the staking module in active state + /// @notice The number of node operators registered in the staking module in active state. uint256 activeNodeOperatorsCount; - /// @notice The current state of the staking module taken from the StakingRouter + /// @notice The current state of the staking module taken from the StakingRouter. StakingModule state; - /// @notice A summary of the staking module's validators + /// @notice A summary of the staking module's validators. StakingModuleSummary summary; } - /// @notice A collection of the node operator data stored in the staking module + /// @notice A collection of the node operator data stored in the staking module. /// @dev This data, first of all, is designed for off-chain usage and might be redundant for - /// on-chain calls. Give preference for dedicated methods for gas-efficient on-chain calls + /// on-chain calls. Give preference for dedicated methods for gas-efficient on-chain calls. struct NodeOperatorDigest { - /// @notice id of the node operator + /// @notice Id of the node operator. uint256 id; - /// @notice Shows whether the node operator is active or not + /// @notice Shows whether the node operator is active or not. bool isActive; - /// @notice A summary of node operator and its validators + /// @notice A summary of node operator and its validators. NodeOperatorSummary summary; } - /// @notice Returns staking module digest for each staking module registered in the staking router + /// @notice Returns staking module digest for each staking module registered in the staking router. + /// @return Array of staking module digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs - /// for data aggregation + /// for data aggregation. function getAllStakingModuleDigests() external view returns (StakingModuleDigest[] memory) { return getStakingModuleDigests(getStakingModuleIds()); } - /// @notice Returns staking module digest for passed staking module ids - /// @param _stakingModuleIds ids of the staking modules to return data for + /// @notice Returns staking module digest for passed staking module ids. + /// @param _stakingModuleIds Ids of the staking modules to return data for. + /// @return digests Array of staking module digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs - /// for data aggregation + /// for data aggregation. function getStakingModuleDigests(uint256[] memory _stakingModuleIds) public view @@ -935,22 +932,24 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /// @notice Returns node operator digest for each node operator registered in the given staking module - /// @param _stakingModuleId id of the staking module to return data for + /// @notice Returns node operator digest for each node operator registered in the given staking module. + /// @param _stakingModuleId Id of the staking module to return data for. + /// @return Array of node operator digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs - /// for data aggregation + /// for data aggregation. function getAllNodeOperatorDigests(uint256 _stakingModuleId) external view returns (NodeOperatorDigest[] memory) { return getNodeOperatorDigests( _stakingModuleId, 0, _getIStakingModuleById(_stakingModuleId).getNodeOperatorsCount() ); } - /// @notice Returns node operator digest for passed node operator ids in the given staking module - /// @param _stakingModuleId id of the staking module where node operators registered - /// @param _offset node operators offset starting with 0 - /// @param _limit the max number of node operators to return + /// @notice Returns node operator digest for passed node operator ids in the given staking module. + /// @param _stakingModuleId Id of the staking module where node operators registered. + /// @param _offset Node operators offset starting with 0. + /// @param _limit The max number of node operators to return. + /// @return Array of node operator digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs - /// for data aggregation + /// for data aggregation. function getNodeOperatorDigests( uint256 _stakingModuleId, uint256 _offset, @@ -962,11 +961,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } /// @notice Returns node operator digest for a slice of node operators registered in the given - /// staking module - /// @param _stakingModuleId id of the staking module where node operators registered - /// @param _nodeOperatorIds ids of the node operators to return data for + /// staking module. + /// @param _stakingModuleId Id of the staking module where node operators registered. + /// @param _nodeOperatorIds Ids of the node operators to return data for. + /// @return digests Array of node operator digests. /// @dev WARNING: This method is not supposed to be used for onchain calls due to high gas costs - /// for data aggregation + /// for data aggregation. function getNodeOperatorDigests(uint256 _stakingModuleId, uint256[] memory _nodeOperatorIds) public view @@ -987,9 +987,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /** - * @notice set the staking module status flag for participation in further deposits and/or reward distribution - */ + /// @notice Sets the staking module status flag for participation in further deposits and/or reward distribution. + /// @param _stakingModuleId Id of the staking module to be updated. + /// @param _status New status of the staking module. + /// @dev The function is restricted to the `STAKING_MODULE_MANAGE_ROLE` role. function setStakingModuleStatus( uint256 _stakingModuleId, StakingModuleStatus _status @@ -999,11 +1000,17 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version _setStakingModuleStatus(stakingModule, _status); } + /// @notice Returns whether the staking module is stopped. + /// @param _stakingModuleId Id of the staking module. + /// @return True if the staking module is stopped, false otherwise. function getStakingModuleIsStopped(uint256 _stakingModuleId) external view returns (bool) { return getStakingModuleStatus(_stakingModuleId) == StakingModuleStatus.Stopped; } + /// @notice Returns whether the deposits are paused for the staking module. + /// @param _stakingModuleId Id of the staking module. + /// @return True if the deposits are paused, false otherwise. function getStakingModuleIsDepositsPaused(uint256 _stakingModuleId) external view @@ -1012,14 +1019,23 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return getStakingModuleStatus(_stakingModuleId) == StakingModuleStatus.DepositsPaused; } + /// @notice Returns whether the staking module is active. + /// @param _stakingModuleId Id of the staking module. + /// @return True if the staking module is active, false otherwise. function getStakingModuleIsActive(uint256 _stakingModuleId) external view returns (bool) { return getStakingModuleStatus(_stakingModuleId) == StakingModuleStatus.Active; } + /// @notice Returns staking module nonce. + /// @param _stakingModuleId Id of the staking module. + /// @return Staking module nonce. function getStakingModuleNonce(uint256 _stakingModuleId) external view returns (uint256) { return _getIStakingModuleById(_stakingModuleId).getNonce(); } + /// @notice Returns the last deposit block for the staking module. + /// @param _stakingModuleId Id of the staking module. + /// @return Last deposit block for the staking module. function getStakingModuleLastDepositBlock(uint256 _stakingModuleId) external view @@ -1028,14 +1044,23 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return _getStakingModuleById(_stakingModuleId).lastDepositBlock; } + /// @notice Returns the min deposit block distance for the staking module. + /// @param _stakingModuleId Id of the staking module. + /// @return Min deposit block distance for the staking module. function getStakingModuleMinDepositBlockDistance(uint256 _stakingModuleId) external view returns (uint256) { return _getStakingModuleById(_stakingModuleId).minDepositBlockDistance; } + /// @notice Returns the max deposit block distance for the staking module. + /// @param _stakingModuleId Id of the staking module. + /// @return Max deposit block distance for the staking module. function getStakingModuleMaxDepositsPerBlock(uint256 _stakingModuleId) external view returns (uint256) { return _getStakingModuleById(_stakingModuleId).maxDepositsPerBlock; } + /// @notice Returns active validators count for the staking module. + /// @param _stakingModuleId Id of the staking module. + /// @return activeValidatorsCount Active validators count for the staking module. function getStakingModuleActiveValidatorsCount(uint256 _stakingModuleId) external view @@ -1053,11 +1078,11 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version ); } - /// @dev calculate the max count of deposits which the staking module can provide data for based - /// on the passed `_maxDepositsValue` amount - /// @param _stakingModuleId id of the staking module to be deposited - /// @param _maxDepositsValue max amount of ether that might be used for deposits count calculation - /// @return max number of deposits might be done using the given staking module + /// @notice Returns the max count of deposits which the staking module can provide data for based + /// on the passed `_maxDepositsValue` amount. + /// @param _stakingModuleId Id of the staking module to be deposited. + /// @param _maxDepositsValue Max amount of ether that might be used for deposits count calculation. + /// @return Max number of deposits might be done using the given staking module. function getStakingModuleMaxDepositsCount(uint256 _stakingModuleId, uint256 _maxDepositsValue) public view @@ -1073,12 +1098,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version newDepositsAllocation[stakingModuleIndex] - stakingModulesCache[stakingModuleIndex].activeValidatorsCount; } - /** - * @notice Returns the aggregate fee distribution proportion - * @return modulesFee modules aggregate fee in base precision - * @return treasuryFee treasury fee in base precision - * @return basePrecision base precision: a value corresponding to the full fee - */ + /// @notice Returns the aggregate fee distribution proportion. + /// @return modulesFee Modules aggregate fee in base precision. + /// @return treasuryFee Treasury fee in base precision. + /// @return basePrecision Base precision: a value corresponding to the full fee. function getStakingFeeAggregateDistribution() public view returns ( uint96 modulesFee, uint96 treasuryFee, @@ -1097,15 +1120,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version treasuryFee = totalFee - modulesFee; } - /** - * @notice Return shares table - * - * @return recipients rewards recipient addresses corresponding to each module - * @return stakingModuleIds module IDs - * @return stakingModuleFees fee of each recipient - * @return totalFee total fee to mint for each staking module and treasury - * @return precisionPoints base precision number, which constitutes 100% fee - */ + /// @notice Return shares table. + /// @return recipients Rewards recipient addresses corresponding to each module. + /// @return stakingModuleIds Module IDs. + /// @return stakingModuleFees Fee of each recipient. + /// @return totalFee Total fee to mint for each staking module and treasury. + /// @return precisionPoints Base precision number, which constitutes 100% fee. function getStakingRewardsDistribution() public view @@ -1120,7 +1140,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version (uint256 totalActiveValidators, StakingModuleCache[] memory stakingModulesCache) = _loadStakingModulesCache(); uint256 stakingModulesCount = stakingModulesCache.length; - /// @dev return empty response if there are no staking modules or active validators yet + /// @dev Return empty response if there are no staking modules or active validators yet. if (stakingModulesCount == 0 || totalActiveValidators == 0) { return (new address[](0), new uint256[](0), new uint96[](0), 0, FEE_PRECISION_POINTS); } @@ -1135,20 +1155,20 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint96 stakingModuleFee; for (uint256 i; i < stakingModulesCount; ) { - /// @dev skip staking modules which have no active validators + /// @dev Skip staking modules which have no active validators. if (stakingModulesCache[i].activeValidatorsCount > 0) { stakingModuleIds[rewardedStakingModulesCount] = stakingModulesCache[i].stakingModuleId; stakingModuleValidatorsShare = ((stakingModulesCache[i].activeValidatorsCount * precisionPoints) / totalActiveValidators); recipients[rewardedStakingModulesCount] = address(stakingModulesCache[i].stakingModuleAddress); stakingModuleFee = uint96((stakingModuleValidatorsShare * stakingModulesCache[i].stakingModuleFee) / TOTAL_BASIS_POINTS); - /// @dev if the staking module has the `Stopped` status for some reason, then + /// @dev If the staking module has the `Stopped` status for some reason, then /// the staking module's rewards go to the treasury, so that the DAO has ability /// to manage them (e.g. to compensate the staking module in case of an error, etc.) if (stakingModulesCache[i].status != StakingModuleStatus.Stopped) { stakingModuleFees[rewardedStakingModulesCount] = stakingModuleFee; } - // else keep stakingModuleFees[rewardedStakingModulesCount] = 0, but increase totalFee + // Else keep stakingModuleFees[rewardedStakingModulesCount] = 0, but increase totalFee. totalFee += (uint96((stakingModuleValidatorsShare * stakingModulesCache[i].treasuryFee) / TOTAL_BASIS_POINTS) + stakingModuleFee); @@ -1162,10 +1182,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - // Total fee never exceeds 100% + // Total fee never exceeds 100%. assert(totalFee <= precisionPoints); - /// @dev shrink arrays + /// @dev Shrink arrays. if (rewardedStakingModulesCount < stakingModulesCount) { assembly { mstore(stakingModuleIds, rewardedStakingModulesCount) @@ -1175,45 +1195,48 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /// @notice Helper for Lido contract (DEPRECATED) - /// Returns total fee total fee to mint for each staking - /// module and treasury in reduced, 1e4 precision. - /// In integrations please use getStakingRewardsDistribution(). - /// reduced, 1e4 precision. + /// @notice Returns the same as getStakingRewardsDistribution() but in reduced, 1e4 precision (DEPRECATED). + /// @dev Helper only for Lido contract. Use getStakingRewardsDistribution() instead. + /// @return totalFee Total fee to mint for each staking module and treasury in reduced, 1e4 precision. function getTotalFeeE4Precision() external view returns (uint16 totalFee) { - /// @dev The logic is placed here but in Lido contract to save Lido bytecode + /// @dev The logic is placed here but in Lido contract to save Lido bytecode. (, , , uint96 totalFeeInHighPrecision, uint256 precision) = getStakingRewardsDistribution(); - // Here we rely on (totalFeeInHighPrecision <= precision) + // Here we rely on (totalFeeInHighPrecision <= precision). totalFee = _toE4Precision(totalFeeInHighPrecision, precision); } - /// @notice Helper for Lido contract (DEPRECATED) - /// Returns the same as getStakingFeeAggregateDistribution() but in reduced, 1e4 precision - /// @dev Helper only for Lido contract. Use getStakingFeeAggregateDistribution() instead + /// @notice Returns the same as getStakingFeeAggregateDistribution() but in reduced, 1e4 precision (DEPRECATED). + /// @dev Helper only for Lido contract. Use getStakingFeeAggregateDistribution() instead. + /// @return modulesFee Modules aggregate fee in reduced, 1e4 precision. + /// @return treasuryFee Treasury fee in reduced, 1e4 precision. function getStakingFeeAggregateDistributionE4Precision() external view returns (uint16 modulesFee, uint16 treasuryFee) { - /// @dev The logic is placed here but in Lido contract to save Lido bytecode + /// @dev The logic is placed here but in Lido contract to save Lido bytecode. ( uint256 modulesFeeHighPrecision, uint256 treasuryFeeHighPrecision, uint256 precision ) = getStakingFeeAggregateDistribution(); - // Here we rely on ({modules,treasury}FeeHighPrecision <= precision) + // Here we rely on ({modules,treasury}FeeHighPrecision <= precision). modulesFee = _toE4Precision(modulesFeeHighPrecision, precision); treasuryFee = _toE4Precision(treasuryFeeHighPrecision, precision); } - /// @notice returns new deposits allocation after the distribution of the `_depositsCount` deposits + /// @notice Returns new deposits allocation after the distribution of the `_depositsCount` deposits. + /// @param _depositsCount The maximum number of deposits to be allocated. + /// @return allocated Number of deposits allocated to the staking modules. + /// @return allocations Array of new deposits allocation to the staking modules. function getDepositsAllocation(uint256 _depositsCount) external view returns (uint256 allocated, uint256[] memory allocations) { (allocated, allocations, ) = _getDepositsAllocation(_depositsCount); } - /// @dev Invokes a deposit call to the official Deposit contract - /// @param _depositsCount number of deposits to make - /// @param _stakingModuleId id of the staking module to be deposited - /// @param _depositCalldata staking module calldata + /// @notice Invokes a deposit call to the official Deposit contract. + /// @param _depositsCount Number of deposits to make. + /// @param _stakingModuleId Id of the staking module to be deposited. + /// @param _depositCalldata Staking module calldata. + /// @dev Only the Lido contract is allowed to call this method. function deposit( uint256 _depositsCount, uint256 _stakingModuleId, @@ -1228,8 +1251,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version if (StakingModuleStatus(stakingModule.status) != StakingModuleStatus.Active) revert StakingModuleNotActive(); - /// @dev firstly update the local state of the contract to prevent a reentrancy attack - /// even though the staking modules are trusted contracts + /// @dev Firstly update the local state of the contract to prevent a reentrancy attack + /// even though the staking modules are trusted contracts. stakingModule.lastDepositAt = uint64(block.timestamp); stakingModule.lastDepositBlock = block.number; @@ -1253,16 +1276,15 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version ); uint256 etherBalanceAfterDeposits = address(this).balance; - /// @dev all sent ETH must be deposited and self balance stay the same + /// @dev All sent ETH must be deposited and self balance stay the same. assert(etherBalanceBeforeDeposits - etherBalanceAfterDeposits == depositsValue); } } - /** - * @notice Set credentials to withdraw ETH on Consensus Layer side after the phase 2 is launched to `_withdrawalCredentials` - * @dev Note that setWithdrawalCredentials discards all unused deposits data as the signatures are invalidated. - * @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs - */ + /// @notice Set credentials to withdraw ETH on Consensus Layer side. + /// @param _withdrawalCredentials withdrawal credentials field as defined in the Consensus Layer specs. + /// @dev Note that setWithdrawalCredentials discards all unused deposits data as the signatures are invalidated. + /// @dev The function is restricted to the `MANAGE_WITHDRAWAL_CREDENTIALS_ROLE` role. function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external onlyRole(MANAGE_WITHDRAWAL_CREDENTIALS_ROLE) { WITHDRAWAL_CREDENTIALS_POSITION.setStorageBytes32(_withdrawalCredentials); @@ -1292,9 +1314,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version emit WithdrawalCredentialsSet(_withdrawalCredentials, msg.sender); } - /** - * @notice Returns current credentials to withdraw ETH on Consensus Layer side after the phase 2 is launched - */ + /// @notice Returns current credentials to withdraw ETH on Consensus Layer side. + /// @return Withdrawal credentials. function getWithdrawalCredentials() public view returns (bytes32) { return WITHDRAWAL_CREDENTIALS_POSITION.getStorageBytes32(); } @@ -1315,10 +1336,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - /// @dev load modules into a memory cache - /// - /// @return totalActiveValidators total active validators across all modules - /// @return stakingModulesCache array of StakingModuleCache structs + /// @dev Loads modules into a memory cache. + /// @return totalActiveValidators Total active validators across all modules. + /// @return stakingModulesCache Array of StakingModuleCache structs. function _loadStakingModulesCache() internal view returns ( uint256 totalActiveValidators, StakingModuleCache[] memory stakingModulesCache @@ -1359,8 +1379,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version ? depositableValidatorsCount : 0; - // the module might not receive all exited validators data yet => we need to replacing - // the exitedValidatorsCount with the one that the staking router is aware of + // The module might not receive all exited validators data yet => we need to replacing + // the exitedValidatorsCount with the one that the staking router is aware of. cacheItem.activeValidatorsCount = totalDepositedValidators - Math256.max(totalExitedValidators, stakingModuleData.exitedValidatorsCount); @@ -1377,7 +1397,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version function _getDepositsAllocation( uint256 _depositsToAllocate ) internal view returns (uint256 allocated, uint256[] memory allocations, StakingModuleCache[] memory stakingModulesCache) { - // calculate total used validators for operators + // Calculate total used validators for operators. uint256 totalActiveValidators; (totalActiveValidators, stakingModulesCache) = _loadStakingModulesCache(); @@ -1385,7 +1405,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 stakingModulesCount = stakingModulesCache.length; allocations = new uint256[](stakingModulesCount); if (stakingModulesCount > 0) { - /// @dev new estimated active validators count + /// @dev New estimated active validators count. totalActiveValidators += _depositsToAllocate; uint256[] memory capacities = new uint256[](stakingModulesCount); uint256 targetValidators; From 4565e725459e5039999f8f063af644c20ab018a6 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 25 Jun 2024 21:02:54 +0400 Subject: [PATCH 086/177] fix: owner agent --- scripts/staking-router-v2/sr-v2-deploy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 6abc9bf46..a6e28fcab 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -111,6 +111,8 @@ async function main() { ); await dsmContract.addGuardians(guardians, quorum); + await dsmContract.setOwner(APP_AGENT_ADDRESS); + log(`Guardians list: ${await dsmContract.getGuardians()}, quorum ${await dsmContract.getGuardianQuorum()}`); const accountingOracleArgs = [LOCATOR, LIDO, LEGACY_ORACLE, SECONDS_PER_SLOT, GENESIS_TIME]; From 8fa0b5834a39e7bbe4e6f14f53efcad18eb510d2 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 26 Jun 2024 00:25:35 +0400 Subject: [PATCH 087/177] fix: get rid of env for contracts parameters --- scripts/staking-router-v2/sr-v2-deploy.ts | 48 ++++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index a6e28fcab..0fad2e2c0 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -26,13 +26,20 @@ function getEnvVariable(name: string, defaultValue?: string) { } async function main() { - // parameters from env variables - const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = parseInt(getEnvVariable("PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS")); - const MAX_OPERATORS_PER_UNVETTING = parseInt(getEnvVariable("MAX_OPERATORS_PER_UNVETTING")); - const SECONDS_PER_SLOT = parseInt(getEnvVariable("SECONDS_PER_SLOT")); - const GENESIS_TIME = parseInt(getEnvVariable("GENESIS_TIME")); + // DSM args + const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 7200; + const MAX_OPERATORS_PER_UNVETTING = 20; + + // Accounting Oracle args + const SECONDS_PER_SLOT = 12; + const GENESIS_TIME = 1606824023; + + // Oracle report sanity checker + // 43200 check value + const LIMITS = [9000, 500, 1000, 50, 600, 4, 59, 7680, 750000, 43200]; + const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; + const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); - const APPEARED_VALIDATORS_PER_DAY_LIMIT = parseInt(getEnvVariable("APPEARED_VALIDATORS_PER_DAY_LIMIT")); const chainId = (await ethers.provider.getNetwork()).chainId; const balance = await ethers.provider.getBalance(deployer); @@ -42,14 +49,25 @@ async function main() { state[Sk.scratchDeployGasUsed] = 0n.toString(); persistNetworkState(state); - const maxPositiveTokenRebaseManagers: string[] = []; - const currentLimits = state[Sk.oracleReportSanityChecker].constructorArgs[2]; - const managersRoster = state[Sk.oracleReportSanityChecker].constructorArgs[3]; - const LIMITS_LIST = [...currentLimits, APPEARED_VALIDATORS_PER_DAY_LIMIT]; - const MANAGERS_ROSTER = [...managersRoster, maxPositiveTokenRebaseManagers]; - - const guardians = state[Sk.depositSecurityModule].guardians.addresses; - const quorum = state[Sk.depositSecurityModule].guardians.quorum; + // holesky + // "0x711B5fCfeD5A30CA78e0CAC321B060dE9D6f8979", + // "0xDAaE8C017f1E2a9bEC6111d288f9ebB165e0E163", + // "0x31fa51343297FFce0CC1E67a50B2D3428057D1b1", + // "0x43464Fe06c18848a2E2e913194D64c1970f4326a", + // "0x79A132BE0c25cED09e745629D47cf05e531bb2bb", + // "0x0bf1B3d1e6f78b12f26204348ABfCA9310259FfA", + // "0xf060ab3d5dCfdC6a0DFd5ca0645ac569b8f105CA" + // quorum = 3 + + const guardians = [ + "0x5fd0dDbC3351d009eb3f88DE7Cd081a614C519F1", + "0x7912Fa976BcDe9c2cf728e213e892AD7588E6AaF", + "0x14D5d5B71E048d2D75a39FfC5B407e3a3AB6F314", + "0xf82D88217C249297C6037BA77CE34b3d8a90ab43", + "0xa56b128Ea2Ea237052b0fA2a96a387C0E43157d8", + "0xd4EF84b638B334699bcf5AF4B0410B8CCD71943f", + ]; + const quorum = 4; // Read contracts addresses from config const DEPOSIT_CONTRACT_ADDRESS = state[Sk.chainSpec].depositContractAddress; @@ -123,7 +141,7 @@ async function main() { log(`AO implementation address: ${accountingOracleAddress}`); - const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS_LIST, MANAGERS_ROSTER]; + const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS, MANAGERS_ROSTER]; const oracleReportSanityCheckerAddress = ( await deployWithoutProxy( From a368e1cbc04ca4a627daca5d9d98bfc8533a90b0 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 26 Jun 2024 01:48:30 +0400 Subject: [PATCH 088/177] fix: deploy script for holesky with verification --- .../staking-router-v2/sr-v2-deploy-holesky.ts | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 scripts/staking-router-v2/sr-v2-deploy-holesky.ts diff --git a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts new file mode 100644 index 000000000..d9da41607 --- /dev/null +++ b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts @@ -0,0 +1,235 @@ +import { ethers, run } from "hardhat"; + +import { DepositSecurityModule, DepositSecurityModule__factory } from "typechain-types"; + +import { + deployImplementation, + deployWithoutProxy, + loadContract, + log, + persistNetworkState, + readNetworkState, + Sk, +} from "lib"; + +function getEnvVariable(name: string, defaultValue?: string) { + const value = process.env[name]; + if (value === undefined) { + if (defaultValue === undefined) { + throw new Error(`Env variable ${name} must be set`); + } + return defaultValue; + } else { + log(`Using env variable ${name}=${value}`); + return value; + } +} + +async function main() { + // DSM args + const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 7200; + const MAX_OPERATORS_PER_UNVETTING = 20; + + // Accounting Oracle args + const SECONDS_PER_SLOT = 12; + const GENESIS_TIME = 1695902400; + + // Oracle report sanity checker + // 43200 check value + const LIMITS = [9000, 500, 1000, 50, 600, 4, 59, 7680, 750000, 43200]; + const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; + + const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); + const chainId = (await ethers.provider.getNetwork()).chainId; + + const balance = await ethers.provider.getBalance(deployer); + log(`Deployer ${deployer} on network ${chainId} has balance: ${ethers.formatEther(balance)} ETH`); + + const state = readNetworkState(); + state[Sk.scratchDeployGasUsed] = 0n.toString(); + persistNetworkState(state); + + const guardians = [ + "0x711B5fCfeD5A30CA78e0CAC321B060dE9D6f8979", + "0xDAaE8C017f1E2a9bEC6111d288f9ebB165e0E163", + "0x31fa51343297FFce0CC1E67a50B2D3428057D1b1", + "0x43464Fe06c18848a2E2e913194D64c1970f4326a", + "0x79A132BE0c25cED09e745629D47cf05e531bb2bb", + "0x0bf1B3d1e6f78b12f26204348ABfCA9310259FfA", + "0xf060ab3d5dCfdC6a0DFd5ca0645ac569b8f105CA", + ]; + const quorum = 3; + + // Read contracts addresses from config + const DEPOSIT_CONTRACT_ADDRESS = state[Sk.chainSpec].depositContractAddress; + const APP_AGENT_ADDRESS = state[Sk.appAgent].proxy.address; + const SC_ADMIN = APP_AGENT_ADDRESS; + const LIDO = state[Sk.appLido].proxy.address; + const STAKING_ROUTER = state[Sk.stakingRouter].proxy.address; + const LOCATOR = state[Sk.lidoLocator].proxy.address; + const LEGACY_ORACLE = state[Sk.appOracle].proxy.address; + const ACCOUNTING_ORACLE_PROXY = state[Sk.accountingOracle].proxy.address; + const EL_REWARDS_VAULT = state[Sk.executionLayerRewardsVault].address; + const BURNER = state[Sk.burner].address; + const TREASURY_ADDRESS = APP_AGENT_ADDRESS; + const VEBO = state[Sk.validatorsExitBusOracle].proxy.address; + const WQ = state[Sk.withdrawalQueueERC721].proxy.address; + const WITHDRAWAL_VAULT = state[Sk.withdrawalVault].proxy.address; + const ORACLE_DAEMON_CONFIG = state[Sk.oracleDaemonConfig].address; + + // Deploy MinFirstAllocationStrategy + const minFirstAllocationStrategyAddress = ( + await deployWithoutProxy(Sk.minFirstAllocationStrategy, "MinFirstAllocationStrategy", deployer) + ).address; + + log(`MinFirstAllocationStrategy address: ${minFirstAllocationStrategyAddress}`); + + const libraries = { + MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, + }; + + const stakingRouterAddress = ( + await deployImplementation(Sk.stakingRouter, "StakingRouter", deployer, [DEPOSIT_CONTRACT_ADDRESS], { libraries }) + ).address; + + log(`StakingRouter implementation address: ${stakingRouterAddress}`); + + const appNodeOperatorsRegistry = ( + await deployImplementation(Sk.appNodeOperatorsRegistry, "NodeOperatorsRegistry", deployer, [], { libraries }) + ).address; + + log(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`); + + const depositSecurityModuleParams = [ + LIDO, + DEPOSIT_CONTRACT_ADDRESS, + STAKING_ROUTER, + PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS, + MAX_OPERATORS_PER_UNVETTING, + ]; + + const depositSecurityModuleAddress = ( + await deployWithoutProxy(Sk.depositSecurityModule, "DepositSecurityModule", deployer, depositSecurityModuleParams) + ).address; + + log(`New DSM address: ${depositSecurityModuleAddress}`); + + const dsmContract = await loadContract( + DepositSecurityModule__factory, + depositSecurityModuleAddress, + ); + await dsmContract.addGuardians(guardians, quorum); + + await dsmContract.setOwner(APP_AGENT_ADDRESS); + + log(`Guardians list: ${await dsmContract.getGuardians()}, quorum ${await dsmContract.getGuardianQuorum()}`); + + const accountingOracleArgs = [LOCATOR, LIDO, LEGACY_ORACLE, SECONDS_PER_SLOT, GENESIS_TIME]; + + const accountingOracleAddress = ( + await deployImplementation(Sk.accountingOracle, "AccountingOracle", deployer, accountingOracleArgs) + ).address; + + log(`AO implementation address: ${accountingOracleAddress}`); + + const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS, MANAGERS_ROSTER]; + + const oracleReportSanityCheckerAddress = ( + await deployWithoutProxy( + Sk.oracleReportSanityChecker, + "OracleReportSanityChecker", + deployer, + oracleReportSanityCheckerArgs, + ) + ).address; + + log(`OracleReportSanityChecker new address ${oracleReportSanityCheckerAddress}`); + + const locatorConfig = [ + [ + ACCOUNTING_ORACLE_PROXY, + depositSecurityModuleAddress, + EL_REWARDS_VAULT, + LEGACY_ORACLE, + LIDO, + oracleReportSanityCheckerAddress, + LEGACY_ORACLE, + BURNER, + STAKING_ROUTER, + TREASURY_ADDRESS, + VEBO, + WQ, + WITHDRAWAL_VAULT, + ORACLE_DAEMON_CONFIG, + ], + ]; + + const locatorAddress = (await deployImplementation(Sk.lidoLocator, "LidoLocator", deployer, locatorConfig)).address; + + log(`Locator implementation address ${locatorAddress}`); + + // verification part + + log("sleep before starting verification of contracts..."); + sleep(60_000); + log("start verification of contracts..."); + + await run("verify:verify", { + address: minFirstAllocationStrategyAddress, + constructorArguments: [], + // contract: "contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy", + }); + + await run("verify:verify", { + address: stakingRouterAddress, + constructorArguments: [DEPOSIT_CONTRACT_ADDRESS], + libraries: { + MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, + }, + // contract: "contracts/0.8.9/StakingRouter.sol:StakingRouter", + }); + + await run("verify:verify", { + address: appNodeOperatorsRegistry, + constructorArguments: [], + libraries: { + MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, + }, + // contract: "contracts/0.4.24/nos/NodeOperatorsRegistry.sol:NodeOperatorsRegistry", + }); + + await run("verify:verify", { + address: depositSecurityModuleAddress, + constructorArguments: depositSecurityModuleParams, + // contract: "contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule", + }); + + await run("verify:verify", { + address: accountingOracleAddress, + constructorArguments: accountingOracleArgs, + // contract: "contracts/0.8.9/oracle/AccountingOracle.sol:AccountingOracle", + }); + + await run("verify:verify", { + address: oracleReportSanityCheckerAddress, + constructorArguments: oracleReportSanityCheckerArgs, + // contract: "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol:OracleReportSanityChecker", + }); + + await run("verify:verify", { + address: locatorAddress, + constructorArguments: locatorConfig, + contract: "contracts/0.8.9/LidoLocator.sol:LidoLocator", + }); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + log.error(error); + process.exit(1); + }); From 9009332499acbc518c689bf13a619018503bf8b2 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 26 Jun 2024 08:29:25 +0400 Subject: [PATCH 089/177] fix: lint --- scripts/staking-router-v2/sr-v2-deploy-holesky.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts index d9da41607..e2ccf78f5 100644 --- a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts +++ b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts @@ -171,7 +171,7 @@ async function main() { // verification part log("sleep before starting verification of contracts..."); - sleep(60_000); + await sleep(10_000); log("start verification of contracts..."); await run("verify:verify", { From a4d3330d6ef10cd25159b02e8b4d3f653213a4b7 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Wed, 26 Jun 2024 12:27:29 +0400 Subject: [PATCH 090/177] fix: verification with concrete contracts impl --- scripts/staking-router-v2/sr-v2-deploy-holesky.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts index e2ccf78f5..a55049cab 100644 --- a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts +++ b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts @@ -177,7 +177,7 @@ async function main() { await run("verify:verify", { address: minFirstAllocationStrategyAddress, constructorArguments: [], - // contract: "contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy", + contract: "contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy", }); await run("verify:verify", { @@ -186,7 +186,7 @@ async function main() { libraries: { MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, }, - // contract: "contracts/0.8.9/StakingRouter.sol:StakingRouter", + contract: "contracts/0.8.9/StakingRouter.sol:StakingRouter", }); await run("verify:verify", { @@ -195,25 +195,25 @@ async function main() { libraries: { MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, }, - // contract: "contracts/0.4.24/nos/NodeOperatorsRegistry.sol:NodeOperatorsRegistry", + contract: "contracts/0.4.24/nos/NodeOperatorsRegistry.sol:NodeOperatorsRegistry", }); await run("verify:verify", { address: depositSecurityModuleAddress, constructorArguments: depositSecurityModuleParams, - // contract: "contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule", + contract: "contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule", }); await run("verify:verify", { address: accountingOracleAddress, constructorArguments: accountingOracleArgs, - // contract: "contracts/0.8.9/oracle/AccountingOracle.sol:AccountingOracle", + contract: "contracts/0.8.9/oracle/AccountingOracle.sol:AccountingOracle", }); await run("verify:verify", { address: oracleReportSanityCheckerAddress, constructorArguments: oracleReportSanityCheckerArgs, - // contract: "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol:OracleReportSanityChecker", + contract: "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol:OracleReportSanityChecker", }); await run("verify:verify", { From 4293a8e2b2314299e958591a8d35062b3778329f Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 27 Jun 2024 16:34:33 +0200 Subject: [PATCH 091/177] feat: update staking limits --- scripts/staking-router-v2/sr-v2-deploy-holesky.ts | 4 ++-- scripts/staking-router-v2/sr-v2-deploy.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts index a55049cab..5572e379c 100644 --- a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts +++ b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts @@ -28,7 +28,7 @@ function getEnvVariable(name: string, defaultValue?: string) { async function main() { // DSM args const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 7200; - const MAX_OPERATORS_PER_UNVETTING = 20; + const MAX_OPERATORS_PER_UNVETTING = 200; // Accounting Oracle args const SECONDS_PER_SLOT = 12; @@ -36,7 +36,7 @@ async function main() { // Oracle report sanity checker // 43200 check value - const LIMITS = [9000, 500, 1000, 50, 600, 4, 59, 7680, 750000, 43200]; + const LIMITS = [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200]; const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 0fad2e2c0..73f09236b 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -28,7 +28,7 @@ function getEnvVariable(name: string, defaultValue?: string) { async function main() { // DSM args const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 7200; - const MAX_OPERATORS_PER_UNVETTING = 20; + const MAX_OPERATORS_PER_UNVETTING = 200; // Accounting Oracle args const SECONDS_PER_SLOT = 12; @@ -36,7 +36,7 @@ async function main() { // Oracle report sanity checker // 43200 check value - const LIMITS = [9000, 500, 1000, 50, 600, 4, 59, 7680, 750000, 43200]; + const LIMITS = [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200]; const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); From df3c6607f8b53459999f2bad4025e3fa80e405e6 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 27 Jun 2024 16:51:25 +0200 Subject: [PATCH 092/177] feat: update PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS --- scripts/staking-router-v2/sr-v2-deploy-holesky.ts | 2 +- scripts/staking-router-v2/sr-v2-deploy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts index 5572e379c..0dfbe5a6e 100644 --- a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts +++ b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts @@ -27,7 +27,7 @@ function getEnvVariable(name: string, defaultValue?: string) { async function main() { // DSM args - const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 7200; + const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; const MAX_OPERATORS_PER_UNVETTING = 200; // Accounting Oracle args diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 73f09236b..b40696905 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -27,7 +27,7 @@ function getEnvVariable(name: string, defaultValue?: string) { async function main() { // DSM args - const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 7200; + const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; const MAX_OPERATORS_PER_UNVETTING = 200; // Accounting Oracle args From d1e7cd93eba9a2e9d8dc738e9261e4caebbe69d0 Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 27 Jun 2024 19:16:07 +0200 Subject: [PATCH 093/177] fix: deploy params --- scripts/staking-router-v2/sr-v2-deploy-holesky.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts index a55049cab..0dfbe5a6e 100644 --- a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts +++ b/scripts/staking-router-v2/sr-v2-deploy-holesky.ts @@ -27,8 +27,8 @@ function getEnvVariable(name: string, defaultValue?: string) { async function main() { // DSM args - const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 7200; - const MAX_OPERATORS_PER_UNVETTING = 20; + const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; + const MAX_OPERATORS_PER_UNVETTING = 200; // Accounting Oracle args const SECONDS_PER_SLOT = 12; @@ -36,7 +36,7 @@ async function main() { // Oracle report sanity checker // 43200 check value - const LIMITS = [9000, 500, 1000, 50, 600, 4, 59, 7680, 750000, 43200]; + const LIMITS = [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200]; const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); From 30bd6ca155e34957faf7b62c0ef27681cd630613 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Sun, 30 Jun 2024 23:55:22 +0400 Subject: [PATCH 094/177] fix: refactoring --- .gitignore | 3 - ...sky.json.template => deployed-holesky.json | 77 ++------ ...net.json.template => deployed-mainnet.json | 47 +++-- lib/state-file.ts | 1 + .../sr-v2-deploy-holesky.ts | 0 scripts/staking-router-v2/sr-v2-deploy.ts | 170 ++++++++++++------ 6 files changed, 152 insertions(+), 146 deletions(-) rename scripts/staking-router-v2/deployed-holesky.json.template => deployed-holesky.json (94%) rename scripts/staking-router-v2/deployed-mainnet.json.template => deployed-mainnet.json (95%) rename scripts/{staking-router-v2 => archive}/sr-v2-deploy-holesky.ts (100%) diff --git a/.gitignore b/.gitignore index 10b013cbb..be3682bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,8 +20,5 @@ foundry/out/ .env deployed-local.json -deployed-mainnet.json -deployed-holesky.json - # MacOS .DS_Store diff --git a/scripts/staking-router-v2/deployed-holesky.json.template b/deployed-holesky.json similarity index 94% rename from scripts/staking-router-v2/deployed-holesky.json.template rename to deployed-holesky.json index 2974128af..199a19df6 100644 --- a/scripts/staking-router-v2/deployed-holesky.json.template +++ b/deployed-holesky.json @@ -363,16 +363,12 @@ "implementation": { "contract": "@aragon/os/contracts/kernel/Kernel.sol", "address": "0x34c0cbf9836FD945423bD3d2d72880da9d068E5F", - "constructorArgs": [ - true - ] + "constructorArgs": [true] }, "proxy": { "address": "0x3b03f75Ec541Ca11a223bB58621A3146246E1644", "contract": "@aragon/os/contracts/kernel/KernelProxy.sol", - "constructorArgs": [ - "0x34c0cbf9836FD945423bD3d2d72880da9d068E5F" - ] + "constructorArgs": ["0x34c0cbf9836FD945423bD3d2d72880da9d068E5F"] } }, "aragonEnsLabelName": "aragonpm", @@ -477,9 +473,7 @@ "eip712StETH": { "contract": "contracts/0.8.9/EIP712StETH.sol", "address": "0xE154732c5Eab277fd88a9fF6Bdff7805eD97BCB1", - "constructorArgs": [ - "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034" - ] + "constructorArgs": ["0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034"] }, "ensAddress": "0x4327d1Fc6E5fa0326CCAE737F67C066c50BcC258", "ensFactoryAddress": "0xADba3e3122F2Da8F7B07723a3e1F1cEDe3fe8d7d", @@ -490,10 +484,7 @@ "executionLayerRewardsVault": { "contract": "contracts/0.8.9/LidoExecutionLayerRewardsVault.sol", "address": "0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8", - "constructorArgs": [ - "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", - "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" - ] + "constructorArgs": ["0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d"] }, "gateSeal": { "factoryAddress": "0x1134F7077055b0B3559BE52AfeF9aA22A0E1eEC2", @@ -625,10 +616,7 @@ "oracleDaemonConfig": { "contract": "contracts/0.8.9/OracleDaemonConfig.sol", "address": "0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7", - "constructorArgs": [ - "0x22896Bfc68814BFD855b1a167255eE497006e730", - [] - ], + "constructorArgs": ["0x22896Bfc68814BFD855b1a167255eE497006e730", []], "deployParameters": { "NORMALIZED_CL_REWARD_PER_EPOCH": 64, "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, @@ -658,29 +646,8 @@ "constructorArgs": [ "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8", "0x22896Bfc68814BFD855b1a167255eE497006e730", - [ - 1500, - 500, - 1000, - 250, - 2000, - 100, - 100, - 128, - 5000000 - ], - [ - [], - [], - [], - [], - [], - [], - [], - [], - [], - [] - ] + [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000], + [[], [], [], [], [], [], [], [], [], []] ] }, "stakingRouter": { @@ -696,9 +663,7 @@ "implementation": { "contract": "contracts/0.8.9/StakingRouter.sol", "address": "0x32f236423928c2c138F46351D9E5FD26331B1aa4", - "constructorArgs": [ - "0x4242424242424242424242424242424242424242" - ] + "constructorArgs": ["0x4242424242424242424242424242424242424242"] } }, "validatorsExitBusOracle": { @@ -717,11 +682,7 @@ "implementation": { "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", "address": "0x210f60EC8A4D020b3e22f15fee2d2364e9b22357", - "constructorArgs": [ - 12, - 1695902400, - "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8" - ] + "constructorArgs": [12, 1695902400, "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8"] } }, "vestingParams": { @@ -753,36 +714,24 @@ "implementation": { "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", "address": "0xFF72B5cdc701E9eE677966B2702c766c38F412a4", - "constructorArgs": [ - "0x8d09a4502Cc8Cf1547aD300E066060D043f6982D", - "stETH Withdrawal NFT", - "unstETH" - ] + "constructorArgs": ["0x8d09a4502Cc8Cf1547aD300E066060D043f6982D", "stETH Withdrawal NFT", "unstETH"] } }, "withdrawalVault": { "implementation": { "contract": "contracts/0.8.9/WithdrawalVault.sol", "address": "0xd517d9d04DA9B47dA23df91261bd3bF435BE964A", - "constructorArgs": [ - "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", - "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d" - ] + "constructorArgs": ["0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d"] }, "proxy": { "contract": "contracts/0.8.4/WithdrawalsManagerProxy.sol", "address": "0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9", - "constructorArgs": [ - "0xdA7d2573Df555002503F29aA4003e398d28cc00f", - "0xd517d9d04DA9B47dA23df91261bd3bF435BE964A" - ] + "constructorArgs": ["0xdA7d2573Df555002503F29aA4003e398d28cc00f", "0xd517d9d04DA9B47dA23df91261bd3bF435BE964A"] } }, "wstETH": { "contract": "contracts/0.6.12/WstETH.sol", "address": "0x8d09a4502Cc8Cf1547aD300E066060D043f6982D", - "constructorArgs": [ - "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034" - ] + "constructorArgs": ["0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034"] } } diff --git a/scripts/staking-router-v2/deployed-mainnet.json.template b/deployed-mainnet.json similarity index 95% rename from scripts/staking-router-v2/deployed-mainnet.json.template rename to deployed-mainnet.json index d78fe8fdd..f274eb26b 100644 --- a/scripts/staking-router-v2/deployed-mainnet.json.template +++ b/deployed-mainnet.json @@ -14,9 +14,8 @@ ] }, "implementation": { - "address": "0xF3c5E0A67f32CF1dc07a8817590efa102079a1aF", "contract": "contracts/0.8.9/oracle/AccountingOracle.sol", - "deployTx": "0x3e27627d3ed236aff8901df187196e9682187dfd0d259c5d5811a6e923436083", + "address": "0x8dF2a20225a5577fB173271c3777CF45305e816d", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", @@ -114,14 +113,9 @@ "address": "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" }, "implementation": { - "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", - "deployTx": "0xbec5b6ffb8fbc775a183e25cd285367993d7201752e1638a07abec8962bab750", - "constructorArgs": [], - "deployParameters": { - "stakingModuleTypeId": "curated-onchain-v1", - "stuckPenaltyDelay": "432000" - } + "address": "0x5f58879Fe3a4330B6D85c1015971Ea6e5175AeDD", + "constructorArgs": [] }, "aragonApp": { "fullName": "node-operators-registry.lidopm.eth", @@ -251,16 +245,15 @@ "deployCommit": "e45c4d6fb8120fd29426b8d969c19d8a798ca974", "deployer": "0x55Bc991b2edF3DDb4c520B222bE4F378418ff0fA", "depositSecurityModule": { - "address": "0xC77F8768774E1c9244BEed705C4354f2113CFc09", + "address": "0x582957C7a35CDfeAAD1Ca4b87AE03913eAAd0Be0", "contract": "contracts/0.8.9/DepositSecurityModule.sol", "deployTx": "0x21307a2321f167f99de11ccec86d7bdd8233481bbffa493e15c519ca8d662c4f", "constructorArgs": [ "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x00000000219ab540356cBB839Cbe05303d7705Fa", "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", - 150, - 25, - 6646 + 6646, + 200 ], "deployParameters": { "maxDepositsPerBlock": 150, @@ -354,17 +347,16 @@ ] }, "implementation": { - "address": "0x1D920cc5bACf7eE506a271a5259f2417CaDeCE1d", "contract": "contracts/0.8.9/LidoLocator.sol", - "deployTx": "0xf90012ef0a40e47c909ab3a5b3503ecee78f6a9be134d1349a742e500d37ae33", + "address": "0x645B0f55268eF561176f3247D06d0b7742f79819", "constructorArgs": [ [ "0x852deD011285fe67063a08005c71a85690503Cee", - "0xC77F8768774E1c9244BEed705C4354f2113CFc09", + "0x582957C7a35CDfeAAD1Ca4b87AE03913eAAd0Be0", "0x388C818CA8B9251b393131C08a736A67ccB19297", "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", + "0xdccF554708B72d0fe9500cBfc1595cDBE3d66e5a", "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", "0xD15a672319Cf0352560eE76d9e89eAB0889046D3", "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", @@ -390,6 +382,11 @@ "0xa0BC4B67F5FacDE4E50EAFF48691Cfc43F4E280A" ] }, + "minFirstAllocationStrategy": { + "contract": "contracts/common/lib/MinFirstAllocationStrategy.sol", + "address": "0xA901DA770A472Caf6E6698261BB02ea58C5d3235", + "constructorArgs": [] + }, "miniMeTokenFactoryAddress": "0x909d05f384d0663ed4be59863815ab43b4f347ec", "networkId": 1, "newDaoTx": "0x3feabd79e8549ad68d1827c074fa7123815c80206498946293d5373a160fd866", @@ -414,22 +411,23 @@ } }, "oracleReportSanityChecker": { - "address": "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", + "address": "0xdccF554708B72d0fe9500cBfc1595cDBE3d66e5a", "contract": "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol", "deployTx": "0x700c83996ad7deefda286044280ad86108dfef9c880909bd8e75a3746f7d631c", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", [ - 20000, + 9000, 500, 1000, 50, 600, - 2, - 100, + 8, + 62, 7680, - 750000 + 750000, + 43200 ], [ [], @@ -441,6 +439,7 @@ [], [], [], + [], [] ] ], @@ -456,6 +455,7 @@ "maxPositiveTokenRebase": 750000 } }, + "scratchDeployGasUsed": "20484611", "stakingRouter": { "proxy": { "address": "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", @@ -468,9 +468,8 @@ ] }, "implementation": { - "address": "0xD8784e748f59Ba711fB5643191Ec3fAdD50Fb6df", "contract": "contracts/0.8.9/StakingRouter.sol", - "deployTx": "0xd6d489f22203c835da6027ff0e532a01a08f36f0fda6c7c0a42e471ae3b3c461", + "address": "0x1966dc8ff30Bc4AeDEd27178642253b3cCC9AA3f", "constructorArgs": [ "0x00000000219ab540356cBB839Cbe05303d7705Fa" ] diff --git a/lib/state-file.ts b/lib/state-file.ts index 877416578..5fe76df77 100644 --- a/lib/state-file.ts +++ b/lib/state-file.ts @@ -30,6 +30,7 @@ export enum Sk { appLido = "app:lido", appOracle = `app:oracle`, appNodeOperatorsRegistry = "app:node-operators-registry", + appSimpleDvt = "app:simple-dvt", aragonAcl = "aragon-acl", aragonEvmScriptRegistry = "aragon-evm-script-registry", aragonApmRegistry = "aragon-apm-registry", diff --git a/scripts/staking-router-v2/sr-v2-deploy-holesky.ts b/scripts/archive/sr-v2-deploy-holesky.ts similarity index 100% rename from scripts/staking-router-v2/sr-v2-deploy-holesky.ts rename to scripts/archive/sr-v2-deploy-holesky.ts diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index b40696905..576f6320e 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -1,10 +1,12 @@ -import { ethers } from "hardhat"; +import { ethers, run } from "hardhat"; import { DepositSecurityModule, DepositSecurityModule__factory } from "typechain-types"; import { + cy, deployImplementation, deployWithoutProxy, + gr, loadContract, log, persistNetworkState, @@ -12,6 +14,8 @@ import { Sk, } from "lib"; +import readline from "readline"; + function getEnvVariable(name: string, defaultValue?: string) { const value = process.env[name]; if (value === undefined) { @@ -25,50 +29,33 @@ function getEnvVariable(name: string, defaultValue?: string) { } } -async function main() { - // DSM args - const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; - const MAX_OPERATORS_PER_UNVETTING = 200; - - // Accounting Oracle args - const SECONDS_PER_SLOT = 12; - const GENESIS_TIME = 1606824023; - - // Oracle report sanity checker - // 43200 check value - const LIMITS = [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200]; - const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; +// Accounting Oracle args +const SECONDS_PER_SLOT = 12; +const GENESIS_TIME = 1606824023; +// Oracle report sanity checker +const LIMITS = [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200]; +const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; +// DSM args +const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; +const MAX_OPERATORS_PER_UNVETTING = 200; +const guardians = [ + "0x5fd0dDbC3351d009eb3f88DE7Cd081a614C519F1", + "0x7912Fa976BcDe9c2cf728e213e892AD7588E6AaF", + "0x14D5d5B71E048d2D75a39FfC5B407e3a3AB6F314", + "0xf82D88217C249297C6037BA77CE34b3d8a90ab43", + "0xa56b128Ea2Ea237052b0fA2a96a387C0E43157d8", + "0xd4EF84b638B334699bcf5AF4B0410B8CCD71943f", +]; +const quorum = 4; +async function main() { const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); const chainId = (await ethers.provider.getNetwork()).chainId; - const balance = await ethers.provider.getBalance(deployer); - log(`Deployer ${deployer} on network ${chainId} has balance: ${ethers.formatEther(balance)} ETH`); - const state = readNetworkState(); state[Sk.scratchDeployGasUsed] = 0n.toString(); persistNetworkState(state); - // holesky - // "0x711B5fCfeD5A30CA78e0CAC321B060dE9D6f8979", - // "0xDAaE8C017f1E2a9bEC6111d288f9ebB165e0E163", - // "0x31fa51343297FFce0CC1E67a50B2D3428057D1b1", - // "0x43464Fe06c18848a2E2e913194D64c1970f4326a", - // "0x79A132BE0c25cED09e745629D47cf05e531bb2bb", - // "0x0bf1B3d1e6f78b12f26204348ABfCA9310259FfA", - // "0xf060ab3d5dCfdC6a0DFd5ca0645ac569b8f105CA" - // quorum = 3 - - const guardians = [ - "0x5fd0dDbC3351d009eb3f88DE7Cd081a614C519F1", - "0x7912Fa976BcDe9c2cf728e213e892AD7588E6AaF", - "0x14D5d5B71E048d2D75a39FfC5B407e3a3AB6F314", - "0xf82D88217C249297C6037BA77CE34b3d8a90ab43", - "0xa56b128Ea2Ea237052b0fA2a96a387C0E43157d8", - "0xd4EF84b638B334699bcf5AF4B0410B8CCD71943f", - ]; - const quorum = 4; - // Read contracts addresses from config const DEPOSIT_CONTRACT_ADDRESS = state[Sk.chainSpec].depositContractAddress; const APP_AGENT_ADDRESS = state[Sk.appAgent].proxy.address; @@ -90,25 +77,30 @@ async function main() { const minFirstAllocationStrategyAddress = ( await deployWithoutProxy(Sk.minFirstAllocationStrategy, "MinFirstAllocationStrategy", deployer) ).address; - - log(`MinFirstAllocationStrategy address: ${minFirstAllocationStrategyAddress}`); + log.success(gr(`MinFirstAllocationStrategy address: ${minFirstAllocationStrategyAddress}`)); + log.emptyLine(); const libraries = { MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, }; + // Deploy StakingRouter const stakingRouterAddress = ( await deployImplementation(Sk.stakingRouter, "StakingRouter", deployer, [DEPOSIT_CONTRACT_ADDRESS], { libraries }) ).address; + log.success(gr(`StakingRouter implementation address: ${stakingRouterAddress}`)); + log.emptyLine(); - log(`StakingRouter implementation address: ${stakingRouterAddress}`); - + // Deploy NOR const appNodeOperatorsRegistry = ( await deployImplementation(Sk.appNodeOperatorsRegistry, "NodeOperatorsRegistry", deployer, [], { libraries }) ).address; + log.success(gr(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`)); + log.emptyLine(); - log(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`); + // TODO: update sdvt too + // Deploy DSM const depositSecurityModuleParams = [ LIDO, DEPOSIT_CONTRACT_ADDRESS, @@ -116,33 +108,32 @@ async function main() { PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS, MAX_OPERATORS_PER_UNVETTING, ]; - const depositSecurityModuleAddress = ( await deployWithoutProxy(Sk.depositSecurityModule, "DepositSecurityModule", deployer, depositSecurityModuleParams) ).address; - - log(`New DSM address: ${depositSecurityModuleAddress}`); + log.success(gr(`New DSM address: ${depositSecurityModuleAddress}`)); + log.emptyLine(); const dsmContract = await loadContract( DepositSecurityModule__factory, depositSecurityModuleAddress, ); await dsmContract.addGuardians(guardians, quorum); - await dsmContract.setOwner(APP_AGENT_ADDRESS); + log.success(gr(`Guardians list: ${await dsmContract.getGuardians()}`)); + log.success(gr(`Quorum: ${await dsmContract.getGuardianQuorum()}`)); + log.emptyLine(); - log(`Guardians list: ${await dsmContract.getGuardians()}, quorum ${await dsmContract.getGuardianQuorum()}`); - + // Deploy AO const accountingOracleArgs = [LOCATOR, LIDO, LEGACY_ORACLE, SECONDS_PER_SLOT, GENESIS_TIME]; - const accountingOracleAddress = ( await deployImplementation(Sk.accountingOracle, "AccountingOracle", deployer, accountingOracleArgs) ).address; + log.success(gr(`AO implementation address: ${accountingOracleAddress}`)); + log.emptyLine(); - log(`AO implementation address: ${accountingOracleAddress}`); - + // Deploy OracleReportSanityCheckerArgs const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS, MANAGERS_ROSTER]; - const oracleReportSanityCheckerAddress = ( await deployWithoutProxy( Sk.oracleReportSanityChecker, @@ -151,8 +142,8 @@ async function main() { oracleReportSanityCheckerArgs, ) ).address; - - log(`OracleReportSanityChecker new address ${oracleReportSanityCheckerAddress}`); + log.success(gr(`OracleReportSanityChecker new address ${oracleReportSanityCheckerAddress}`)); + log.emptyLine(); const locatorConfig = [ [ @@ -174,8 +165,77 @@ async function main() { ]; const locatorAddress = (await deployImplementation(Sk.lidoLocator, "LidoLocator", deployer, locatorConfig)).address; + log.success(gr(`Locator implementation address ${locatorAddress}`)); + log.emptyLine(); + + if (getEnvVariable("RUN_ON_FORK", "false") === "true") { + log(cy("Deploy script was executed on fork, will skip verification")); + return; + } - log(`Locator implementation address ${locatorAddress}`); + await waitForPressButton(); + + log(cy("Continuing...")); + + await run("verify:verify", { + address: minFirstAllocationStrategyAddress, + constructorArguments: [], + contract: "contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy", + }); + + await run("verify:verify", { + address: stakingRouterAddress, + constructorArguments: [DEPOSIT_CONTRACT_ADDRESS], + libraries: { + MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, + }, + contract: "contracts/0.8.9/StakingRouter.sol:StakingRouter", + }); + + await run("verify:verify", { + address: appNodeOperatorsRegistry, + constructorArguments: [], + libraries: { + MinFirstAllocationStrategy: minFirstAllocationStrategyAddress, + }, + contract: "contracts/0.4.24/nos/NodeOperatorsRegistry.sol:NodeOperatorsRegistry", + }); + + await run("verify:verify", { + address: depositSecurityModuleAddress, + constructorArguments: depositSecurityModuleParams, + contract: "contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule", + }); + + await run("verify:verify", { + address: accountingOracleAddress, + constructorArguments: accountingOracleArgs, + contract: "contracts/0.8.9/oracle/AccountingOracle.sol:AccountingOracle", + }); + + await run("verify:verify", { + address: oracleReportSanityCheckerAddress, + constructorArguments: oracleReportSanityCheckerArgs, + contract: "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol:OracleReportSanityChecker", + }); + + await run("verify:verify", { + address: locatorAddress, + constructorArguments: locatorConfig, + contract: "contracts/0.8.9/LidoLocator.sol:LidoLocator", + }); +} + +async function waitForPressButton(): Promise { + return new Promise((resolve) => { + log(cy("When contracts will be ready for verification step, press Enter to continue...")); + const rl = readline.createInterface({ input: process.stdin }); + + rl.on("line", () => { + rl.close(); + resolve(); + }); + }); } main() From 006245714a0f1bf49869a771b8d7248a1c6c8c18 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 1 Jul 2024 00:13:50 +0400 Subject: [PATCH 095/177] fix: addresses --- deployed-holesky.json | 52 ++++++++++++++--------------- deployed-mainnet.json | 78 ++++++------------------------------------- 2 files changed, 36 insertions(+), 94 deletions(-) diff --git a/deployed-holesky.json b/deployed-holesky.json index 199a19df6..dea0d543f 100644 --- a/deployed-holesky.json +++ b/deployed-holesky.json @@ -14,7 +14,7 @@ }, "implementation": { "contract": "contracts/0.8.9/oracle/AccountingOracle.sol", - "address": "0x6AcA050709469F1f98d8f40f68b1C83B533cd2b2", + "address": "0x748CE008ac6b15634ceD5a6083796f75695052a2", "constructorArgs": [ "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8", "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", @@ -133,7 +133,7 @@ "app:node-operators-registry": { "implementation": { "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", - "address": "0xE0270CF2564d81E02284e16539F59C1B5a4718fE", + "address": "0x605A3AFadF35A8a8fa4f4Cd4fe34a09Bbcea7718", "constructorArgs": [] }, "aragonApp": { @@ -206,6 +206,11 @@ "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4", "0x" ] + }, + "implementation": { + "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", + "address": "0x605A3AFadF35A8a8fa4f4Cd4fe34a09Bbcea7718", + "constructorArgs": [] } }, "aragon-acl": { @@ -443,27 +448,14 @@ "pauseIntentValidityPeriodBlocks": 6646 }, "contract": "contracts/0.8.9/DepositSecurityModule.sol", - "address": "0x045dd46212A178428c088573A7d102B9d89a022A", + "address": "0x808DE3b26Be9438F12E9B45528955EA94C17f217", "constructorArgs": [ "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", "0x4242424242424242424242424242424242424242", "0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229", - 150, - 5, - 6646 - ], - "guardians": { - "addresses": [ - "0x711B5fCfeD5A30CA78e0CAC321B060dE9D6f8979", - "0xDAaE8C017f1E2a9bEC6111d288f9ebB165e0E163", - "0x31fa51343297FFce0CC1E67a50B2D3428057D1b1", - "0x43464Fe06c18848a2E2e913194D64c1970f4326a", - "0x79A132BE0c25cED09e745629D47cf05e531bb2bb", - "0x0bf1B3d1e6f78b12f26204348ABfCA9310259FfA", - "0xf060ab3d5dCfdC6a0DFd5ca0645ac569b8f105CA" - ], - "quorum": 3 - } + 6646, + 200 + ] }, "dummyEmptyContract": { "contract": "contracts/0.8.9/test_helpers/DummyEmptyContract.sol", @@ -567,15 +559,15 @@ }, "implementation": { "contract": "contracts/0.8.9/LidoLocator.sol", - "address": "0xDba5Ad530425bb1b14EECD76F1b4a517780de537", + "address": "0xab89ED3D8f31bcF8BB7De53F02084d1e6F043D34", "constructorArgs": [ [ "0x4E97A3972ce8511D87F334dA17a2C332542a5246", - "0x045dd46212A178428c088573A7d102B9d89a022A", + "0x808DE3b26Be9438F12E9B45528955EA94C17f217", "0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8", "0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019", "0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034", - "0xF0d576c7d934bBeCc68FE15F1c5DAF98ea2B78bb", + "0xd7C870777e08325Ad0A3A85F41E66E7D84B63E4f", "0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019", "0x4E46BD7147ccf666E1d73A3A456fC7a68de82eCA", "0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229", @@ -603,6 +595,11 @@ }, "lidoTemplateCreateStdAppReposTx": "0x3f5b8918667bd3e971606a54a907798720158587df355a54ce07c0d0f9750d3c", "lidoTemplateNewDaoTx": "0x3346246f09f91ffbc260b6c300b11ababce9f5ca54d7880a277860961f343112", + "minFirstAllocationStrategy": { + "contract": "contracts/common/lib/MinFirstAllocationStrategy.sol", + "address": "0xf95A8103E6d83B4437Ad20454F86a75ecf1E32Ef", + "constructorArgs": [] + }, "miniMeTokenFactoryAddress": "0x15ef666c9620C0f606Ba35De2aF668fe987E26ae", "miniMeTokenFactoryConstructorArgs": [], "networkId": 17000, @@ -642,14 +639,15 @@ "maxPositiveTokenRebase": 5000000 }, "contract": "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol", - "address": "0xF0d576c7d934bBeCc68FE15F1c5DAF98ea2B78bb", + "address": "0xd7C870777e08325Ad0A3A85F41E66E7D84B63E4f", "constructorArgs": [ "0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8", - "0x22896Bfc68814BFD855b1a167255eE497006e730", - [1500, 500, 1000, 250, 2000, 100, 100, 128, 5000000], - [[], [], [], [], [], [], [], [], [], []] + "0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d", + [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200], + [[], [], [], [], [], [], [], [], [], [], []] ] }, + "scratchDeployGasUsed": "20484707", "stakingRouter": { "proxy": { "contract": "contracts/0.8.9/proxy/OssifiableProxy.sol", @@ -662,7 +660,7 @@ }, "implementation": { "contract": "contracts/0.8.9/StakingRouter.sol", - "address": "0x32f236423928c2c138F46351D9E5FD26331B1aa4", + "address": "0x9b5890E950E3Df487Bb64E0A6743cdE791139152", "constructorArgs": ["0x4242424242424242424242424242424242424242"] } }, diff --git a/deployed-mainnet.json b/deployed-mainnet.json index f274eb26b..a42a9c60b 100644 --- a/deployed-mainnet.json +++ b/deployed-mainnet.json @@ -114,7 +114,7 @@ }, "implementation": { "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", - "address": "0x5f58879Fe3a4330B6D85c1015971Ea6e5175AeDD", + "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", "constructorArgs": [] }, "aragonApp": { @@ -189,9 +189,7 @@ "implementation": { "contract": "@aragon/os/contracts/kernel/Kernel.sol", "address": "0x2b33CF282f867A7FF693A66e11B0FcC5552e4425", - "constructorArgs": [ - true - ] + "constructorArgs": [true] }, "proxy": { "address": "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", @@ -259,17 +257,6 @@ "maxDepositsPerBlock": 150, "minDepositBlockDistance": 25, "pauseIntentValidityPeriodBlocks": 6646 - }, - "guardians": { - "addresses": [ - "0x5fd0dDbC3351d009eb3f88DE7Cd081a614C519F1", - "0x7912Fa976BcDe9c2cf728e213e892AD7588E6AaF", - "0x14D5d5B71E048d2D75a39FfC5B407e3a3AB6F314", - "0xf82D88217C249297C6037BA77CE34b3d8a90ab43", - "0xa56b128Ea2Ea237052b0fA2a96a387C0E43157d8", - "0xd4EF84b638B334699bcf5AF4B0410B8CCD71943f" - ], - "quorum": 4 } }, "dummyEmptyContract": { @@ -282,9 +269,7 @@ "address": "0x8F73e4C2A6D852bb4ab2A45E6a9CF5715b3228B7", "contract": "contracts/0.8.9/EIP712StETH.sol", "deployTx": "0xecb5010620fb13b0e2bbc98b8a0c82de0d7385491452cd36cf303cd74216ed91", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] }, "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "executionLayerRewardsVault": { @@ -394,10 +379,7 @@ "address": "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09", "contract": "contracts/0.8.9/OracleDaemonConfig.sol", "deployTx": "0xa4f380b8806f5a504ef67fce62989e09be5a48bf114af63483c01c22f0c9a36f", - "constructorArgs": [ - "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", - [] - ], + "constructorArgs": ["0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", []], "deployParameters": { "NORMALIZED_CL_REWARD_PER_EPOCH": 64, "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, @@ -417,31 +399,8 @@ "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - [ - 9000, - 500, - 1000, - 50, - 600, - 8, - 62, - 7680, - 750000, - 43200 - ], - [ - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [] - ] + [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200], + [[], [], [], [], [], [], [], [], [], [], []] ], "deployParameters": { "churnValidatorsPerDayLimit": 20000, @@ -470,9 +429,7 @@ "implementation": { "contract": "contracts/0.8.9/StakingRouter.sol", "address": "0x1966dc8ff30Bc4AeDEd27178642253b3cCC9AA3f", - "constructorArgs": [ - "0x00000000219ab540356cBB839Cbe05303d7705Fa" - ] + "constructorArgs": ["0x00000000219ab540356cBB839Cbe05303d7705Fa"] } }, "validatorsExitBusOracle": { @@ -490,11 +447,7 @@ "address": "0xA89Ea51FddE660f67d1850e03C9c9862d33Bc42c", "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", "deployTx": "0x5ab545276f78a72a432c3e971c96384973abfab6394e08cb077a006c25aef7a7", - "constructorArgs": [ - 12, - 1606824023, - "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" - ], + "constructorArgs": [12, 1606824023, "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb"], "deployParameters": { "consensusVersion": 1 } @@ -584,11 +537,7 @@ "address": "0xE42C659Dc09109566720EA8b2De186c2Be7D94D9", "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", "deployTx": "0x6ab0151735c01acdef518421358d41a08752169bc383c57d57f5bfa135ac6eb1", - "constructorArgs": [ - "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", - "Lido: stETH Withdrawal NFT", - "unstETH" - ], + "constructorArgs": ["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "Lido: stETH Withdrawal NFT", "unstETH"], "deployParameters": { "name": "Lido: stETH Withdrawal NFT", "symbol": "unstETH" @@ -603,18 +552,13 @@ "address": "0xCC52f17756C04bBa7E377716d7062fC36D7f69Fd", "contract": "contracts/0.8.9/WithdrawalVault.sol", "deployTx": "0xd9eb2eca684770e4d2b192709b6071875f75072a0ce794a582824ee907a704f3", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] } }, "wstETH": { "address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "contract": "contracts/0.6.12/WstETH.sol", "deployTx": "0xaf2c1a501d2b290ef1e84ddcfc7beb3406f8ece2c46dee14e212e8233654ff05", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] } } From d6422572af21cddecf16fe92b012246b0d847841 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 1 Jul 2024 13:07:08 +0400 Subject: [PATCH 096/177] fix: sdvt updtae in config --- deployed-mainnet.json | 2 +- scripts/staking-router-v2/sr-v2-deploy.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/deployed-mainnet.json b/deployed-mainnet.json index a42a9c60b..93ab4228b 100644 --- a/deployed-mainnet.json +++ b/deployed-mainnet.json @@ -122,7 +122,7 @@ "name": "node-operators-registry", "id": "0x7071f283424072341f856ac9e947e7ec0eb68719f757a7e785979b6b8717579d", "ipfsCid": "Qma7PXHmEj4js2gjM9vtHPtqvuK82iS5EYPiJmzKLzU58G", - "contentURI": "0x697066733a516d61375058486d456a346a7332676a4d3976744850747176754b3832695335455950694a6d7a4b4c7a55353847" + "contentURI": "0x697066733a516d54346a64693146684d454b5576575351316877786e33365748394b6a656743755a7441684a6b6368526b7a70" } }, "app:oracle": { diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 576f6320e..6faaa80a7 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -12,6 +12,7 @@ import { persistNetworkState, readNetworkState, Sk, + updateObjectInState, } from "lib"; import readline from "readline"; @@ -98,7 +99,13 @@ async function main() { log.success(gr(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`)); log.emptyLine(); - // TODO: update sdvt too + updateObjectInState(Sk.appSimpleDvt, { + implementation: { + contract: "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", + address: appNodeOperatorsRegistry, + constructorArgs: [], + }, + }); // Deploy DSM const depositSecurityModuleParams = [ From b2e20fa3c19ec1002213ecad624f0a7f6fa749ad Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 1 Jul 2024 14:46:10 +0200 Subject: [PATCH 097/177] fix: typo --- contracts/0.8.9/interfaces/IStakingModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index 61c5daad0..82d55cf05 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -118,7 +118,7 @@ interface IStakingModule { /// @notice Updates the limit of the validators that can be used for deposit /// @param _nodeOperatorId Id of the node operator - /// @param _targetLimitMode taret limit mode + /// @param _targetLimitMode target limit mode /// @param _targetLimit Target limit of the node operator function updateTargetValidatorsLimits( uint256 _nodeOperatorId, From 3816e90890cca150e11e5665acce033863adf61a Mon Sep 17 00:00:00 2001 From: KRogLA Date: Mon, 1 Jul 2024 14:52:13 +0200 Subject: [PATCH 098/177] fix: typo --- contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 9980d8dd4..9e30bdf02 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -773,7 +773,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { error IncorrectCLBalanceIncrease(uint256 annualBalanceDiff); error IncorrectAppearedValidators(uint256 appearedValidatorsLimit); error IncorrectNumberOfExitRequestsPerReport(uint256 maxRequestsCount); - error IncorrectExitedValidators(uint256 exitedValudatorsLimit); + error IncorrectExitedValidators(uint256 exitedValidatorsLimit); error IncorrectRequestFinalization(uint256 requestCreationBlock); error ActualShareRateIsZero(); error IncorrectSimulatedShareRate(uint256 simulatedShareRate, uint256 actualShareRate); From 5be99762c2586ffdc8bec3bf6ce65986b55b9e82 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 1 Jul 2024 17:47:18 +0400 Subject: [PATCH 099/177] fix: env sample --- deployed-mainnet.json | 88 +++++++++++++++++------ scripts/staking-router-v2/.env.sample | 9 +++ scripts/staking-router-v2/sr-v2-deploy.ts | 4 ++ 3 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 scripts/staking-router-v2/.env.sample diff --git a/deployed-mainnet.json b/deployed-mainnet.json index 93ab4228b..f43184949 100644 --- a/deployed-mainnet.json +++ b/deployed-mainnet.json @@ -15,7 +15,7 @@ }, "implementation": { "contract": "contracts/0.8.9/oracle/AccountingOracle.sol", - "address": "0x8dF2a20225a5577fB173271c3777CF45305e816d", + "address": "0x9A8Ec3B44ee760b629e204900c86d67414a67e8f", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", @@ -114,7 +114,7 @@ }, "implementation": { "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", - "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", + "address": "0xDe1112a0960B9619da7F91D51fB571cdefE48B5E", "constructorArgs": [] }, "aragonApp": { @@ -181,15 +181,18 @@ ] }, "implementation": { - "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", - "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol" + "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", + "address": "0xDe1112a0960B9619da7F91D51fB571cdefE48B5E", + "constructorArgs": [] } }, "aragon-kernel": { "implementation": { "contract": "@aragon/os/contracts/kernel/Kernel.sol", "address": "0x2b33CF282f867A7FF693A66e11B0FcC5552e4425", - "constructorArgs": [true] + "constructorArgs": [ + true + ] }, "proxy": { "address": "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", @@ -243,7 +246,7 @@ "deployCommit": "e45c4d6fb8120fd29426b8d969c19d8a798ca974", "deployer": "0x55Bc991b2edF3DDb4c520B222bE4F378418ff0fA", "depositSecurityModule": { - "address": "0x582957C7a35CDfeAAD1Ca4b87AE03913eAAd0Be0", + "address": "0x1D87585dF4D48E52436e26521a3C5856E4553e3F", "contract": "contracts/0.8.9/DepositSecurityModule.sol", "deployTx": "0x21307a2321f167f99de11ccec86d7bdd8233481bbffa493e15c519ca8d662c4f", "constructorArgs": [ @@ -269,7 +272,9 @@ "address": "0x8F73e4C2A6D852bb4ab2A45E6a9CF5715b3228B7", "contract": "contracts/0.8.9/EIP712StETH.sol", "deployTx": "0xecb5010620fb13b0e2bbc98b8a0c82de0d7385491452cd36cf303cd74216ed91", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + ] }, "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "executionLayerRewardsVault": { @@ -333,15 +338,15 @@ }, "implementation": { "contract": "contracts/0.8.9/LidoLocator.sol", - "address": "0x645B0f55268eF561176f3247D06d0b7742f79819", + "address": "0xb60971942E4528A811D24826768Bc91ad1383D21", "constructorArgs": [ [ "0x852deD011285fe67063a08005c71a85690503Cee", - "0x582957C7a35CDfeAAD1Ca4b87AE03913eAAd0Be0", + "0x1D87585dF4D48E52436e26521a3C5856E4553e3F", "0x388C818CA8B9251b393131C08a736A67ccB19297", "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0xdccF554708B72d0fe9500cBfc1595cDBE3d66e5a", + "0xA899118f4BCCb62F8c6A37887a4F450D8a4E92E0", "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", "0xD15a672319Cf0352560eE76d9e89eAB0889046D3", "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", @@ -369,7 +374,7 @@ }, "minFirstAllocationStrategy": { "contract": "contracts/common/lib/MinFirstAllocationStrategy.sol", - "address": "0xA901DA770A472Caf6E6698261BB02ea58C5d3235", + "address": "0xe58cBE144dD5556C84874deC1b3F2d0D6Ac45F1b", "constructorArgs": [] }, "miniMeTokenFactoryAddress": "0x909d05f384d0663ed4be59863815ab43b4f347ec", @@ -379,7 +384,10 @@ "address": "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09", "contract": "contracts/0.8.9/OracleDaemonConfig.sol", "deployTx": "0xa4f380b8806f5a504ef67fce62989e09be5a48bf114af63483c01c22f0c9a36f", - "constructorArgs": ["0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", []], + "constructorArgs": [ + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + [] + ], "deployParameters": { "NORMALIZED_CL_REWARD_PER_EPOCH": 64, "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, @@ -393,14 +401,37 @@ } }, "oracleReportSanityChecker": { - "address": "0xdccF554708B72d0fe9500cBfc1595cDBE3d66e5a", + "address": "0xA899118f4BCCb62F8c6A37887a4F450D8a4E92E0", "contract": "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol", "deployTx": "0x700c83996ad7deefda286044280ad86108dfef9c880909bd8e75a3746f7d631c", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200], - [[], [], [], [], [], [], [], [], [], [], []] + [ + 9000, + 500, + 1000, + 50, + 600, + 8, + 62, + 7680, + 750000, + 43200 + ], + [ + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [] + ] ], "deployParameters": { "churnValidatorsPerDayLimit": 20000, @@ -428,8 +459,10 @@ }, "implementation": { "contract": "contracts/0.8.9/StakingRouter.sol", - "address": "0x1966dc8ff30Bc4AeDEd27178642253b3cCC9AA3f", - "constructorArgs": ["0x00000000219ab540356cBB839Cbe05303d7705Fa"] + "address": "0xDC0a0B1Cd093d321bD1044B5e0Acb71b525ABb6b", + "constructorArgs": [ + "0x00000000219ab540356cBB839Cbe05303d7705Fa" + ] } }, "validatorsExitBusOracle": { @@ -447,7 +480,11 @@ "address": "0xA89Ea51FddE660f67d1850e03C9c9862d33Bc42c", "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", "deployTx": "0x5ab545276f78a72a432c3e971c96384973abfab6394e08cb077a006c25aef7a7", - "constructorArgs": [12, 1606824023, "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb"], + "constructorArgs": [ + 12, + 1606824023, + "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" + ], "deployParameters": { "consensusVersion": 1 } @@ -537,7 +574,11 @@ "address": "0xE42C659Dc09109566720EA8b2De186c2Be7D94D9", "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", "deployTx": "0x6ab0151735c01acdef518421358d41a08752169bc383c57d57f5bfa135ac6eb1", - "constructorArgs": ["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "Lido: stETH Withdrawal NFT", "unstETH"], + "constructorArgs": [ + "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "Lido: stETH Withdrawal NFT", + "unstETH" + ], "deployParameters": { "name": "Lido: stETH Withdrawal NFT", "symbol": "unstETH" @@ -552,13 +593,18 @@ "address": "0xCC52f17756C04bBa7E377716d7062fC36D7f69Fd", "contract": "contracts/0.8.9/WithdrawalVault.sol", "deployTx": "0xd9eb2eca684770e4d2b192709b6071875f75072a0ce794a582824ee907a704f3", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" + ] } }, "wstETH": { "address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "contract": "contracts/0.6.12/WstETH.sol", "deployTx": "0xaf2c1a501d2b290ef1e84ddcfc7beb3406f8ece2c46dee14e212e8233654ff05", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + ] } } diff --git a/scripts/staking-router-v2/.env.sample b/scripts/staking-router-v2/.env.sample new file mode 100644 index 000000000..de1dbbde4 --- /dev/null +++ b/scripts/staking-router-v2/.env.sample @@ -0,0 +1,9 @@ +# Deployer +DEPLOYER= +DEPLOYER_PRIVATE_KEY= +# Chain config +RPC_URL= +NETWORK= +# Deploy transactions gas +GAS_PRIORITY_FEE= +GAS_MAX_FEE= \ No newline at end of file diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 6faaa80a7..f7290b2a2 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -16,6 +16,10 @@ import { } from "lib"; import readline from "readline"; +import * as dotenv from "dotenv"; +import { join } from "path"; + +dotenv.config({ path: join(__dirname, "../../.env") }); function getEnvVariable(name: string, defaultValue?: string) { const value = process.env[name]; From 66dd925250edf4e41cd703984ffecbc72f799c7b Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 1 Jul 2024 17:22:39 +0300 Subject: [PATCH 100/177] feat: support old method to set soft limit --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 9 ++++ .../nor/node-operators-registry.test.ts | 45 ++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 018243a15..de41524a9 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -687,8 +687,17 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// @notice Updates the limit of the validators that can be used for deposit by DAO /// @param _nodeOperatorId Id of the node operator + /// @param _isTargetLimitActive Flag indicating if the soft target limit is active /// @param _targetLimit Target limit of the node operator + /// @dev This function is deprecated, use updateTargetValidatorsLimits instead + function updateTargetValidatorsLimits(uint256 _nodeOperatorId, bool _isTargetLimitActive, uint256 _targetLimit) public { + updateTargetValidatorsLimits(_nodeOperatorId, _isTargetLimitActive ? 1 : 0, _targetLimit); + } + + /// @notice Updates the limit of the validators that can be used for deposit by DAO + /// @param _nodeOperatorId Id of the node operator /// @param _targetLimitMode target limit mode (0 = disabled, 1 = soft mode, 2 = forced mode) + /// @param _targetLimit Target limit of the node operator function updateTargetValidatorsLimits(uint256 _nodeOperatorId, uint256 _targetLimitMode, uint256 _targetLimit) public { _onlyExistedNodeOperator(_nodeOperatorId); _auth(STAKING_ROUTER_ROLE); diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index 64fab4e38..05a1651bd 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -402,7 +402,7 @@ describe("NodeOperatorsRegistry", () => { it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; await expect( - nor.updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + nor["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, targetLimitMode, targetLimit), ).to.be.revertedWith("APP_AUTH_FAILED"); }); @@ -410,7 +410,11 @@ describe("NodeOperatorsRegistry", () => { const targetLimitWrong = BigInt("0x10000000000000000"); await expect( - nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimitWrong), + nor + .connect(stakingRouter) + [ + "updateTargetValidatorsLimits(uint256,uint256,uint256)" + ](firstNodeOperatorId, targetLimitMode, targetLimitWrong), ).to.be.revertedWith("OUT_OF_RANGE"); }); @@ -421,7 +425,9 @@ describe("NodeOperatorsRegistry", () => { targetLimit = 10; await expect( - nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode, targetLimit), + nor + .connect(stakingRouter) + ["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, targetLimitMode, targetLimit), ) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); @@ -454,11 +460,13 @@ describe("NodeOperatorsRegistry", () => { expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; const targetLimitMode1 = 1; - const targetLimitMode2 = 1; + const targetLimitMode2 = 2; targetLimit = 10; await expect( - nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, targetLimitMode1, targetLimit), + nor + .connect(stakingRouter) + ["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, targetLimitMode1, targetLimit), ) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode1); @@ -467,7 +475,11 @@ describe("NodeOperatorsRegistry", () => { expect(noSummary.targetLimitMode).to.equal(targetLimitMode1); await expect( - nor.connect(stakingRouter).updateTargetValidatorsLimits(secondNodeOperatorId, targetLimitMode2, targetLimit), + nor + .connect(stakingRouter) + [ + "updateTargetValidatorsLimits(uint256,uint256,uint256)" + ](secondNodeOperatorId, targetLimitMode2, targetLimit), ) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(secondNodeOperatorId, targetLimit, targetLimitMode2); @@ -475,7 +487,11 @@ describe("NodeOperatorsRegistry", () => { expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); // reset limit - await expect(nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, 0, targetLimit)) + await expect( + nor + .connect(stakingRouter) + ["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, 0, targetLimit), + ) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, 0, 0); // expect limit set to 0 @@ -486,6 +502,21 @@ describe("NodeOperatorsRegistry", () => { noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); }); + + it("updates node operator target limit with deprecated method correctly", async () => { + expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; + + await expect( + nor + .connect(stakingRouter) + ["updateTargetValidatorsLimits(uint256,bool,uint256)"](firstNodeOperatorId, true, targetLimit), + ) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1); + + const noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1); + }); }); context("getRewardDistributionState()", () => { From ca60d6bfa10578cfa2822d02e7659b99ea470a69 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 1 Jul 2024 17:53:47 +0300 Subject: [PATCH 101/177] test: unset soft limit via deprecated updateTargetValidatorsLimits --- .../nor/node-operators-registry.test.ts | 37 +++++++------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index 05a1651bd..7731af150 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -389,6 +389,9 @@ describe("NodeOperatorsRegistry", () => { }); context("updateTargetValidatorsLimits", () => { + const updateTargetLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)"; + const updateTargetLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)"; + const firstNodeOperatorId = 0; const secondNodeOperatorId = 1; let targetLimitMode = 0; @@ -463,35 +466,21 @@ describe("NodeOperatorsRegistry", () => { const targetLimitMode2 = 2; targetLimit = 10; - await expect( - nor - .connect(stakingRouter) - ["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, targetLimitMode1, targetLimit), - ) + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, targetLimitMode1, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode1); let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(targetLimitMode1); - await expect( - nor - .connect(stakingRouter) - [ - "updateTargetValidatorsLimits(uint256,uint256,uint256)" - ](secondNodeOperatorId, targetLimitMode2, targetLimit), - ) + await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, targetLimitMode2, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(secondNodeOperatorId, targetLimit, targetLimitMode2); noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); // reset limit - await expect( - nor - .connect(stakingRouter) - ["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, 0, targetLimit), - ) + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, 0, 0); // expect limit set to 0 @@ -506,16 +495,18 @@ describe("NodeOperatorsRegistry", () => { it("updates node operator target limit with deprecated method correctly", async () => { expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - await expect( - nor - .connect(stakingRouter) - ["updateTargetValidatorsLimits(uint256,bool,uint256)"](firstNodeOperatorId, true, targetLimit), - ) + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, 100)) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1); + .withArgs(firstNodeOperatorId, 100, 1); const noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(1); + + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, false, 0)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0, 0); + const noSummary2 = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary2.targetLimitMode).to.equal(0); }); }); From 771bd9ea38fce2456e06fb4bff063d290ccfb926 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 1 Jul 2024 17:55:30 +0300 Subject: [PATCH 102/177] refactor: updateTargetValidatorsLimits notation --- .../0.4.24/nor/node-operators-registry.test.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index 7731af150..616b9c94f 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -404,20 +404,16 @@ describe("NodeOperatorsRegistry", () => { it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; - await expect( - nor["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, targetLimitMode, targetLimit), - ).to.be.revertedWith("APP_AUTH_FAILED"); + await expect(nor[updateTargetLimits](firstNodeOperatorId, targetLimitMode, targetLimit)).to.be.revertedWith( + "APP_AUTH_FAILED", + ); }); it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { const targetLimitWrong = BigInt("0x10000000000000000"); await expect( - nor - .connect(stakingRouter) - [ - "updateTargetValidatorsLimits(uint256,uint256,uint256)" - ](firstNodeOperatorId, targetLimitMode, targetLimitWrong), + nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, targetLimitMode, targetLimitWrong), ).to.be.revertedWith("OUT_OF_RANGE"); }); @@ -427,11 +423,7 @@ describe("NodeOperatorsRegistry", () => { targetLimitMode = 1; targetLimit = 10; - await expect( - nor - .connect(stakingRouter) - ["updateTargetValidatorsLimits(uint256,uint256,uint256)"](firstNodeOperatorId, targetLimitMode, targetLimit), - ) + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, targetLimitMode, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); From 1f30fb674a6cfc672e1a84bb673525edc5f8cc81 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Mon, 1 Jul 2024 20:02:27 +0400 Subject: [PATCH 103/177] fix: logs --- scripts/staking-router-v2/.env.sample | 2 +- scripts/staking-router-v2/sr-v2-deploy.ts | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/scripts/staking-router-v2/.env.sample b/scripts/staking-router-v2/.env.sample index de1dbbde4..8157a2159 100644 --- a/scripts/staking-router-v2/.env.sample +++ b/scripts/staking-router-v2/.env.sample @@ -6,4 +6,4 @@ RPC_URL= NETWORK= # Deploy transactions gas GAS_PRIORITY_FEE= -GAS_MAX_FEE= \ No newline at end of file +GAS_MAX_FEE= diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index f7290b2a2..e16cf5f90 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -57,6 +57,8 @@ async function main() { const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); const chainId = (await ethers.provider.getNetwork()).chainId; + log(cy(`Deploy of contracts on chain ${chainId}`)); + const state = readNetworkState(); state[Sk.scratchDeployGasUsed] = 0n.toString(); persistNetworkState(state); @@ -82,7 +84,7 @@ async function main() { const minFirstAllocationStrategyAddress = ( await deployWithoutProxy(Sk.minFirstAllocationStrategy, "MinFirstAllocationStrategy", deployer) ).address; - log.success(gr(`MinFirstAllocationStrategy address: ${minFirstAllocationStrategyAddress}`)); + log.success(`MinFirstAllocationStrategy address: ${minFirstAllocationStrategyAddress}`); log.emptyLine(); const libraries = { @@ -93,14 +95,14 @@ async function main() { const stakingRouterAddress = ( await deployImplementation(Sk.stakingRouter, "StakingRouter", deployer, [DEPOSIT_CONTRACT_ADDRESS], { libraries }) ).address; - log.success(gr(`StakingRouter implementation address: ${stakingRouterAddress}`)); + log.success(`StakingRouter implementation address: ${stakingRouterAddress}`); log.emptyLine(); // Deploy NOR const appNodeOperatorsRegistry = ( await deployImplementation(Sk.appNodeOperatorsRegistry, "NodeOperatorsRegistry", deployer, [], { libraries }) ).address; - log.success(gr(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`)); + log.success(`NodeOperatorsRegistry address implementation: ${appNodeOperatorsRegistry}`); log.emptyLine(); updateObjectInState(Sk.appSimpleDvt, { @@ -122,7 +124,7 @@ async function main() { const depositSecurityModuleAddress = ( await deployWithoutProxy(Sk.depositSecurityModule, "DepositSecurityModule", deployer, depositSecurityModuleParams) ).address; - log.success(gr(`New DSM address: ${depositSecurityModuleAddress}`)); + log.success(`New DSM address: ${depositSecurityModuleAddress}`); log.emptyLine(); const dsmContract = await loadContract( @@ -131,8 +133,8 @@ async function main() { ); await dsmContract.addGuardians(guardians, quorum); await dsmContract.setOwner(APP_AGENT_ADDRESS); - log.success(gr(`Guardians list: ${await dsmContract.getGuardians()}`)); - log.success(gr(`Quorum: ${await dsmContract.getGuardianQuorum()}`)); + log.success(`Guardians list: ${await dsmContract.getGuardians()}`); + log.success(`Quorum: ${await dsmContract.getGuardianQuorum()}`); log.emptyLine(); // Deploy AO @@ -140,7 +142,7 @@ async function main() { const accountingOracleAddress = ( await deployImplementation(Sk.accountingOracle, "AccountingOracle", deployer, accountingOracleArgs) ).address; - log.success(gr(`AO implementation address: ${accountingOracleAddress}`)); + log.success(`AO implementation address: ${accountingOracleAddress}`); log.emptyLine(); // Deploy OracleReportSanityCheckerArgs @@ -153,7 +155,7 @@ async function main() { oracleReportSanityCheckerArgs, ) ).address; - log.success(gr(`OracleReportSanityChecker new address ${oracleReportSanityCheckerAddress}`)); + log.success(`OracleReportSanityChecker new address ${oracleReportSanityCheckerAddress}`); log.emptyLine(); const locatorConfig = [ @@ -176,7 +178,7 @@ async function main() { ]; const locatorAddress = (await deployImplementation(Sk.lidoLocator, "LidoLocator", deployer, locatorConfig)).address; - log.success(gr(`Locator implementation address ${locatorAddress}`)); + log.success(`Locator implementation address ${locatorAddress}`); log.emptyLine(); if (getEnvVariable("RUN_ON_FORK", "false") === "true") { From bf5158fcf75caa04fe4d229dabf42962d622cc6f Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 2 Jul 2024 13:01:38 +0400 Subject: [PATCH 104/177] fix: script --- scripts/staking-router-v2/sr-v2-deploy.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index e16cf5f90..d5428a965 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -1,12 +1,9 @@ import { ethers, run } from "hardhat"; - import { DepositSecurityModule, DepositSecurityModule__factory } from "typechain-types"; - import { cy, deployImplementation, deployWithoutProxy, - gr, loadContract, log, persistNetworkState, From 14aa14ec5578503be4b53fb42515937d5f49270c Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 2 Jul 2024 13:13:37 +0400 Subject: [PATCH 105/177] fix: reset mainnet config --- deployed-mainnet.json | 106 +++++++++++++----------------------------- 1 file changed, 32 insertions(+), 74 deletions(-) diff --git a/deployed-mainnet.json b/deployed-mainnet.json index f43184949..bfc09113f 100644 --- a/deployed-mainnet.json +++ b/deployed-mainnet.json @@ -14,8 +14,9 @@ ] }, "implementation": { + "address": "0xF3c5E0A67f32CF1dc07a8817590efa102079a1aF", "contract": "contracts/0.8.9/oracle/AccountingOracle.sol", - "address": "0x9A8Ec3B44ee760b629e204900c86d67414a67e8f", + "deployTx": "0x3e27627d3ed236aff8901df187196e9682187dfd0d259c5d5811a6e923436083", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", @@ -113,16 +114,21 @@ "address": "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" }, "implementation": { + "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", - "address": "0xDe1112a0960B9619da7F91D51fB571cdefE48B5E", - "constructorArgs": [] + "deployTx": "0xbec5b6ffb8fbc775a183e25cd285367993d7201752e1638a07abec8962bab750", + "constructorArgs": [], + "deployParameters": { + "stakingModuleTypeId": "curated-onchain-v1", + "stuckPenaltyDelay": "432000" + } }, "aragonApp": { "fullName": "node-operators-registry.lidopm.eth", "name": "node-operators-registry", "id": "0x7071f283424072341f856ac9e947e7ec0eb68719f757a7e785979b6b8717579d", "ipfsCid": "Qma7PXHmEj4js2gjM9vtHPtqvuK82iS5EYPiJmzKLzU58G", - "contentURI": "0x697066733a516d54346a64693146684d454b5576575351316877786e33365748394b6a656743755a7441684a6b6368526b7a70" + "contentURI": "0x697066733a516d61375058486d456a346a7332676a4d3976744850747176754b3832695335455950694a6d7a4b4c7a55353847" } }, "app:oracle": { @@ -181,8 +187,8 @@ ] }, "implementation": { + "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", - "address": "0xDe1112a0960B9619da7F91D51fB571cdefE48B5E", "constructorArgs": [] } }, @@ -190,9 +196,7 @@ "implementation": { "contract": "@aragon/os/contracts/kernel/Kernel.sol", "address": "0x2b33CF282f867A7FF693A66e11B0FcC5552e4425", - "constructorArgs": [ - true - ] + "constructorArgs": [true] }, "proxy": { "address": "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", @@ -246,15 +250,16 @@ "deployCommit": "e45c4d6fb8120fd29426b8d969c19d8a798ca974", "deployer": "0x55Bc991b2edF3DDb4c520B222bE4F378418ff0fA", "depositSecurityModule": { - "address": "0x1D87585dF4D48E52436e26521a3C5856E4553e3F", + "address": "0xC77F8768774E1c9244BEed705C4354f2113CFc09", "contract": "contracts/0.8.9/DepositSecurityModule.sol", "deployTx": "0x21307a2321f167f99de11ccec86d7bdd8233481bbffa493e15c519ca8d662c4f", "constructorArgs": [ "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x00000000219ab540356cBB839Cbe05303d7705Fa", "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", - 6646, - 200 + 150, + 25, + 6646 ], "deployParameters": { "maxDepositsPerBlock": 150, @@ -272,9 +277,7 @@ "address": "0x8F73e4C2A6D852bb4ab2A45E6a9CF5715b3228B7", "contract": "contracts/0.8.9/EIP712StETH.sol", "deployTx": "0xecb5010620fb13b0e2bbc98b8a0c82de0d7385491452cd36cf303cd74216ed91", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] }, "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "executionLayerRewardsVault": { @@ -337,16 +340,17 @@ ] }, "implementation": { + "address": "0x1D920cc5bACf7eE506a271a5259f2417CaDeCE1d", "contract": "contracts/0.8.9/LidoLocator.sol", - "address": "0xb60971942E4528A811D24826768Bc91ad1383D21", + "deployTx": "0xf90012ef0a40e47c909ab3a5b3503ecee78f6a9be134d1349a742e500d37ae33", "constructorArgs": [ [ "0x852deD011285fe67063a08005c71a85690503Cee", - "0x1D87585dF4D48E52436e26521a3C5856E4553e3F", + "0xC77F8768774E1c9244BEed705C4354f2113CFc09", "0x388C818CA8B9251b393131C08a736A67ccB19297", "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0xA899118f4BCCb62F8c6A37887a4F450D8a4E92E0", + "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", "0xD15a672319Cf0352560eE76d9e89eAB0889046D3", "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", @@ -372,11 +376,6 @@ "0xa0BC4B67F5FacDE4E50EAFF48691Cfc43F4E280A" ] }, - "minFirstAllocationStrategy": { - "contract": "contracts/common/lib/MinFirstAllocationStrategy.sol", - "address": "0xe58cBE144dD5556C84874deC1b3F2d0D6Ac45F1b", - "constructorArgs": [] - }, "miniMeTokenFactoryAddress": "0x909d05f384d0663ed4be59863815ab43b4f347ec", "networkId": 1, "newDaoTx": "0x3feabd79e8549ad68d1827c074fa7123815c80206498946293d5373a160fd866", @@ -384,10 +383,7 @@ "address": "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09", "contract": "contracts/0.8.9/OracleDaemonConfig.sol", "deployTx": "0xa4f380b8806f5a504ef67fce62989e09be5a48bf114af63483c01c22f0c9a36f", - "constructorArgs": [ - "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", - [] - ], + "constructorArgs": ["0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", []], "deployParameters": { "NORMALIZED_CL_REWARD_PER_EPOCH": 64, "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, @@ -401,37 +397,14 @@ } }, "oracleReportSanityChecker": { - "address": "0xA899118f4BCCb62F8c6A37887a4F450D8a4E92E0", + "address": "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", "contract": "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol", "deployTx": "0x700c83996ad7deefda286044280ad86108dfef9c880909bd8e75a3746f7d631c", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - [ - 9000, - 500, - 1000, - 50, - 600, - 8, - 62, - 7680, - 750000, - 43200 - ], - [ - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [] - ] + [20000, 500, 1000, 50, 600, 2, 100, 7680, 750000], + [[], [], [], [], [], [], [], [], [], []] ], "deployParameters": { "churnValidatorsPerDayLimit": 20000, @@ -445,7 +418,6 @@ "maxPositiveTokenRebase": 750000 } }, - "scratchDeployGasUsed": "20484611", "stakingRouter": { "proxy": { "address": "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", @@ -458,11 +430,10 @@ ] }, "implementation": { + "address": "0xD8784e748f59Ba711fB5643191Ec3fAdD50Fb6df", "contract": "contracts/0.8.9/StakingRouter.sol", - "address": "0xDC0a0B1Cd093d321bD1044B5e0Acb71b525ABb6b", - "constructorArgs": [ - "0x00000000219ab540356cBB839Cbe05303d7705Fa" - ] + "deployTx": "0xd6d489f22203c835da6027ff0e532a01a08f36f0fda6c7c0a42e471ae3b3c461", + "constructorArgs": ["0x00000000219ab540356cBB839Cbe05303d7705Fa"] } }, "validatorsExitBusOracle": { @@ -480,11 +451,7 @@ "address": "0xA89Ea51FddE660f67d1850e03C9c9862d33Bc42c", "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", "deployTx": "0x5ab545276f78a72a432c3e971c96384973abfab6394e08cb077a006c25aef7a7", - "constructorArgs": [ - 12, - 1606824023, - "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" - ], + "constructorArgs": [12, 1606824023, "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb"], "deployParameters": { "consensusVersion": 1 } @@ -574,11 +541,7 @@ "address": "0xE42C659Dc09109566720EA8b2De186c2Be7D94D9", "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", "deployTx": "0x6ab0151735c01acdef518421358d41a08752169bc383c57d57f5bfa135ac6eb1", - "constructorArgs": [ - "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", - "Lido: stETH Withdrawal NFT", - "unstETH" - ], + "constructorArgs": ["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "Lido: stETH Withdrawal NFT", "unstETH"], "deployParameters": { "name": "Lido: stETH Withdrawal NFT", "symbol": "unstETH" @@ -593,18 +556,13 @@ "address": "0xCC52f17756C04bBa7E377716d7062fC36D7f69Fd", "contract": "contracts/0.8.9/WithdrawalVault.sol", "deployTx": "0xd9eb2eca684770e4d2b192709b6071875f75072a0ce794a582824ee907a704f3", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] } }, "wstETH": { "address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "contract": "contracts/0.6.12/WstETH.sol", "deployTx": "0xaf2c1a501d2b290ef1e84ddcfc7beb3406f8ece2c46dee14e212e8233654ff05", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] } } From dca2f5f4a84dffa79a639a964327abcd26c7d34a Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 3 Jul 2024 17:20:19 +0200 Subject: [PATCH 106/177] fix: remove unused total keys count --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 018243a15..372aa0042 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -126,7 +126,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint8 internal constant SUMMARY_MAX_VALIDATORS_COUNT_OFFSET = 0; /// @dev Number of keys of all operators which were in the EXITED state for all time uint8 internal constant SUMMARY_EXITED_KEYS_COUNT_OFFSET = 1; - /// @dev Total number of keys of all operators for all time + /// @dev [deprecated] Total number of keys of all operators for all time uint8 internal constant SUMMARY_TOTAL_KEYS_COUNT_OFFSET = 2; /// @dev Number of keys of all operators which were in the DEPOSITED state for all time uint8 internal constant SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET = 3; @@ -271,7 +271,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { SUMMARY_EXITED_KEYS_COUNT_OFFSET, signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET) ); - summarySigningKeysStats.add(SUMMARY_TOTAL_KEYS_COUNT_OFFSET, totalSigningKeysCount); } _saveSummarySigningKeysStats(summarySigningKeysStats); @@ -299,6 +298,11 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { require(hasInitialized(), "CONTRACT_NOT_INITIALIZED"); _checkContractVersion(2); _initialize_v3(); + + // clear deprecated total keys count storage + Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); + summarySigningKeysStats.set(SUMMARY_TOTAL_KEYS_COUNT_OFFSET, 0); + _saveSummarySigningKeysStats(summarySigningKeysStats); } function _initialize_v3() internal { @@ -831,9 +835,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { } if (totalTrimmedKeysCount > 0) { - Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); - summarySigningKeysStats.sub(SUMMARY_TOTAL_KEYS_COUNT_OFFSET, totalTrimmedKeysCount); - _saveSummarySigningKeysStats(summarySigningKeysStats); _increaseValidatorsKeysNonce(); } } @@ -1149,11 +1150,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, totalSigningKeysCount); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); - // upd totals - Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); - summarySigningKeysStats.add(SUMMARY_TOTAL_KEYS_COUNT_OFFSET, _keysCount); - _saveSummarySigningKeysStats(summarySigningKeysStats); - _increaseValidatorsKeysNonce(); } @@ -1218,10 +1214,6 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { } _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); - // upd totals - Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); - summarySigningKeysStats.sub(SUMMARY_TOTAL_KEYS_COUNT_OFFSET, _keysCount); - _saveSummarySigningKeysStats(summarySigningKeysStats); _updateSummaryMaxValidatorsCount(_nodeOperatorId); _increaseValidatorsKeysNonce(); From 3930b8191d6d4e4ff996f7edd78594619fd96594 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 3 Jul 2024 17:52:00 +0200 Subject: [PATCH 107/177] refactor: simplify method descriptionj --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index de41524a9..ff9aa76e0 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -589,32 +589,19 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// @notice Permissionless method for distributing all accumulated module rewards among node operators /// based on the latest accounting report. /// - /// @dev Rewards can be distributed after node operators' statistics are updated - /// until the next reward is transferred to the module during the next oracle frame. + /// @dev Rewards can be distributed after all necessary data required to distribute rewards among operators + /// has been delivered, including exited and stuck keys. /// - /// ===================================== Start report frame 1 ===================================== + /// The reward distribution lifecycle: /// - /// 1. Oracle first phase: Reach hash consensus. - /// 2. Oracle second phase: Module receives rewards. - /// 3. Oracle third phase: Operator statistics are updated. + /// 1. TransferredToModule: Rewards are transferred to the module during an oracle main report. + /// 2. ReadyForDistribution: All necessary data required to distribute rewards among operators has been delivered. + /// 3. Distributed: Rewards have been successfully distributed. /// - /// ... Reward can be distributed ... + /// The function can only be called when the state is ReadyForDistribution. /// - /// ===================================== Start report frame 2 ===================================== - /// - /// ... Reward can be distributed ... - /// (if not distributed yet) - /// - /// 1. Oracle first phase: Reach hash consensus. - /// 2. Oracle second phase: Module receives rewards. - /// - /// ... Reward CANNOT be distributed ... - /// - /// 3. Oracle third phase: Operator statistics are updated. - /// - /// ... Reward can be distributed ... - /// - /// ===================================== Start report frame 3 ===================================== + /// @dev Rewards can be distributed after node operators' statistics are updated until the next reward + /// is transferred to the module during the next oracle frame. function distributeReward() external { require(getRewardDistributionState() == RewardDistributionState.ReadyForDistribution, "DISTRIBUTION_NOT_READY"); _updateRewardDistributionState(RewardDistributionState.Distributed); From 1fc28bb1ec524117ee082d354b04bcbad25c7f02 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 3 Jul 2024 18:47:48 +0200 Subject: [PATCH 108/177] test: refactor naming --- .../test_helpers/NodeOperatorsRegistryMock.sol | 2 +- test/0.4.24/nor/node-operators-registry.test.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol index 0c93e04c2..f600c8b07 100644 --- a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol +++ b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol @@ -143,7 +143,7 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { _setContractVersion(_newBaseVersion); } - function testing_setRewardDistributionStatus(RewardDistributionState _state) external { + function testing_setRewardDistributionState(RewardDistributionState _state) external { _updateRewardDistributionState(_state); } diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts index 616b9c94f..223659659 100644 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ b/test/0.4.24/nor/node-operators-registry.test.ts @@ -225,7 +225,7 @@ describe("NodeOperatorsRegistry", () => { beforeEach(async () => { // reset version there to test upgrade finalization await nor.testing_setBaseVersion(2); - await nor.testing_setRewardDistributionStatus(0); + await nor.testing_setRewardDistributionState(0); }); it("fails with CONTRACT_NOT_INITIALIZED error when called on implementation", async () => { @@ -504,20 +504,20 @@ describe("NodeOperatorsRegistry", () => { context("getRewardDistributionState()", () => { it("returns correct reward distribution state", async () => { - await nor.testing_setRewardDistributionStatus(RewardDistributionState.ReadyForDistribution); + await nor.testing_setRewardDistributionState(RewardDistributionState.ReadyForDistribution); expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); - await nor.testing_setRewardDistributionStatus(RewardDistributionState.TransferredToModule); + await nor.testing_setRewardDistributionState(RewardDistributionState.TransferredToModule); expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.TransferredToModule); - await nor.testing_setRewardDistributionStatus(RewardDistributionState.Distributed); + await nor.testing_setRewardDistributionState(RewardDistributionState.Distributed); expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); }); }); context("distributeReward()", () => { it('distribute reward when module not in "ReadyForDistribution" status', async () => { - await nor.testing_setRewardDistributionStatus(RewardDistributionState.ReadyForDistribution); + await nor.testing_setRewardDistributionState(RewardDistributionState.ReadyForDistribution); expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); await expect(nor.distributeReward()) @@ -527,10 +527,10 @@ describe("NodeOperatorsRegistry", () => { }); it('reverts with "DISTRIBUTION_NOT_READY" error when module not in "ReadyForDistribution" status', async () => { - await nor.testing_setRewardDistributionStatus(RewardDistributionState.TransferredToModule); + await nor.testing_setRewardDistributionState(RewardDistributionState.TransferredToModule); await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); - await nor.testing_setRewardDistributionStatus(RewardDistributionState.Distributed); + await nor.testing_setRewardDistributionState(RewardDistributionState.Distributed); await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); }); }); From da28d3799b463c394a10ce50ca6ba9629a0bd602 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 19 Jun 2024 16:30:22 +0500 Subject: [PATCH 109/177] refactor: sr codesize --- contracts/0.8.9/StakingRouter.sol | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 60dbcc797..2ced55d3a 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -281,9 +281,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @dev Simulate zero value deposit to prevent real deposits into the new StakingModule via /// DepositSecurityModule just after the addition. - newStakingModule.lastDepositAt = uint64(block.timestamp); - newStakingModule.lastDepositBlock = block.number; - emit StakingRouterETHDeposited(newStakingModuleId, 0); + _updateModuleLastDepositState(newStakingModule, newStakingModuleId, 0); _setStakingModuleIndexById(newStakingModuleId, newStakingModuleIndex); LAST_STAKING_MODULE_ID_POSITION.setStorageUint256(newStakingModuleId); @@ -1253,14 +1251,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// @dev Firstly update the local state of the contract to prevent a reentrancy attack /// even though the staking modules are trusted contracts. - stakingModule.lastDepositAt = uint64(block.timestamp); - stakingModule.lastDepositBlock = block.number; - uint256 depositsValue = msg.value; - emit StakingRouterETHDeposited(_stakingModuleId, depositsValue); + if (depositsValue != _depositsCount * DEPOSIT_SIZE) revert InvalidDepositsValue(depositsValue, _depositsCount); - if (depositsValue != _depositsCount * DEPOSIT_SIZE) - revert InvalidDepositsValue(depositsValue, _depositsCount); + _updateModuleLastDepositState(stakingModule, _stakingModuleId, depositsValue); if (_depositsCount > 0) { (bytes memory publicKeysBatch, bytes memory signaturesBatch) = @@ -1336,6 +1330,21 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } + /// @dev Save the last deposit state for the staking module and emit the event + /// @param stakingModule staking module storage ref + /// @param stakingModuleId id of the staking module to be deposited + /// @param depositsValue value to deposit + function _updateModuleLastDepositState( + StakingModule storage stakingModule, + uint256 stakingModuleId, + uint256 depositsValue + ) internal { + stakingModule.lastDepositAt = uint64(block.timestamp); + stakingModule.lastDepositBlock = block.number; + emit StakingRouterETHDeposited(stakingModuleId, depositsValue); + } + + /// @dev Loads modules into a memory cache. /// @return totalActiveValidators Total active validators across all modules. /// @return stakingModulesCache Array of StakingModuleCache structs. From e6091ec80912b9ab7a46f1eeb7fe0c7210fa61d7 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 4 Jul 2024 01:00:44 +0200 Subject: [PATCH 110/177] style: fixes --- contracts/0.8.9/oracle/AccountingOracle.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index ed20cf447..d2ec51951 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -738,7 +738,7 @@ contract AccountingOracle is BaseOracle { } // at least 32 bytes for the next hash value + 35 bytes for the first item with 1 node operator - if(data.length < 67) { + if (data.length < 67) { revert UnexpectedExtraDataLength(); } @@ -763,7 +763,7 @@ contract AccountingOracle is BaseOracle { _processExtraDataItems(data, iter); uint256 itemsProcessed = iter.index + 1; - if(dataHash == ZERO_HASH) { + if (dataHash == ZERO_HASH) { if (itemsProcessed != procState.itemsCount) { revert UnexpectedExtraDataItemsCount(procState.itemsCount, itemsProcessed); } From 4e86e99b0c32ad2a7fad4912b5fb91b895c11838 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Thu, 4 Jul 2024 03:10:52 +0200 Subject: [PATCH 111/177] test: sr coverage --- .../0.8.9/test_helpers/StakingRouterMock.sol | 13 +- .../stakingRouter/stakingRouter.misc.test.ts | 128 +++++++++++++++++- .../stakingRouter.module-management.test.ts | 17 ++- .../stakingRouter.status-control.test.ts | 18 ++- 4 files changed, 162 insertions(+), 14 deletions(-) diff --git a/contracts/0.8.9/test_helpers/StakingRouterMock.sol b/contracts/0.8.9/test_helpers/StakingRouterMock.sol index 5a102c193..c5bf5190d 100644 --- a/contracts/0.8.9/test_helpers/StakingRouterMock.sol +++ b/contracts/0.8.9/test_helpers/StakingRouterMock.sol @@ -10,9 +10,7 @@ import {UnstructuredStorage} from "../lib/UnstructuredStorage.sol"; contract StakingRouterMock is StakingRouter { using UnstructuredStorage for bytes32; - constructor(address _depositContract) StakingRouter(_depositContract) { - CONTRACT_VERSION_POSITION.setStorageUint256(0); - } + constructor(address _depositContract) StakingRouter(_depositContract) {} function getStakingModuleIndexById(uint256 _stakingModuleId) external view returns (uint256) { return _getStakingModuleIndexById(_stakingModuleId); @@ -21,4 +19,13 @@ contract StakingRouterMock is StakingRouter { function getStakingModuleByIndex(uint256 _stakingModuleIndex) external view returns (StakingModule memory) { return _getStakingModuleByIndex(_stakingModuleIndex); } + + function testing_setBaseVersion(uint256 version) external { + CONTRACT_VERSION_POSITION.setStorageUint256(version); + } + + function testing_setStakingModuleStatus(uint256 _stakingModuleId, StakingModuleStatus _status) external { + StakingModule storage stakingModule = _getStakingModuleById(_stakingModuleId); + _setStakingModuleStatus(stakingModule, _status); + } } diff --git a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts index 000e56ff2..759ab5f56 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.misc.test.ts @@ -8,12 +8,12 @@ import { DepositContract__MockForBeaconChainDepositor, DepositContract__MockForBeaconChainDepositor__factory, MinFirstAllocationStrategy__factory, - StakingRouter, - StakingRouter__factory, + StakingRouterMock, + StakingRouterMock__factory, } from "typechain-types"; import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; -import { certainAddress, ether, proxify } from "lib"; +import { certainAddress, ether, MAX_UINT256, proxify, randomString } from "lib"; describe("StakingRouter", () => { let deployer: HardhatEthersSigner; @@ -22,7 +22,8 @@ describe("StakingRouter", () => { let user: HardhatEthersSigner; let depositContract: DepositContract__MockForBeaconChainDepositor; - let stakingRouter: StakingRouter; + let stakingRouter: StakingRouterMock; + let impl: StakingRouterMock; const lido = certainAddress("test:staking-router:lido"); const withdrawalCredentials = hexlify(randomBytes(32)); @@ -37,7 +38,8 @@ describe("StakingRouter", () => { ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), }; - const impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); + impl = await new StakingRouterMock__factory(allocLibAddr, deployer).deploy(depositContract); + [stakingRouter] = await proxify({ impl, admin: proxyAdmin, caller: user }); }); @@ -70,6 +72,122 @@ describe("StakingRouter", () => { }); }); + context("finalizeUpgrade_v2()", () => { + const STAKE_SHARE_LIMIT = 1_00n; + const PRIORITY_EXIT_SHARE_THRESHOLD = STAKE_SHARE_LIMIT; + const MODULE_FEE = 5_00n; + const TREASURY_FEE = 5_00n; + const MAX_DEPOSITS_PER_BLOCK = 150n; + const MIN_DEPOSIT_BLOCK_DISTANCE = 25n; + + const modulesCount = 3; + const newPriorityExitShareThresholds = [2_01n, 2_02n, 2_03n]; + const newMaxDepositsPerBlock = [201n, 202n, 203n]; + const newMinDepositBlockDistances = [31n, 32n, 33n]; + + beforeEach(async () => { + // initialize staking router + await stakingRouter.initialize(stakingRouterAdmin.address, lido, withdrawalCredentials); + // grant roles + await stakingRouter + .connect(stakingRouterAdmin) + .grantRole(await stakingRouter.STAKING_MODULE_MANAGE_ROLE(), stakingRouterAdmin); + + for (let i = 0; i < modulesCount; i++) { + await stakingRouter + .connect(stakingRouterAdmin) + .addStakingModule( + randomString(8), + certainAddress(`test:staking-router:staking-module-${i}`), + STAKE_SHARE_LIMIT, + PRIORITY_EXIT_SHARE_THRESHOLD, + MODULE_FEE, + TREASURY_FEE, + MAX_DEPOSITS_PER_BLOCK, + MIN_DEPOSIT_BLOCK_DISTANCE, + ); + } + expect(await stakingRouter.getStakingModulesCount()).to.equal(modulesCount); + }); + + it("fails with UnexpectedContractVersion error when called on implementation", async () => { + await expect(impl.finalizeUpgrade_v2([], [], [])) + .to.be.revertedWithCustomError(impl, "UnexpectedContractVersion") + .withArgs(MAX_UINT256, 1); + }); + + it("fails with UnexpectedContractVersion error when called on deployed from scratch SRv2", async () => { + await expect(stakingRouter.finalizeUpgrade_v2([], [], [])) + .to.be.revertedWithCustomError(impl, "UnexpectedContractVersion") + .withArgs(2, 1); + }); + + context("simulate upgrade from v1", () => { + beforeEach(async () => { + // reset contract version + await stakingRouter.testing_setBaseVersion(1); + }); + + it("fails with ArraysLengthMismatch error when _priorityExitShareThresholds input array length mismatch", async () => { + const wrongPriorityExitShareThresholds = [1n]; + await expect( + stakingRouter.finalizeUpgrade_v2( + wrongPriorityExitShareThresholds, + newMaxDepositsPerBlock, + newMinDepositBlockDistances, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "ArraysLengthMismatch") + .withArgs(3, 1); + }); + + it("fails with ArraysLengthMismatch error when _maxDepositsPerBlock input array length mismatch", async () => { + const wrongMaxDepositsPerBlock = [100n, 101n]; + await expect( + stakingRouter.finalizeUpgrade_v2( + newPriorityExitShareThresholds, + wrongMaxDepositsPerBlock, + newMinDepositBlockDistances, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "ArraysLengthMismatch") + .withArgs(3, 2); + }); + + it("fails with ArraysLengthMismatch error when _minDepositBlockDistances input array length mismatch", async () => { + const wrongMinDepositBlockDistances = [41n, 42n, 43n, 44n]; + await expect( + stakingRouter.finalizeUpgrade_v2( + newPriorityExitShareThresholds, + newMaxDepositsPerBlock, + wrongMinDepositBlockDistances, + ), + ) + .to.be.revertedWithCustomError(stakingRouter, "ArraysLengthMismatch") + .withArgs(3, 4); + }); + + it("sets correct contract version", async () => { + expect(await stakingRouter.getContractVersion()).to.equal(1); + await stakingRouter.finalizeUpgrade_v2( + newPriorityExitShareThresholds, + newMaxDepositsPerBlock, + newMinDepositBlockDistances, + ); + expect(await stakingRouter.getContractVersion()).to.be.equal(2); + + const modules = await stakingRouter.getStakingModules(); + expect(modules.length).to.be.equal(modulesCount); + + for (let i = 0; i < modulesCount; i++) { + expect(modules[i].priorityExitShareThreshold).to.be.equal(newPriorityExitShareThresholds[i]); + expect(modules[i].maxDepositsPerBlock).to.be.equal(newMaxDepositsPerBlock[i]); + expect(modules[i].minDepositBlockDistance).to.be.equal(newMinDepositBlockDistances[i]); + } + }); + }); + }); + context("receive", () => { it("Reverts", async () => { await expect( diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts index 573e6319b..54aaef284 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts @@ -71,8 +71,7 @@ describe("StakingRouter:module-management", () => { ), ).to.be.revertedWithOZAccessControlError(user.address, await stakingRouter.STAKING_MODULE_MANAGE_ROLE()); }); - //todo priority < share - //todo priority > 100 + it("Reverts if the target share is greater than 100%", async () => { const STAKE_SHARE_LIMIT_OVER_100 = 100_01; @@ -370,6 +369,20 @@ describe("StakingRouter:module-management", () => { ).to.be.revertedWithCustomError(stakingRouter, "InvalidPriorityExitShareThreshold"); }); + it("Reverts if the new deposit block distance is zero", async () => { + await expect( + stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + 0n, + ), + ).to.be.revertedWithCustomError(stakingRouter, "InvalidMinDepositBlockDistance"); + }); + it("Reverts if the sum of the new module and treasury fees is greater than 100%", async () => { const NEW_MODULE_FEE_INVALID = 100_01n - TREASURY_FEE; diff --git a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts index 482ab6c05..e99bcf681 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.status-control.test.ts @@ -8,8 +8,8 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { DepositContract__MockForBeaconChainDepositor__factory, MinFirstAllocationStrategy__factory, - StakingRouter, - StakingRouter__factory, + StakingRouterMock, + StakingRouterMock__factory, } from "typechain-types"; import { StakingRouterLibraryAddresses } from "typechain-types/factories/contracts/0.8.9/StakingRouter__factory"; @@ -26,7 +26,7 @@ context("StakingRouter:status-control", () => { let admin: HardhatEthersSigner; let user: HardhatEthersSigner; - let stakingRouter: StakingRouter; + let stakingRouter: StakingRouterMock; let moduleId: bigint; beforeEach(async () => { @@ -39,7 +39,7 @@ context("StakingRouter:status-control", () => { ["contracts/common/lib/MinFirstAllocationStrategy.sol:MinFirstAllocationStrategy"]: await allocLib.getAddress(), }; - const impl = await new StakingRouter__factory(allocLibAddr, deployer).deploy(depositContract); + const impl = await new StakingRouterMock__factory(allocLibAddr, deployer).deploy(depositContract); [stakingRouter] = await proxify({ impl, admin }); @@ -85,6 +85,16 @@ context("StakingRouter:status-control", () => { .to.emit(stakingRouter, "StakingModuleStatusSet") .withArgs(moduleId, Status.DepositsPaused, admin.address); }); + + it("Not emit event when new status is the same", async () => { + await stakingRouter.setStakingModuleStatus(moduleId, Status.DepositsPaused); + + await expect(stakingRouter.testing_setStakingModuleStatus(moduleId, Status.DepositsPaused)).to.not.emit( + stakingRouter, + "StakingModuleStatusSet", + ); + expect(await stakingRouter.getStakingModuleStatus(moduleId)).to.equal(Status.DepositsPaused); + }); }); context("getStakingModuleIsStopped", () => { From 434d60bb9d3462fcfe1f5ce19e4d36f4d621bad0 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 4 Jul 2024 18:58:05 +0200 Subject: [PATCH 112/177] feat: fix errors after negative rebase merge --- .../OracleReportSanityChecker.sol | 19 +------------------ ...untingOracle.submitReportExtraData.test.ts | 1 + .../baseOracleReportSanityChecker.test.ts | 19 ++++++------------- .../negativeRebaseSanityChecker.test.ts | 3 ++- 4 files changed, 10 insertions(+), 32 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 2008e5d01..11ecfdefb 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -136,7 +136,6 @@ struct LimitsList { /// @dev The packed version of the LimitsList struct to be effectively persisted in storage struct LimitsListPacked { uint16 exitedValidatorsPerDayLimit; - uint16 appearedValidatorsPerDayLimit; uint16 annualBalanceIncreaseBPLimit; uint16 simulatedShareRateDeviationBPLimit; uint16 maxValidatorExitRequestsPerReport; @@ -147,6 +146,7 @@ struct LimitsListPacked { uint16 initialSlashingAmountPWei; uint16 inactivityPenaltiesAmountPWei; uint16 clBalanceOraclesErrorUpperBPLimit; + uint16 appearedValidatorsPerDayLimit; } struct ReportData { @@ -296,23 +296,6 @@ contract OracleReportSanityChecker is AccessControlEnumerable { _updateLimits(limitsList); } - /// @notice Sets the new value for the appearedValidatorsPerDayLimit - /// - /// NB: AccountingOracle reports validators as appeared once they become `pending` - /// (might be not `activated` yet). Thus, this limit should be high enough because consensus layer - /// has no intrinsic churn limit for the amount of `pending` validators (only for `activated` instead). - /// For Lido it depends on the amount of deposits that can be made via DepositSecurityModule daily. - /// - /// @param _exitedValidatorsPerDayLimit new exitedValidatorsPerDayLimit value - function setExitedValidatorsPerDayLimit(uint256 _exitedValidatorsPerDayLimit) - external - onlyRole(EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE) - { - LimitsList memory limitsList = _limits.unpack(); - limitsList.exitedValidatorsPerDayLimit = _exitedValidatorsPerDayLimit; - _updateLimits(limitsList); - } - /// @notice Sets the new value for the appearedValidatorsPerDayLimit /// /// NB: AccountingOracle reports validators as appeared once they become `pending` diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 5b4e6af6a..2a8b1aa23 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -917,6 +917,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await oracleMemberSubmitReportData(report); + await sanityChecker.grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin); await sanityChecker.setMaxAccountingExtraDataListItemsCount(maxItemsPerChunk - 1); await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) .to.be.revertedWithCustomError(sanityChecker, "MaxAccountingExtraDataItemsCountExceeded") diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index 18b4eec04..aea6e794f 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -141,7 +141,6 @@ describe("OracleReportSanityChecker.sol", () => { it("sets limits correctly", async () => { const newLimitsList = { exitedValidatorsPerDayLimit: 50, - appearedValidatorsPerDayLimit: 75, annualBalanceIncreaseBPLimit: 15_00, simulatedShareRateDeviationBPLimit: 1_50, // 1.5% maxValidatorExitRequestsPerReport: 3000, @@ -152,6 +151,7 @@ describe("OracleReportSanityChecker.sol", () => { initialSlashingAmountPWei: 2000, inactivityPenaltiesAmountPWei: 303, clBalanceOraclesErrorUpperBPLimit: 12, + appearedValidatorsPerDayLimit: 75, }; const limitsBefore = await oracleReportSanityChecker.getOracleReportLimits(); expect(limitsBefore.exitedValidatorsPerDayLimit).to.not.equal(newLimitsList.exitedValidatorsPerDayLimit); @@ -1183,7 +1183,7 @@ describe("OracleReportSanityChecker.sol", () => { .connect(admin) .grantRole( await oracleReportSanityChecker.EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), - managersRoster.churnValidatorsPerDayLimitManagers[0], + managersRoster.exitedValidatorsPerDayLimitManagers[0], ); const tx = await oracleReportSanityChecker .connect(managersRoster.exitedValidatorsPerDayLimitManagers[0]) @@ -1235,8 +1235,8 @@ describe("OracleReportSanityChecker.sol", () => { await oracleReportSanityChecker .connect(admin) .grantRole( - await oracleReportSanityChecker.EXITED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), - managersRoster.churnValidatorsPerDayLimitManagers[0], + await oracleReportSanityChecker.APPEARED_VALIDATORS_PER_DAY_LIMIT_MANAGER_ROLE(), + managersRoster.appearedValidatorsPerDayLimitManagers[0], ); const tx = await oracleReportSanityChecker @@ -1457,13 +1457,6 @@ describe("OracleReportSanityChecker.sol", () => { await oracleReportSanityChecker .connect(admin) .grantRole(await oracleReportSanityChecker.ALL_LIMITS_MANAGER_ROLE(), managersRoster.allLimitsManagers[0]); - await expect( - oracleReportSanityChecker - .connect(managersRoster.allLimitsManagers[0]) - .setOracleReportLimits({ ...defaultLimitsList, churnValidatorsPerDayLimit: INVALID_VALUE }, ZeroAddress), - ) - .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectLimitValue") - .withArgs(INVALID_VALUE, 0, MAX_UINT_16); await expect( oracleReportSanityChecker @@ -1479,7 +1472,7 @@ describe("OracleReportSanityChecker.sol", () => { await expect( oracleReportSanityChecker .connect(managersRoster.allLimitsManagers[0]) - .setOracleReportLimits({ ...defaultLimitsList, exitedValidatorsPerDayLimit: INVALID_VALUE }), + .setOracleReportLimits({ ...defaultLimitsList, exitedValidatorsPerDayLimit: INVALID_VALUE }, ZeroAddress), ) .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectLimitValue") .withArgs(INVALID_VALUE, 0, MAX_UINT_16); @@ -1487,7 +1480,7 @@ describe("OracleReportSanityChecker.sol", () => { await expect( oracleReportSanityChecker .connect(managersRoster.allLimitsManagers[0]) - .setOracleReportLimits({ ...defaultLimitsList, appearedValidatorsPerDayLimit: INVALID_VALUE }), + .setOracleReportLimits({ ...defaultLimitsList, appearedValidatorsPerDayLimit: INVALID_VALUE }, ZeroAddress), ) .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectLimitValue") .withArgs(INVALID_VALUE, 0, MAX_UINT_16); diff --git a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts index 7cd4ccc91..fecf92b0c 100644 --- a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts @@ -27,7 +27,7 @@ describe("OracleReportSanityChecker.sol", () => { const SLOTS_PER_DAY = 7200; const defaultLimitsList = { - churnValidatorsPerDayLimit: 55, + exitedValidatorsPerDayLimit: 50, annualBalanceIncreaseBPLimit: 10_00, // 10% simulatedShareRateDeviationBPLimit: 2_50, // 2.5% maxValidatorExitRequestsPerReport: 2000, @@ -38,6 +38,7 @@ describe("OracleReportSanityChecker.sol", () => { initialSlashingAmountPWei: 1000, // 1 ETH = 1000 PWei inactivityPenaltiesAmountPWei: 101, // 0.101 ETH = 101 PWei clBalanceOraclesErrorUpperBPLimit: 74, // 0.74% + appearedValidatorsPerDayLimit: 75, }; const gweis = (x: number) => parseUnits(x.toString(), "gwei"); From 8ec7dc9887f8e4bfa6180a79558f735686fafff2 Mon Sep 17 00:00:00 2001 From: Eddort Date: Fri, 5 Jul 2024 09:10:33 +0200 Subject: [PATCH 113/177] fix: nor tests --- test/0.4.24/nor/nor.aux.test.ts | 37 +++++++++++-------- .../0.4.24/nor/nor.initialize.upgrade.test.ts | 9 ++++- test/0.4.24/nor/nor.management.flow.test.ts | 13 +++++-- .../nor/nor.rewards.penalties.flow.test.ts | 9 ++++- test/0.4.24/nor/nor.signing.keys.test.ts | 9 ++++- 5 files changed, 56 insertions(+), 21 deletions(-) diff --git a/test/0.4.24/nor/nor.aux.test.ts b/test/0.4.24/nor/nor.aux.test.ts index 57f45a3c8..e8aafb37f 100644 --- a/test/0.4.24/nor/nor.aux.test.ts +++ b/test/0.4.24/nor/nor.aux.test.ts @@ -10,9 +10,11 @@ import { Lido, LidoLocator, LidoLocator__factory, + MinFirstAllocationStrategy__factory, NodeOperatorsRegistry__Harness, NodeOperatorsRegistry__Harness__factory, } from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; import { addNodeOperator, certainAddress, NodeOperatorConfig, prepIdsCountsPayload } from "lib"; @@ -107,7 +109,12 @@ describe("NodeOperatorsRegistry:auxiliary", () => { }, })); - impl = await new NodeOperatorsRegistry__Harness__factory(deployer).deploy(); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + + impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); const appProxy = await addAragonApp({ dao, name: "node-operators-registry", @@ -218,7 +225,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { expect(await acl["hasPermission(address,address,bytes32)"](stranger, nor, await nor.STAKING_ROUTER_ROLE())).to.be .false; - await expect(nor.updateTargetValidatorsLimits(firstNodeOperatorId, true, targetLimit)).to.be.revertedWith( + await expect(nor['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimit)).to.be.revertedWith( "APP_AUTH_FAILED", ); }); @@ -227,7 +234,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { const targetLimitWrong = BigInt("0x10000000000000000"); await expect( - nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, true, targetLimitWrong), + nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimitWrong), ).to.be.revertedWith("OUT_OF_RANGE"); }); @@ -237,9 +244,9 @@ describe("NodeOperatorsRegistry:auxiliary", () => { targetLimit = 10n; - await expect(nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, true, targetLimit)) + await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit); + .withArgs(firstNodeOperatorId, targetLimit, 1n); const keysStatTotal = await nor.getStakingModuleSummary(); const expectedExitedValidatorsCount = @@ -274,30 +281,30 @@ describe("NodeOperatorsRegistry:auxiliary", () => { targetLimit = 10n; - await expect(nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, true, targetLimit)) + await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit); + .withArgs(firstNodeOperatorId, targetLimit, 1n); let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.isTargetLimitActive).to.be.true; + expect(noSummary.targetLimitMode).to.be.equal(1n); - await expect(nor.connect(stakingRouter).updateTargetValidatorsLimits(secondNodeOperatorId, false, targetLimit)) + await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](secondNodeOperatorId, false, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, targetLimit); + .withArgs(secondNodeOperatorId, 0n, 0n); noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.isTargetLimitActive).to.be.false; + expect(noSummary.targetLimitMode).to.be.equal(0n); // reset limit - await expect(nor.connect(stakingRouter).updateTargetValidatorsLimits(firstNodeOperatorId, false, targetLimit)) + await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, false, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 10n); // expect limit set to 0 + .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.isTargetLimitActive).to.equal(false); + expect(noSummary.targetLimitMode).to.equal(0n); noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.isTargetLimitActive).to.equal(false); + expect(noSummary.targetLimitMode).to.equal(0n); }); }); diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 1c1cd05de..25bfaf378 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -11,9 +11,11 @@ import { Lido, LidoLocator, LidoLocator__factory, + MinFirstAllocationStrategy__factory, NodeOperatorsRegistry__Harness, NodeOperatorsRegistry__Harness__factory, } from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; import { addNodeOperator, certainAddress, NodeOperatorConfig } from "lib"; @@ -105,7 +107,12 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { }, })); - const impl = await new NodeOperatorsRegistry__Harness__factory(deployer).deploy(); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + + const impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); expect(await impl.getInitializationBlock()).to.equal(MaxUint256); const appProxy = await addAragonApp({ dao, diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index 90bc83361..486497ac8 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -10,9 +10,11 @@ import { Lido, LidoLocator, LidoLocator__factory, + MinFirstAllocationStrategy__factory, NodeOperatorsRegistry__Harness, NodeOperatorsRegistry__Harness__factory, } from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; import { addNodeOperator, certainAddress, NodeOperatorConfig, randomAddress } from "lib"; @@ -94,7 +96,12 @@ describe("NodeOperatorsRegistry:management", () => { }, })); - impl = await new NodeOperatorsRegistry__Harness__factory(deployer).deploy(); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + + impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); const appProxy = await addAragonApp({ dao, name: "node-operators-registry", @@ -568,7 +575,7 @@ describe("NodeOperatorsRegistry:management", () => { const noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.isTargetLimitActive).to.be.false; + expect(noSummary.targetLimitMode).to.be.equal(0n); expect(noSummary.targetValidatorsCount).to.be.equal(0n); expect(noSummary.stuckValidatorsCount).to.be.equal(0n); expect(noSummary.refundedValidatorsCount).to.be.equal(0n); @@ -591,7 +598,7 @@ describe("NodeOperatorsRegistry:management", () => { const noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.isTargetLimitActive).to.be.false; + expect(noSummary.targetLimitMode).to.be.equal(0n);; expect(noSummary.targetValidatorsCount).to.be.equal(0n); expect(noSummary.stuckValidatorsCount).to.be.equal(0n); expect(noSummary.refundedValidatorsCount).to.be.equal(0n); diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index ca15e88f4..fcdc54f4a 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -12,9 +12,11 @@ import { Lido, LidoLocator, LidoLocator__factory, + MinFirstAllocationStrategy__factory, NodeOperatorsRegistry__Harness, NodeOperatorsRegistry__Harness__factory, } from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; import { addNodeOperator, @@ -107,7 +109,12 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { }, })); - impl = await new NodeOperatorsRegistry__Harness__factory(deployer).deploy(); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + + impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); const appProxy = await addAragonApp({ dao, name: "node-operators-registry", diff --git a/test/0.4.24/nor/nor.signing.keys.test.ts b/test/0.4.24/nor/nor.signing.keys.test.ts index a0d66bcf5..de4c189d6 100644 --- a/test/0.4.24/nor/nor.signing.keys.test.ts +++ b/test/0.4.24/nor/nor.signing.keys.test.ts @@ -10,9 +10,11 @@ import { Lido, LidoLocator, LidoLocator__factory, + MinFirstAllocationStrategy__factory, NodeOperatorsRegistry__Harness, NodeOperatorsRegistry__Harness__factory, } from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; import { addNodeOperator, @@ -125,7 +127,12 @@ describe("NodeOperatorsRegistry:signing-keys", () => { }, })); - impl = await new NodeOperatorsRegistry__Harness__factory(deployer).deploy(); + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + + impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); const appProxy = await addAragonApp({ dao, name: "node-operators-registry", From cdcb3d3ecc87149d597212a78642949eb8aca661 Mon Sep 17 00:00:00 2001 From: Eddort Date: Fri, 5 Jul 2024 09:35:29 +0200 Subject: [PATCH 114/177] fix: remove reward distribution tests and handle NOR v3 --- .../0.4.24/nor/nor.initialize.upgrade.test.ts | 15 +++++++------ .../nor/nor.rewards.penalties.flow.test.ts | 21 +++---------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 25bfaf378..7924ddee9 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -93,7 +93,8 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { ]; const moduleType = encodeBytes32String("curated-onchain-v1"); - const contractVersion = 2n; + const contractVersionV2 = 2n; + const contractVersionV3 = 3n; before(async () => { [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager] = @@ -190,7 +191,9 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { await expect(nor.initialize(locator, moduleType, 86400n)) .to.emit(nor, "ContractVersionSet") - .withArgs(contractVersion) + .withArgs(contractVersionV2) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV3) .and.to.emit(nor, "StuckPenaltyDelayChanged") .withArgs(86400n) .and.to.emit(nor, "LocatorContractSet") @@ -202,7 +205,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { expect(await nor.getInitializationBlock()).to.equal(latestBlock + 1n); expect(await lido.allowance(await nor.getAddress(), burnerAddress)).to.equal(MaxUint256); expect(await nor.getStuckPenaltyDelay()).to.equal(86400n); - expect(await nor.getContractVersion()).to.equal(contractVersion); + expect(await nor.getContractVersion()).to.equal(3); expect(await nor.getType()).to.equal(moduleType); }); }); @@ -261,7 +264,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { await expect(nor.finalizeUpgrade_v2(locator, moduleType, 86400n)) .to.emit(nor, "ContractVersionSet") - .withArgs(contractVersion) + .withArgs(contractVersionV2) .and.to.emit(nor, "StuckPenaltyDelayChanged") .withArgs(86400n) .and.to.emit(nor, "LocatorContractSet") @@ -273,7 +276,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { expect(await nor.getInitializationBlock()).to.equal(latestBlock); expect(await lido.allowance(await nor.getAddress(), burnerAddress)).to.equal(MaxUint256); expect(await nor.getStuckPenaltyDelay()).to.equal(86400n); - expect(await nor.getContractVersion()).to.equal(contractVersion); + expect(await nor.getContractVersion()).to.equal(contractVersionV2); expect(await nor.getType()).to.equal(moduleType); }); @@ -312,7 +315,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { await expect(nor.finalizeUpgrade_v2(locator, moduleType, 86400n)) .to.emit(nor, "ContractVersionSet") - .withArgs(contractVersion) + .withArgs(contractVersionV2) .and.to.emit(nor, "StuckPenaltyDelayChanged") .withArgs(86400n) .and.to.emit(nor, "LocatorContractSet") diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index fcdc54f4a..1a7dbcac8 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -495,20 +495,6 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { expect(await lido.sharesOf(nor)).to.equal(1n); }); - it("Performs rewards distribution when called by StakingRouter", async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) - .to.be.true; - - await lido.connect(user).resume(); - await user.sendTransaction({ to: await lido.getAddress(), value: ether("1.0") }); - await lido.connect(user).transfer(await nor.getAddress(), await lido.balanceOf(user)); - - await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()).to.emit( - nor, - "RewardsDistributed", - ); - }); - it("Penalizes node operators with stuck penalty active", async () => { await lido.connect(user).resume(); await user.sendTransaction({ to: await lido.getAddress(), value: ether("1.0") }); @@ -523,10 +509,9 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { .withArgs(nonce + 1n) .to.emit(nor, "StuckPenaltyStateChanged") .withArgs(1n, 2n, 0n, 0n); - - await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()) - .to.emit(nor, "RewardsDistributed") - .to.emit(nor, "NodeOperatorPenalized"); + // TODO: how to cover it now? + // await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()) + // .to.emit(nor, "NodeOperatorPenalized"); }); }); From 7ce2811089e1fc6780712b05b1ed55a8d34433ca Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 5 Jul 2024 15:09:51 +0200 Subject: [PATCH 115/177] feat: reduce requestTimestampMargin size in order to fit all limits in one storage slot --- contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol | 4 ++-- test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 11ecfdefb..708729e75 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -141,7 +141,7 @@ struct LimitsListPacked { uint16 maxValidatorExitRequestsPerReport; uint16 maxAccountingExtraDataListItemsCount; uint16 maxNodeOperatorsPerExtraDataItemCount; - uint48 requestTimestampMargin; + uint32 requestTimestampMargin; uint64 maxPositiveTokenRebase; uint16 initialSlashingAmountPWei; uint16 inactivityPenaltiesAmountPWei; @@ -968,7 +968,7 @@ library LimitsListPacker { res.appearedValidatorsPerDayLimit = SafeCast.toUint16(_limitsList.appearedValidatorsPerDayLimit); res.annualBalanceIncreaseBPLimit = _toBasisPoints(_limitsList.annualBalanceIncreaseBPLimit); res.simulatedShareRateDeviationBPLimit = _toBasisPoints(_limitsList.simulatedShareRateDeviationBPLimit); - res.requestTimestampMargin = SafeCastExt.toUint48(_limitsList.requestTimestampMargin); + res.requestTimestampMargin = SafeCast.toUint32(_limitsList.requestTimestampMargin); res.maxPositiveTokenRebase = SafeCast.toUint64(_limitsList.maxPositiveTokenRebase); res.maxValidatorExitRequestsPerReport = SafeCast.toUint16(_limitsList.maxValidatorExitRequestsPerReport); res.maxAccountingExtraDataListItemsCount = SafeCast.toUint16(_limitsList.maxAccountingExtraDataListItemsCount); diff --git a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts index fecf92b0c..504039c06 100644 --- a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts @@ -123,8 +123,8 @@ describe("OracleReportSanityChecker.sol", () => { return 256; case "uint64": return 64; - case "uint48": - return 48; + case "uint32": + return 32; case "uint16": return 16; default: From ea0d2e200fe8e3dd771eaa50b17fb7ad4b1fa55a Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 5 Jul 2024 15:39:48 +0200 Subject: [PATCH 116/177] feat: fix scratch deploy --- scripts/scratch/deployed-testnet-defaults.json | 5 ++++- scripts/scratch/steps/09-deploy-non-aragon-contracts.ts | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/scratch/deployed-testnet-defaults.json b/scripts/scratch/deployed-testnet-defaults.json index 579f33810..ed1d067d9 100644 --- a/scripts/scratch/deployed-testnet-defaults.json +++ b/scripts/scratch/deployed-testnet-defaults.json @@ -110,7 +110,10 @@ "maxAccountingExtraDataListItemsCount": 100, "maxNodeOperatorsPerExtraDataItemCount": 100, "requestTimestampMargin": 128, - "maxPositiveTokenRebase": 5000000 + "maxPositiveTokenRebase": 5000000, + "initialSlashingAmountPWei": 1000, + "inactivityPenaltiesAmountPWei": 101, + "clBalanceOraclesErrorUpperBPLimit": 74 } }, "oracleDaemonConfig": { diff --git a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts index 31b77baf5..44e1fd07f 100644 --- a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts +++ b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts @@ -78,7 +78,6 @@ async function main() { admin, [ sanityChecks.exitedValidatorsPerDayLimit, - sanityChecks.oneOffCLBalanceDecreaseBPLimit, sanityChecks.annualBalanceIncreaseBPLimit, sanityChecks.simulatedShareRateDeviationBPLimit, sanityChecks.maxValidatorExitRequestsPerReport, @@ -86,9 +85,11 @@ async function main() { sanityChecks.maxNodeOperatorsPerExtraDataItemCount, sanityChecks.requestTimestampMargin, sanityChecks.maxPositiveTokenRebase, + sanityChecks.initialSlashingAmountPWei, + sanityChecks.inactivityPenaltiesAmountPWei, + sanityChecks.clBalanceOraclesErrorUpperBPLimit, sanityChecks.appearedValidatorsPerDayLimit, ], - [[], [], [], [], [], [], [], [], [], [], []], ]; const oracleReportSanityChecker = await deployWithoutProxy( Sk.oracleReportSanityChecker, From 068d8231838d43582f4b7cc4d3734829104b22c6 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Fri, 5 Jul 2024 17:47:15 +0400 Subject: [PATCH 117/177] fix: lint --- scripts/staking-router-v2/sr-v2-deploy.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index d5428a965..0c7283592 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -1,5 +1,10 @@ +import * as dotenv from "dotenv"; import { ethers, run } from "hardhat"; +import { join } from "path"; +import readline from "readline"; + import { DepositSecurityModule, DepositSecurityModule__factory } from "typechain-types"; + import { cy, deployImplementation, @@ -12,10 +17,6 @@ import { updateObjectInState, } from "lib"; -import readline from "readline"; -import * as dotenv from "dotenv"; -import { join } from "path"; - dotenv.config({ path: join(__dirname, "../../.env") }); function getEnvVariable(name: string, defaultValue?: string) { From 3c610761ccd34eab15140c3d5fc290e708a43b4d Mon Sep 17 00:00:00 2001 From: Eddort Date: Fri, 5 Jul 2024 17:57:05 +0200 Subject: [PATCH 118/177] fix: nor distributeReward test --- .../contracts/NodeOperatorsRegistry__Harness.sol | 4 ++++ test/0.4.24/nor/nor.rewards.penalties.flow.test.ts | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol b/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol index 1f2931dd4..f8704ec61 100644 --- a/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol +++ b/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol @@ -161,4 +161,8 @@ contract NodeOperatorsRegistry__Harness is NodeOperatorsRegistry { signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, _newVettedKeys); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); } + + function harness__setRewardDistributionState(RewardDistributionState _state) external { + _updateRewardDistributionState(_state); + } } diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index 1a7dbcac8..39c8271f8 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -30,6 +30,12 @@ import { import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; +enum RewardDistributionState { + TransferredToModule, // New reward portion minted and transferred to the module + ReadyForDistribution, // Operators' statistics updated, reward ready for distribution + Distributed, // Reward distributed among operators +} + describe("NodeOperatorsRegistry:rewards-penalties", () => { let deployer: HardhatEthersSigner; let user: HardhatEthersSigner; @@ -509,9 +515,10 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { .withArgs(nonce + 1n) .to.emit(nor, "StuckPenaltyStateChanged") .withArgs(1n, 2n, 0n, 0n); - // TODO: how to cover it now? - // await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()) - // .to.emit(nor, "NodeOperatorPenalized"); + + await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); + await expect(nor.connect(stakingRouter).distributeReward()) + .to.emit(nor, "NodeOperatorPenalized"); }); }); From 4f0be1af611356f6989db769682003204dd5059d Mon Sep 17 00:00:00 2001 From: Eddort Date: Fri, 5 Jul 2024 18:11:06 +0200 Subject: [PATCH 119/177] fix: use updateTargetLimits and updateTargetLimitsDeprecated in aux tests --- test/0.4.24/nor/nor.aux.test.ts | 55 +++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/test/0.4.24/nor/nor.aux.test.ts b/test/0.4.24/nor/nor.aux.test.ts index e8aafb37f..31dbe50d6 100644 --- a/test/0.4.24/nor/nor.aux.test.ts +++ b/test/0.4.24/nor/nor.aux.test.ts @@ -21,6 +21,9 @@ import { addNodeOperator, certainAddress, NodeOperatorConfig, prepIdsCountsPaylo import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; +const updateTargetLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)"; +const updateTargetLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)"; + describe("NodeOperatorsRegistry:auxiliary", () => { let deployer: HardhatEthersSigner; let user: HardhatEthersSigner; @@ -225,7 +228,11 @@ describe("NodeOperatorsRegistry:auxiliary", () => { expect(await acl["hasPermission(address,address,bytes32)"](stranger, nor, await nor.STAKING_ROUTER_ROLE())).to.be .false; - await expect(nor['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimit)).to.be.revertedWith( + await expect(nor[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)).to.be.revertedWith( + "APP_AUTH_FAILED", + ); + + await expect(nor[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)).to.be.revertedWith( "APP_AUTH_FAILED", ); }); @@ -234,7 +241,11 @@ describe("NodeOperatorsRegistry:auxiliary", () => { const targetLimitWrong = BigInt("0x10000000000000000"); await expect( - nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimitWrong), + nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimitWrong), + ).to.be.revertedWith("OUT_OF_RANGE"); + + await expect( + nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimitWrong), ).to.be.revertedWith("OUT_OF_RANGE"); }); @@ -244,7 +255,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { targetLimit = 10n; - await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimit)) + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, targetLimit, 1n); @@ -281,14 +292,46 @@ describe("NodeOperatorsRegistry:auxiliary", () => { targetLimit = 10n; - await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, true, targetLimit)) + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + + await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(secondNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(0n); + + // reset limit + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + }); + + it("updates node operator target limit mode correctly using updateTargetLimitsDeprecated", async () => { + expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) + .to.be.true; + + targetLimit = 10n; + + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, targetLimit, 1n); let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.be.equal(1n); - await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](secondNodeOperatorId, false, targetLimit)) + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](secondNodeOperatorId, false, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(secondNodeOperatorId, 0n, 0n); @@ -296,7 +339,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { expect(noSummary.targetLimitMode).to.be.equal(0n); // reset limit - await expect(nor.connect(stakingRouter)['updateTargetValidatorsLimits(uint256,bool,uint256)'](firstNodeOperatorId, false, targetLimit)) + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, false, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 From cfa60eda916d3ccd558a940f80e13bc01779194e Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 8 Jul 2024 14:07:54 +0200 Subject: [PATCH 120/177] feat: refactor and decreaseVettedSigningKeysCount tests --- .../NodeOperatorsRegistryMock.sol | 228 ------ lib/nor.ts | 6 + .../NodeOperatorsRegistry__Harness.sol | 30 +- .../nor/node-operators-registry.test.ts | 669 ------------------ .../0.4.24/nor/nor.initialize.upgrade.test.ts | 39 +- test/0.4.24/nor/nor.management.flow.test.ts | 35 +- test/0.4.24/nor/nor.staking.limit.test.ts | 370 ++++++++++ 7 files changed, 469 insertions(+), 908 deletions(-) delete mode 100644 contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol delete mode 100644 test/0.4.24/nor/node-operators-registry.test.ts create mode 100644 test/0.4.24/nor/nor.staking.limit.test.ts diff --git a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol deleted file mode 100644 index f600c8b07..000000000 --- a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol +++ /dev/null @@ -1,228 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.4.24; - -import "../nos/NodeOperatorsRegistry.sol"; - -contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { - - function increaseNodeOperatorDepositedSigningKeysCount(uint256 _nodeOperatorId, uint64 _keysCount) external { - Packed64x4.Packed memory signingKeysStats = _nodeOperators[_nodeOperatorId].signingKeysStats; - signingKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET) + _keysCount); - _nodeOperators[_nodeOperatorId].signingKeysStats = signingKeysStats; - - Packed64x4.Packed memory totalSigningKeysStats = _loadSummarySigningKeysStats(); - totalSigningKeysStats.set( - TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET).add(_keysCount) - ); - _saveSummarySigningKeysStats(totalSigningKeysStats); - - _updateSummaryMaxValidatorsCount(_nodeOperatorId); - } - - function testing_markAllKeysDeposited() external { - uint256 nodeOperatorsCount = getNodeOperatorsCount(); - Packed64x4.Packed memory signingKeysStats; - for (uint256 i; i < nodeOperatorsCount; ++i) { - signingKeysStats = _loadOperatorSigningKeysStats(i); - testing_setDepositedSigningKeysCount(i, signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET)); - } - } - - function testing_markAllKeysDeposited(uint256 _nodeOperatorId) external { - _onlyExistedNodeOperator(_nodeOperatorId); - Packed64x4.Packed memory signingKeysStats = _nodeOperators[_nodeOperatorId].signingKeysStats; - testing_setDepositedSigningKeysCount(_nodeOperatorId, signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET)); - } - - function testing_setDepositedSigningKeysCount(uint256 _nodeOperatorId, uint256 _depositedSigningKeysCount) public { - _onlyExistedNodeOperator(_nodeOperatorId); - // NodeOperator storage nodeOperator = _nodeOperators[_nodeOperatorId]; - Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - uint256 depositedSigningKeysCountBefore = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); - if (_depositedSigningKeysCount == depositedSigningKeysCountBefore) { - return; - } - - require( - _depositedSigningKeysCount <= signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET), - "DEPOSITED_SIGNING_KEYS_COUNT_TOO_HIGH" - ); - require( - _depositedSigningKeysCount >= signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET), "DEPOSITED_SIGNING_KEYS_COUNT_TOO_LOW" - ); - - signingKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, uint64(_depositedSigningKeysCount)); - _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); - - emit DepositedSigningKeysCountChanged(_nodeOperatorId, _depositedSigningKeysCount); - _increaseValidatorsKeysNonce(); - } - - function testing_unsafeDeactivateNodeOperator(uint256 _nodeOperatorId) external { - NodeOperator storage operator = _nodeOperators[_nodeOperatorId]; - operator.active = false; - } - - function testing_addNodeOperator( - string _name, - address _rewardAddress, - uint64 totalSigningKeysCount, - uint64 vettedSigningKeysCount, - uint64 depositedSigningKeysCount, - uint64 exitedSigningKeysCount - ) external returns (uint256 id) { - id = getNodeOperatorsCount(); - - TOTAL_OPERATORS_COUNT_POSITION.setStorageUint256(id + 1); - - NodeOperator storage operator = _nodeOperators[id]; - - uint256 activeOperatorsCount = getActiveNodeOperatorsCount(); - ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(activeOperatorsCount + 1); - - operator.active = true; - operator.name = _name; - operator.rewardAddress = _rewardAddress; - - Packed64x4.Packed memory signingKeysStats; - signingKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); - signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCount); - signingKeysStats.set(TOTAL_EXITED_KEYS_COUNT_OFFSET, exitedSigningKeysCount); - signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, totalSigningKeysCount); - - operator.signingKeysStats = signingKeysStats; - - Packed64x4.Packed memory operatorTargetStats; - operatorTargetStats.set(MAX_VALIDATORS_COUNT_OFFSET, vettedSigningKeysCount); - operator.targetValidatorsStats = operatorTargetStats; - - emit NodeOperatorAdded(id, _name, _rewardAddress, 0); - - Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); - summarySigningKeysStats.add(SUMMARY_MAX_VALIDATORS_COUNT_OFFSET, vettedSigningKeysCount); - summarySigningKeysStats.add(SUMMARY_EXITED_KEYS_COUNT_OFFSET, exitedSigningKeysCount); - summarySigningKeysStats.add(SUMMARY_TOTAL_KEYS_COUNT_OFFSET, totalSigningKeysCount); - summarySigningKeysStats.add(SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); - _saveSummarySigningKeysStats(summarySigningKeysStats); - } - - function testing_setNodeOperatorLimits( - uint256 _nodeOperatorId, - uint64 stuckValidatorsCount, - uint64 refundedValidatorsCount, - uint64 stuckPenaltyEndAt - ) external { - Packed64x4.Packed memory stuckPenaltyStats = _nodeOperators[_nodeOperatorId].stuckPenaltyStats; - stuckPenaltyStats.set(STUCK_VALIDATORS_COUNT_OFFSET, stuckValidatorsCount); - stuckPenaltyStats.set(REFUNDED_VALIDATORS_COUNT_OFFSET, refundedValidatorsCount); - stuckPenaltyStats.set(STUCK_PENALTY_END_TIMESTAMP_OFFSET, stuckPenaltyEndAt); - _nodeOperators[_nodeOperatorId].stuckPenaltyStats = stuckPenaltyStats; - _updateSummaryMaxValidatorsCount(_nodeOperatorId); - } - - function testing_getTotalSigningKeysStats() - external - view - returns ( - uint256 totalSigningKeysCount, - uint256 maxValidatorsCount, - uint256 depositedSigningKeysCount, - uint256 exitedSigningKeysCount - ) - { - Packed64x4.Packed memory summarySigningKeysStats = _loadSummarySigningKeysStats(); - totalSigningKeysCount = summarySigningKeysStats.get(SUMMARY_TOTAL_KEYS_COUNT_OFFSET); - maxValidatorsCount = summarySigningKeysStats.get(SUMMARY_MAX_VALIDATORS_COUNT_OFFSET); - depositedSigningKeysCount = summarySigningKeysStats.get(SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET); - exitedSigningKeysCount = summarySigningKeysStats.get(SUMMARY_EXITED_KEYS_COUNT_OFFSET); - } - - function testing_setBaseVersion(uint256 _newBaseVersion) external { - _setContractVersion(_newBaseVersion); - } - - function testing_setRewardDistributionState(RewardDistributionState _state) external { - _updateRewardDistributionState(_state); - } - - function testing_resetRegistry() external { - uint256 totalOperatorsCount = TOTAL_OPERATORS_COUNT_POSITION.getStorageUint256(); - TOTAL_OPERATORS_COUNT_POSITION.setStorageUint256(0); - ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(0); - KEYS_OP_INDEX_POSITION.setStorageUint256(0); - - _nodeOperatorSummary = NodeOperatorSummary({ - summarySigningKeysStats: Packed64x4.Packed(0) - }); - - Packed64x4.Packed memory tmp; - for (uint256 i = 0; i < totalOperatorsCount; ++i) { - _nodeOperators[i] = NodeOperator(false, address(0), new string(0), tmp, tmp, tmp); - } - } - - function testing_getSigningKeysAllocationData(uint256 _keysCount) - external - view - returns ( - uint256 allocatedKeysCount, - uint256[] memory nodeOperatorIds, - uint256[] memory activeKeyCountsAfterAllocation - ) - { - return _getSigningKeysAllocationData(_keysCount); - } - - function testing_obtainDepositData(uint256 _keysToAllocate) - external - returns (uint256 loadedValidatorsKeysCount, bytes memory publicKeys, bytes memory signatures) - { - (publicKeys, signatures) = this.obtainDepositData(_keysToAllocate, new bytes(0)); - emit ValidatorsKeysLoaded(publicKeys, signatures); - } - - function testing_isNodeOperatorPenalized(uint256 operatorId) external view returns (bool) { - Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats(operatorId); - if ( - stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET) < stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET) - || block.timestamp <= stuckPenaltyStats.get(STUCK_PENALTY_END_TIMESTAMP_OFFSET) - ) { - return true; - } - return false; - } - - function testing_getNodeOperator(uint256 operatorId) external view - returns (uint256 exitedSigningKeysCount, uint256 depositedSigningKeysCount, uint256 maxSigningKeysCount) - { - return _getNodeOperator(operatorId); - } - - event ValidatorsKeysLoaded(bytes publicKeys, bytes signatures); - - function testing_distributeRewards() external returns (uint256) { - return _distributeRewards(); - } - - function testing_setNodeOperatorPenalty( - uint256 _nodeOperatorId, - uint256 _refundedValidatorsCount, - uint256 _stuckValidatorsCount, - uint256 _stuckPenaltyEndTimestamp - ) external { - _requireValidRange(_refundedValidatorsCount <= UINT64_MAX); - _requireValidRange(_stuckValidatorsCount <= UINT64_MAX); - _requireValidRange(_stuckPenaltyEndTimestamp <= UINT64_MAX); - Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats( - _nodeOperatorId - ); - - stuckPenaltyStats.set(REFUNDED_VALIDATORS_COUNT_OFFSET, uint64(_refundedValidatorsCount)); - stuckPenaltyStats.set(STUCK_VALIDATORS_COUNT_OFFSET, uint64(_stuckValidatorsCount)); - stuckPenaltyStats.set(STUCK_PENALTY_END_TIMESTAMP_OFFSET, uint64(_stuckPenaltyEndTimestamp)); - _saveOperatorStuckPenaltyStats(_nodeOperatorId, stuckPenaltyStats); - _updateSummaryMaxValidatorsCount(_nodeOperatorId); - } -} diff --git a/lib/nor.ts b/lib/nor.ts index 34c4850a5..6b87317fb 100644 --- a/lib/nor.ts +++ b/lib/nor.ts @@ -133,3 +133,9 @@ export function prepIdsCountsPayload(ids: bigint[], counts: bigint[]): IdsCounts keysCounts: "0x" + counts.map((count) => numberToHex(count, 16)).join(""), }; } + +export enum RewardDistributionState { + TransferredToModule, // New reward portion minted and transferred to the module + ReadyForDistribution, // Operators' statistics updated, reward ready for distribution + Distributed, // Reward distributed among operators +} diff --git a/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol b/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol index f8704ec61..a627a020d 100644 --- a/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol +++ b/test/0.4.24/contracts/NodeOperatorsRegistry__Harness.sol @@ -112,7 +112,11 @@ contract NodeOperatorsRegistry__Harness is NodeOperatorsRegistry { uint256[] _nodeOperatorIds, uint256[] _activeKeyCountsAfterAllocation ) external returns (bytes memory pubkeys, bytes memory signatures) { - (pubkeys, signatures) = _loadAllocatedSigningKeys(_keysCountToLoad, _nodeOperatorIds, _activeKeyCountsAfterAllocation); + (pubkeys, signatures) = _loadAllocatedSigningKeys( + _keysCountToLoad, + _nodeOperatorIds, + _activeKeyCountsAfterAllocation + ); obtainedPublicKeys = pubkeys; obtainedSignatures = signatures; @@ -120,11 +124,17 @@ contract NodeOperatorsRegistry__Harness is NodeOperatorsRegistry { emit ValidatorsKeysLoaded(pubkeys, signatures); } - function harness__getSigningKeysAllocationData(uint256 _keysCount) external view returns ( - uint256 allocatedKeysCount, - uint256[] memory nodeOperatorIds, - uint256[] memory activeKeyCountsAfterAllocation - ) { + function harness__getSigningKeysAllocationData( + uint256 _keysCount + ) + external + view + returns ( + uint256 allocatedKeysCount, + uint256[] memory nodeOperatorIds, + uint256[] memory activeKeyCountsAfterAllocation + ) + { return _getSigningKeysAllocationData(_keysCount); } @@ -163,6 +173,10 @@ contract NodeOperatorsRegistry__Harness is NodeOperatorsRegistry { } function harness__setRewardDistributionState(RewardDistributionState _state) external { - _updateRewardDistributionState(_state); - } + _updateRewardDistributionState(_state); + } + + function harness__setBaseVersion(uint256 _newBaseVersion) external { + _setContractVersion(_newBaseVersion); + } } diff --git a/test/0.4.24/nor/node-operators-registry.test.ts b/test/0.4.24/nor/node-operators-registry.test.ts deleted file mode 100644 index 223659659..000000000 --- a/test/0.4.24/nor/node-operators-registry.test.ts +++ /dev/null @@ -1,669 +0,0 @@ -import assert from "node:assert"; - -import { expect } from "chai"; -import { ZeroAddress } from "ethers"; -import { ethers } from "hardhat"; - -import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; - -import { - ACL, - Kernel, - Lido, - LidoLocator, - LidoLocator__factory, - MinFirstAllocationStrategy__factory, - NodeOperatorsRegistryMock, - NodeOperatorsRegistryMock__factory, -} from "typechain-types"; -import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; - -import { addAragonApp, deployLidoDao, hasPermission } from "test/deploy"; - -const CURATED_TYPE = "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000"; // "curated-onchain-v1" -const PENALTY_DELAY = 2 * 24 * 60 * 60; // 2 days -const ADDRESS_1 = "0x0000000000000000000000000000000000000001"; -const ADDRESS_2 = "0x0000000000000000000000000000000000000002"; -const ADDRESS_3 = "0x0000000000000000000000000000000000000003"; -const ADDRESS_4 = "0x0000000000000000000000000000000000000005"; - -const NODE_OPERATORS: NodeOperatorConfig[] = [ - { - name: "foo", - rewardAddress: ADDRESS_1, - totalSigningKeysCount: 10, - depositedSigningKeysCount: 5, - exitedSigningKeysCount: 1, - vettedSigningKeysCount: 6, - stuckValidatorsCount: 0, - refundedValidatorsCount: 0, - stuckPenaltyEndAt: 0, - }, - { - name: " bar", - rewardAddress: ADDRESS_2, - totalSigningKeysCount: 15, - depositedSigningKeysCount: 7, - exitedSigningKeysCount: 0, - vettedSigningKeysCount: 10, - stuckValidatorsCount: 0, - refundedValidatorsCount: 0, - stuckPenaltyEndAt: 0, - }, - { - name: "deactivated", - isActive: false, - rewardAddress: ADDRESS_3, - totalSigningKeysCount: 10, - depositedSigningKeysCount: 0, - exitedSigningKeysCount: 0, - vettedSigningKeysCount: 5, - stuckValidatorsCount: 0, - refundedValidatorsCount: 0, - stuckPenaltyEndAt: 0, - }, -]; - -enum RewardDistributionState { - TransferredToModule, // New reward portion minted and transferred to the module - ReadyForDistribution, // Operators' statistics updated, reward ready for distribution - Distributed, // Reward distributed among operators -} - -describe("NodeOperatorsRegistry", () => { - let deployer: HardhatEthersSigner; - let user: HardhatEthersSigner; - let stranger: HardhatEthersSigner; - - let limitsManager: HardhatEthersSigner; - let nodeOperatorsManager: HardhatEthersSigner; - let signingKeysManager: HardhatEthersSigner; - let stakingRouter: HardhatEthersSigner; - let lido: Lido; - let dao: Kernel; - let acl: ACL; - let locator: LidoLocator; - - let impl: NodeOperatorsRegistryMock; - let nor: NodeOperatorsRegistryMock; - - beforeEach(async () => { - [deployer, user, stranger, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager] = - await ethers.getSigners(); - - ({ lido, dao, acl } = await deployLidoDao({ - rootAccount: deployer, - initialized: true, - locatorConfig: { - stakingRouter, - }, - })); - - const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); - const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { - ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), - }; - - impl = await new NodeOperatorsRegistryMock__factory(allocLibAddr, deployer).deploy(); - const appProxy = await addAragonApp({ - dao, - name: "node-operators-registry", - impl, - rootAccount: deployer, - }); - - nor = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); - - await acl.createPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), deployer); - await acl.createPermission(signingKeysManager, nor, await nor.MANAGE_SIGNING_KEYS(), deployer); - await acl.createPermission(nodeOperatorsManager, nor, await nor.MANAGE_NODE_OPERATOR_ROLE(), deployer); - await acl.createPermission(limitsManager, nor, await nor.SET_NODE_OPERATOR_LIMIT_ROLE(), deployer); - - // grant role to nor itself cause it uses solidity's call method to itself - // inside the testing_requestValidatorsKeysForDeposits() method - await acl.grantPermission(nor, nor, await nor.STAKING_ROUTER_ROLE()); - - locator = LidoLocator__factory.connect(await lido.getLidoLocator(), user); - - // Initialize the nor's proxy. - await expect(nor.initialize(locator, CURATED_TYPE, PENALTY_DELAY)) - .to.emit(nor, "ContractVersionSet") - .withArgs(2) - .and.to.emit(nor, "LocatorContractSet") - .withArgs(locator) - .and.to.emit(nor, "StakingModuleTypeSet") - .withArgs(CURATED_TYPE); - - nor = nor.connect(user); - }); - - context("initialize", () => { - it("sets module type correctly", async () => { - expect(await nor.getType()).to.be.equal(CURATED_TYPE); - }); - - it("sets locator correctly", async () => { - expect(await nor.getLocator()).to.be.equal(locator); - }); - - it("sets contract version correctly", async () => { - expect(await nor.getContractVersion()).to.be.equal(3); - }); - - it("sets reward distribution state correctly", async () => { - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); - }); - - it("sets hasInitialized() to true", async () => { - expect(await nor.hasInitialized()).to.be.true; - }); - - it("can't be initialized second time", async () => { - await expect(nor.initialize(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith("INIT_ALREADY_INITIALIZED"); - }); - - it('reverts with error "ZERO_ADDRESS" when locator is zero address', async () => { - const appProxy = await addAragonApp({ - dao, - name: "new-node-operators-registry", - impl, - rootAccount: deployer, - }); - const registry = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); - await expect(registry.initialize(ZeroAddress, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith("ZERO_ADDRESS"); - }); - - it('call on implementation reverts with error "INIT_ALREADY_INITIALIZED"', async () => { - // Implementation initializer reverts because initialization block was set to max(uint256) - // in the Autopetrified base contract - await expect(impl.initialize(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( - "INIT_ALREADY_INITIALIZED", - ); - }); - }); - - context("finalizeUpgrade_v2()", () => { - beforeEach(async () => { - // reset version there to test upgrade finalization - await nor.testing_setBaseVersion(0); - }); - - it("fails with CONTRACT_NOT_INITIALIZED error when called on implementation", async () => { - await expect(impl.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( - "CONTRACT_NOT_INITIALIZED", - ); - }); - - it("fails with CONTRACT_NOT_INITIALIZED error when nor instance not initialized yet", async () => { - const appProxy = await addAragonApp({ - dao, - name: "new-node-operators-registry", - impl, - rootAccount: deployer, - }); - const registry = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); - await expect(registry.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( - "CONTRACT_NOT_INITIALIZED", - ); - }); - - it("sets correct contract version", async () => { - await nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY); - expect(await nor.getContractVersion()).to.be.equal(2); - }); - - it("reverts with error UNEXPECTED_CONTRACT_VERSION when called on already initialized contract", async () => { - await nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY); - expect(await nor.getContractVersion()).to.be.equal(2); - await expect(nor.finalizeUpgrade_v2(locator, CURATED_TYPE, PENALTY_DELAY)).to.be.revertedWith( - "UNEXPECTED_CONTRACT_VERSION", - ); - }); - }); - - context("finalizeUpgrade_v3()", () => { - beforeEach(async () => { - // reset version there to test upgrade finalization - await nor.testing_setBaseVersion(2); - await nor.testing_setRewardDistributionState(0); - }); - - it("fails with CONTRACT_NOT_INITIALIZED error when called on implementation", async () => { - await expect(impl.finalizeUpgrade_v3()).to.be.revertedWith("CONTRACT_NOT_INITIALIZED"); - }); - - it("fails with CONTRACT_NOT_INITIALIZED error when nor instance not initialized yet", async () => { - const appProxy = await addAragonApp({ - dao, - name: "new-node-operators-registry", - impl, - rootAccount: deployer, - }); - const registry = NodeOperatorsRegistryMock__factory.connect(appProxy, deployer); - await expect(registry.finalizeUpgrade_v3()).to.be.revertedWith("CONTRACT_NOT_INITIALIZED"); - }); - - it("sets correct contract version", async () => { - await nor.finalizeUpgrade_v3(); - expect(await nor.getContractVersion()).to.be.equal(3); - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); - }); - - it("reverts with error UNEXPECTED_CONTRACT_VERSION when called on already initialized contract", async () => { - await nor.finalizeUpgrade_v3(); - expect(await nor.getContractVersion()).to.be.equal(3); - await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); - }); - }); - - context("setNodeOperatorName()", async () => { - const firstNodeOperatorId = 0; - const secondNodeOperatorId = 1; - - beforeEach(async () => { - expect(await addNodeOperator(nor, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal(firstNodeOperatorId); - expect(await addNodeOperator(nor, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal(secondNodeOperatorId); - }); - - it('reverts with "OUT_OF_RANGE" error when called on non existent node operator', async () => { - const notExitedNodeOperatorId = await nor.getNodeOperatorsCount(); - await expect( - nor.connect(nodeOperatorsManager).setNodeOperatorName(notExitedNodeOperatorId, "new name"), - ).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it('reverts with "WRONG_NAME_LENGTH" error when called with empty name', async () => { - await expect(nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, "")).to.be.revertedWith( - "WRONG_NAME_LENGTH", - ); - }); - - it('reverts with "WRONG_NAME_LENGTH" error when name exceeds MAX_NODE_OPERATOR_NAME_LENGTH', async () => { - const maxNameLength = await nor.MAX_NODE_OPERATOR_NAME_LENGTH(); - const tooLongName = "#".repeat(Number(maxNameLength) + 1); - assert(tooLongName.length > maxNameLength); - await expect( - nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, tooLongName), - ).to.be.revertedWith("WRONG_NAME_LENGTH"); - }); - - it('reverts with "APP_AUTH_FAILED" error when called by address without MANAGE_NODE_OPERATOR_ROLE', async () => { - expect(await hasPermission(dao, nor, "MANAGE_NODE_OPERATOR_ROLE", stranger)).to.be.false; - await expect(nor.connect(stranger).setNodeOperatorName(firstNodeOperatorId, "new name")).to.be.revertedWith( - "APP_AUTH_FAILED", - ); - }); - - it('reverts with "VALUE_IS_THE_SAME" error when called with the same name', async () => { - const { name: currentName } = await nor.getNodeOperator(firstNodeOperatorId, true); - await expect( - nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, currentName), - ).to.be.revertedWith("VALUE_IS_THE_SAME"); - }); - - it("updates the node operator name", async () => { - const newName = "new name"; - await nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, newName); - const { name: nameAfter } = await nor.getNodeOperator(firstNodeOperatorId, true); - expect(nameAfter).to.be.equal(newName); - }); - - it("emits NodeOperatorNameSet event with correct params", async () => { - const newName = "new name"; - await expect(nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, newName)) - .to.emit(nor, "NodeOperatorNameSet") - .withArgs(firstNodeOperatorId, newName); - }); - - it("doesn't affect the names of other node operators", async () => { - const newName = "new name"; - const { name: anotherNodeOperatorNameBefore } = await nor.getNodeOperator(secondNodeOperatorId, true); - await nor.connect(nodeOperatorsManager).setNodeOperatorName(firstNodeOperatorId, newName); - const { name: anotherNodeOperatorNameAfter } = await nor.getNodeOperator(secondNodeOperatorId, true); - expect(anotherNodeOperatorNameBefore).to.be.equal(anotherNodeOperatorNameAfter); - }); - }); - - context("setNodeOperatorRewardAddress()", async () => { - const firstNodeOperatorId = 0; - const secondNodeOperatorId = 1; - const notExistedNodeOperatorId = 2; - - beforeEach(async () => { - expect(await addNodeOperator(nor, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal(firstNodeOperatorId); - expect(await addNodeOperator(nor, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal(secondNodeOperatorId); - }); - - it('reverts with "OUT_OF_RANGE" error when called on non existent node operator', async () => { - await expect( - nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(notExistedNodeOperatorId, ADDRESS_4), - ).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it('reverts with "ZERO_ADDRESS" error when new address is zero', async () => { - await expect( - nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ZeroAddress), - ).to.be.revertedWith("ZERO_ADDRESS"); - }); - - it('reverts with error "LIDO_REWARD_ADDRESS" when new reward address is lido', async () => { - await expect( - nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, lido), - ).to.be.revertedWith("LIDO_REWARD_ADDRESS"); - }); - - it(`reverts with "APP_AUTH_FAILED" error when caller doesn't have MANAGE_NODE_OPERATOR_ROLE`, async () => { - expect(await hasPermission(dao, nor, "MANAGE_NODE_OPERATOR_ROLE", stranger)).to.be.false; - await expect( - nor.connect(stranger).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4), - ).to.be.revertedWith("APP_AUTH_FAILED"); - }); - - it(`reverts with "VALUE_IS_THE_SAME" error when new reward address is the same`, async () => { - const nodeOperator = await nor.getNodeOperator(firstNodeOperatorId, false); - await expect( - nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, nodeOperator.rewardAddress), - ).to.be.revertedWith("VALUE_IS_THE_SAME"); - }); - - it("updates the reward address of the node operator", async () => { - const { rewardAddress: rewardAddressBefore } = await nor.getNodeOperator(firstNodeOperatorId, false); - expect(rewardAddressBefore).to.be.not.equal(ADDRESS_4); - await nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4); - const { rewardAddress: rewardAddressAfter } = await nor.getNodeOperator(firstNodeOperatorId, false); - expect(rewardAddressAfter).to.be.equal(ADDRESS_4); - }); - - it('emits "NodeOperatorRewardAddressSet" event with correct params', async () => { - await expect(nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4)) - .to.emit(nor, "NodeOperatorRewardAddressSet") - .withArgs(firstNodeOperatorId, ADDRESS_4); - }); - - it("doesn't affect other node operators reward addresses", async () => { - const { rewardAddress: rewardAddressBefore } = await nor.getNodeOperator(secondNodeOperatorId, false); - await nor.connect(nodeOperatorsManager).setNodeOperatorRewardAddress(firstNodeOperatorId, ADDRESS_4); - const { rewardAddress: rewardAddressAfter } = await nor.getNodeOperator(secondNodeOperatorId, false); - expect(rewardAddressAfter).to.be.equal(rewardAddressBefore); - }); - }); - - context("updateTargetValidatorsLimits", () => { - const updateTargetLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)"; - const updateTargetLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)"; - - const firstNodeOperatorId = 0; - const secondNodeOperatorId = 1; - let targetLimitMode = 0; - let targetLimit = 0; - - beforeEach(async () => { - expect(await addNodeOperator(nor, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal(firstNodeOperatorId); - expect(await addNodeOperator(nor, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal(secondNodeOperatorId); - }); - - it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; - await expect(nor[updateTargetLimits](firstNodeOperatorId, targetLimitMode, targetLimit)).to.be.revertedWith( - "APP_AUTH_FAILED", - ); - }); - - it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { - const targetLimitWrong = BigInt("0x10000000000000000"); - - await expect( - nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, targetLimitMode, targetLimitWrong), - ).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it("updates node operator target limit if called by sender with STAKING_ROUTER_ROLE", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - - targetLimitMode = 1; - targetLimit = 10; - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, targetLimitMode, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); - - const keysStatTotal = await nor.getStakingModuleSummary(); - const expectedExitedValidatorsCount = - NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount + - NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; - expect(keysStatTotal.totalExitedValidators).to.equal(expectedExitedValidatorsCount); - - const expectedDepositedValidatorsCount = - NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount + - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; - expect(keysStatTotal.totalDepositedValidators).to.equal(expectedDepositedValidatorsCount); - - const firstNodeOperatorDepositableValidators = - NODE_OPERATORS[firstNodeOperatorId].vettedSigningKeysCount - - NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount; - - const secondNodeOperatorDepositableValidators = - NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; - - const expectedDepositableValidatorsCount = - Math.min(targetLimit, firstNodeOperatorDepositableValidators) + secondNodeOperatorDepositableValidators; - expect(keysStatTotal.depositableValidatorsCount).to.equal(expectedDepositableValidatorsCount); - }); - - it("updates node operator target limit mode correctly", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - - const targetLimitMode1 = 1; - const targetLimitMode2 = 2; - targetLimit = 10; - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, targetLimitMode1, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode1); - - let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(targetLimitMode1); - - await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, targetLimitMode2, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, targetLimit, targetLimitMode2); - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); - - // reset limit - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0, 0); // expect limit set to 0 - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0); - - // mode for 2nt NO is not changed - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(targetLimitMode2); - }); - - it("updates node operator target limit with deprecated method correctly", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, 100)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 100, 1); - - const noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(1); - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, false, 0)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0, 0); - const noSummary2 = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary2.targetLimitMode).to.equal(0); - }); - }); - - context("getRewardDistributionState()", () => { - it("returns correct reward distribution state", async () => { - await nor.testing_setRewardDistributionState(RewardDistributionState.ReadyForDistribution); - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); - - await nor.testing_setRewardDistributionState(RewardDistributionState.TransferredToModule); - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.TransferredToModule); - - await nor.testing_setRewardDistributionState(RewardDistributionState.Distributed); - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); - }); - }); - - context("distributeReward()", () => { - it('distribute reward when module not in "ReadyForDistribution" status', async () => { - await nor.testing_setRewardDistributionState(RewardDistributionState.ReadyForDistribution); - - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); - await expect(nor.distributeReward()) - .to.emit(nor, "RewardDistributionStateChanged") - .withArgs(RewardDistributionState.Distributed); - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); - }); - - it('reverts with "DISTRIBUTION_NOT_READY" error when module not in "ReadyForDistribution" status', async () => { - await nor.testing_setRewardDistributionState(RewardDistributionState.TransferredToModule); - await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); - - await nor.testing_setRewardDistributionState(RewardDistributionState.Distributed); - await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); - }); - }); - - describe("onRewardsMinted()", () => { - it("reverts with no STAKING_ROUTER_ROLE", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; - await expect(nor.connect(stranger).onRewardsMinted(123)).to.be.revertedWith("APP_AUTH_FAILED"); - }); - - it("no reverts with STAKING_ROUTER_ROLE", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - await nor.connect(stakingRouter).onRewardsMinted(123); - }); - - it("emits RewardDistributionStateChanged event", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - await expect(nor.connect(stakingRouter).onRewardsMinted(123)) - .to.emit(nor, "RewardDistributionStateChanged") - .withArgs(RewardDistributionState.TransferredToModule); - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.TransferredToModule); - }); - }); - - describe("onExitedAndStuckValidatorsCountsUpdated()", () => { - it("reverts with no STAKING_ROUTER_ROLE", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stranger)).to.be.false; - await expect(nor.connect(stranger).onExitedAndStuckValidatorsCountsUpdated()).to.be.revertedWith( - "APP_AUTH_FAILED", - ); - }); - - it("no reverts with STAKING_ROUTER_ROLE", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - await nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated(); - }); - - it("emits ExitedAndStuckValidatorsCountsUpdated event", async () => { - expect(await hasPermission(dao, nor, "STAKING_ROUTER_ROLE", stakingRouter)).to.be.true; - await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()) - .to.emit(nor, "RewardDistributionStateChanged") - .withArgs(RewardDistributionState.ReadyForDistribution); - expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); - }); - }); -}); - -interface NodeOperatorConfig { - name: string; - rewardAddress: string; - totalSigningKeysCount: number; - depositedSigningKeysCount: number; - exitedSigningKeysCount: number; - vettedSigningKeysCount: number; - stuckValidatorsCount: number; - refundedValidatorsCount: number; - stuckPenaltyEndAt: number; - isActive?: boolean; -} - -/*** - * Adds new Node Operator to the registry and configures it - * @param {object} norMock Node operators registry mocked instance - * @param {object} config Configuration of the added node operator - * @param {string} config.name Name of the new node operator - * @param {string} config.rewardAddress Reward address of the new node operator - * @param {number} config.totalSigningKeysCount Count of the validators in the new node operator - * @param {number} config.depositedSigningKeysCount Count of used signing keys in the new node operator - * @param {number} config.exitedSigningKeysCount Count of stopped signing keys in the new node operator - * @param {number} config.vettedSigningKeysCount Staking limit of the new node operator - * @param {number} config.stuckValidatorsCount Stuck keys count of the new node operator - * @param {number} config.refundedValidatorsKeysCount Repaid keys count of the new node operator - * @param {number} config.isActive The active state of new node operator - * @returns {bigint} newOperatorId Id of newly added Node Operator - */ -async function addNodeOperator(norMock: NodeOperatorsRegistryMock, config: NodeOperatorConfig): Promise { - const isActive = config.isActive === undefined ? true : config.isActive; - - if (config.vettedSigningKeysCount < config.depositedSigningKeysCount) { - throw new Error("Invalid keys config: vettedSigningKeysCount < depositedSigningKeysCount"); - } - - if (config.vettedSigningKeysCount > config.totalSigningKeysCount) { - throw new Error("Invalid keys config: vettedSigningKeysCount > totalSigningKeysCount"); - } - - if (config.exitedSigningKeysCount > config.depositedSigningKeysCount) { - throw new Error("Invalid keys config: depositedSigningKeysCount < exitedSigningKeysCount"); - } - - if (config.stuckValidatorsCount > config.depositedSigningKeysCount - config.exitedSigningKeysCount) { - throw new Error("Invalid keys config: stuckValidatorsCount > depositedSigningKeysCount - exitedSigningKeysCount"); - } - - if (config.totalSigningKeysCount < config.exitedSigningKeysCount + config.depositedSigningKeysCount) { - throw new Error("Invalid keys config: totalSigningKeys < stoppedValidators + usedSigningKeys"); - } - - const newOperatorId = await norMock.getNodeOperatorsCount(); - await norMock.testing_addNodeOperator( - config.name, - config.rewardAddress, - config.totalSigningKeysCount, - config.vettedSigningKeysCount, - config.depositedSigningKeysCount, - config.exitedSigningKeysCount, - ); - await norMock.testing_setNodeOperatorLimits( - newOperatorId, - config.stuckValidatorsCount, - config.refundedValidatorsCount, - config.stuckPenaltyEndAt, - ); - - if (!isActive) { - await norMock.testing_unsafeDeactivateNodeOperator(newOperatorId); - } - - const nodeOperatorsSummary = await norMock.getNodeOperatorSummary(newOperatorId); - const nodeOperator = await norMock.getNodeOperator(newOperatorId, true); - - if (isActive) { - expect(nodeOperator.totalVettedValidators).to.equal(config.vettedSigningKeysCount); - expect(nodeOperator.totalAddedValidators).to.equal(config.totalSigningKeysCount); - expect(nodeOperatorsSummary.totalExitedValidators).to.equal(config.exitedSigningKeysCount); - expect(nodeOperatorsSummary.totalDepositedValidators).to.equal(config.depositedSigningKeysCount); - expect(nodeOperatorsSummary.depositableValidatorsCount).to.equal( - config.vettedSigningKeysCount - config.depositedSigningKeysCount, - ); - } else { - expect(nodeOperatorsSummary.totalExitedValidators).to.equal(config.exitedSigningKeysCount); - expect(nodeOperatorsSummary.totalDepositedValidators).to.equal(config.depositedSigningKeysCount); - expect(nodeOperatorsSummary.depositableValidatorsCount).to.equal(0); - } - return newOperatorId; -} diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 7924ddee9..ff2f4891c 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -17,7 +17,7 @@ import { } from "typechain-types"; import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; -import { addNodeOperator, certainAddress, NodeOperatorConfig } from "lib"; +import { addNodeOperator, certainAddress, NodeOperatorConfig, RewardDistributionState } from "lib"; import { addAragonApp, deployLidoDao, deployLidoLocator } from "test/deploy"; import { Snapshot } from "test/suite"; @@ -36,6 +36,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { let dao: Kernel; let acl: ACL; let locator: LidoLocator; + let impl: NodeOperatorsRegistry__Harness; let originalState: string; @@ -113,7 +114,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), }; - const impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); + impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); expect(await impl.getInitializationBlock()).to.equal(MaxUint256); const appProxy = await addAragonApp({ dao, @@ -349,4 +350,38 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { ); }); }); + + context("finalizeUpgrade_v3()", () => { + beforeEach(async () => { + locator = await deployLidoLocator({ lido: lido }); + await nor.harness__initialize(2n); + }); + + it("fails with CONTRACT_NOT_INITIALIZED error when called on implementation", async () => { + await expect(impl.finalizeUpgrade_v3()).to.be.revertedWith("CONTRACT_NOT_INITIALIZED"); + }); + + it("fails with CONTRACT_NOT_INITIALIZED error when nor instance not initialized yet", async () => { + const appProxy = await addAragonApp({ + dao, + name: "new-node-operators-registry", + impl, + rootAccount: deployer, + }); + const registry = NodeOperatorsRegistry__Harness__factory.connect(appProxy, deployer); + await expect(registry.finalizeUpgrade_v3()).to.be.revertedWith("CONTRACT_NOT_INITIALIZED"); + }); + + it("sets correct contract version", async () => { + await nor.finalizeUpgrade_v3(); + expect(await nor.getContractVersion()).to.be.equal(3); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); + }); + + it("reverts with error UNEXPECTED_CONTRACT_VERSION when called on already initialized contract", async () => { + await nor.finalizeUpgrade_v3(); + expect(await nor.getContractVersion()).to.be.equal(3); + await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); + }); + }); }); diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index 486497ac8..e01766765 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -16,7 +16,7 @@ import { } from "typechain-types"; import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; -import { addNodeOperator, certainAddress, NodeOperatorConfig, randomAddress } from "lib"; +import { addNodeOperator, certainAddress, NodeOperatorConfig, randomAddress, RewardDistributionState } from "lib"; import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; @@ -730,6 +730,39 @@ describe("NodeOperatorsRegistry:management", () => { }); }); + context("getRewardDistributionState()", () => { + it("returns correct reward distribution state", async () => { + await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); + + await nor.harness__setRewardDistributionState(RewardDistributionState.TransferredToModule); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.TransferredToModule); + + await nor.harness__setRewardDistributionState(RewardDistributionState.Distributed); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); + }); + }); + + context("distributeReward()", () => { + it('distribute reward when module not in "ReadyForDistribution" status', async () => { + await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); + + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); + await expect(nor.distributeReward()) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); + expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); + }); + + it('reverts with "DISTRIBUTION_NOT_READY" error when module not in "ReadyForDistribution" status', async () => { + await nor.harness__setRewardDistributionState(RewardDistributionState.TransferredToModule); + await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); + + await nor.harness__setRewardDistributionState(RewardDistributionState.Distributed); + await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); + }); + }); + context("getNodeOperatorIds", () => { let beforePopulating: string; diff --git a/test/0.4.24/nor/nor.staking.limit.test.ts b/test/0.4.24/nor/nor.staking.limit.test.ts new file mode 100644 index 000000000..b736917de --- /dev/null +++ b/test/0.4.24/nor/nor.staking.limit.test.ts @@ -0,0 +1,370 @@ +import { expect } from "chai"; +import { encodeBytes32String } from "ethers"; +import { ethers } from "hardhat"; + +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; + +import { + ACL, + Kernel, + Lido, + LidoLocator, + LidoLocator__factory, + MinFirstAllocationStrategy__factory, + NodeOperatorsRegistry__Harness, + NodeOperatorsRegistry__Harness__factory, +} from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; + +import { addNodeOperator, certainAddress, NodeOperatorConfig, prepIdsCountsPayload } from "lib"; + +import { addAragonApp, deployLidoDao } from "test/deploy"; +import { Snapshot } from "test/suite"; + +describe("NodeOperatorsRegistry:management", () => { + let deployer: HardhatEthersSigner; + let user: HardhatEthersSigner; + + let limitsManager: HardhatEthersSigner; + let nodeOperatorsManager: HardhatEthersSigner; + let signingKeysManager: HardhatEthersSigner; + let stakingRouter: HardhatEthersSigner; + let lido: Lido; + let dao: Kernel; + let acl: ACL; + let locator: LidoLocator; + + let impl: NodeOperatorsRegistry__Harness; + let nor: NodeOperatorsRegistry__Harness; + + let originalState: string; + + const firstNodeOperatorId = 0; + const secondNodeOperatorId = 1; + const thirdNodeOperatorId = 2; + + const NODE_OPERATORS: NodeOperatorConfig[] = [ + { + name: "foo", + rewardAddress: certainAddress("node-operator-1"), + totalSigningKeysCount: 10n, + depositedSigningKeysCount: 5n, + exitedSigningKeysCount: 1n, + vettedSigningKeysCount: 6n, + stuckValidatorsCount: 0n, + refundedValidatorsCount: 0n, + stuckPenaltyEndAt: 0n, + }, + { + name: " bar", + rewardAddress: certainAddress("node-operator-2"), + totalSigningKeysCount: 15n, + depositedSigningKeysCount: 7n, + exitedSigningKeysCount: 0n, + vettedSigningKeysCount: 10n, + stuckValidatorsCount: 0n, + refundedValidatorsCount: 0n, + stuckPenaltyEndAt: 0n, + }, + { + name: "deactivated", + isActive: false, + rewardAddress: certainAddress("node-operator-3"), + totalSigningKeysCount: 10n, + depositedSigningKeysCount: 0n, + exitedSigningKeysCount: 0n, + vettedSigningKeysCount: 5n, + stuckValidatorsCount: 0n, + refundedValidatorsCount: 0n, + stuckPenaltyEndAt: 0n, + }, + ]; + + const moduleType = encodeBytes32String("curated-onchain-v1"); + const penaltyDelay = 86400n; + const contractVersion = 2n; + + before(async () => { + [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager] = + await ethers.getSigners(); + + ({ lido, dao, acl } = await deployLidoDao({ + rootAccount: deployer, + initialized: true, + locatorConfig: { + stakingRouter, + }, + })); + + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + + impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); + const appProxy = await addAragonApp({ + dao, + name: "node-operators-registry", + impl, + rootAccount: deployer, + }); + + nor = NodeOperatorsRegistry__Harness__factory.connect(appProxy, deployer); + + await acl.createPermission(user, lido, await lido.RESUME_ROLE(), deployer); + + await acl.createPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), deployer); + await acl.createPermission(signingKeysManager, nor, await nor.MANAGE_SIGNING_KEYS(), deployer); + await acl.createPermission(nodeOperatorsManager, nor, await nor.MANAGE_NODE_OPERATOR_ROLE(), deployer); + await acl.createPermission(limitsManager, nor, await nor.SET_NODE_OPERATOR_LIMIT_ROLE(), deployer); + + // grant role to nor itself cause it uses solidity's call method to itself + // inside the testing_requestValidatorsKeysForDeposits() method + await acl.grantPermission(nor, nor, await nor.STAKING_ROUTER_ROLE()); + + locator = LidoLocator__factory.connect(await lido.getLidoLocator(), user); + + // Initialize the nor's proxy. + await expect(nor.initialize(locator, moduleType, penaltyDelay)) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersion) + .and.to.emit(nor, "LocatorContractSet") + .withArgs(locator) + .and.to.emit(nor, "StakingModuleTypeSet") + .withArgs(moduleType); + + nor = nor.connect(user); + }); + + beforeEach(async () => (originalState = await Snapshot.take())); + + afterEach(async () => await Snapshot.restore(originalState)); + + context("setNodeOperatorStakingLimit", () => { + beforeEach(async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( + thirdNodeOperatorId, + ); + }); + + it("Reverts if no such an operator exists", async () => { + await expect(nor.setNodeOperatorStakingLimit(5n, 10n)).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it("Reverts if no SET_NODE_OPERATOR_LIMIT_ROLE assigned", async () => { + await expect(nor.setNodeOperatorStakingLimit(firstNodeOperatorId, 0n)).to.be.revertedWith("APP_AUTH_FAILED"); + }); + + it("Reverts if the node operator is inactive", async () => { + await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(thirdNodeOperatorId, 0n)).to.be.revertedWith( + "WRONG_OPERATOR_ACTIVE_STATE", + ); + }); + + it("Does nothing if vetted keys count stays the same", async () => { + const vetted = (await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators; + + await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, vetted)).to.not.emit( + nor, + "VettedSigningKeysCountChanged", + ); + }); + + it("Able to set decrease vetted keys count", async () => { + const oldVetted = 6n; + const newVetted = 5n; + expect(newVetted < oldVetted); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); + const oldNonce = await nor.getNonce(); + + await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, newVetted)) + .to.emit(nor, "VettedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, newVetted) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(oldNonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(oldNonce + 1n); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(newVetted); + }); + + it("Able to increase vetted keys count", async () => { + const oldVetted = 6n; + const newVetted = 8n; + expect(newVetted > oldVetted); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); + const oldNonce = await nor.getNonce(); + + await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, newVetted)) + .to.emit(nor, "VettedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, newVetted) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(oldNonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(oldNonce + 1n); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(newVetted); + }); + + it("Vetted keys count can only be ≥ deposited", async () => { + const oldVetted = 6n; + const vettedBelowDeposited = 3n; + + expect(vettedBelowDeposited < oldVetted); + const firstNo = await nor.getNodeOperator(firstNodeOperatorId, false); + expect(vettedBelowDeposited < firstNo.totalDepositedValidators); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); + const oldNonce = await nor.getNonce(); + + await expect( + nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, firstNo.totalDepositedValidators), + ) + .to.emit(nor, "VettedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, firstNo.totalDepositedValidators) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(oldNonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(oldNonce + 1n); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal( + firstNo.totalDepositedValidators, + ); + }); + + it("Vetted keys count can only be ≤ total added", async () => { + const oldVetted = 6n; + const vettedAboveTotal = 11n; + + expect(vettedAboveTotal > oldVetted); + const firstNo = await nor.getNodeOperator(firstNodeOperatorId, false); + expect(vettedAboveTotal > firstNo.totalAddedValidators); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); + const oldNonce = await nor.getNonce(); + + await expect( + nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, firstNo.totalAddedValidators), + ) + .to.emit(nor, "VettedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, firstNo.totalAddedValidators) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(oldNonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(oldNonce + 1n); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal( + firstNo.totalAddedValidators, + ); + }); + }); + + context("decreaseVettedSigningKeysCount", () => { + beforeEach(async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( + thirdNodeOperatorId, + ); + }); + + it("Reverts if no such an operator exists", async () => { + const idsPayload = prepIdsCountsPayload([5n], [10n]); + + await expect( + nor.connect(stakingRouter).decreaseVettedSigningKeysCount(idsPayload.operatorIds, idsPayload.keysCounts), + ).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it("Reverts if no STAKING_ROUTER_ROLE assigned", async () => { + const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [0n]); + + await expect( + nor.decreaseVettedSigningKeysCount(idsPayload.operatorIds, idsPayload.keysCounts), + ).to.be.revertedWith("APP_AUTH_FAILED"); + }); + + it("Does nothing if vetted keys count stays the same", async () => { + const vetted = (await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators; + + const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [vetted]); + await expect( + nor.connect(stakingRouter).decreaseVettedSigningKeysCount(idsPayload.operatorIds, idsPayload.keysCounts), + ).to.not.emit(nor, "VettedSigningKeysCountChanged"); + }); + + it("Able to set decrease vetted keys count", async () => { + const oldVetted = 6n; + const newVetted = 5n; + expect(newVetted < oldVetted); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); + const oldNonce = await nor.getNonce(); + + const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [newVetted]); + await expect( + nor.connect(stakingRouter).decreaseVettedSigningKeysCount(idsPayload.operatorIds, idsPayload.keysCounts), + ) + .to.emit(nor, "VettedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, newVetted) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(oldNonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(oldNonce + 1n); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(newVetted); + }); + + it("Not able to increase vetted keys count", async () => { + const oldVetted = 6n; + const newVetted = 8n; + expect(newVetted > oldVetted); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); + + const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [newVetted]); + await expect( + nor.connect(stakingRouter).decreaseVettedSigningKeysCount(idsPayload.operatorIds, idsPayload.keysCounts), + ).to.be.revertedWith("VETTED_KEYS_COUNT_INCREASED"); + }); + + it("Vetted keys count can only be ≥ deposited", async () => { + const oldVetted = 6n; + const vettedBelowDeposited = 3n; + + expect(vettedBelowDeposited < oldVetted); + const firstNo = await nor.getNodeOperator(firstNodeOperatorId, false); + expect(vettedBelowDeposited < firstNo.totalDepositedValidators); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); + const oldNonce = await nor.getNonce(); + + const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [firstNo.totalDepositedValidators]); + await expect( + nor.connect(stakingRouter).decreaseVettedSigningKeysCount(idsPayload.operatorIds, idsPayload.keysCounts), + ) + .to.emit(nor, "VettedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, firstNo.totalDepositedValidators) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(oldNonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(oldNonce + 1n); + + expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal( + firstNo.totalDepositedValidators, + ); + }); + }); +}); From db572a3e5cff5fb78a475f5c967fd4323120de8d Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 8 Jul 2024 14:14:32 +0200 Subject: [PATCH 121/177] refactor: move staking limit tests --- test/0.4.24/nor/nor.management.flow.test.ts | 127 -------------------- test/0.4.24/nor/nor.staking.limit.test.ts | 2 +- 2 files changed, 1 insertion(+), 128 deletions(-) diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index e01766765..99b6e99ec 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -358,133 +358,6 @@ describe("NodeOperatorsRegistry:management", () => { }); }); - context("setNodeOperatorStakingLimit", () => { - beforeEach(async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( - firstNodeOperatorId, - ); - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( - secondNodeOperatorId, - ); - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( - thirdNodeOperatorId, - ); - }); - - it("Reverts if no such an operator exists", async () => { - await expect(nor.setNodeOperatorStakingLimit(5n, 10n)).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it("Reverts if no SET_NODE_OPERATOR_LIMIT_ROLE assigned", async () => { - await expect(nor.setNodeOperatorStakingLimit(firstNodeOperatorId, 0n)).to.be.revertedWith("APP_AUTH_FAILED"); - }); - - it("Reverts if the node operator is inactive", async () => { - await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(thirdNodeOperatorId, 0n)).to.be.revertedWith( - "WRONG_OPERATOR_ACTIVE_STATE", - ); - }); - - it("Does nothing if vetted keys count stays the same", async () => { - const vetted = (await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators; - - await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, vetted)).to.not.emit( - nor, - "VettedSigningKeysCountChanged", - ); - }); - - it("Able to set decrease vetted keys count", async () => { - const oldVetted = 6n; - const newVetted = 5n; - expect(newVetted < oldVetted); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); - const oldNonce = await nor.getNonce(); - - await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, newVetted)) - .to.emit(nor, "VettedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, newVetted) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(oldNonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(oldNonce + 1n); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(newVetted); - }); - - it("Able to increase vetted keys count", async () => { - const oldVetted = 6n; - const newVetted = 8n; - expect(newVetted > oldVetted); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); - const oldNonce = await nor.getNonce(); - - await expect(nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, newVetted)) - .to.emit(nor, "VettedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, newVetted) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(oldNonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(oldNonce + 1n); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(newVetted); - }); - - it("Vetted keys count can only be ≥ deposited", async () => { - const oldVetted = 6n; - const vettedBelowDeposited = 3n; - - expect(vettedBelowDeposited < oldVetted); - const firstNo = await nor.getNodeOperator(firstNodeOperatorId, false); - expect(vettedBelowDeposited < firstNo.totalDepositedValidators); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); - const oldNonce = await nor.getNonce(); - - await expect( - nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, firstNo.totalDepositedValidators), - ) - .to.emit(nor, "VettedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, firstNo.totalDepositedValidators) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(oldNonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(oldNonce + 1n); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal( - firstNo.totalDepositedValidators, - ); - }); - - it("Vetted keys count can only be ≤ total added", async () => { - const oldVetted = 6n; - const vettedAboveTotal = 11n; - - expect(vettedAboveTotal > oldVetted); - const firstNo = await nor.getNodeOperator(firstNodeOperatorId, false); - expect(vettedAboveTotal > firstNo.totalAddedValidators); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal(oldVetted); - const oldNonce = await nor.getNonce(); - - await expect( - nor.connect(limitsManager).setNodeOperatorStakingLimit(firstNodeOperatorId, firstNo.totalAddedValidators), - ) - .to.emit(nor, "VettedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, firstNo.totalAddedValidators) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(oldNonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(oldNonce + 1n); - - expect((await nor.getNodeOperator(firstNodeOperatorId, false)).totalVettedValidators).to.be.equal( - firstNo.totalAddedValidators, - ); - }); - }); - context("getNodeOperator", () => { beforeEach(async () => { expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( diff --git a/test/0.4.24/nor/nor.staking.limit.test.ts b/test/0.4.24/nor/nor.staking.limit.test.ts index b736917de..c689eb15a 100644 --- a/test/0.4.24/nor/nor.staking.limit.test.ts +++ b/test/0.4.24/nor/nor.staking.limit.test.ts @@ -21,7 +21,7 @@ import { addNodeOperator, certainAddress, NodeOperatorConfig, prepIdsCountsPaylo import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; -describe("NodeOperatorsRegistry:management", () => { +describe("NodeOperatorsRegistry:stakingLimit", () => { let deployer: HardhatEthersSigner; let user: HardhatEthersSigner; From 583da54df535c2bfbbefd755c902e6ba3cde7717 Mon Sep 17 00:00:00 2001 From: Maksim Kuraian Date: Tue, 9 Jul 2024 11:07:58 +0200 Subject: [PATCH 122/177] Update contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol Co-authored-by: avsetsin --- contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 708729e75..b80dddddc 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -895,7 +895,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { emit MaxNodeOperatorsPerExtraDataItemCountSet(_newLimitsList.maxNodeOperatorsPerExtraDataItemCount); } if (_oldLimitsList.requestTimestampMargin != _newLimitsList.requestTimestampMargin) { - _checkLimitValue(_newLimitsList.requestTimestampMargin, 0, type(uint48).max); + _checkLimitValue(_newLimitsList.requestTimestampMargin, 0, type(uint32).max); emit RequestTimestampMarginSet(_newLimitsList.requestTimestampMargin); } if (_oldLimitsList.maxPositiveTokenRebase != _newLimitsList.maxPositiveTokenRebase) { From 55ed77701cc4b6de2f8d1830a988e79decaf0e73 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 9 Jul 2024 11:26:08 +0200 Subject: [PATCH 123/177] feat: remove unused SafeCastExt --- contracts/0.8.9/lib/SafeCastExt.sol | 31 ------------------- .../OracleReportSanityChecker.sol | 1 - 2 files changed, 32 deletions(-) delete mode 100644 contracts/0.8.9/lib/SafeCastExt.sol diff --git a/contracts/0.8.9/lib/SafeCastExt.sol b/contracts/0.8.9/lib/SafeCastExt.sol deleted file mode 100644 index dff4e74cc..000000000 --- a/contracts/0.8.9/lib/SafeCastExt.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.2) (utils/math/SafeCast.sol) -// Code extracted from https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.2 - -// See contracts/COMPILERS.md -pragma solidity 0.8.9; - -library SafeCastExt { - /** - * @dev Value doesn't fit in an uint of `bits` size. - */ - error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); - - /** - * @dev Returns the downcasted uint48 from uint256, reverting on - * overflow (when the input is greater than largest uint48). - * - * Counterpart to Solidity's `uint48` operator. - * - * Requirements: - * - * - input must fit into 48 bits - */ - function toUint48(uint256 value) internal pure returns (uint48) { - if (value > type(uint48).max) { - revert SafeCastOverflowedUintDowncast(48, value); - } - return uint48(value); - } -} - diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index b80dddddc..bad9788d8 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.9; import {SafeCast} from "@openzeppelin/contracts-v4.4/utils/math/SafeCast.sol"; -import {SafeCastExt} from "../lib/SafeCastExt.sol"; import {Math256} from "../../common/lib/Math256.sol"; import {AccessControlEnumerable} from "../utils/access/AccessControlEnumerable.sol"; From 3ede3de8a844cf2195ccefb329f683d57675e715 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 9 Jul 2024 13:53:17 +0200 Subject: [PATCH 124/177] test: fix requestTimestampMargin test replace unit48 to uint32 --- .../baseOracleReportSanityChecker.test.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index df5c8de33..624eac8f0 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -1517,8 +1517,9 @@ describe("OracleReportSanityChecker.sol", () => { it("values must be less or equals to type(uint64).max", async () => { const MAX_UINT_64 = 2n ** 64n - 1n; - const MAX_UINT_48 = 2n ** 48n - 1n; - const INVALID_VALUE = MAX_UINT_64 + 1n; + const MAX_UINT_32 = 2n ** 32n - 1n; + const INVALID_VALUE_UINT_64 = MAX_UINT_64 + 1n; + const INVALID_VALUE_UINT_32 = MAX_UINT_32 + 1n; await oracleReportSanityChecker .connect(admin) @@ -1526,18 +1527,18 @@ describe("OracleReportSanityChecker.sol", () => { await expect( oracleReportSanityChecker .connect(managersRoster.allLimitsManagers[0]) - .setOracleReportLimits({ ...defaultLimitsList, requestTimestampMargin: INVALID_VALUE }, ZeroAddress), + .setOracleReportLimits({ ...defaultLimitsList, requestTimestampMargin: INVALID_VALUE_UINT_32 }, ZeroAddress), ) .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectLimitValue") - .withArgs(INVALID_VALUE.toString(), 0, MAX_UINT_48); + .withArgs(INVALID_VALUE_UINT_32.toString(), 0, MAX_UINT_32); await expect( oracleReportSanityChecker .connect(managersRoster.allLimitsManagers[0]) - .setOracleReportLimits({ ...defaultLimitsList, maxPositiveTokenRebase: INVALID_VALUE }, ZeroAddress), + .setOracleReportLimits({ ...defaultLimitsList, maxPositiveTokenRebase: INVALID_VALUE_UINT_64 }, ZeroAddress), ) .to.be.revertedWithCustomError(oracleReportSanityChecker, "IncorrectLimitValue") - .withArgs(INVALID_VALUE.toString(), 1, MAX_UINT_64); + .withArgs(INVALID_VALUE_UINT_64.toString(), 1, MAX_UINT_64); }); it("value must be greater than zero", async () => { From 50778589518a09fe2e0711ba15a90983bbb176b3 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 9 Jul 2024 13:53:56 +0200 Subject: [PATCH 125/177] test: fix post negative rebase merge errors --- test/0.8.9/contracts/StakingRouterMockForValidatorsCount.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.8.9/contracts/StakingRouterMockForValidatorsCount.sol b/test/0.8.9/contracts/StakingRouterMockForValidatorsCount.sol index b9df264e0..4b00785a1 100644 --- a/test/0.8.9/contracts/StakingRouterMockForValidatorsCount.sol +++ b/test/0.8.9/contracts/StakingRouterMockForValidatorsCount.sol @@ -16,7 +16,7 @@ contract StakingRouterMockForValidatorsCount { } function addStakingModuleExitedValidators(uint24 moduleId, uint256 exitedValidators) external { - StakingRouter.StakingModule memory module = StakingRouter.StakingModule(moduleId, address(0), 0, 0, 0, 0, "", 0, 0, exitedValidators); + StakingRouter.StakingModule memory module = StakingRouter.StakingModule(moduleId, address(0), 0, 0, 0, 0, "", 0, 0, exitedValidators, 0, 0 ,0); modules[moduleId] = module; moduleIds.push(moduleId); } From 29a94e055e3b58fa815dabfccef2e44648869813 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 9 Jul 2024 14:46:25 +0200 Subject: [PATCH 126/177] feat: move appearedValidatorsPerDayLimit to deprecatedOneOffCLBalanceDecreaseBPLimit place --- .../OracleReportSanityChecker.sol | 18 ++++++------------ .../steps/09-deploy-non-aragon-contracts.ts | 2 +- .../baseOracleReportSanityChecker.test.ts | 6 ++---- .../negativeRebaseSanityChecker.test.ts | 3 +-- test/deploy/accountingOracle.ts | 16 +--------------- 5 files changed, 11 insertions(+), 34 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index de77ff297..4fc617080 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -62,11 +62,11 @@ struct LimitsList { /// @dev Must fit into uint16 (<= 65_535) uint256 exitedValidatorsPerDayLimit; - /// @notice Left for structure backward compatibility. Currently always return 0. Previously the max - // decrease of the total validators' balances on the Consensus Layer since - /// the previous oracle report. - /// @dev Represented in the Basis Points (100% == 10_000) - uint256 deprecatedOneOffCLBalanceDecreaseBPLimit; + /// @notice The max possible number of validators that might be reported as `appeared` + /// per single day, limited by the max daily deposits via DepositSecurityModule in practice + /// isn't limited by a consensus layer (because `appeared` includes `pending`, i.e., not `activated` yet) + /// @dev Must fit into uint16 (<= 65_535) + uint256 appearedValidatorsPerDayLimit; /// @notice The max annual increase of the total validators' balances on the Consensus Layer /// since the previous oracle report @@ -110,17 +110,12 @@ struct LimitsList { /// can be greater as calculated for the withdrawal credentials. /// @dev Represented in the Basis Points (100% == 10_000) uint256 clBalanceOraclesErrorUpperBPLimit; - - /// @notice The max possible number of validators that might be reported as `appeared` - /// per single day, limited by the max daily deposits via DepositSecurityModule in practice - /// isn't limited by a consensus layer (because `appeared` includes `pending`, i.e., not `activated` yet) - /// @dev Must fit into uint16 (<= 65_535) - uint256 appearedValidatorsPerDayLimit; } /// @dev The packed version of the LimitsList struct to be effectively persisted in storage struct LimitsListPacked { uint16 exitedValidatorsPerDayLimit; + uint16 appearedValidatorsPerDayLimit; uint16 annualBalanceIncreaseBPLimit; uint16 simulatedShareRateDeviationBPLimit; uint16 maxValidatorExitRequestsPerReport; @@ -131,7 +126,6 @@ struct LimitsListPacked { uint16 initialSlashingAmountPWei; uint16 inactivityPenaltiesAmountPWei; uint16 clBalanceOraclesErrorUpperBPLimit; - uint16 appearedValidatorsPerDayLimit; } struct ReportData { diff --git a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts index 44e1fd07f..22029158f 100644 --- a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts +++ b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts @@ -78,6 +78,7 @@ async function main() { admin, [ sanityChecks.exitedValidatorsPerDayLimit, + sanityChecks.appearedValidatorsPerDayLimit, sanityChecks.annualBalanceIncreaseBPLimit, sanityChecks.simulatedShareRateDeviationBPLimit, sanityChecks.maxValidatorExitRequestsPerReport, @@ -88,7 +89,6 @@ async function main() { sanityChecks.initialSlashingAmountPWei, sanityChecks.inactivityPenaltiesAmountPWei, sanityChecks.clBalanceOraclesErrorUpperBPLimit, - sanityChecks.appearedValidatorsPerDayLimit, ], ]; const oracleReportSanityChecker = await deployWithoutProxy( diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index 624eac8f0..6b8fc9eeb 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -28,7 +28,7 @@ describe("OracleReportSanityChecker.sol", () => { const defaultLimitsList = { exitedValidatorsPerDayLimit: 55, - deprecatedOneOffCLBalanceDecreaseBPLimit: 0, + appearedValidatorsPerDayLimit: 100, annualBalanceIncreaseBPLimit: 10_00, // 10% simulatedShareRateDeviationBPLimit: 2_50, // 2.5% maxValidatorExitRequestsPerReport: 2000, @@ -39,7 +39,6 @@ describe("OracleReportSanityChecker.sol", () => { initialSlashingAmountPWei: 1000, inactivityPenaltiesAmountPWei: 101, clBalanceOraclesErrorUpperBPLimit: 74, // 0.74% - appearedValidatorsPerDayLimit: 100, }; const correctLidoOracleReport = { @@ -142,7 +141,7 @@ describe("OracleReportSanityChecker.sol", () => { it("sets limits correctly", async () => { const newLimitsList = { exitedValidatorsPerDayLimit: 50, - deprecatedOneOffCLBalanceDecreaseBPLimit: 0, + appearedValidatorsPerDayLimit: 75, annualBalanceIncreaseBPLimit: 15_00, simulatedShareRateDeviationBPLimit: 1_50, // 1.5% maxValidatorExitRequestsPerReport: 3000, @@ -153,7 +152,6 @@ describe("OracleReportSanityChecker.sol", () => { initialSlashingAmountPWei: 2000, inactivityPenaltiesAmountPWei: 303, clBalanceOraclesErrorUpperBPLimit: 12, - appearedValidatorsPerDayLimit: 75, }; const limitsBefore = await oracleReportSanityChecker.getOracleReportLimits(); expect(limitsBefore.exitedValidatorsPerDayLimit).to.not.equal(newLimitsList.exitedValidatorsPerDayLimit); diff --git a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts index 6d06cc51d..a921beb19 100644 --- a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts @@ -28,7 +28,7 @@ describe("OracleReportSanityChecker.sol", () => { const defaultLimitsList = { exitedValidatorsPerDayLimit: 50, - deprecatedOneOffCLBalanceDecreaseBPLimit: 0, + appearedValidatorsPerDayLimit: 75, annualBalanceIncreaseBPLimit: 10_00, // 10% simulatedShareRateDeviationBPLimit: 2_50, // 2.5% maxValidatorExitRequestsPerReport: 2000, @@ -39,7 +39,6 @@ describe("OracleReportSanityChecker.sol", () => { initialSlashingAmountPWei: 1000, // 1 ETH = 1000 PWei inactivityPenaltiesAmountPWei: 101, // 0.101 ETH = 101 PWei clBalanceOraclesErrorUpperBPLimit: 74, // 0.74% - appearedValidatorsPerDayLimit: 75, }; const gweis = (x: number) => parseUnits(x.toString(), "gwei"); diff --git a/test/deploy/accountingOracle.ts b/test/deploy/accountingOracle.ts index 5eb0635e9..4027ec699 100644 --- a/test/deploy/accountingOracle.ts +++ b/test/deploy/accountingOracle.ts @@ -158,21 +158,7 @@ export async function initAccountingOracle({ async function deployOracleReportSanityCheckerForAccounting(lidoLocator: string, admin: string) { const exitedValidatorsPerDayLimit = 55; const appearedValidatorsPerDayLimit = 100; - const limitsList = [ - exitedValidatorsPerDayLimit, - 0, - 0, - 0, - 32 * 12, - 15, - 16, - 0, - 0, - 0, - 0, - 0, - appearedValidatorsPerDayLimit, - ]; + const limitsList = [exitedValidatorsPerDayLimit, appearedValidatorsPerDayLimit, 0, 0, 32 * 12, 15, 16, 0, 0, 0, 0, 0]; return await ethers.deployContract("OracleReportSanityChecker", [lidoLocator, admin, limitsList]); } From ddfeed596c32ff9f55d3998df0b13a409949ccc3 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 10 Jul 2024 12:05:56 +0200 Subject: [PATCH 127/177] feat: rename limits --- contracts/0.8.9/oracle/AccountingOracle.sol | 2 +- .../OracleReportSanityChecker.sol | 56 +++++++-------- .../scratch/deployed-testnet-defaults.json | 4 +- .../steps/09-deploy-non-aragon-contracts.ts | 4 +- .../accountingOracle.submitReport.test.ts | 4 +- ...untingOracle.submitReportExtraData.test.ts | 2 +- .../baseOracleReportSanityChecker.test.ts | 72 ++++++++----------- .../negativeRebaseSanityChecker.test.ts | 4 +- 8 files changed, 69 insertions(+), 79 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index d2ec51951..62461e763 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -318,7 +318,7 @@ contract AccountingOracle is BaseOracle { /// order. Each count is a 16-byte uint, counts are packed tightly. Thus, /// byteLength(stuckValidatorsCounts) = nodeOpsCount * 16. /// - /// nodeOpsCount must not be greater than maxAccountingExtraDataListItemsCount specified + /// nodeOpsCount must not be greater than maxNodeOperatorsPerExtraDataItem specified /// in OracleReportSanityChecker contract. If a staking module has more node operators /// with total stuck validators counts changed compared to the staking module smart contract /// storage (as observed at the reference slot), reporting for that module should be split diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 4fc617080..92e06a307 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -83,11 +83,11 @@ struct LimitsList { /// @notice The max number of data list items reported to accounting oracle in extra data per single transaction /// @dev Must fit into uint16 (<= 65_535) - uint256 maxAccountingExtraDataListItemsCount; + uint256 maxItemsPerExtraDataTransaction; /// @notice The max number of node operators reported per extra data list item /// @dev Must fit into uint16 (<= 65_535) - uint256 maxNodeOperatorsPerExtraDataItemCount; + uint256 maxNodeOperatorsPerExtraDataItem; /// @notice The min time required to be passed from the creation of the request to be /// finalized till the time of the oracle report @@ -119,8 +119,8 @@ struct LimitsListPacked { uint16 annualBalanceIncreaseBPLimit; uint16 simulatedShareRateDeviationBPLimit; uint16 maxValidatorExitRequestsPerReport; - uint16 maxAccountingExtraDataListItemsCount; - uint16 maxNodeOperatorsPerExtraDataItemCount; + uint16 maxItemsPerExtraDataTransaction; + uint16 maxNodeOperatorsPerExtraDataItem; uint32 requestTimestampMargin; uint64 maxPositiveTokenRebase; uint16 initialSlashingAmountPWei; @@ -352,25 +352,25 @@ contract OracleReportSanityChecker is AccessControlEnumerable { _updateLimits(limitsList); } - /// @notice Sets the new value for the maxAccountingExtraDataListItemsCount - /// @param _maxAccountingExtraDataListItemsCount new maxAccountingExtraDataListItemsCount value - function setMaxAccountingExtraDataListItemsCount(uint256 _maxAccountingExtraDataListItemsCount) + /// @notice Sets the new value for the maxItemsPerExtraDataTransaction + /// @param _maxItemsPerExtraDataTransaction new maxItemsPerExtraDataTransaction value + function setMaxAccountingExtraDataListItemsCount(uint256 _maxItemsPerExtraDataTransaction) external onlyRole(MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE) { LimitsList memory limitsList = _limits.unpack(); - limitsList.maxAccountingExtraDataListItemsCount = _maxAccountingExtraDataListItemsCount; + limitsList.maxItemsPerExtraDataTransaction = _maxItemsPerExtraDataTransaction; _updateLimits(limitsList); } - /// @notice Sets the new value for the max maxNodeOperatorsPerExtraDataItemCount - /// @param _maxNodeOperatorsPerExtraDataItemCount new maxNodeOperatorsPerExtraDataItemCount value - function setMaxNodeOperatorsPerExtraDataItemCount(uint256 _maxNodeOperatorsPerExtraDataItemCount) + /// @notice Sets the new value for the max maxNodeOperatorsPerExtraDataItem + /// @param _maxNodeOperatorsPerExtraDataItem new maxNodeOperatorsPerExtraDataItem value + function setMaxNodeOperatorsPerExtraDataItemCount(uint256 _maxNodeOperatorsPerExtraDataItem) external onlyRole(MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE) { LimitsList memory limitsList = _limits.unpack(); - limitsList.maxNodeOperatorsPerExtraDataItemCount = _maxNodeOperatorsPerExtraDataItemCount; + limitsList.maxNodeOperatorsPerExtraDataItem = _maxNodeOperatorsPerExtraDataItem; _updateLimits(limitsList); } @@ -555,7 +555,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external view { - uint256 limit = _limits.unpack().maxNodeOperatorsPerExtraDataItemCount; + uint256 limit = _limits.unpack().maxNodeOperatorsPerExtraDataItem; if (_nodeOperatorsCount > limit) { revert TooManyNodeOpsPerExtraDataItem(_itemIndex, _nodeOperatorsCount); } @@ -567,9 +567,9 @@ contract OracleReportSanityChecker is AccessControlEnumerable { external view { - uint256 limit = _limits.unpack().maxAccountingExtraDataListItemsCount; + uint256 limit = _limits.unpack().maxItemsPerExtraDataTransaction; if (_extraDataListItemsCount > limit) { - revert MaxAccountingExtraDataItemsCountExceeded(limit, _extraDataListItemsCount); + revert TooManyItemsPerExtraDataTransaction(limit, _extraDataListItemsCount); } } @@ -867,13 +867,13 @@ contract OracleReportSanityChecker is AccessControlEnumerable { _checkLimitValue(_newLimitsList.maxValidatorExitRequestsPerReport, 0, type(uint16).max); emit MaxValidatorExitRequestsPerReportSet(_newLimitsList.maxValidatorExitRequestsPerReport); } - if (_oldLimitsList.maxAccountingExtraDataListItemsCount != _newLimitsList.maxAccountingExtraDataListItemsCount) { - _checkLimitValue(_newLimitsList.maxAccountingExtraDataListItemsCount, 0, type(uint16).max); - emit MaxAccountingExtraDataListItemsCountSet(_newLimitsList.maxAccountingExtraDataListItemsCount); + if (_oldLimitsList.maxItemsPerExtraDataTransaction != _newLimitsList.maxItemsPerExtraDataTransaction) { + _checkLimitValue(_newLimitsList.maxItemsPerExtraDataTransaction, 0, type(uint16).max); + emit MaxItemsPerExtraDataTransactionSet(_newLimitsList.maxItemsPerExtraDataTransaction); } - if (_oldLimitsList.maxNodeOperatorsPerExtraDataItemCount != _newLimitsList.maxNodeOperatorsPerExtraDataItemCount) { - _checkLimitValue(_newLimitsList.maxNodeOperatorsPerExtraDataItemCount, 0, type(uint16).max); - emit MaxNodeOperatorsPerExtraDataItemCountSet(_newLimitsList.maxNodeOperatorsPerExtraDataItemCount); + if (_oldLimitsList.maxNodeOperatorsPerExtraDataItem != _newLimitsList.maxNodeOperatorsPerExtraDataItem) { + _checkLimitValue(_newLimitsList.maxNodeOperatorsPerExtraDataItem, 0, type(uint16).max); + emit MaxNodeOperatorsPerExtraDataItemSet(_newLimitsList.maxNodeOperatorsPerExtraDataItem); } if (_oldLimitsList.requestTimestampMargin != _newLimitsList.requestTimestampMargin) { _checkLimitValue(_newLimitsList.requestTimestampMargin, 0, type(uint32).max); @@ -911,8 +911,8 @@ contract OracleReportSanityChecker is AccessControlEnumerable { event SimulatedShareRateDeviationBPLimitSet(uint256 simulatedShareRateDeviationBPLimit); event MaxPositiveTokenRebaseSet(uint256 maxPositiveTokenRebase); event MaxValidatorExitRequestsPerReportSet(uint256 maxValidatorExitRequestsPerReport); - event MaxAccountingExtraDataListItemsCountSet(uint256 maxAccountingExtraDataListItemsCount); - event MaxNodeOperatorsPerExtraDataItemCountSet(uint256 maxNodeOperatorsPerExtraDataItemCount); + event MaxItemsPerExtraDataTransactionSet(uint256 maxItemsPerExtraDataTransaction); + event MaxNodeOperatorsPerExtraDataItemSet(uint256 maxNodeOperatorsPerExtraDataItem); event RequestTimestampMarginSet(uint256 requestTimestampMargin); event InitialSlashingAmountSet(uint256 initialSlashingAmountPWei); event InactivityPenaltiesAmountSet(uint256 inactivityPenaltiesAmountPWei); @@ -931,7 +931,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { error IncorrectRequestFinalization(uint256 requestCreationBlock); error ActualShareRateIsZero(); error IncorrectSimulatedShareRate(uint256 simulatedShareRate, uint256 actualShareRate); - error MaxAccountingExtraDataItemsCountExceeded(uint256 maxItemsCount, uint256 receivedItemsCount); + error TooManyItemsPerExtraDataTransaction(uint256 maxItemsCount, uint256 receivedItemsCount); error ExitedValidatorsLimitExceeded(uint256 limitPerDay, uint256 exitedPerDay); error TooManyNodeOpsPerExtraDataItem(uint256 itemIndex, uint256 nodeOpsCount); error AdminCannotBeZero(); @@ -952,8 +952,8 @@ library LimitsListPacker { res.requestTimestampMargin = SafeCast.toUint32(_limitsList.requestTimestampMargin); res.maxPositiveTokenRebase = SafeCast.toUint64(_limitsList.maxPositiveTokenRebase); res.maxValidatorExitRequestsPerReport = SafeCast.toUint16(_limitsList.maxValidatorExitRequestsPerReport); - res.maxAccountingExtraDataListItemsCount = SafeCast.toUint16(_limitsList.maxAccountingExtraDataListItemsCount); - res.maxNodeOperatorsPerExtraDataItemCount = SafeCast.toUint16(_limitsList.maxNodeOperatorsPerExtraDataItemCount); + res.maxItemsPerExtraDataTransaction = SafeCast.toUint16(_limitsList.maxItemsPerExtraDataTransaction); + res.maxNodeOperatorsPerExtraDataItem = SafeCast.toUint16(_limitsList.maxNodeOperatorsPerExtraDataItem); res.initialSlashingAmountPWei = SafeCast.toUint16(_limitsList.initialSlashingAmountPWei); res.inactivityPenaltiesAmountPWei = SafeCast.toUint16(_limitsList.inactivityPenaltiesAmountPWei); res.clBalanceOraclesErrorUpperBPLimit = _toBasisPoints(_limitsList.clBalanceOraclesErrorUpperBPLimit); @@ -974,8 +974,8 @@ library LimitsListUnpacker { res.requestTimestampMargin = _limitsList.requestTimestampMargin; res.maxPositiveTokenRebase = _limitsList.maxPositiveTokenRebase; res.maxValidatorExitRequestsPerReport = _limitsList.maxValidatorExitRequestsPerReport; - res.maxAccountingExtraDataListItemsCount = _limitsList.maxAccountingExtraDataListItemsCount; - res.maxNodeOperatorsPerExtraDataItemCount = _limitsList.maxNodeOperatorsPerExtraDataItemCount; + res.maxItemsPerExtraDataTransaction = _limitsList.maxItemsPerExtraDataTransaction; + res.maxNodeOperatorsPerExtraDataItem = _limitsList.maxNodeOperatorsPerExtraDataItem; res.initialSlashingAmountPWei = _limitsList.initialSlashingAmountPWei; res.inactivityPenaltiesAmountPWei = _limitsList.inactivityPenaltiesAmountPWei; res.clBalanceOraclesErrorUpperBPLimit = _limitsList.clBalanceOraclesErrorUpperBPLimit; diff --git a/scripts/scratch/deployed-testnet-defaults.json b/scripts/scratch/deployed-testnet-defaults.json index ed1d067d9..8aecf4f31 100644 --- a/scripts/scratch/deployed-testnet-defaults.json +++ b/scripts/scratch/deployed-testnet-defaults.json @@ -107,8 +107,8 @@ "annualBalanceIncreaseBPLimit": 1000, "simulatedShareRateDeviationBPLimit": 250, "maxValidatorExitRequestsPerReport": 2000, - "maxAccountingExtraDataListItemsCount": 100, - "maxNodeOperatorsPerExtraDataItemCount": 100, + "maxItemsPerExtraDataTransaction": 100, + "maxNodeOperatorsPerExtraDataItem": 100, "requestTimestampMargin": 128, "maxPositiveTokenRebase": 5000000, "initialSlashingAmountPWei": 1000, diff --git a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts index 22029158f..d1c04d3ca 100644 --- a/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts +++ b/scripts/scratch/steps/09-deploy-non-aragon-contracts.ts @@ -82,8 +82,8 @@ async function main() { sanityChecks.annualBalanceIncreaseBPLimit, sanityChecks.simulatedShareRateDeviationBPLimit, sanityChecks.maxValidatorExitRequestsPerReport, - sanityChecks.maxAccountingExtraDataListItemsCount, - sanityChecks.maxNodeOperatorsPerExtraDataItemCount, + sanityChecks.maxItemsPerExtraDataTransaction, + sanityChecks.maxNodeOperatorsPerExtraDataItem, sanityChecks.requestTimestampMargin, sanityChecks.maxPositiveTokenRebase, sanityChecks.initialSlashingAmountPWei, diff --git a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts index cb14ecfbd..4d79877fb 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts @@ -383,7 +383,7 @@ describe("AccountingOracle.sol:submitReport", () => { .grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin.address); await sanityChecker.connect(admin).setMaxAccountingExtraDataListItemsCount(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); - expect((await sanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount).to.be.equal( + expect((await sanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( MAX_ACCOUNTING_EXTRA_DATA_LIMIT, ); @@ -398,7 +398,7 @@ describe("AccountingOracle.sol:submitReport", () => { .grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin.address); await sanityChecker.connect(admin).setMaxAccountingExtraDataListItemsCount(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); - expect((await sanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount).to.be.equal( + expect((await sanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( MAX_ACCOUNTING_EXTRA_DATA_LIMIT, ); diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 2a8b1aa23..3d8b9e687 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -920,7 +920,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await sanityChecker.grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin); await sanityChecker.setMaxAccountingExtraDataListItemsCount(maxItemsPerChunk - 1); await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) - .to.be.revertedWithCustomError(sanityChecker, "MaxAccountingExtraDataItemsCountExceeded") + .to.be.revertedWithCustomError(sanityChecker, "TooManyItemsPerExtraDataTransaction") .withArgs(maxItemsPerChunk - 1, maxItemsPerChunk); await sanityChecker.setMaxAccountingExtraDataListItemsCount(maxItemsPerChunk); diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index 6b8fc9eeb..fa5dddb3b 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -32,8 +32,8 @@ describe("OracleReportSanityChecker.sol", () => { annualBalanceIncreaseBPLimit: 10_00, // 10% simulatedShareRateDeviationBPLimit: 2_50, // 2.5% maxValidatorExitRequestsPerReport: 2000, - maxAccountingExtraDataListItemsCount: 15, - maxNodeOperatorsPerExtraDataItemCount: 16, + maxItemsPerExtraDataTransaction: 15, + maxNodeOperatorsPerExtraDataItem: 16, requestTimestampMargin: 128, maxPositiveTokenRebase: 5_000_000, // 0.05% initialSlashingAmountPWei: 1000, @@ -101,8 +101,8 @@ describe("OracleReportSanityChecker.sol", () => { annualBalanceIncreaseLimitManagers: accounts.slice(8, 10), shareRateDeviationLimitManagers: accounts.slice(10, 12), maxValidatorExitRequestsPerReportManagers: accounts.slice(12, 14), - maxAccountingExtraDataListItemsCountManagers: accounts.slice(14, 16), - maxNodeOperatorsPerExtraDataItemCountManagers: accounts.slice(16, 18), + maxItemsPerExtraDataTransactionManagers: accounts.slice(14, 16), + maxNodeOperatorsPerExtraDataItemManagers: accounts.slice(16, 18), requestTimestampMarginManagers: accounts.slice(18, 20), maxPositiveTokenRebaseManagers: accounts.slice(20, 22), }; @@ -145,8 +145,8 @@ describe("OracleReportSanityChecker.sol", () => { annualBalanceIncreaseBPLimit: 15_00, simulatedShareRateDeviationBPLimit: 1_50, // 1.5% maxValidatorExitRequestsPerReport: 3000, - maxAccountingExtraDataListItemsCount: 15 + 1, - maxNodeOperatorsPerExtraDataItemCount: 16 + 1, + maxItemsPerExtraDataTransaction: 15 + 1, + maxNodeOperatorsPerExtraDataItem: 16 + 1, requestTimestampMargin: 2048, maxPositiveTokenRebase: 10_000_000, initialSlashingAmountPWei: 2000, @@ -163,11 +163,9 @@ describe("OracleReportSanityChecker.sol", () => { expect(limitsBefore.maxValidatorExitRequestsPerReport).to.not.equal( newLimitsList.maxValidatorExitRequestsPerReport, ); - expect(limitsBefore.maxAccountingExtraDataListItemsCount).to.not.equal( - newLimitsList.maxAccountingExtraDataListItemsCount, - ); - expect(limitsBefore.maxNodeOperatorsPerExtraDataItemCount).to.not.equal( - newLimitsList.maxNodeOperatorsPerExtraDataItemCount, + expect(limitsBefore.maxItemsPerExtraDataTransaction).to.not.equal(newLimitsList.maxItemsPerExtraDataTransaction); + expect(limitsBefore.maxNodeOperatorsPerExtraDataItem).to.not.equal( + newLimitsList.maxNodeOperatorsPerExtraDataItem, ); expect(limitsBefore.requestTimestampMargin).to.not.equal(newLimitsList.requestTimestampMargin); expect(limitsBefore.maxPositiveTokenRebase).to.not.equal(newLimitsList.maxPositiveTokenRebase); @@ -197,12 +195,8 @@ describe("OracleReportSanityChecker.sol", () => { expect(limitsAfter.annualBalanceIncreaseBPLimit).to.equal(newLimitsList.annualBalanceIncreaseBPLimit); expect(limitsAfter.simulatedShareRateDeviationBPLimit).to.equal(newLimitsList.simulatedShareRateDeviationBPLimit); expect(limitsAfter.maxValidatorExitRequestsPerReport).to.equal(newLimitsList.maxValidatorExitRequestsPerReport); - expect(limitsAfter.maxAccountingExtraDataListItemsCount).to.equal( - newLimitsList.maxAccountingExtraDataListItemsCount, - ); - expect(limitsAfter.maxNodeOperatorsPerExtraDataItemCount).to.equal( - newLimitsList.maxNodeOperatorsPerExtraDataItemCount, - ); + expect(limitsAfter.maxItemsPerExtraDataTransaction).to.equal(newLimitsList.maxItemsPerExtraDataTransaction); + expect(limitsAfter.maxNodeOperatorsPerExtraDataItem).to.equal(newLimitsList.maxNodeOperatorsPerExtraDataItem); expect(limitsAfter.requestTimestampMargin).to.equal(newLimitsList.requestTimestampMargin); expect(limitsAfter.maxPositiveTokenRebase).to.equal(newLimitsList.maxPositiveTokenRebase); expect(limitsAfter.clBalanceOraclesErrorUpperBPLimit).to.equal(newLimitsList.clBalanceOraclesErrorUpperBPLimit); @@ -1346,9 +1340,8 @@ describe("OracleReportSanityChecker.sol", () => { .setOracleReportLimits(defaultLimitsList, ZeroAddress); }); - it("set maxNodeOperatorsPerExtraDataItemCount", async () => { - const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()) - .maxNodeOperatorsPerExtraDataItemCount; + it("set maxNodeOperatorsPerExtraDataItem", async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItem; const newValue = 33; expect(newValue).to.not.equal(previousValue); await expect( @@ -1361,22 +1354,19 @@ describe("OracleReportSanityChecker.sol", () => { .connect(admin) .grantRole( await oracleReportSanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE(), - managersRoster.maxNodeOperatorsPerExtraDataItemCountManagers[0], + managersRoster.maxNodeOperatorsPerExtraDataItemManagers[0], ); const tx = await oracleReportSanityChecker - .connect(managersRoster.maxNodeOperatorsPerExtraDataItemCountManagers[0]) + .connect(managersRoster.maxNodeOperatorsPerExtraDataItemManagers[0]) .setMaxNodeOperatorsPerExtraDataItemCount(newValue); - expect( - (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount, - ).to.be.equal(newValue); - await expect(tx) - .to.emit(oracleReportSanityChecker, "MaxNodeOperatorsPerExtraDataItemCountSet") - .withArgs(newValue); + expect((await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItem).to.be.equal( + newValue, + ); + await expect(tx).to.emit(oracleReportSanityChecker, "MaxNodeOperatorsPerExtraDataItemSet").withArgs(newValue); }); - it("set maxAccountingExtraDataListItemsCount", async () => { - const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()) - .maxAccountingExtraDataListItemsCount; + it("set maxItemsPerExtraDataTransaction", async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction; const newValue = 31; expect(newValue).to.not.equal(previousValue); await expect( @@ -1389,19 +1379,19 @@ describe("OracleReportSanityChecker.sol", () => { .connect(admin) .grantRole( await oracleReportSanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), - managersRoster.maxAccountingExtraDataListItemsCountManagers[0], + managersRoster.maxItemsPerExtraDataTransactionManagers[0], ); const tx = await oracleReportSanityChecker - .connect(managersRoster.maxAccountingExtraDataListItemsCountManagers[0]) + .connect(managersRoster.maxItemsPerExtraDataTransactionManagers[0]) .setMaxAccountingExtraDataListItemsCount(newValue); - expect( - (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount, - ).to.be.equal(newValue); - await expect(tx).to.emit(oracleReportSanityChecker, "MaxAccountingExtraDataListItemsCountSet").withArgs(newValue); + expect((await oracleReportSanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( + newValue, + ); + await expect(tx).to.emit(oracleReportSanityChecker, "MaxItemsPerExtraDataTransactionSet").withArgs(newValue); }); it("checkNodeOperatorsPerExtraDataItemCount", async () => { - const maxCount = (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount; + const maxCount = (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItem; await oracleReportSanityChecker.checkNodeOperatorsPerExtraDataItemCount(12, maxCount); @@ -1411,12 +1401,12 @@ describe("OracleReportSanityChecker.sol", () => { }); it("checkExtraDataItemsCountPerTransaction", async () => { - const maxCount = (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount; + const maxCount = (await oracleReportSanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction; await oracleReportSanityChecker.checkExtraDataItemsCountPerTransaction(maxCount); await expect(oracleReportSanityChecker.checkExtraDataItemsCountPerTransaction(maxCount + 1n)) - .to.be.revertedWithCustomError(oracleReportSanityChecker, "MaxAccountingExtraDataItemsCountExceeded") + .to.be.revertedWithCustomError(oracleReportSanityChecker, "TooManyItemsPerExtraDataTransaction") .withArgs(maxCount, maxCount + 1n); }); }); @@ -1489,7 +1479,7 @@ describe("OracleReportSanityChecker.sol", () => { oracleReportSanityChecker .connect(managersRoster.allLimitsManagers[0]) .setOracleReportLimits( - { ...defaultLimitsList, maxNodeOperatorsPerExtraDataItemCount: INVALID_VALUE }, + { ...defaultLimitsList, maxNodeOperatorsPerExtraDataItem: INVALID_VALUE }, ZeroAddress, ), ) diff --git a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts index a921beb19..fe233b820 100644 --- a/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/negativeRebaseSanityChecker.test.ts @@ -32,8 +32,8 @@ describe("OracleReportSanityChecker.sol", () => { annualBalanceIncreaseBPLimit: 10_00, // 10% simulatedShareRateDeviationBPLimit: 2_50, // 2.5% maxValidatorExitRequestsPerReport: 2000, - maxAccountingExtraDataListItemsCount: 15, - maxNodeOperatorsPerExtraDataItemCount: 16, + maxItemsPerExtraDataTransaction: 15, + maxNodeOperatorsPerExtraDataItem: 16, requestTimestampMargin: 128, maxPositiveTokenRebase: 5_000_000, // 0.05% initialSlashingAmountPWei: 1000, // 1 ETH = 1000 PWei From 5fdce69e86f119be6dbaa41246ac172fb59cfa6d Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 10 Jul 2024 13:42:58 +0200 Subject: [PATCH 128/177] feat: fix deploy script after merging negative rebase --- scripts/staking-router-v2/sr-v2-deploy.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 0c7283592..ed0500f59 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -36,8 +36,7 @@ function getEnvVariable(name: string, defaultValue?: string) { const SECONDS_PER_SLOT = 12; const GENESIS_TIME = 1606824023; // Oracle report sanity checker -const LIMITS = [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200]; -const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; +const LIMITS = [9000, 43200, 1000, 50, 600, 8, 62, 7680, 750000, 1000, 101, 74]; // DSM args const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; const MAX_OPERATORS_PER_UNVETTING = 200; @@ -144,7 +143,7 @@ async function main() { log.emptyLine(); // Deploy OracleReportSanityCheckerArgs - const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS, MANAGERS_ROSTER]; + const oracleReportSanityCheckerArgs = [LOCATOR, SC_ADMIN, LIMITS]; const oracleReportSanityCheckerAddress = ( await deployWithoutProxy( Sk.oracleReportSanityChecker, From c258f4ba240c9f00b22af58e16939a6b1b455bfa Mon Sep 17 00:00:00 2001 From: Eddort Date: Mon, 15 Jul 2024 12:16:35 +0200 Subject: [PATCH 129/177] refactor: tests code review --- test/0.4.24/nor/nor.aux.test.ts | 2 +- test/0.4.24/nor/nor.initialize.upgrade.test.ts | 2 +- test/0.4.24/nor/nor.management.flow.test.ts | 2 +- test/0.4.24/nor/nor.rewards.penalties.flow.test.ts | 11 +++-------- test/0.4.24/nor/nor.signing.keys.test.ts | 2 +- test/0.4.24/nor/nor.staking.limit.test.ts | 2 +- 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/test/0.4.24/nor/nor.aux.test.ts b/test/0.4.24/nor/nor.aux.test.ts index 31dbe50d6..7a92876dd 100644 --- a/test/0.4.24/nor/nor.aux.test.ts +++ b/test/0.4.24/nor/nor.aux.test.ts @@ -61,7 +61,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { stuckPenaltyEndAt: 0n, }, { - name: " bar", + name: "bar", rewardAddress: certainAddress("node-operator-2"), totalSigningKeysCount: 15n, depositedSigningKeysCount: 7n, diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index ff2f4891c..74dfb4daa 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -58,7 +58,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { stuckPenaltyEndAt: 0n, }, { - name: " bar", + name: "bar", rewardAddress: certainAddress("node-operator-2"), totalSigningKeysCount: 15n, depositedSigningKeysCount: 7n, diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index 99b6e99ec..e7f188834 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -56,7 +56,7 @@ describe("NodeOperatorsRegistry:management", () => { stuckPenaltyEndAt: 0n, }, { - name: " bar", + name: "bar", rewardAddress: certainAddress("node-operator-2"), totalSigningKeysCount: 15n, depositedSigningKeysCount: 7n, diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index 39c8271f8..bafbbedb5 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -25,17 +25,12 @@ import { ether, NodeOperatorConfig, prepIdsCountsPayload, + RewardDistributionState, } from "lib"; import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; -enum RewardDistributionState { - TransferredToModule, // New reward portion minted and transferred to the module - ReadyForDistribution, // Operators' statistics updated, reward ready for distribution - Distributed, // Reward distributed among operators -} - describe("NodeOperatorsRegistry:rewards-penalties", () => { let deployer: HardhatEthersSigner; let user: HardhatEthersSigner; @@ -72,7 +67,7 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { stuckPenaltyEndAt: 0n, }, { - name: " bar", + name: "bar", rewardAddress: certainAddress("node-operator-2"), totalSigningKeysCount: 15n, depositedSigningKeysCount: 7n, @@ -376,7 +371,7 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { .to.not.emit(nor, "ExitedSigningKeysCountChanged"); }); - it("Reverts on attemp to decrease exited keys count", async () => { + it("Reverts on attempt to decrease exited keys count", async () => { const nonce = await nor.getNonce(); const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [2n]); diff --git a/test/0.4.24/nor/nor.signing.keys.test.ts b/test/0.4.24/nor/nor.signing.keys.test.ts index de4c189d6..854742c2a 100644 --- a/test/0.4.24/nor/nor.signing.keys.test.ts +++ b/test/0.4.24/nor/nor.signing.keys.test.ts @@ -73,7 +73,7 @@ describe("NodeOperatorsRegistry:signing-keys", () => { stuckPenaltyEndAt: 0n, }, { - name: " bar", + name: "bar", rewardAddress: certainAddress("node-operator-2"), totalSigningKeysCount: 12n, depositedSigningKeysCount: 7n, diff --git a/test/0.4.24/nor/nor.staking.limit.test.ts b/test/0.4.24/nor/nor.staking.limit.test.ts index c689eb15a..f29e1605e 100644 --- a/test/0.4.24/nor/nor.staking.limit.test.ts +++ b/test/0.4.24/nor/nor.staking.limit.test.ts @@ -56,7 +56,7 @@ describe("NodeOperatorsRegistry:stakingLimit", () => { stuckPenaltyEndAt: 0n, }, { - name: " bar", + name: "bar", rewardAddress: certainAddress("node-operator-2"), totalSigningKeysCount: 15n, depositedSigningKeysCount: 7n, From d38623452c408074225757ae18e9094ca4d2f1da Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 16 Jul 2024 15:06:38 +0200 Subject: [PATCH 130/177] fix: code review nor aux --- test/0.4.24/nor/nor.aux.test.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/0.4.24/nor/nor.aux.test.ts b/test/0.4.24/nor/nor.aux.test.ts index 7a92876dd..69c36b488 100644 --- a/test/0.4.24/nor/nor.aux.test.ts +++ b/test/0.4.24/nor/nor.aux.test.ts @@ -16,7 +16,13 @@ import { } from "typechain-types"; import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; -import { addNodeOperator, certainAddress, NodeOperatorConfig, prepIdsCountsPayload } from "lib"; +import { + addNodeOperator, + certainAddress, + NodeOperatorConfig, + prepIdsCountsPayload, + RewardDistributionState, +} from "lib"; import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; @@ -98,7 +104,8 @@ describe("NodeOperatorsRegistry:auxiliary", () => { const moduleType = encodeBytes32String("curated-onchain-v1"); const penaltyDelay = 86400n; - const contractVersion = 2n; + const contractVersionV2 = 2n; + const contractVersionV3 = 3n; before(async () => { [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager, stranger] = @@ -143,11 +150,15 @@ describe("NodeOperatorsRegistry:auxiliary", () => { // Initialize the nor's proxy. await expect(nor.initialize(locator, moduleType, penaltyDelay)) .to.emit(nor, "ContractVersionSet") - .withArgs(contractVersion) + .withArgs(contractVersionV2) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV3) .and.to.emit(nor, "LocatorContractSet") .withArgs(locator) .and.to.emit(nor, "StakingModuleTypeSet") - .withArgs(moduleType); + .withArgs(moduleType) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); nor = nor.connect(user); }); @@ -232,9 +243,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { "APP_AUTH_FAILED", ); - await expect(nor[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)).to.be.revertedWith( - "APP_AUTH_FAILED", - ); + await expect(nor[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)).to.be.revertedWith("APP_AUTH_FAILED"); }); it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { From 878dcb7148503b9f5f483b703a210025f229b4cb Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 16 Jul 2024 15:17:54 +0200 Subject: [PATCH 131/177] fix: move getRewardsDistribution to penalties flow file --- test/0.4.24/nor/nor.aux.test.ts | 111 +----------------- .../nor/nor.rewards.penalties.flow.test.ts | 89 +++++++++++++- 2 files changed, 90 insertions(+), 110 deletions(-) diff --git a/test/0.4.24/nor/nor.aux.test.ts b/test/0.4.24/nor/nor.aux.test.ts index 69c36b488..708f83489 100644 --- a/test/0.4.24/nor/nor.aux.test.ts +++ b/test/0.4.24/nor/nor.aux.test.ts @@ -16,13 +16,7 @@ import { } from "typechain-types"; import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; -import { - addNodeOperator, - certainAddress, - NodeOperatorConfig, - prepIdsCountsPayload, - RewardDistributionState, -} from "lib"; +import { addNodeOperator, certainAddress, NodeOperatorConfig, RewardDistributionState } from "lib"; import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; @@ -52,7 +46,6 @@ describe("NodeOperatorsRegistry:auxiliary", () => { const firstNodeOperatorId = 0; const secondNodeOperatorId = 1; const thirdNodeOperatorId = 2; - const fourthNodeOperatorId = 3; const NODE_OPERATORS: NodeOperatorConfig[] = [ { @@ -77,21 +70,9 @@ describe("NodeOperatorsRegistry:auxiliary", () => { refundedValidatorsCount: 0n, stuckPenaltyEndAt: 0n, }, - { - name: "deactivated", - isActive: false, - rewardAddress: certainAddress("node-operator-3"), - totalSigningKeysCount: 10n, - depositedSigningKeysCount: 0n, - exitedSigningKeysCount: 0n, - vettedSigningKeysCount: 5n, - stuckValidatorsCount: 0n, - refundedValidatorsCount: 0n, - stuckPenaltyEndAt: 0n, - }, { name: "extra-no", - rewardAddress: certainAddress("node-operator-4"), + rewardAddress: certainAddress("node-operator-3"), totalSigningKeysCount: 3n, depositedSigningKeysCount: 3n, exitedSigningKeysCount: 0n, @@ -421,7 +402,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { }); it("Invalidates the deposit data even if no trimming needed", async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[fourthNodeOperatorId])).to.be.equal( + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( firstNodeOperatorId, ); @@ -474,90 +455,4 @@ describe("NodeOperatorsRegistry:auxiliary", () => { .withArgs(nonce + 1n); }); }); - - context("getRewardsDistribution", () => { - it("Returns empty lists if no operators", async () => { - const [recipients, shares, penalized] = await nor.getRewardsDistribution(10n); - - expect(recipients).to.be.empty; - expect(shares).to.be.empty; - expect(penalized).to.be.empty; - }); - - it("Returns zero rewards if zero shares distributed", async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( - firstNodeOperatorId, - ); - - const [recipients, shares, penalized] = await nor.getRewardsDistribution(0n); - - expect(recipients.length).to.be.equal(1n); - expect(shares.length).to.be.equal(1n); - expect(penalized.length).to.be.equal(1n); - - expect(recipients[0]).to.be.equal(NODE_OPERATORS[firstNodeOperatorId].rewardAddress); - expect(shares[0]).to.be.equal(0n); - expect(penalized[0]).to.be.equal(false); - }); - - it("Distributes all rewards to a single active operator if no others", async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( - firstNodeOperatorId, - ); - - const [recipients, shares, penalized] = await nor.getRewardsDistribution(10n); - - expect(recipients.length).to.be.equal(1n); - expect(shares.length).to.be.equal(1n); - expect(penalized.length).to.be.equal(1n); - - expect(recipients[0]).to.be.equal(NODE_OPERATORS[firstNodeOperatorId].rewardAddress); - expect(shares[0]).to.be.equal(10n); - expect(penalized[0]).to.be.equal(false); - }); - - it("Returns correct reward distribution for multiple NOs", async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( - firstNodeOperatorId, - ); - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( - secondNodeOperatorId, - ); - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( - thirdNodeOperatorId, - ); - - const nonce = await nor.getNonce(); - const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [2n]); - await expect(nor.connect(stakingRouter).updateStuckValidatorsCount(idsPayload.operatorIds, idsPayload.keysCounts)) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(nonce + 1n) - .to.emit(nor, "StuckPenaltyStateChanged") - .withArgs(firstNodeOperatorId, 2n, 0n, 0n); - - const [recipients, shares, penalized] = await nor.getRewardsDistribution(100n); - - expect(recipients.length).to.be.equal(2n); - expect(shares.length).to.be.equal(2n); - expect(penalized.length).to.be.equal(2n); - - const firstNOActiveKeys = - NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount - - NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount; - const secondNOActiveKeys = - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount - - NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; - const totalActiveKeys = firstNOActiveKeys + secondNOActiveKeys; - - expect(recipients[0]).to.be.equal(NODE_OPERATORS[firstNodeOperatorId].rewardAddress); - expect(shares[0]).to.be.equal((100n * firstNOActiveKeys) / totalActiveKeys); - expect(penalized[0]).to.be.equal(true); - - expect(recipients[1]).to.be.equal(NODE_OPERATORS[secondNodeOperatorId].rewardAddress); - expect(shares[1]).to.be.equal((100n * secondNOActiveKeys) / totalActiveKeys); - expect(penalized[1]).to.be.equal(false); - }); - }); }); diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index bafbbedb5..3dd0b365e 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -512,8 +512,7 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { .withArgs(1n, 2n, 0n, 0n); await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); - await expect(nor.connect(stakingRouter).distributeReward()) - .to.emit(nor, "NodeOperatorPenalized"); + await expect(nor.connect(stakingRouter).distributeReward()).to.emit(nor, "NodeOperatorPenalized"); }); }); @@ -769,4 +768,90 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { expect(await nor.isOperatorPenaltyCleared(secondNodeOperatorId)).to.be.true; }); }); + + context("getRewardsDistribution", () => { + it("Returns empty lists if no operators", async () => { + const [recipients, shares, penalized] = await nor.getRewardsDistribution(10n); + + expect(recipients).to.be.empty; + expect(shares).to.be.empty; + expect(penalized).to.be.empty; + }); + + it("Returns zero rewards if zero shares distributed", async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + + const [recipients, shares, penalized] = await nor.getRewardsDistribution(0n); + + expect(recipients.length).to.be.equal(1n); + expect(shares.length).to.be.equal(1n); + expect(penalized.length).to.be.equal(1n); + + expect(recipients[0]).to.be.equal(NODE_OPERATORS[firstNodeOperatorId].rewardAddress); + expect(shares[0]).to.be.equal(0n); + expect(penalized[0]).to.be.equal(false); + }); + + it("Distributes all rewards to a single active operator if no others", async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + + const [recipients, shares, penalized] = await nor.getRewardsDistribution(10n); + + expect(recipients.length).to.be.equal(1n); + expect(shares.length).to.be.equal(1n); + expect(penalized.length).to.be.equal(1n); + + expect(recipients[0]).to.be.equal(NODE_OPERATORS[firstNodeOperatorId].rewardAddress); + expect(shares[0]).to.be.equal(10n); + expect(penalized[0]).to.be.equal(false); + }); + + it("Returns correct reward distribution for multiple NOs", async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( + thirdNodeOperatorId, + ); + + const nonce = await nor.getNonce(); + const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [2n]); + await expect(nor.connect(stakingRouter).updateStuckValidatorsCount(idsPayload.operatorIds, idsPayload.keysCounts)) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 1n) + .to.emit(nor, "StuckPenaltyStateChanged") + .withArgs(firstNodeOperatorId, 2n, 0n, 0n); + + const [recipients, shares, penalized] = await nor.getRewardsDistribution(100n); + + expect(recipients.length).to.be.equal(2n); + expect(shares.length).to.be.equal(2n); + expect(penalized.length).to.be.equal(2n); + + const firstNOActiveKeys = + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount - + NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount; + const secondNOActiveKeys = + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount - + NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; + const totalActiveKeys = firstNOActiveKeys + secondNOActiveKeys; + + expect(recipients[0]).to.be.equal(NODE_OPERATORS[firstNodeOperatorId].rewardAddress); + expect(shares[0]).to.be.equal((100n * firstNOActiveKeys) / totalActiveKeys); + expect(penalized[0]).to.be.equal(true); + + expect(recipients[1]).to.be.equal(NODE_OPERATORS[secondNodeOperatorId].rewardAddress); + expect(shares[1]).to.be.equal((100n * secondNOActiveKeys) / totalActiveKeys); + expect(penalized[1]).to.be.equal(false); + }); + }); }); From 5028f776ed69be9f5ad1dc46c9dfbe19ee2ba9b4 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 16 Jul 2024 15:30:39 +0200 Subject: [PATCH 132/177] refactor: move the limit tests to a separate file --- test/0.4.24/nor/nor.aux.test.ts | 199 +---------------- test/0.4.24/nor/nor.limits.test.ts | 331 +++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+), 198 deletions(-) create mode 100644 test/0.4.24/nor/nor.limits.test.ts diff --git a/test/0.4.24/nor/nor.aux.test.ts b/test/0.4.24/nor/nor.aux.test.ts index 708f83489..845170290 100644 --- a/test/0.4.24/nor/nor.aux.test.ts +++ b/test/0.4.24/nor/nor.aux.test.ts @@ -21,13 +21,9 @@ import { addNodeOperator, certainAddress, NodeOperatorConfig, RewardDistribution import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; -const updateTargetLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)"; -const updateTargetLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)"; - describe("NodeOperatorsRegistry:auxiliary", () => { let deployer: HardhatEthersSigner; let user: HardhatEthersSigner; - let stranger: HardhatEthersSigner; let limitsManager: HardhatEthersSigner; let nodeOperatorsManager: HardhatEthersSigner; @@ -89,7 +85,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { const contractVersionV3 = 3n; before(async () => { - [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager, stranger] = + [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager] = await ethers.getSigners(); ({ lido, dao, acl } = await deployLidoDao({ @@ -148,199 +144,6 @@ describe("NodeOperatorsRegistry:auxiliary", () => { afterEach(async () => await Snapshot.restore(originalState)); - context("unsafeUpdateValidatorsCount", () => { - beforeEach(async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( - firstNodeOperatorId, - ); - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( - secondNodeOperatorId, - ); - }); - - it("Reverts if no such an operator exists", async () => { - await expect(nor.unsafeUpdateValidatorsCount(3n, 0n, 0n)).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it("Reverts if has not STAKING_ROUTER_ROLE assigned", async () => { - await expect(nor.connect(stranger).unsafeUpdateValidatorsCount(firstNodeOperatorId, 0n, 0n)).to.be.revertedWith( - "APP_AUTH_FAILED", - ); - }); - - it("Can change stuck and exited keys arbitrary (even decreasing exited)", async () => { - const nonce = await nor.getNonce(); - - const beforeNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(beforeNOSummary.stuckValidatorsCount).to.be.equal(0n); - expect(beforeNOSummary.totalExitedValidators).to.be.equal(1n); - - await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 3n, 2n)) - .to.emit(nor, "StuckPenaltyStateChanged") - .withArgs(firstNodeOperatorId, 2n, 0n, 0n) // doesn't affect stuck penalty deadline - .to.emit(nor, "ExitedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, 3n) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(nonce + 1n); - - const middleNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(middleNOSummary.stuckValidatorsCount).to.be.equal(2n); - expect(middleNOSummary.totalExitedValidators).to.be.equal(3n); - - await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 1n, 2n)) - .to.emit(nor, "ExitedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, 1n) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 2n) - .to.emit(nor, "NonceChanged") - .withArgs(nonce + 2n) - .to.not.emit(nor, "StuckPenaltyStateChanged"); - - const lastNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(lastNOSummary.stuckValidatorsCount).to.be.equal(2n); - expect(lastNOSummary.totalExitedValidators).to.be.equal(1n); - }); - }); - - context("updateTargetValidatorsLimits", () => { - let targetLimit = 0n; - - beforeEach(async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( - firstNodeOperatorId, - ); - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( - secondNodeOperatorId, - ); - }); - - it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stranger, nor, await nor.STAKING_ROUTER_ROLE())).to.be - .false; - - await expect(nor[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)).to.be.revertedWith( - "APP_AUTH_FAILED", - ); - - await expect(nor[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)).to.be.revertedWith("APP_AUTH_FAILED"); - }); - - it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { - const targetLimitWrong = BigInt("0x10000000000000000"); - - await expect( - nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimitWrong), - ).to.be.revertedWith("OUT_OF_RANGE"); - - await expect( - nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimitWrong), - ).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it("updates node operator target limit if called by sender with STAKING_ROUTER_ROLE", async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) - .to.be.true; - - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - const keysStatTotal = await nor.getStakingModuleSummary(); - const expectedExitedValidatorsCount = - NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount + - NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; - expect(keysStatTotal.totalExitedValidators).to.equal(expectedExitedValidatorsCount); - - const expectedDepositedValidatorsCount = - NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount + - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; - expect(keysStatTotal.totalDepositedValidators).to.equal(expectedDepositedValidatorsCount); - - const firstNodeOperatorDepositableValidators = - NODE_OPERATORS[firstNodeOperatorId].vettedSigningKeysCount - - NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount; - - const secondNodeOperatorDepositableValidators = - NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; - - const expectedDepositableValidatorsCount = - targetLimit < firstNodeOperatorDepositableValidators - ? targetLimit - : firstNodeOperatorDepositableValidators + secondNodeOperatorDepositableValidators; - - expect(keysStatTotal.depositableValidatorsCount).to.equal(expectedDepositableValidatorsCount); - }); - - it("updates node operator target limit mode correctly", async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) - .to.be.true; - - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(1n); - - await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, 0n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, 0n, 0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(0n); - - // reset limit - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - }); - - it("updates node operator target limit mode correctly using updateTargetLimitsDeprecated", async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) - .to.be.true; - - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(1n); - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](secondNodeOperatorId, false, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, 0n, 0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(0n); - - // reset limit - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, false, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - }); - }); - context("onWithdrawalCredentialsChanged", () => { it("Reverts if has no STAKING_ROUTER_ROLE assigned", async () => { await expect(nor.onWithdrawalCredentialsChanged()).to.be.revertedWith("APP_AUTH_FAILED"); diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts new file mode 100644 index 000000000..7bf7a132d --- /dev/null +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -0,0 +1,331 @@ +import { expect } from "chai"; +import { encodeBytes32String } from "ethers"; +import { ethers } from "hardhat"; + +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; + +import { + ACL, + Kernel, + Lido, + LidoLocator, + LidoLocator__factory, + MinFirstAllocationStrategy__factory, + NodeOperatorsRegistry__Harness, + NodeOperatorsRegistry__Harness__factory, +} from "typechain-types"; +import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; + +import { addNodeOperator, certainAddress, NodeOperatorConfig, RewardDistributionState } from "lib"; + +import { addAragonApp, deployLidoDao } from "test/deploy"; +import { Snapshot } from "test/suite"; + +const updateTargetLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)"; +const updateTargetLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)"; + +describe("NodeOperatorsRegistry:validatorsLimits", () => { + let deployer: HardhatEthersSigner; + let user: HardhatEthersSigner; + let stranger: HardhatEthersSigner; + + let limitsManager: HardhatEthersSigner; + let nodeOperatorsManager: HardhatEthersSigner; + let signingKeysManager: HardhatEthersSigner; + let stakingRouter: HardhatEthersSigner; + let lido: Lido; + let dao: Kernel; + let acl: ACL; + let locator: LidoLocator; + + let impl: NodeOperatorsRegistry__Harness; + let nor: NodeOperatorsRegistry__Harness; + + let originalState: string; + + const firstNodeOperatorId = 0; + const secondNodeOperatorId = 1; + + const NODE_OPERATORS: NodeOperatorConfig[] = [ + { + name: "foo", + rewardAddress: certainAddress("node-operator-1"), + totalSigningKeysCount: 10n, + depositedSigningKeysCount: 5n, + exitedSigningKeysCount: 1n, + vettedSigningKeysCount: 6n, + stuckValidatorsCount: 0n, + refundedValidatorsCount: 0n, + stuckPenaltyEndAt: 0n, + }, + { + name: "bar", + rewardAddress: certainAddress("node-operator-2"), + totalSigningKeysCount: 15n, + depositedSigningKeysCount: 7n, + exitedSigningKeysCount: 0n, + vettedSigningKeysCount: 10n, + stuckValidatorsCount: 0n, + refundedValidatorsCount: 0n, + stuckPenaltyEndAt: 0n, + }, + ]; + + const moduleType = encodeBytes32String("curated-onchain-v1"); + const penaltyDelay = 86400n; + const contractVersionV2 = 2n; + const contractVersionV3 = 3n; + + before(async () => { + [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager, stranger] = + await ethers.getSigners(); + + ({ lido, dao, acl } = await deployLidoDao({ + rootAccount: deployer, + initialized: true, + locatorConfig: { + stakingRouter, + }, + })); + + const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); + const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { + ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), + }; + + impl = await new NodeOperatorsRegistry__Harness__factory(allocLibAddr, deployer).deploy(); + const appProxy = await addAragonApp({ + dao, + name: "node-operators-registry", + impl, + rootAccount: deployer, + }); + + nor = NodeOperatorsRegistry__Harness__factory.connect(appProxy, deployer); + + await acl.createPermission(user, lido, await lido.RESUME_ROLE(), deployer); + + await acl.createPermission(stakingRouter, nor, await nor.STAKING_ROUTER_ROLE(), deployer); + await acl.createPermission(signingKeysManager, nor, await nor.MANAGE_SIGNING_KEYS(), deployer); + await acl.createPermission(nodeOperatorsManager, nor, await nor.MANAGE_NODE_OPERATOR_ROLE(), deployer); + await acl.createPermission(limitsManager, nor, await nor.SET_NODE_OPERATOR_LIMIT_ROLE(), deployer); + + // grant role to nor itself cause it uses solidity's call method to itself + // inside the harness__requestValidatorsKeysForDeposits() method + await acl.grantPermission(nor, nor, await nor.STAKING_ROUTER_ROLE()); + + locator = LidoLocator__factory.connect(await lido.getLidoLocator(), user); + + // Initialize the nor's proxy. + await expect(nor.initialize(locator, moduleType, penaltyDelay)) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV2) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV3) + .and.to.emit(nor, "LocatorContractSet") + .withArgs(locator) + .and.to.emit(nor, "StakingModuleTypeSet") + .withArgs(moduleType) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); + + nor = nor.connect(user); + }); + + beforeEach(async () => (originalState = await Snapshot.take())); + + afterEach(async () => await Snapshot.restore(originalState)); + + context("unsafeUpdateValidatorsCount", () => { + beforeEach(async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + }); + + it("Reverts if no such an operator exists", async () => { + await expect(nor.unsafeUpdateValidatorsCount(3n, 0n, 0n)).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it("Reverts if has not STAKING_ROUTER_ROLE assigned", async () => { + await expect(nor.connect(stranger).unsafeUpdateValidatorsCount(firstNodeOperatorId, 0n, 0n)).to.be.revertedWith( + "APP_AUTH_FAILED", + ); + }); + + it("Can change stuck and exited keys arbitrary (even decreasing exited)", async () => { + const nonce = await nor.getNonce(); + + const beforeNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(beforeNOSummary.stuckValidatorsCount).to.be.equal(0n); + expect(beforeNOSummary.totalExitedValidators).to.be.equal(1n); + + await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 3n, 2n)) + .to.emit(nor, "StuckPenaltyStateChanged") + .withArgs(firstNodeOperatorId, 2n, 0n, 0n) // doesn't affect stuck penalty deadline + .to.emit(nor, "ExitedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, 3n) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 1n); + + const middleNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(middleNOSummary.stuckValidatorsCount).to.be.equal(2n); + expect(middleNOSummary.totalExitedValidators).to.be.equal(3n); + + await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 1n, 2n)) + .to.emit(nor, "ExitedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, 1n) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 2n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 2n) + .to.not.emit(nor, "StuckPenaltyStateChanged"); + + const lastNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(lastNOSummary.stuckValidatorsCount).to.be.equal(2n); + expect(lastNOSummary.totalExitedValidators).to.be.equal(1n); + }); + }); + + context("updateTargetValidatorsLimits", () => { + let targetLimit = 0n; + + beforeEach(async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + }); + + it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { + expect(await acl["hasPermission(address,address,bytes32)"](stranger, nor, await nor.STAKING_ROUTER_ROLE())).to.be + .false; + + await expect(nor[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)).to.be.revertedWith( + "APP_AUTH_FAILED", + ); + + await expect(nor[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)).to.be.revertedWith("APP_AUTH_FAILED"); + }); + + it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { + const targetLimitWrong = BigInt("0x10000000000000000"); + + await expect( + nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimitWrong), + ).to.be.revertedWith("OUT_OF_RANGE"); + + await expect( + nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimitWrong), + ).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it("updates node operator target limit if called by sender with STAKING_ROUTER_ROLE", async () => { + expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) + .to.be.true; + + targetLimit = 10n; + + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + const keysStatTotal = await nor.getStakingModuleSummary(); + const expectedExitedValidatorsCount = + NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount + + NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; + expect(keysStatTotal.totalExitedValidators).to.equal(expectedExitedValidatorsCount); + + const expectedDepositedValidatorsCount = + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount + + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; + expect(keysStatTotal.totalDepositedValidators).to.equal(expectedDepositedValidatorsCount); + + const firstNodeOperatorDepositableValidators = + NODE_OPERATORS[firstNodeOperatorId].vettedSigningKeysCount - + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount; + + const secondNodeOperatorDepositableValidators = + NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; + + const expectedDepositableValidatorsCount = + targetLimit < firstNodeOperatorDepositableValidators + ? targetLimit + : firstNodeOperatorDepositableValidators + secondNodeOperatorDepositableValidators; + + expect(keysStatTotal.depositableValidatorsCount).to.equal(expectedDepositableValidatorsCount); + }); + + it("updates node operator target limit mode correctly", async () => { + expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) + .to.be.true; + + targetLimit = 10n; + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + + await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(secondNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(0n); + + // reset limit + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + }); + + it("updates node operator target limit mode correctly using updateTargetLimitsDeprecated", async () => { + expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) + .to.be.true; + + targetLimit = 10n; + + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](secondNodeOperatorId, false, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(secondNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(0n); + + // reset limit + await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, false, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + }); + }); +}); From 9d04d5faae4244f16608f142e045e3385fab8e74 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 16 Jul 2024 15:32:39 +0200 Subject: [PATCH 133/177] fix: check targetValidatorsCount in limits tests --- test/0.4.24/nor/nor.limits.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index 7bf7a132d..c33bfdee9 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -294,6 +294,7 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); }); it("updates node operator target limit mode correctly using updateTargetLimitsDeprecated", async () => { @@ -326,6 +327,7 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); }); }); }); From ee7d64b433fe772690ddd9ebcc60e7ef98fde592 Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 16 Jul 2024 15:35:11 +0200 Subject: [PATCH 134/177] refactor: added todos for test cases --- test/0.4.24/nor/nor.limits.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index c33bfdee9..b0fa82fec 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -204,6 +204,10 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { ); }); + it("nonce changing"); + it("targetValidatorsCount changing"); + it("module stats updating (summarySigningKeysStats)"); + it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { expect(await acl["hasPermission(address,address,bytes32)"](stranger, nor, await nor.STAKING_ROUTER_ROLE())).to.be .false; From a33c0caa90d1f579c29a57557f16b3a61d3a2cd4 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 16 Jul 2024 17:37:40 +0400 Subject: [PATCH 135/177] Update test/0.4.24/nor/nor.management.flow.test.ts Co-authored-by: avsetsin --- test/0.4.24/nor/nor.management.flow.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index e7f188834..7cec726f9 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -471,7 +471,7 @@ describe("NodeOperatorsRegistry:management", () => { const noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(0n);; + expect(noSummary.targetLimitMode).to.be.equal(0n); expect(noSummary.targetValidatorsCount).to.be.equal(0n); expect(noSummary.stuckValidatorsCount).to.be.equal(0n); expect(noSummary.refundedValidatorsCount).to.be.equal(0n); From 9753ca3eda512dd2487b82cb81eb6f1985376368 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 17 Jul 2024 11:49:41 +0200 Subject: [PATCH 136/177] feat: rename setter for maxItemsPerExtraDataTransaction --- .../sanity_checks/OracleReportSanityChecker.sol | 2 +- .../oracle/accountingOracle.submitReport.test.ts | 14 +++++++------- .../accountingOracle.submitReportExtraData.test.ts | 6 +++--- .../baseOracleReportSanityChecker.test.ts | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 92e06a307..e41954f84 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -354,7 +354,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { /// @notice Sets the new value for the maxItemsPerExtraDataTransaction /// @param _maxItemsPerExtraDataTransaction new maxItemsPerExtraDataTransaction value - function setMaxAccountingExtraDataListItemsCount(uint256 _maxItemsPerExtraDataTransaction) + function setMaxItemsPerExtraDataTransaction(uint256 _maxItemsPerExtraDataTransaction) external onlyRole(MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE) { diff --git a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts index 4d79877fb..58565a960 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts @@ -374,32 +374,32 @@ describe("AccountingOracle.sol:submitReport", () => { context("enforces data safety boundaries", () => { it("passes fine when extra data do not feet in a single third phase transaction", async () => { - const MAX_ACCOUNTING_EXTRA_DATA_LIMIT = 1; + const MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION = 1; - expect(reportFields.extraDataItemsCount).to.be.greaterThan(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); + expect(reportFields.extraDataItemsCount).to.be.greaterThan(MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION); await sanityChecker .connect(admin) .grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin.address); - await sanityChecker.connect(admin).setMaxAccountingExtraDataListItemsCount(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); + await sanityChecker.connect(admin).setMaxItemsPerExtraDataTransaction(MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION); expect((await sanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( - MAX_ACCOUNTING_EXTRA_DATA_LIMIT, + MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, ); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); }); it("passes fine when extra data feet in a single third phase transaction", async () => { - const MAX_ACCOUNTING_EXTRA_DATA_LIMIT = reportFields.extraDataItemsCount; + const MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION = reportFields.extraDataItemsCount; await sanityChecker .connect(admin) .grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin.address); - await sanityChecker.connect(admin).setMaxAccountingExtraDataListItemsCount(MAX_ACCOUNTING_EXTRA_DATA_LIMIT); + await sanityChecker.connect(admin).setMaxItemsPerExtraDataTransaction(MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION); expect((await sanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( - MAX_ACCOUNTING_EXTRA_DATA_LIMIT, + MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, ); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 3d8b9e687..44af5d256 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -898,7 +898,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin.address, ); - await sanityChecker.setMaxAccountingExtraDataListItemsCount(problematicItemsCount); + await sanityChecker.setMaxItemsPerExtraDataTransaction(problematicItemsCount); const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); }); @@ -918,12 +918,12 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await oracleMemberSubmitReportData(report); await sanityChecker.grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin); - await sanityChecker.setMaxAccountingExtraDataListItemsCount(maxItemsPerChunk - 1); + await sanityChecker.setMaxItemsPerExtraDataTransaction(maxItemsPerChunk - 1); await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) .to.be.revertedWithCustomError(sanityChecker, "TooManyItemsPerExtraDataTransaction") .withArgs(maxItemsPerChunk - 1, maxItemsPerChunk); - await sanityChecker.setMaxAccountingExtraDataListItemsCount(maxItemsPerChunk); + await sanityChecker.setMaxItemsPerExtraDataTransaction(maxItemsPerChunk); const tx0 = await oracleMemberSubmitExtraData(extraDataChunks[0]); await expect(tx0) diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index fa5dddb3b..297526dd9 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -1370,7 +1370,7 @@ describe("OracleReportSanityChecker.sol", () => { const newValue = 31; expect(newValue).to.not.equal(previousValue); await expect( - oracleReportSanityChecker.connect(deployer).setMaxAccountingExtraDataListItemsCount(newValue), + oracleReportSanityChecker.connect(deployer).setMaxItemsPerExtraDataTransaction(newValue), ).to.be.revertedWithOZAccessControlError( deployer.address, await oracleReportSanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), @@ -1383,7 +1383,7 @@ describe("OracleReportSanityChecker.sol", () => { ); const tx = await oracleReportSanityChecker .connect(managersRoster.maxItemsPerExtraDataTransactionManagers[0]) - .setMaxAccountingExtraDataListItemsCount(newValue); + .setMaxItemsPerExtraDataTransaction(newValue); expect((await oracleReportSanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( newValue, ); From 6bca5769f099cc3dcd29fe0847e5b3b7219f1dba Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 17 Jul 2024 11:57:56 +0200 Subject: [PATCH 137/177] feat: rename role for maxItemsPerExtraDataTransaction parameter --- .../0.8.9/sanity_checks/OracleReportSanityChecker.sol | 6 +++--- test/0.8.9/oracle/accountingOracle.submitReport.test.ts | 4 ++-- .../oracle/accountingOracle.submitReportExtraData.test.ts | 7 ++----- .../sanityChecks/baseOracleReportSanityChecker.test.ts | 4 ++-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index e41954f84..9b648c3ef 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -157,8 +157,8 @@ contract OracleReportSanityChecker is AccessControlEnumerable { keccak256("SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE"); bytes32 public constant MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE = keccak256("MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE"); - bytes32 public constant MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE = - keccak256("MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE"); + bytes32 public constant MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE = + keccak256("MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE"); bytes32 public constant MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE = keccak256("MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE"); bytes32 public constant REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE = keccak256("REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE"); @@ -356,7 +356,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { /// @param _maxItemsPerExtraDataTransaction new maxItemsPerExtraDataTransaction value function setMaxItemsPerExtraDataTransaction(uint256 _maxItemsPerExtraDataTransaction) external - onlyRole(MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE) + onlyRole(MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE) { LimitsList memory limitsList = _limits.unpack(); limitsList.maxItemsPerExtraDataTransaction = _maxItemsPerExtraDataTransaction; diff --git a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts index 58565a960..69634320d 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReport.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReport.test.ts @@ -380,7 +380,7 @@ describe("AccountingOracle.sol:submitReport", () => { await sanityChecker .connect(admin) - .grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin.address); + .grantRole(await sanityChecker.MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE(), admin.address); await sanityChecker.connect(admin).setMaxItemsPerExtraDataTransaction(MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION); expect((await sanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( @@ -395,7 +395,7 @@ describe("AccountingOracle.sol:submitReport", () => { await sanityChecker .connect(admin) - .grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin.address); + .grantRole(await sanityChecker.MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE(), admin.address); await sanityChecker.connect(admin).setMaxItemsPerExtraDataTransaction(MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION); expect((await sanityChecker.getOracleReportLimits()).maxItemsPerExtraDataTransaction).to.be.equal( diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 44af5d256..fe5a90c0b 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -894,10 +894,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await consensus.advanceTimeToNextFrameStart(); const { reportFields, extraDataList } = await submitReportHash({ extraData }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); - await sanityChecker.grantRole( - await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), - admin.address, - ); + await sanityChecker.grantRole(await sanityChecker.MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE(), admin.address); await sanityChecker.setMaxItemsPerExtraDataTransaction(problematicItemsCount); const tx = await oracle.connect(member1).submitReportExtraDataList(extraDataList); await expect(tx).to.emit(oracle, "ExtraDataSubmitted").withArgs(reportFields.refSlot, anyValue, anyValue); @@ -917,7 +914,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await oracleMemberSubmitReportData(report); - await sanityChecker.grantRole(await sanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), admin); + await sanityChecker.grantRole(await sanityChecker.MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE(), admin); await sanityChecker.setMaxItemsPerExtraDataTransaction(maxItemsPerChunk - 1); await expect(oracleMemberSubmitExtraData(extraDataChunks[0])) .to.be.revertedWithCustomError(sanityChecker, "TooManyItemsPerExtraDataTransaction") diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index 297526dd9..ee27c6d35 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -1373,12 +1373,12 @@ describe("OracleReportSanityChecker.sol", () => { oracleReportSanityChecker.connect(deployer).setMaxItemsPerExtraDataTransaction(newValue), ).to.be.revertedWithOZAccessControlError( deployer.address, - await oracleReportSanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), + await oracleReportSanityChecker.MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE(), ); await oracleReportSanityChecker .connect(admin) .grantRole( - await oracleReportSanityChecker.MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE(), + await oracleReportSanityChecker.MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE(), managersRoster.maxItemsPerExtraDataTransactionManagers[0], ); const tx = await oracleReportSanityChecker From b222cb865d193f84417df1d308fda7a91205077c Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 17 Jul 2024 12:02:45 +0200 Subject: [PATCH 138/177] feat: rename setter for maxNodeOperatorsPerExtraDataItem limit --- contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol | 2 +- .../oracle/accountingOracle.submitReportExtraData.test.ts | 2 +- test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 9b648c3ef..cfb4967fc 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -365,7 +365,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { /// @notice Sets the new value for the max maxNodeOperatorsPerExtraDataItem /// @param _maxNodeOperatorsPerExtraDataItem new maxNodeOperatorsPerExtraDataItem value - function setMaxNodeOperatorsPerExtraDataItemCount(uint256 _maxNodeOperatorsPerExtraDataItem) + function setMaxNodeOperatorsPerExtraDataItem(uint256 _maxNodeOperatorsPerExtraDataItem) external onlyRole(MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE) { diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index fe5a90c0b..18ed1070f 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -878,7 +878,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { await sanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE(), admin.address, ); - await sanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(problematicItemsCount - 1); + await sanityChecker.setMaxNodeOperatorsPerExtraDataItem(problematicItemsCount - 1); await expect(oracle.connect(member1).submitReportExtraDataList(extraDataList)) .to.be.revertedWithCustomError(sanityChecker, "TooManyNodeOpsPerExtraDataItem") .withArgs(problematicItemIdx, problematicItemsCount); diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index ee27c6d35..1e610963f 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -1345,7 +1345,7 @@ describe("OracleReportSanityChecker.sol", () => { const newValue = 33; expect(newValue).to.not.equal(previousValue); await expect( - oracleReportSanityChecker.connect(deployer).setMaxNodeOperatorsPerExtraDataItemCount(newValue), + oracleReportSanityChecker.connect(deployer).setMaxNodeOperatorsPerExtraDataItem(newValue), ).to.be.revertedWithOZAccessControlError( deployer.address, await oracleReportSanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE(), @@ -1358,7 +1358,7 @@ describe("OracleReportSanityChecker.sol", () => { ); const tx = await oracleReportSanityChecker .connect(managersRoster.maxNodeOperatorsPerExtraDataItemManagers[0]) - .setMaxNodeOperatorsPerExtraDataItemCount(newValue); + .setMaxNodeOperatorsPerExtraDataItem(newValue); expect((await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItem).to.be.equal( newValue, ); From dfffad4c2f2cf0a0839e4e9d6c0c8486cff46dd8 Mon Sep 17 00:00:00 2001 From: maxim Date: Wed, 17 Jul 2024 12:08:15 +0200 Subject: [PATCH 139/177] feat: rename role for maxNodeOperatorsPerExtraDataItem limit --- contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol | 6 +++--- .../oracle/accountingOracle.submitReportExtraData.test.ts | 2 +- .../sanityChecks/baseOracleReportSanityChecker.test.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index cfb4967fc..1b3f940b8 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -159,8 +159,8 @@ contract OracleReportSanityChecker is AccessControlEnumerable { keccak256("MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE"); bytes32 public constant MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE = keccak256("MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION_ROLE"); - bytes32 public constant MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE = - keccak256("MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE"); + bytes32 public constant MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE = + keccak256("MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE"); bytes32 public constant REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE = keccak256("REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE"); bytes32 public constant MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE = keccak256("MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE"); @@ -367,7 +367,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { /// @param _maxNodeOperatorsPerExtraDataItem new maxNodeOperatorsPerExtraDataItem value function setMaxNodeOperatorsPerExtraDataItem(uint256 _maxNodeOperatorsPerExtraDataItem) external - onlyRole(MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE) + onlyRole(MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE) { LimitsList memory limitsList = _limits.unpack(); limitsList.maxNodeOperatorsPerExtraDataItem = _maxNodeOperatorsPerExtraDataItem; diff --git a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts index 18ed1070f..b147ac68e 100644 --- a/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts +++ b/test/0.8.9/oracle/accountingOracle.submitReportExtraData.test.ts @@ -875,7 +875,7 @@ describe("AccountingOracle.sol:submitReportExtraData", () => { const { reportFields, extraDataList } = await submitReportHash({ extraData }); await oracle.connect(member1).submitReportData(reportFields, oracleVersion); await sanityChecker.grantRole( - await sanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE(), + await sanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE(), admin.address, ); await sanityChecker.setMaxNodeOperatorsPerExtraDataItem(problematicItemsCount - 1); diff --git a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts index 1e610963f..770d04efa 100644 --- a/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts +++ b/test/0.8.9/sanityChecks/baseOracleReportSanityChecker.test.ts @@ -1348,12 +1348,12 @@ describe("OracleReportSanityChecker.sol", () => { oracleReportSanityChecker.connect(deployer).setMaxNodeOperatorsPerExtraDataItem(newValue), ).to.be.revertedWithOZAccessControlError( deployer.address, - await oracleReportSanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE(), + await oracleReportSanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE(), ); await oracleReportSanityChecker .connect(admin) .grantRole( - await oracleReportSanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE(), + await oracleReportSanityChecker.MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_ROLE(), managersRoster.maxNodeOperatorsPerExtraDataItemManagers[0], ); const tx = await oracleReportSanityChecker From 0e2c21e783c0e13cc0bf2f5ebdd687475abf17b1 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jul 2024 13:26:58 +0200 Subject: [PATCH 140/177] refactor: nor tests code review --- .../0.4.24/nor/nor.initialize.upgrade.test.ts | 15 +++++++- .../nor/nor.rewards.penalties.flow.test.ts | 38 +++++++++---------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 74dfb4daa..f8f007ae8 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -200,7 +200,9 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { .and.to.emit(nor, "LocatorContractSet") .withArgs(await locator.getAddress()) .and.to.emit(nor, "StakingModuleTypeSet") - .withArgs(moduleType); + .withArgs(moduleType) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); expect(await nor.getLocator()).to.equal(await locator.getAddress()); expect(await nor.getInitializationBlock()).to.equal(latestBlock + 1n); @@ -373,7 +375,12 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { }); it("sets correct contract version", async () => { - await nor.finalizeUpgrade_v3(); + await expect(nor.finalizeUpgrade_v3()) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV3) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); + expect(await nor.getContractVersion()).to.be.equal(3); expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); }); @@ -383,5 +390,9 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { expect(await nor.getContractVersion()).to.be.equal(3); await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); }); + + it("Migrates the contract storage from v2 to v3"); + it("Calling finalizeUpgrade_v3 on v1 version"); + it("Happy path test for update from v1: finalizeUpgrade_v2 -> finalizeUpgrade_v3"); }); }); diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index 3dd0b365e..15e633da1 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -281,6 +281,25 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { .to.emit(nor, "StuckPenaltyStateChanged") .withArgs(firstNodeOperatorId, 0n, 0n, timestamp + 86400n + 1n); }); + + it("Penalizes node operators with stuck penalty active", async () => { + await lido.connect(user).resume(); + await user.sendTransaction({ to: await lido.getAddress(), value: ether("1.0") }); + await lido.connect(user).transfer(await nor.getAddress(), await lido.balanceOf(user)); + + const nonce = await nor.getNonce(); + const idsPayload = prepIdsCountsPayload([1n], [2n]); + await expect(nor.connect(stakingRouter).updateStuckValidatorsCount(idsPayload.operatorIds, idsPayload.keysCounts)) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 1n) + .to.emit(nor, "StuckPenaltyStateChanged") + .withArgs(1n, 2n, 0n, 0n); + + await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); + await expect(nor.connect(stakingRouter).distributeReward()).to.emit(nor, "NodeOperatorPenalized"); + }); }); context("updateExitedValidatorsCount", () => { @@ -495,25 +514,6 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { expect(await lido.sharesOf(nor)).to.equal(1n); }); - - it("Penalizes node operators with stuck penalty active", async () => { - await lido.connect(user).resume(); - await user.sendTransaction({ to: await lido.getAddress(), value: ether("1.0") }); - await lido.connect(user).transfer(await nor.getAddress(), await lido.balanceOf(user)); - - const nonce = await nor.getNonce(); - const idsPayload = prepIdsCountsPayload([1n], [2n]); - await expect(nor.connect(stakingRouter).updateStuckValidatorsCount(idsPayload.operatorIds, idsPayload.keysCounts)) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(nonce + 1n) - .to.emit(nor, "StuckPenaltyStateChanged") - .withArgs(1n, 2n, 0n, 0n); - - await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); - await expect(nor.connect(stakingRouter).distributeReward()).to.emit(nor, "NodeOperatorPenalized"); - }); }); context("isOperatorPenalized", () => { From 0761feee9558e0debe469e4c9977e8d484f630f0 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jul 2024 14:10:31 +0200 Subject: [PATCH 141/177] feat: finalizeUpgrade_v3 test cases --- .../0.4.24/nor/nor.initialize.upgrade.test.ts | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index f8f007ae8..220e9b241 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -354,8 +354,10 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { }); context("finalizeUpgrade_v3()", () => { + let preInitState: string; beforeEach(async () => { locator = await deployLidoLocator({ lido: lido }); + preInitState = await Snapshot.take(); await nor.harness__initialize(2n); }); @@ -392,7 +394,50 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { }); it("Migrates the contract storage from v2 to v3"); - it("Calling finalizeUpgrade_v3 on v1 version"); - it("Happy path test for update from v1: finalizeUpgrade_v2 -> finalizeUpgrade_v3"); + + it("Calling finalizeUpgrade_v3 on v1 version", async () => { + preInitState = await Snapshot.refresh(preInitState); + await nor.harness__initialize(1n); + await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); + }); + + it("Happy path test for update from v1: finalizeUpgrade_v2 -> finalizeUpgrade_v3", async () => { + preInitState = await Snapshot.refresh(preInitState); + + await nor.harness__initialize(0n); + + const latestBlock = BigInt(await time.latestBlock()); + const burnerAddress = await locator.burner(); + + await expect(nor.finalizeUpgrade_v2(locator, moduleType, 86400n)) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV2) + .and.to.emit(nor, "StuckPenaltyDelayChanged") + .withArgs(86400n) + .and.to.emit(nor, "LocatorContractSet") + .withArgs(await locator.getAddress()) + .and.to.emit(nor, "StakingModuleTypeSet") + .withArgs(moduleType); + + expect(await nor.getLocator()).to.equal(await locator.getAddress()); + expect(await nor.getInitializationBlock()).to.equal(latestBlock); + expect(await lido.allowance(await nor.getAddress(), burnerAddress)).to.equal(MaxUint256); + expect(await nor.getStuckPenaltyDelay()).to.equal(86400n); + expect(await nor.getContractVersion()).to.equal(contractVersionV2); + expect(await nor.getType()).to.equal(moduleType); + + await expect(nor.finalizeUpgrade_v3()) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV3) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); + + expect(await nor.getLocator()).to.equal(await locator.getAddress()); + expect(await nor.getInitializationBlock()).to.equal(latestBlock); + expect(await lido.allowance(await nor.getAddress(), burnerAddress)).to.equal(MaxUint256); + expect(await nor.getStuckPenaltyDelay()).to.equal(86400n); + expect(await nor.getContractVersion()).to.equal(contractVersionV3); + expect(await nor.getType()).to.equal(moduleType); + }); }); }); From a87fbe10b465a50826142ab75652885447c10e4c Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jul 2024 14:12:21 +0200 Subject: [PATCH 142/177] feat: v2 to v3 upgrade test --- .../0.4.24/nor/nor.initialize.upgrade.test.ts | 87 ++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 220e9b241..2f137a7df 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -393,11 +393,94 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); }); - it("Migrates the contract storage from v2 to v3"); + it("Migrates the contract storage from v2 to v3", async () => { + preInitState = await Snapshot.refresh(preInitState); + + await nor.harness__initialize(0n); + + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( + thirdNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[fourthNodeOperatorId])).to.be.equal( + fourthNodeOperatorId, + ); + + await nor.harness__unsafeResetModuleSummary(); + const resetSummary = await nor.getStakingModuleSummary(); + expect(resetSummary.totalExitedValidators).to.be.equal(0n); + expect(resetSummary.totalDepositedValidators).to.be.equal(0n); + expect(resetSummary.depositableValidatorsCount).to.be.equal(0n); + + await nor.harness__unsafeSetVettedKeys( + firstNodeOperatorId, + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount - 1n, + ); + await nor.harness__unsafeSetVettedKeys( + secondNodeOperatorId, + NODE_OPERATORS[secondNodeOperatorId].totalSigningKeysCount + 1n, + ); + await nor.harness__unsafeSetVettedKeys( + thirdNodeOperatorId, + NODE_OPERATORS[thirdNodeOperatorId].totalSigningKeysCount, + ); + + const checkStorage = async () => { + const summary = await nor.getStakingModuleSummary(); + expect(summary.totalExitedValidators).to.be.equal(1n + 0n + 0n + 1n); + expect(summary.totalDepositedValidators).to.be.equal(5n + 7n + 0n + 2n); + expect(summary.depositableValidatorsCount).to.be.equal(0n + 8n + 0n + 0n); + + const firstNoInfo = await nor.getNodeOperator(firstNodeOperatorId, true); + expect(firstNoInfo.totalVettedValidators).to.be.equal( + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount, + ); + + const secondNoInfo = await nor.getNodeOperator(secondNodeOperatorId, true); + expect(secondNoInfo.totalVettedValidators).to.be.equal( + NODE_OPERATORS[secondNodeOperatorId].totalSigningKeysCount, + ); + + const thirdNoInfo = await nor.getNodeOperator(thirdNodeOperatorId, true); + expect(thirdNoInfo.totalVettedValidators).to.be.equal( + NODE_OPERATORS[thirdNodeOperatorId].depositedSigningKeysCount, + ); + + const fourthNoInfo = await nor.getNodeOperator(fourthNodeOperatorId, true); + expect(fourthNoInfo.totalVettedValidators).to.be.equal( + NODE_OPERATORS[fourthNodeOperatorId].vettedSigningKeysCount, + ); + }; + + await expect(nor.finalizeUpgrade_v2(locator, moduleType, 86400n)) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV2) + .and.to.emit(nor, "StuckPenaltyDelayChanged") + .withArgs(86400n) + .and.to.emit(nor, "LocatorContractSet") + .withArgs(await locator.getAddress()) + .and.to.emit(nor, "StakingModuleTypeSet") + .withArgs(moduleType); + + await checkStorage(); + + await expect(nor.finalizeUpgrade_v3()) + .to.emit(nor, "ContractVersionSet") + .withArgs(contractVersionV3) + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.Distributed); + + await checkStorage(); + }); it("Calling finalizeUpgrade_v3 on v1 version", async () => { preInitState = await Snapshot.refresh(preInitState); - await nor.harness__initialize(1n); + await nor.harness__initialize(0n); await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); }); From 78025983a1edf72a779ffb2a5dba5869830ca8be Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jul 2024 15:15:13 +0200 Subject: [PATCH 143/177] feat: updateTargetValidatorsLimits nonce and targetValidatorsCount changing tests --- test/0.4.24/nor/nor.limits.test.ts | 56 +++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index b0fa82fec..5f91b4a5b 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -204,10 +204,6 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { ); }); - it("nonce changing"); - it("targetValidatorsCount changing"); - it("module stats updating (summarySigningKeysStats)"); - it('reverts with "APP_AUTH_FAILED" error when called by sender without STAKING_ROUTER_ROLE', async () => { expect(await acl["hasPermission(address,address,bytes32)"](stranger, nor, await nor.STAKING_ROUTER_ROLE())).to.be .false; @@ -301,6 +297,58 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { expect(noSummary.targetValidatorsCount).to.equal(0n); }); + it("nonce changing", async () => { + targetLimit = 10n; + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(1n); + + await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(secondNodeOperatorId, 0n, 0n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(2n); + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(3n); + }); + + it("targetValidatorsCount changing", async () => { + targetLimit = 10n; + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + }); + + it("module stats updating (summarySigningKeysStats)"); + it("updates node operator target limit mode correctly using updateTargetLimitsDeprecated", async () => { expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) .to.be.true; From 6a01c8eee699db3fa77ec95288b3ecd3b37183fb Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jul 2024 20:48:04 +0200 Subject: [PATCH 144/177] refactor: move unsafeUpdateValidatorsCount to aux test --- test/0.4.24/nor/nor.aux.test.ts | 59 ++++++++++++++++++- test/0.4.24/nor/nor.limits.test.ts | 94 ++++++++++-------------------- 2 files changed, 90 insertions(+), 63 deletions(-) diff --git a/test/0.4.24/nor/nor.aux.test.ts b/test/0.4.24/nor/nor.aux.test.ts index 845170290..a03a421a4 100644 --- a/test/0.4.24/nor/nor.aux.test.ts +++ b/test/0.4.24/nor/nor.aux.test.ts @@ -29,6 +29,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { let nodeOperatorsManager: HardhatEthersSigner; let signingKeysManager: HardhatEthersSigner; let stakingRouter: HardhatEthersSigner; + let stranger: HardhatEthersSigner; let lido: Lido; let dao: Kernel; let acl: ACL; @@ -85,7 +86,7 @@ describe("NodeOperatorsRegistry:auxiliary", () => { const contractVersionV3 = 3n; before(async () => { - [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager] = + [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager, stranger] = await ethers.getSigners(); ({ lido, dao, acl } = await deployLidoDao({ @@ -144,6 +145,62 @@ describe("NodeOperatorsRegistry:auxiliary", () => { afterEach(async () => await Snapshot.restore(originalState)); + context("unsafeUpdateValidatorsCount", () => { + beforeEach(async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + }); + + it("Reverts if no such an operator exists", async () => { + await expect(nor.unsafeUpdateValidatorsCount(3n, 0n, 0n)).to.be.revertedWith("OUT_OF_RANGE"); + }); + + it("Reverts if has not STAKING_ROUTER_ROLE assigned", async () => { + await expect(nor.connect(stranger).unsafeUpdateValidatorsCount(firstNodeOperatorId, 0n, 0n)).to.be.revertedWith( + "APP_AUTH_FAILED", + ); + }); + + it("Can change stuck and exited keys arbitrary (even decreasing exited)", async () => { + const nonce = await nor.getNonce(); + + const beforeNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(beforeNOSummary.stuckValidatorsCount).to.be.equal(0n); + expect(beforeNOSummary.totalExitedValidators).to.be.equal(1n); + + await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 3n, 2n)) + .to.emit(nor, "StuckPenaltyStateChanged") + .withArgs(firstNodeOperatorId, 2n, 0n, 0n) // doesn't affect stuck penalty deadline + .to.emit(nor, "ExitedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, 3n) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 1n); + + const middleNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(middleNOSummary.stuckValidatorsCount).to.be.equal(2n); + expect(middleNOSummary.totalExitedValidators).to.be.equal(3n); + + await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 1n, 2n)) + .to.emit(nor, "ExitedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, 1n) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 2n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 2n) + .to.not.emit(nor, "StuckPenaltyStateChanged"); + + const lastNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(lastNOSummary.stuckValidatorsCount).to.be.equal(2n); + expect(lastNOSummary.totalExitedValidators).to.be.equal(1n); + }); + }); + context("onWithdrawalCredentialsChanged", () => { it("Reverts if has no STAKING_ROUTER_ROLE assigned", async () => { await expect(nor.onWithdrawalCredentialsChanged()).to.be.revertedWith("APP_AUTH_FAILED"); diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index 5f91b4a5b..130d09809 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -136,62 +136,6 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { afterEach(async () => await Snapshot.restore(originalState)); - context("unsafeUpdateValidatorsCount", () => { - beforeEach(async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( - firstNodeOperatorId, - ); - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( - secondNodeOperatorId, - ); - }); - - it("Reverts if no such an operator exists", async () => { - await expect(nor.unsafeUpdateValidatorsCount(3n, 0n, 0n)).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it("Reverts if has not STAKING_ROUTER_ROLE assigned", async () => { - await expect(nor.connect(stranger).unsafeUpdateValidatorsCount(firstNodeOperatorId, 0n, 0n)).to.be.revertedWith( - "APP_AUTH_FAILED", - ); - }); - - it("Can change stuck and exited keys arbitrary (even decreasing exited)", async () => { - const nonce = await nor.getNonce(); - - const beforeNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(beforeNOSummary.stuckValidatorsCount).to.be.equal(0n); - expect(beforeNOSummary.totalExitedValidators).to.be.equal(1n); - - await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 3n, 2n)) - .to.emit(nor, "StuckPenaltyStateChanged") - .withArgs(firstNodeOperatorId, 2n, 0n, 0n) // doesn't affect stuck penalty deadline - .to.emit(nor, "ExitedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, 3n) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 1n) - .to.emit(nor, "NonceChanged") - .withArgs(nonce + 1n); - - const middleNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(middleNOSummary.stuckValidatorsCount).to.be.equal(2n); - expect(middleNOSummary.totalExitedValidators).to.be.equal(3n); - - await expect(nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperatorId, 1n, 2n)) - .to.emit(nor, "ExitedSigningKeysCountChanged") - .withArgs(firstNodeOperatorId, 1n) - .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 2n) - .to.emit(nor, "NonceChanged") - .withArgs(nonce + 2n) - .to.not.emit(nor, "StuckPenaltyStateChanged"); - - const lastNOSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(lastNOSummary.stuckValidatorsCount).to.be.equal(2n); - expect(lastNOSummary.totalExitedValidators).to.be.equal(1n); - }); - }); - context("updateTargetValidatorsLimits", () => { let targetLimit = 0n; @@ -319,7 +263,7 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(3n); }); - it("targetValidatorsCount changing", async () => { + it("target validator limit changing", async () => { targetLimit = 10n; await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) @@ -329,14 +273,26 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.be.equal(1n); expect(noSummary.targetValidatorsCount).to.equal(10n); - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, 0n)) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 0n); + .withArgs(firstNodeOperatorId, 0n, 1n); noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetLimitMode).to.equal(1n); expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(0n); await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") @@ -345,9 +301,23 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(1n); expect(noSummary.targetValidatorsCount).to.equal(10n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); }); - it("module stats updating (summarySigningKeysStats)"); + // describe("all target limits: 0,1,2"); + + it("module stats updating (NodeOperatorSummary)", async () => { + targetLimit = 10n; + + await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + }); it("updates node operator target limit mode correctly using updateTargetLimitsDeprecated", async () => { expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) From 9e30c4a527db7d7721dbfa4a19012c1353b4aedd Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jul 2024 22:35:22 +0200 Subject: [PATCH 145/177] feat: use both abi versions in limit tests --- test/0.4.24/nor/nor.limits.test.ts | 372 ++++++++++++++--------------- 1 file changed, 179 insertions(+), 193 deletions(-) diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index 130d09809..8b4605441 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -21,8 +21,13 @@ import { addNodeOperator, certainAddress, NodeOperatorConfig, RewardDistribution import { addAragonApp, deployLidoDao } from "test/deploy"; import { Snapshot } from "test/suite"; -const updateTargetLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)"; -const updateTargetLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)"; +const updateTargetLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)" as const; +const updateTargetLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)" as const; + +enum UpdateTargetLimitsMethods { + UpdateTargetValidatorsLimits = "updateTargetValidatorsLimits(uint256,uint256,uint256)", + UpdateTargetValidatorsLimitsDeprecated = "updateTargetValidatorsLimits(uint256,bool,uint256)", +} describe("NodeOperatorsRegistry:validatorsLimits", () => { let deployer: HardhatEthersSigner; @@ -130,16 +135,26 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { .withArgs(RewardDistributionState.Distributed); nor = nor.connect(user); + originalState = await Snapshot.take(); }); - beforeEach(async () => (originalState = await Snapshot.take())); - - afterEach(async () => await Snapshot.restore(originalState)); - - context("updateTargetValidatorsLimits", () => { - let targetLimit = 0n; - - beforeEach(async () => { + afterEach(async () => (originalState = await Snapshot.refresh(originalState))); + + const updateLimitCall = ( + updateTargetLimitsMethod: UpdateTargetLimitsMethods, + nodeOperatorId: number, + isTargetLimitActiveOrMode: bigint, + targetLimit: bigint, + ) => { + const id = BigInt(nodeOperatorId); + if (updateTargetLimitsMethod === UpdateTargetLimitsMethods.UpdateTargetValidatorsLimitsDeprecated) + return nor.connect(stakingRouter)[updateTargetLimitsMethod](id, Boolean(isTargetLimitActiveOrMode), targetLimit); + return nor.connect(stakingRouter)[updateTargetLimitsMethod](id, isTargetLimitActiveOrMode, targetLimit); + }; + + context(`updateTargetValidatorsLimits auth`, () => { + const targetLimit = 0n; + before(async () => { expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( firstNodeOperatorId, ); @@ -158,198 +173,169 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { await expect(nor[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)).to.be.revertedWith("APP_AUTH_FAILED"); }); + }); - it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { - const targetLimitWrong = BigInt("0x10000000000000000"); - - await expect( - nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimitWrong), - ).to.be.revertedWith("OUT_OF_RANGE"); - - await expect( - nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimitWrong), - ).to.be.revertedWith("OUT_OF_RANGE"); - }); - - it("updates node operator target limit if called by sender with STAKING_ROUTER_ROLE", async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) - .to.be.true; - - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - const keysStatTotal = await nor.getStakingModuleSummary(); - const expectedExitedValidatorsCount = - NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount + - NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; - expect(keysStatTotal.totalExitedValidators).to.equal(expectedExitedValidatorsCount); - - const expectedDepositedValidatorsCount = - NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount + - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; - expect(keysStatTotal.totalDepositedValidators).to.equal(expectedDepositedValidatorsCount); - - const firstNodeOperatorDepositableValidators = - NODE_OPERATORS[firstNodeOperatorId].vettedSigningKeysCount - - NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount; - - const secondNodeOperatorDepositableValidators = - NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - - NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; - - const expectedDepositableValidatorsCount = - targetLimit < firstNodeOperatorDepositableValidators - ? targetLimit - : firstNodeOperatorDepositableValidators + secondNodeOperatorDepositableValidators; - - expect(keysStatTotal.depositableValidatorsCount).to.equal(expectedDepositableValidatorsCount); - }); - - it("updates node operator target limit mode correctly", async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) - .to.be.true; - - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(1n); - - await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, 0n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, 0n, 0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(0n); - - // reset limit - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - expect(noSummary.targetValidatorsCount).to.equal(0n); - }); - - it("nonce changing", async () => { - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); + const runTests = (updateTargetLimitsMethod: UpdateTargetLimitsMethods) => { + context(`updateTargetValidatorsLimits:${updateTargetLimitsMethod}`, () => { + let targetLimit = 0n; - await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(1n); + beforeEach(async () => { + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[firstNodeOperatorId])).to.be.equal( + firstNodeOperatorId, + ); + expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[secondNodeOperatorId])).to.be.equal( + secondNodeOperatorId, + ); + }); - await expect(nor.connect(stakingRouter)[updateTargetLimits](secondNodeOperatorId, 0n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, 0n, 0n); + it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { + const targetLimitWrong = BigInt("0x10000000000000000"); - await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(2n); + await expect( + updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimitWrong), + ).to.be.revertedWith("OUT_OF_RANGE"); + }); - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 0n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 0n); + it("updates node operator target limit if called by sender with STAKING_ROUTER_ROLE", async () => { + expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) + .to.be.true; - await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(3n); - }); - - it("target validator limit changing", async () => { - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(1n); - expect(noSummary.targetValidatorsCount).to.equal(10n); - expect(noSummary.stuckValidatorsCount).to.be.equal(0n); - expect(noSummary.refundedValidatorsCount).to.be.equal(0n); - expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); - expect(noSummary.totalExitedValidators).to.be.equal(1n); - expect(noSummary.totalDepositedValidators).to.be.equal(5n); - expect(noSummary.depositableValidatorsCount).to.be.equal(1n); - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, 0n)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 1n); - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(1n); - expect(noSummary.targetValidatorsCount).to.equal(0n); - expect(noSummary.stuckValidatorsCount).to.be.equal(0n); - expect(noSummary.refundedValidatorsCount).to.be.equal(0n); - expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); - expect(noSummary.totalExitedValidators).to.be.equal(1n); - expect(noSummary.totalDepositedValidators).to.be.equal(5n); - expect(noSummary.depositableValidatorsCount).to.be.equal(0n); - - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(1n); - expect(noSummary.targetValidatorsCount).to.equal(10n); - expect(noSummary.stuckValidatorsCount).to.be.equal(0n); - expect(noSummary.refundedValidatorsCount).to.be.equal(0n); - expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); - expect(noSummary.totalExitedValidators).to.be.equal(1n); - expect(noSummary.totalDepositedValidators).to.be.equal(5n); - expect(noSummary.depositableValidatorsCount).to.be.equal(1n); - }); + targetLimit = 10n; - // describe("all target limits: 0,1,2"); + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); - it("module stats updating (NodeOperatorSummary)", async () => { - targetLimit = 10n; + const keysStatTotal = await nor.getStakingModuleSummary(); + const expectedExitedValidatorsCount = + NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount + + NODE_OPERATORS[secondNodeOperatorId].exitedSigningKeysCount; + expect(keysStatTotal.totalExitedValidators).to.equal(expectedExitedValidatorsCount); - await expect(nor.connect(stakingRouter)[updateTargetLimits](firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); + const expectedDepositedValidatorsCount = + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount + + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; + expect(keysStatTotal.totalDepositedValidators).to.equal(expectedDepositedValidatorsCount); + + const firstNodeOperatorDepositableValidators = + NODE_OPERATORS[firstNodeOperatorId].vettedSigningKeysCount - + NODE_OPERATORS[firstNodeOperatorId].depositedSigningKeysCount; + + const secondNodeOperatorDepositableValidators = + NODE_OPERATORS[secondNodeOperatorId].vettedSigningKeysCount - + NODE_OPERATORS[secondNodeOperatorId].depositedSigningKeysCount; + + const expectedDepositableValidatorsCount = + targetLimit < firstNodeOperatorDepositableValidators + ? targetLimit + : firstNodeOperatorDepositableValidators + secondNodeOperatorDepositableValidators; + + expect(keysStatTotal.depositableValidatorsCount).to.equal(expectedDepositableValidatorsCount); + }); + + it("updates node operator target limit mode correctly", async () => { + expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) + .to.be.true; + + targetLimit = 10n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, secondNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(secondNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(0n); + + // reset limit + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + + noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + }); + + it("nonce changing", async () => { + targetLimit = 10n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, secondNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(secondNodeOperatorId, 0n, 0n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(2n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(3n); + }); + + it("target validator limit changing", async () => { + targetLimit = 10n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, 0n)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 1n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(0n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + }); }); + }; - it("updates node operator target limit mode correctly using updateTargetLimitsDeprecated", async () => { - expect(await acl["hasPermission(address,address,bytes32)"](stakingRouter, nor, await nor.STAKING_ROUTER_ROLE())) - .to.be.true; - - targetLimit = 10n; - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, true, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(1n); - - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](secondNodeOperatorId, false, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, 0n, 0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(0n); - - // reset limit - await expect(nor.connect(stakingRouter)[updateTargetLimitsDeprecated](firstNodeOperatorId, false, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 0n); // expect limit set to 0 - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(0n); - expect(noSummary.targetValidatorsCount).to.equal(0n); - }); - }); + runTests(UpdateTargetLimitsMethods.UpdateTargetValidatorsLimits); + runTests(UpdateTargetLimitsMethods.UpdateTargetValidatorsLimitsDeprecated); }); From bcdf4c627abb252043dc1e45ccadeba7180ea338 Mon Sep 17 00:00:00 2001 From: Eddort Date: Wed, 17 Jul 2024 22:58:26 +0200 Subject: [PATCH 146/177] feat: nor limits no summary invariants --- test/0.4.24/nor/nor.limits.test.ts | 232 +++++++++++++++++++++++------ 1 file changed, 188 insertions(+), 44 deletions(-) diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index 8b4605441..c9d77abe1 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -288,50 +288,194 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(3n); }); - it("target validator limit changing", async () => { - targetLimit = 10n; - - await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(1n); - expect(noSummary.targetValidatorsCount).to.equal(10n); - expect(noSummary.stuckValidatorsCount).to.be.equal(0n); - expect(noSummary.refundedValidatorsCount).to.be.equal(0n); - expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); - expect(noSummary.totalExitedValidators).to.be.equal(1n); - expect(noSummary.totalDepositedValidators).to.be.equal(5n); - expect(noSummary.depositableValidatorsCount).to.be.equal(1n); - - await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, 0n)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, 0n, 1n); - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(1n); - expect(noSummary.targetValidatorsCount).to.equal(0n); - expect(noSummary.stuckValidatorsCount).to.be.equal(0n); - expect(noSummary.refundedValidatorsCount).to.be.equal(0n); - expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); - expect(noSummary.totalExitedValidators).to.be.equal(1n); - expect(noSummary.totalDepositedValidators).to.be.equal(5n); - expect(noSummary.depositableValidatorsCount).to.be.equal(0n); - - await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) - .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(firstNodeOperatorId, targetLimit, 1n); - - noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); - expect(noSummary.targetLimitMode).to.equal(1n); - expect(noSummary.targetValidatorsCount).to.equal(10n); - expect(noSummary.stuckValidatorsCount).to.be.equal(0n); - expect(noSummary.refundedValidatorsCount).to.be.equal(0n); - expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); - expect(noSummary.totalExitedValidators).to.be.equal(1n); - expect(noSummary.totalDepositedValidators).to.be.equal(5n); - expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + context("the change in the target limit affects in node operator summary", () => { + it("targetLimitMode = 1; targetLimit = 10", async () => { + const targetLimitMode = 1n; + targetLimit = 10n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, 0n)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 1n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(0n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + }); + + it("targetLimitMode = 0; targetLimit = 10", async () => { + const targetLimitMode = 0n; + targetLimit = 10n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, targetLimitMode); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, 0n)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + }); + + it("targetLimitMode = 1; targetLimit = 5", async () => { + const targetLimitMode = 1n; + targetLimit = 5n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(5n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, 0n)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 1n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(0n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(1n); + expect(noSummary.targetValidatorsCount).to.equal(5n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + }); + + it("targetLimitMode = 0; targetLimit = 5", async () => { + const targetLimitMode = 0n; + targetLimit = 5n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, targetLimitMode); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, 0n)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, targetLimitMode, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + }); }); }); }; From 7578a9bd50d3ce41884b93b09dd606bd6870ea5a Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 18 Jul 2024 13:33:30 +0200 Subject: [PATCH 147/177] feat: distributeReward tests --- .../Lido__DistributeRewardMock.sol | 97 +++++++ .../Burner__MockForDistributeReward.sol | 26 ++ test/0.4.24/nor/nor.management.flow.test.ts | 237 +++++++++++++++++- .../nor/nor.rewards.penalties.flow.test.ts | 23 +- test/deploy/dao.ts | 27 ++ 5 files changed, 381 insertions(+), 29 deletions(-) create mode 100644 contracts/0.4.24/test_helpers/Lido__DistributeRewardMock.sol create mode 100644 test/0.4.24/contracts/Burner__MockForDistributeReward.sol diff --git a/contracts/0.4.24/test_helpers/Lido__DistributeRewardMock.sol b/contracts/0.4.24/test_helpers/Lido__DistributeRewardMock.sol new file mode 100644 index 000000000..3d81ec7e1 --- /dev/null +++ b/contracts/0.4.24/test_helpers/Lido__DistributeRewardMock.sol @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.4.24; + +import "../Lido.sol"; +import "./VaultMock.sol"; +// import "./StETHMock.sol"; +// distributeReward +/** + * @dev Only for testing purposes! Lido version with some functions exposed. + */ +contract Lido__DistributeRewardMock is Lido { + bytes32 internal constant ALLOW_TOKEN_POSITION = keccak256("lido.Lido.allowToken"); + uint256 internal constant UNLIMITED_TOKEN_REBASE = uint256(-1); + uint256 private totalPooledEther; + + function initialize( + address _lidoLocator, + address _eip712StETH + ) + public + payable + { + super.initialize( + _lidoLocator, + _eip712StETH + ); + + _resume(); + // _bootstrapInitialHolder + uint256 balance = address(this).balance; + assert(balance != 0); + + // address(0xdead) is a holder for initial shares + setTotalPooledEther(balance); + _mintInitialShares(balance); + setAllowRecoverability(true); + } + + /** + * @dev For use in tests to make protocol operational after deployment + */ + function resumeProtocolAndStaking() public { + _resume(); + _resumeStaking(); + } + + /** + * @dev Only for testing recovery vault + */ + function makeUnaccountedEther() public payable {} + + function setVersion(uint256 _version) external { + CONTRACT_VERSION_POSITION.setStorageUint256(_version); + } + + function allowRecoverability(address /*token*/) public view returns (bool) { + return getAllowRecoverability(); + } + + function setAllowRecoverability(bool allow) public { + ALLOW_TOKEN_POSITION.setStorageBool(allow); + } + + function getAllowRecoverability() public view returns (bool) { + return ALLOW_TOKEN_POSITION.getStorageBool(); + } + + function resetEip712StETH() external { + EIP712_STETH_POSITION.setStorageAddress(0); + } + + function setTotalPooledEther(uint256 _totalPooledEther) public { + totalPooledEther = _totalPooledEther; + } + + function _getTotalPooledEther() internal view returns (uint256) { + return totalPooledEther; + } + + function mintShares(address _to, uint256 _sharesAmount) public returns (uint256 newTotalShares) { + newTotalShares = _mintShares(_to, _sharesAmount); + _emitTransferAfterMintingShares(_to, _sharesAmount); + } + + function mintSteth(address _to) public payable { + uint256 sharesAmount = getSharesByPooledEth(msg.value); + mintShares(_to, sharesAmount); + setTotalPooledEther(_getTotalPooledEther().add(msg.value)); + } + + function burnShares(address _account, uint256 _sharesAmount) public returns (uint256 newTotalShares) { + return _burnShares(_account, _sharesAmount); + } + +} diff --git a/test/0.4.24/contracts/Burner__MockForDistributeReward.sol b/test/0.4.24/contracts/Burner__MockForDistributeReward.sol new file mode 100644 index 000000000..0d7bcc8b2 --- /dev/null +++ b/test/0.4.24/contracts/Burner__MockForDistributeReward.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +// for testing purposes only +pragma solidity 0.4.24; + +contract Burner__MockForDistributeReward { + event StETHBurnRequested( + bool indexed isCover, + address indexed requestedBy, + uint256 amountOfStETH, + uint256 amountOfShares + ); + + event Mock__CommitSharesToBurnWasCalled(); + + function requestBurnShares(address _from, uint256 _sharesAmountToBurn) external { + // imitating share to steth rate 1:2 + uint256 _stETHAmount = _sharesAmountToBurn * 2; + emit StETHBurnRequested(false, msg.sender, _stETHAmount, _sharesAmountToBurn); + } + + function commitSharesToBurn(uint256 _sharesToBurn) external { + _sharesToBurn; + + emit Mock__CommitSharesToBurnWasCalled(); + } +} diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index 7cec726f9..32c3037a7 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -6,8 +6,9 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { ACL, + Burner__MockForLidoHandleOracleReport, Kernel, - Lido, + Lido__DistributeRewardMock, LidoLocator, LidoLocator__factory, MinFirstAllocationStrategy__factory, @@ -16,9 +17,17 @@ import { } from "typechain-types"; import { NodeOperatorsRegistryLibraryAddresses } from "typechain-types/factories/contracts/0.4.24/nos/NodeOperatorsRegistry.sol/NodeOperatorsRegistry__factory"; -import { addNodeOperator, certainAddress, NodeOperatorConfig, randomAddress, RewardDistributionState } from "lib"; - -import { addAragonApp, deployLidoDao } from "test/deploy"; +import { + addNodeOperator, + advanceChainTime, + certainAddress, + ether, + NodeOperatorConfig, + randomAddress, + RewardDistributionState, +} from "lib"; + +import { addAragonApp, deployLidoDaoForNor } from "test/deploy"; import { Snapshot } from "test/suite"; describe("NodeOperatorsRegistry:management", () => { @@ -29,7 +38,10 @@ describe("NodeOperatorsRegistry:management", () => { let nodeOperatorsManager: HardhatEthersSigner; let signingKeysManager: HardhatEthersSigner; let stakingRouter: HardhatEthersSigner; - let lido: Lido; + let user1: HardhatEthersSigner; + let user2: HardhatEthersSigner; + let user3: HardhatEthersSigner; + let lido: Lido__DistributeRewardMock; let dao: Kernel; let acl: ACL; let locator: LidoLocator; @@ -39,6 +51,8 @@ describe("NodeOperatorsRegistry:management", () => { let originalState: string; + let burner: Burner__MockForLidoHandleOracleReport; + const firstNodeOperatorId = 0; const secondNodeOperatorId = 1; const thirdNodeOperatorId = 2; @@ -85,17 +99,17 @@ describe("NodeOperatorsRegistry:management", () => { const contractVersion = 2n; before(async () => { - [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager] = + [deployer, user, stakingRouter, nodeOperatorsManager, signingKeysManager, limitsManager, user1, user2, user3] = await ethers.getSigners(); - ({ lido, dao, acl } = await deployLidoDao({ + ({ lido, dao, acl, burner } = await deployLidoDaoForNor({ rootAccount: deployer, initialized: true, locatorConfig: { stakingRouter, }, })); - + // await burner.grantRole(web3.utils.keccak256(`REQUEST_BURN_SHARES_ROLE`), app.address, { from: voting }) const allocLib = await new MinFirstAllocationStrategy__factory(deployer).deploy(); const allocLibAddr: NodeOperatorsRegistryLibraryAddresses = { ["__contracts/common/lib/MinFirstAllocat__"]: await allocLib.getAddress(), @@ -617,9 +631,18 @@ describe("NodeOperatorsRegistry:management", () => { }); context("distributeReward()", () => { - it('distribute reward when module not in "ReadyForDistribution" status', async () => { + const firstNodeOperator = 0; + const secondNodeOperator = 1; + + beforeEach(async () => { + await nor.harness__addNodeOperator("0", user1, 3, 3, 3, 0); + await nor.harness__addNodeOperator("1", user2, 7, 7, 7, 0); + await nor.harness__addNodeOperator("2", user3, 0, 0, 0, 0); + await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); + }); + it('distribute reward when module not in "ReadyForDistribution" status', async () => { expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); await expect(nor.distributeReward()) .to.emit(nor, "RewardDistributionStateChanged") @@ -634,6 +657,202 @@ describe("NodeOperatorsRegistry:management", () => { await nor.harness__setRewardDistributionState(RewardDistributionState.Distributed); await expect(nor.distributeReward()).to.be.revertedWith("DISTRIBUTION_NOT_READY"); }); + + it("doesn't distributes rewards if no shares to distribute", async () => { + const sharesCount = await lido.sharesOf(await nor.getAddress()); + expect(sharesCount).to.be.eq(0); + + const recipientsSharesBefore = await Promise.all([ + lido.sharesOf(user1), + lido.sharesOf(user2), + lido.sharesOf(user3), + ]); + + // calls distributeRewards() inside + await nor.distributeReward(); + + const recipientsSharesAfter = await Promise.all([ + lido.sharesOf(user1), + lido.sharesOf(user2), + lido.sharesOf(user3), + ]); + expect(recipientsSharesBefore).to.have.length(recipientsSharesAfter.length); + for (let i = 0; i < recipientsSharesBefore.length; ++i) { + expect(recipientsSharesBefore[i]).to.equal(recipientsSharesAfter[i]); + } + }); + + it("must distribute rewards to operators", async () => { + await lido.setTotalPooledEther(ether("100")); + await lido.mintShares(await nor.getAddress(), ether("10")); + + // calls distributeRewards() inside + await nor.distributeReward(); + expect(await lido.sharesOf(user1)).to.be.equal(ether("3")); + expect(await lido.sharesOf(user2)).to.be.equal(ether("7")); + expect(await lido.sharesOf(user3)).to.be.equal(0); + }); + + it("emits RewardsDistributed with correct params on reward distribution", async () => { + await lido.setTotalPooledEther(ether("100")); + await lido.mintShares(await nor.getAddress(), ether("10")); + + // calls distributeRewards() inside + await expect(nor.distributeReward()) + .to.emit(nor, "RewardsDistributed") + .withArgs(await user1.getAddress(), ether("3")) + .and.to.emit(nor, "RewardsDistributed") + .withArgs(await user2.getAddress(), ether("7")); + }); + + it("distribute with stopped works", async () => { + const totalRewardShares = ether("10"); + + await lido.setTotalPooledEther(ether("100")); + await lido.mintShares(await nor.getAddress(), totalRewardShares); + + // before + // operatorId | Total | Deposited | Exited | Active (deposited-exited) + // 0 3 3 0 3 + // 1 7 7 0 7 + // 2 0 0 0 0 + // ----------------------------------------------------------------------------- + // total 3 10 10 0 10 + // + // perValidatorShare 10*10^18 / 10 = 10^18 + + // update [operator, exited, stuck] + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperator, 1, 0); + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0); + + // after + // operatorId | Total | Deposited | Exited | Stuck | Active (deposited-exited) + // 0 3 3 1 0 2 + // 1 7 7 1 0 6 + // 2 0 0 0 0 0 + // ----------------------------------------------------------------------------- + // total 3 10 10 2 0 8 + // + // perValidatorShare 10*10^18 / 8 = 1250000000000000000 == 1.25 * 10^18 + + await expect(nor.distributeReward()) + .to.emit(nor, "RewardsDistributed") + .withArgs(await user1.getAddress(), ether(2 * 1.25 + "")) + .and.to.emit(nor, "RewardsDistributed") + .withArgs(await user2.getAddress(), ether(6 * 1.25 + "")); + }); + + it("penalized keys with stopped and stuck works", async () => { + const totalRewardShares = ether("10"); + + await lido.setTotalPooledEther(ether("100")); + await lido.mintShares(await nor.getAddress(), totalRewardShares); + + // before + // operatorId | Total | Deposited | Exited | Active (deposited-exited) + // 0 3 3 0 3 + // 1 7 7 0 7 + // 2 0 0 0 0 + // ----------------------------------------------------------------------------- + // total 3 10 10 0 10 + // + // perValidatorShare 10*10^18 / 10 = 10^18 + + // update [operator, exited, stuck] + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1); + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0); + + // after + // operatorId | Total | Deposited | Exited | Stuck | Active (deposited-exited) + // 0 3 3 1 1 2 + // 1 7 7 1 0 6 + // 2 0 0 0 0 0 + // ----------------------------------------------------------------------------- + // total 3 10 10 2 1 8 + // + // perValidatorShare 10*10^18 / 8 = 1250000000000000000 == 1.25 * 10^18 + // but half goes to burner + await expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); + // calls distributeRewards() inside + await expect(nor.distributeReward()) + .to.emit(nor, "RewardsDistributed") + .withArgs(await user1.getAddress(), ether(1.25 + "")) + .and.to.emit(nor, "RewardsDistributed") + .withArgs(await user2.getAddress(), ether(6 * 1.25 + "")) + .and.to.emit(nor, "NodeOperatorPenalized") + .withArgs(await user1.getAddress(), ether(1.25 + "")) + .and.to.emit(burner, "StETHBurnRequested") + .withArgs(false, await nor.getAddress(), ether("2.5"), ether("1.25")); + }); + + it("penalized firstOperator, add refund but 2 days have not passed yet", async () => { + await lido.setTotalPooledEther(ether("100")); + await lido.mintShares(await nor.getAddress(), ether("10")); + + // update [operator, exited, stuck] + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1); + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0); + + await nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperator, 1); + + // calls distributeRewards() inside + await expect(nor.distributeReward()) + .to.emit(nor, "RewardsDistributed") + .withArgs(await user1.getAddress(), ether(1.25 + "")) + .and.to.emit(nor, "RewardsDistributed") + .withArgs(await user2.getAddress(), ether(6 * 1.25 + "")) + .and.to.emit(nor, "NodeOperatorPenalized") + .withArgs(await user1.getAddress(), ether(1.25 + "")); + }); + + it("penalized firstOperator, add refund less than stuck validators", async () => { + await lido.setTotalPooledEther(ether("100")); + await lido.mintShares(await nor.getAddress(), ether("10")); + + // update [operator, exited, stuck] + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperator, 2, 1); + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(secondNodeOperator, 3, 0); + + // perValidator = ETH(10) / 5 = 2 eth + + await nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperator, 1); + + // calls distributeRewards() inside + await expect(nor.distributeReward()) + .to.emit(nor, "RewardsDistributed") + .withArgs(await user1.getAddress(), ether(1 + "")) + .and.to.emit(nor, "RewardsDistributed") + .withArgs(await user2.getAddress(), ether(4 * 2 + "")) + .and.to.emit(nor, "NodeOperatorPenalized") + .withArgs(await user1.getAddress(), ether(1 + "")); + }); + + it("penalized firstOperator, add refund and 2 days passed", async () => { + await lido.setTotalPooledEther(ether("100")); + await lido.mintShares(await nor.getAddress(), ether("10")); + + expect(await nor.isOperatorPenalized(firstNodeOperator)).to.be.false; + + // update [operator, exited, stuck] + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1); + await nor.connect(stakingRouter).unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0); + expect(await nor.isOperatorPenalized(firstNodeOperator)).to.be.true; + + await nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperator, 1); + expect(await nor.isOperatorPenalized(firstNodeOperator)).to.be.true; + + await advanceChainTime(2 * 24 * 60 * 60 + 10); + + expect(await nor.isOperatorPenalized(firstNodeOperator)).to.be.false; + + // calls distributeRewards() inside + + await expect(nor.distributeReward()) + .to.emit(nor, "RewardsDistributed") + .withArgs(await user1.getAddress(), ether(2.5 + "")) + .and.to.emit(nor, "RewardsDistributed") + .withArgs(await user2.getAddress(), ether(7.5 + "")); + }); }); context("getNodeOperatorIds", () => { diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index 15e633da1..115a9f59e 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -492,27 +492,10 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { ); }); - it("Returns early if nothing to distribute", async () => { + it("Update reward distribution state", async () => { await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()) - .to.not.emit(nor, "RewardsDistributed") - .to.not.emit(nor, "NodeOperatorPenalized"); - }); - - it("Doesn't distribute dust amounts", async () => { - await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()) - .to.not.emit(nor, "RewardsDistributed") - .to.not.emit(nor, "NodeOperatorPenalized"); - - await lido.connect(user).resume(); - await user.sendTransaction({ to: await lido.getAddress(), value: ether("1.0") }); - await lido.connect(user).transferShares(await nor.getAddress(), 1n); - - await expect(nor.connect(stakingRouter).onExitedAndStuckValidatorsCountsUpdated()).to.not.emit( - nor, - "RewardsDistributed", - ); - - expect(await lido.sharesOf(nor)).to.equal(1n); + .to.emit(nor, "RewardDistributionStateChanged") + .withArgs(RewardDistributionState.ReadyForDistribution); }); }); diff --git a/test/deploy/dao.ts b/test/deploy/dao.ts index bb3857cab..df9cffe3b 100644 --- a/test/deploy/dao.ts +++ b/test/deploy/dao.ts @@ -4,11 +4,13 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { ACL__factory, + Burner__MockForDistributeReward__factory, DAOFactory__factory, EIP712StETH__factory, EVMScriptRegistryFactory__factory, Kernel, Kernel__factory, + Lido__DistributeRewardMock__factory, Lido__factory, LidoLocator, } from "typechain-types"; @@ -86,6 +88,31 @@ export async function deployLidoDao({ rootAccount, initialized, locatorConfig = return { lido, dao, acl }; } +export async function deployLidoDaoForNor({ rootAccount, initialized, locatorConfig = {} }: DeployLidoDaoArgs) { + const { dao, acl } = await createAragonDao(rootAccount); + + const impl = await new Lido__DistributeRewardMock__factory(rootAccount).deploy(); + + const lidoProxyAddress = await addAragonApp({ + dao, + name: "lido", + impl, + rootAccount, + }); + + const lido = Lido__DistributeRewardMock__factory.connect(lidoProxyAddress, rootAccount); + + const burner = await new Burner__MockForDistributeReward__factory(rootAccount).deploy(); + + if (initialized) { + const locator = await deployLidoLocator({ lido, burner, ...locatorConfig }, rootAccount); + const eip712steth = await new EIP712StETH__factory(rootAccount).deploy(lido); + await lido.initialize(locator, eip712steth, { value: ether("1.0") }); + } + + return { lido, dao, acl, burner }; +} + export async function hasPermission( dao: Kernel, app: BaseContract, From adab914db7ea4b874bb2192e4f6fe12bac25c73e Mon Sep 17 00:00:00 2001 From: Eddort Date: Thu, 18 Jul 2024 17:15:39 +0200 Subject: [PATCH 148/177] fix: remove unused test case --- test/0.4.24/nor/nor.signing.keys.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/0.4.24/nor/nor.signing.keys.test.ts b/test/0.4.24/nor/nor.signing.keys.test.ts index 854742c2a..9b0a680b6 100644 --- a/test/0.4.24/nor/nor.signing.keys.test.ts +++ b/test/0.4.24/nor/nor.signing.keys.test.ts @@ -351,19 +351,6 @@ describe("NodeOperatorsRegistry:signing-keys", () => { ).to.be.revertedWith("LENGTH_MISMATCH"); }); - it("Reverts if too many keys in total across node operators", async () => { - expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( - thirdNodeOperatorId, - ); - - const keysCount = 1; - const [publicKeys, signatures] = thirdNOKeys.slice(0, keysCount); - - await expect( - addKeysFn(nor.connect(signingKeysManager), thirdNodeOperatorId, keysCount, publicKeys, signatures), - ).to.be.revertedWith("PACKED_OVERFLOW"); - }); - it("Reverts if too many keys passed for a single node operator", async () => { expect(await addNodeOperator(nor, nodeOperatorsManager, NODE_OPERATORS[thirdNodeOperatorId])).to.be.equal( thirdNodeOperatorId, From 6f05f346c1bf93f690ad1ac0ea0cca916480f75f Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 19 Jul 2024 13:45:46 +0400 Subject: [PATCH 149/177] Update test/0.4.24/nor/nor.initialize.upgrade.test.ts Co-authored-by: avsetsin --- test/0.4.24/nor/nor.initialize.upgrade.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 2f137a7df..02da354b2 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -186,7 +186,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { ); }); - it("Makes the contract initialized to v2", async () => { + it("Makes the contract initialized to v3", async () => { const burnerAddress = await locator.burner(); const latestBlock = BigInt(await time.latestBlock()); From 85665da29c7a3a5f8205b6f0567ccb31997e4554 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 19 Jul 2024 13:45:58 +0400 Subject: [PATCH 150/177] Update test/0.4.24/nor/nor.initialize.upgrade.test.ts Co-authored-by: avsetsin --- test/0.4.24/nor/nor.initialize.upgrade.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 02da354b2..6e302a7d7 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -166,7 +166,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { ); }); - it("Reverts if already initialized with v2", async () => { + it("Reverts if already initialized", async () => { const MAX_STUCK_PENALTY_DELAY = await nor.MAX_STUCK_PENALTY_DELAY(); await nor.initialize(locator, encodeBytes32String("curated-onchain-v1"), MAX_STUCK_PENALTY_DELAY); From 4f079497a8cd86f2c59ac992f773519c4547c3ae Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 19 Jul 2024 13:46:20 +0400 Subject: [PATCH 151/177] Update test/0.4.24/nor/nor.initialize.upgrade.test.ts Co-authored-by: avsetsin --- test/0.4.24/nor/nor.initialize.upgrade.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 6e302a7d7..63ed27acc 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -387,7 +387,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.Distributed); }); - it("reverts with error UNEXPECTED_CONTRACT_VERSION when called on already initialized contract", async () => { + it("reverts with error UNEXPECTED_CONTRACT_VERSION when called on already upgraded contract", async () => { await nor.finalizeUpgrade_v3(); expect(await nor.getContractVersion()).to.be.equal(3); await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); From 02e29f4484d70763ce20bf09b2ceaf8857d75895 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 19 Jul 2024 13:46:31 +0400 Subject: [PATCH 152/177] Update test/0.4.24/nor/nor.initialize.upgrade.test.ts Co-authored-by: avsetsin --- test/0.4.24/nor/nor.initialize.upgrade.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 63ed27acc..08beaef9c 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -243,7 +243,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { ); }); - it("Reverts if already initialized to v2", async () => { + it("Reverts if already initialized to v3", async () => { await Snapshot.restore(preInitState); const MAX_STUCK_PENALTY_DELAY = await nor.MAX_STUCK_PENALTY_DELAY(); await nor.initialize(locator, encodeBytes32String("curated-onchain-v1"), MAX_STUCK_PENALTY_DELAY); From acd4b8aed57e49ce02f935834d1d22995133d678 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 19 Jul 2024 13:48:13 +0400 Subject: [PATCH 153/177] Update test/0.4.24/nor/nor.limits.test.ts Co-authored-by: avsetsin --- test/0.4.24/nor/nor.limits.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index c9d77abe1..c6edb432e 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -189,7 +189,7 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { }); it('reverts with "OUT_OF_RANGE" error when called with targetLimit > UINT64_MAX', async () => { - const targetLimitWrong = BigInt("0x10000000000000000"); + const targetLimitWrong = 2n ** 64n; await expect( updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimitWrong), From 974001ae2b0c3ad858373d32b9d56383d9c1ae35 Mon Sep 17 00:00:00 2001 From: Eddort Date: Fri, 19 Jul 2024 13:05:44 +0200 Subject: [PATCH 154/177] refactor: code review --- test/0.4.24/nor/nor.limits.test.ts | 68 +++++++++++++++++++++ test/0.4.24/nor/nor.management.flow.test.ts | 11 +--- test/0.4.24/nor/nor.staking.limit.test.ts | 23 +++++++ 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index c6edb432e..fe66eb7a0 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -336,6 +336,74 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { expect(noSummary.depositableValidatorsCount).to.be.equal(1n); }); + it("targetLimitMode = 2; targetLimit = 10", async () => { + const targetLimitMode = 2n; + targetLimit = 10n; + + await expect( + updateLimitCall( + UpdateTargetLimitsMethods.UpdateTargetValidatorsLimits, + firstNodeOperatorId, + targetLimitMode, + targetLimit, + ), + ) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, targetLimitMode); + + let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.be.equal(2n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + + await expect( + updateLimitCall( + UpdateTargetLimitsMethods.UpdateTargetValidatorsLimits, + firstNodeOperatorId, + targetLimitMode, + 0n, + ), + ) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 2n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(2n); + expect(noSummary.targetValidatorsCount).to.equal(0n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(0n); + + await expect( + updateLimitCall( + UpdateTargetLimitsMethods.UpdateTargetValidatorsLimits, + firstNodeOperatorId, + targetLimitMode, + targetLimit, + ), + ) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 2n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + expect(noSummary.targetLimitMode).to.equal(2n); + expect(noSummary.targetValidatorsCount).to.equal(10n); + expect(noSummary.stuckValidatorsCount).to.be.equal(0n); + expect(noSummary.refundedValidatorsCount).to.be.equal(0n); + expect(noSummary.stuckPenaltyEndTimestamp).to.be.equal(0n); + expect(noSummary.totalExitedValidators).to.be.equal(1n); + expect(noSummary.totalDepositedValidators).to.be.equal(5n); + expect(noSummary.depositableValidatorsCount).to.be.equal(1n); + }); + it("targetLimitMode = 0; targetLimit = 10", async () => { const targetLimitMode = 0n; targetLimit = 10n; diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index 32c3037a7..215918b30 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -642,7 +642,7 @@ describe("NodeOperatorsRegistry:management", () => { await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); }); - it('distribute reward when module not in "ReadyForDistribution" status', async () => { + it('distribute reward when module in "ReadyForDistribution" status', async () => { expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); await expect(nor.distributeReward()) .to.emit(nor, "RewardDistributionStateChanged") @@ -668,7 +668,6 @@ describe("NodeOperatorsRegistry:management", () => { lido.sharesOf(user3), ]); - // calls distributeRewards() inside await nor.distributeReward(); const recipientsSharesAfter = await Promise.all([ @@ -686,7 +685,6 @@ describe("NodeOperatorsRegistry:management", () => { await lido.setTotalPooledEther(ether("100")); await lido.mintShares(await nor.getAddress(), ether("10")); - // calls distributeRewards() inside await nor.distributeReward(); expect(await lido.sharesOf(user1)).to.be.equal(ether("3")); expect(await lido.sharesOf(user2)).to.be.equal(ether("7")); @@ -697,7 +695,6 @@ describe("NodeOperatorsRegistry:management", () => { await lido.setTotalPooledEther(ether("100")); await lido.mintShares(await nor.getAddress(), ether("10")); - // calls distributeRewards() inside await expect(nor.distributeReward()) .to.emit(nor, "RewardsDistributed") .withArgs(await user1.getAddress(), ether("3")) @@ -773,7 +770,7 @@ describe("NodeOperatorsRegistry:management", () => { // perValidatorShare 10*10^18 / 8 = 1250000000000000000 == 1.25 * 10^18 // but half goes to burner await expect(await nor.getRewardDistributionState()).to.be.equal(RewardDistributionState.ReadyForDistribution); - // calls distributeRewards() inside + await expect(nor.distributeReward()) .to.emit(nor, "RewardsDistributed") .withArgs(await user1.getAddress(), ether(1.25 + "")) @@ -795,7 +792,6 @@ describe("NodeOperatorsRegistry:management", () => { await nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperator, 1); - // calls distributeRewards() inside await expect(nor.distributeReward()) .to.emit(nor, "RewardsDistributed") .withArgs(await user1.getAddress(), ether(1.25 + "")) @@ -817,7 +813,6 @@ describe("NodeOperatorsRegistry:management", () => { await nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperator, 1); - // calls distributeRewards() inside await expect(nor.distributeReward()) .to.emit(nor, "RewardsDistributed") .withArgs(await user1.getAddress(), ether(1 + "")) @@ -845,8 +840,6 @@ describe("NodeOperatorsRegistry:management", () => { expect(await nor.isOperatorPenalized(firstNodeOperator)).to.be.false; - // calls distributeRewards() inside - await expect(nor.distributeReward()) .to.emit(nor, "RewardsDistributed") .withArgs(await user1.getAddress(), ether(2.5 + "")) diff --git a/test/0.4.24/nor/nor.staking.limit.test.ts b/test/0.4.24/nor/nor.staking.limit.test.ts index f29e1605e..e7828e1f6 100644 --- a/test/0.4.24/nor/nor.staking.limit.test.ts +++ b/test/0.4.24/nor/nor.staking.limit.test.ts @@ -366,5 +366,28 @@ describe("NodeOperatorsRegistry:stakingLimit", () => { firstNo.totalDepositedValidators, ); }); + + it("depositableValidatorsCount in getStakingModuleSummary is decreasing", async () => { + let summary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + const oldNonce = await nor.getNonce(); + const oldDepositableValidatorsCount = summary.depositableValidatorsCount; + const firstNo = await nor.getNodeOperator(firstNodeOperatorId, false); + const idsPayload = prepIdsCountsPayload([BigInt(firstNodeOperatorId)], [firstNo.totalDepositedValidators]); + + await expect( + nor.connect(stakingRouter).decreaseVettedSigningKeysCount(idsPayload.operatorIds, idsPayload.keysCounts), + ) + .to.emit(nor, "VettedSigningKeysCountChanged") + .withArgs(firstNodeOperatorId, firstNo.totalDepositedValidators) + .to.emit(nor, "KeysOpIndexSet") + .withArgs(oldNonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(oldNonce + 1n); + + summary = await nor.getNodeOperatorSummary(firstNodeOperatorId); + const newDepositableValidatorsCount = summary.depositableValidatorsCount; + + expect(newDepositableValidatorsCount).to.be.lessThan(oldDepositableValidatorsCount); + }); }); }); From e8c1d3261a42dd0e404c2ae93eb1db09499f2220 Mon Sep 17 00:00:00 2001 From: Eddort Date: Fri, 19 Jul 2024 13:52:39 +0200 Subject: [PATCH 155/177] refactor: code review --- .../0.4.24/nor/nor.initialize.upgrade.test.ts | 4 +-- test/0.4.24/nor/nor.limits.test.ts | 29 ++++++++++++++++--- test/0.4.24/nor/nor.management.flow.test.ts | 4 +++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/test/0.4.24/nor/nor.initialize.upgrade.test.ts b/test/0.4.24/nor/nor.initialize.upgrade.test.ts index 08beaef9c..5289fb60f 100644 --- a/test/0.4.24/nor/nor.initialize.upgrade.test.ts +++ b/test/0.4.24/nor/nor.initialize.upgrade.test.ts @@ -376,7 +376,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { await expect(registry.finalizeUpgrade_v3()).to.be.revertedWith("CONTRACT_NOT_INITIALIZED"); }); - it("sets correct contract version", async () => { + it("sets correct contract version and reward distribution state", async () => { await expect(nor.finalizeUpgrade_v3()) .to.emit(nor, "ContractVersionSet") .withArgs(contractVersionV3) @@ -393,7 +393,7 @@ describe("NodeOperatorsRegistry:initialize-and-upgrade", () => { await expect(nor.finalizeUpgrade_v3()).to.be.revertedWith("UNEXPECTED_CONTRACT_VERSION"); }); - it("Migrates the contract storage from v2 to v3", async () => { + it("Migrates the contract storage from v1 to v3", async () => { preInitState = await Snapshot.refresh(preInitState); await nor.harness__initialize(0n); diff --git a/test/0.4.24/nor/nor.limits.test.ts b/test/0.4.24/nor/nor.limits.test.ts index fe66eb7a0..0e1bb802d 100644 --- a/test/0.4.24/nor/nor.limits.test.ts +++ b/test/0.4.24/nor/nor.limits.test.ts @@ -246,12 +246,12 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { let noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.be.equal(1n); - await expect(updateLimitCall(updateTargetLimitsMethod, secondNodeOperatorId, 0n, targetLimit)) + await expect(updateLimitCall(updateTargetLimitsMethod, secondNodeOperatorId, 1n, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") - .withArgs(secondNodeOperatorId, 0n, 0n); + .withArgs(secondNodeOperatorId, targetLimit, 1n); noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); - expect(noSummary.targetLimitMode).to.be.equal(0n); + expect(noSummary.targetLimitMode).to.be.equal(1n); // reset limit await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 0n, targetLimit)) @@ -260,8 +260,13 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(0n); + expect(noSummary.targetValidatorsCount).to.equal(0n); - noSummary = await nor.getNodeOperatorSummary(secondNodeOperatorId); + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, 0n, 0n); + + noSummary = await nor.getNodeOperatorSummary(firstNodeOperatorId); expect(noSummary.targetLimitMode).to.equal(0n); expect(noSummary.targetValidatorsCount).to.equal(0n); }); @@ -280,6 +285,22 @@ describe("NodeOperatorsRegistry:validatorsLimits", () => { .withArgs(secondNodeOperatorId, 0n, 0n); await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(2n); + }); + + it("nonce changing even if limit is the same", async () => { + targetLimit = 10n; + + await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 1n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(firstNodeOperatorId, targetLimit, 1n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(1n); + + await expect(updateLimitCall(updateTargetLimitsMethod, secondNodeOperatorId, 0n, targetLimit)) + .to.emit(nor, "TargetValidatorsCountChanged") + .withArgs(secondNodeOperatorId, 0n, 0n); + + await expect(await nor.connect(stakingRouter).getNonce()).to.be.equal(2n); await expect(updateLimitCall(updateTargetLimitsMethod, firstNodeOperatorId, 0n, targetLimit)) .to.emit(nor, "TargetValidatorsCountChanged") diff --git a/test/0.4.24/nor/nor.management.flow.test.ts b/test/0.4.24/nor/nor.management.flow.test.ts index 215918b30..66ddbed66 100644 --- a/test/0.4.24/nor/nor.management.flow.test.ts +++ b/test/0.4.24/nor/nor.management.flow.test.ts @@ -640,6 +640,10 @@ describe("NodeOperatorsRegistry:management", () => { await nor.harness__addNodeOperator("2", user3, 0, 0, 0, 0); await nor.harness__setRewardDistributionState(RewardDistributionState.ReadyForDistribution); + + expect(await lido.sharesOf(user1)).to.be.equal(0); + expect(await lido.sharesOf(user2)).to.be.equal(0); + expect(await lido.sharesOf(user3)).to.be.equal(0); }); it('distribute reward when module in "ReadyForDistribution" status', async () => { From b8d5e0ecd407ee9dc719015c8b50a87d2115050b Mon Sep 17 00:00:00 2001 From: Eddort Date: Tue, 30 Jul 2024 11:28:55 +0200 Subject: [PATCH 156/177] fix: hash consensus version test --- test/0.8.9/oracle/hashConsensus.submitReport.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/0.8.9/oracle/hashConsensus.submitReport.test.ts b/test/0.8.9/oracle/hashConsensus.submitReport.test.ts index d087c17e5..55c69d2e6 100644 --- a/test/0.8.9/oracle/hashConsensus.submitReport.test.ts +++ b/test/0.8.9/oracle/hashConsensus.submitReport.test.ts @@ -9,7 +9,7 @@ import { CONSENSUS_VERSION } from "lib"; import { deployHashConsensus, HASH_1, HASH_2, ZERO_HASH } from "test/deploy"; import { Snapshot } from "test/suite"; -const CONSENSUS_VERSION_2 = 2n; +const CONSENSUS_VERSION_NEW = 3n; describe("HashConsensus:submitReport", function () { let admin: Signer; @@ -50,9 +50,9 @@ describe("HashConsensus:submitReport", function () { }); it("reverts with UnexpectedConsensusVersion", async () => { - await expect(consensus.connect(member1).submitReport(frame.refSlot, HASH_1, CONSENSUS_VERSION_2)) + await expect(consensus.connect(member1).submitReport(frame.refSlot, HASH_1, CONSENSUS_VERSION_NEW)) .to.be.revertedWithCustomError(consensus, "UnexpectedConsensusVersion") - .withArgs(CONSENSUS_VERSION, CONSENSUS_VERSION_2); + .withArgs(CONSENSUS_VERSION, CONSENSUS_VERSION_NEW); }); it("reverts with EmptyReport", async () => { From 4be7ff5cc74d4dce77c7602d5b93bf3e7cee42b2 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 28 Aug 2024 23:11:44 +0200 Subject: [PATCH 157/177] docs: fix description --- contracts/0.8.9/StakingRouter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 2ced55d3a..4df4ce605 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -1049,9 +1049,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return _getStakingModuleById(_stakingModuleId).minDepositBlockDistance; } - /// @notice Returns the max deposit block distance for the staking module. + /// @notice Returns the max deposits count per block for the staking module. /// @param _stakingModuleId Id of the staking module. - /// @return Max deposit block distance for the staking module. + /// @return Max deposits count per block for the staking module. function getStakingModuleMaxDepositsPerBlock(uint256 _stakingModuleId) external view returns (uint256) { return _getStakingModuleById(_stakingModuleId).maxDepositsPerBlock; } From f53575442ea48da63ac164f015228991ed3b5bd8 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 29 Aug 2024 19:18:37 +0200 Subject: [PATCH 158/177] refactor: unify array length validation to reduce duplication and contract size --- contracts/0.8.9/StakingRouter.sol | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 4df4ce605..4dbd9f903 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -191,17 +191,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 stakingModulesCount = getStakingModulesCount(); - if (stakingModulesCount != _priorityExitShareThresholds.length) { - revert ArraysLengthMismatch(stakingModulesCount, _priorityExitShareThresholds.length); - } - - if (stakingModulesCount != _maxDepositsPerBlock.length) { - revert ArraysLengthMismatch(stakingModulesCount, _maxDepositsPerBlock.length); - } - - if (stakingModulesCount != _minDepositBlockDistances.length) { - revert ArraysLengthMismatch(stakingModulesCount, _minDepositBlockDistances.length); - } + _validateEqualArrayLengths(stakingModulesCount, _priorityExitShareThresholds.length); + _validateEqualArrayLengths(stakingModulesCount, _maxDepositsPerBlock.length); + _validateEqualArrayLengths(stakingModulesCount, _minDepositBlockDistances.length); for (uint256 i; i < stakingModulesCount; ) { StakingModule storage stakingModule = _getStakingModuleByIndex(i); @@ -401,9 +393,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version external onlyRole(REPORT_REWARDS_MINTED_ROLE) { - if (_stakingModuleIds.length != _totalShares.length) { - revert ArraysLengthMismatch(_stakingModuleIds.length, _totalShares.length); - } + _validateEqualArrayLengths(_stakingModuleIds.length, _totalShares.length); for (uint256 i = 0; i < _stakingModuleIds.length; ) { if (_totalShares[i] > 0) { @@ -475,9 +465,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version onlyRole(REPORT_EXITED_VALIDATORS_ROLE) returns (uint256) { - if (_stakingModuleIds.length != _exitedValidatorsCounts.length) { - revert ArraysLengthMismatch(_stakingModuleIds.length, _exitedValidatorsCounts.length); - } + _validateEqualArrayLengths(_stakingModuleIds.length, _exitedValidatorsCounts.length); uint256 newlyExitedValidatorsCount; @@ -1479,4 +1467,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version function _toE4Precision(uint256 _value, uint256 _precision) internal pure returns (uint16) { return uint16((_value * TOTAL_BASIS_POINTS) / _precision); } + + function _validateEqualArrayLengths(uint256 firstArrayLength, uint256 secondArrayLength) internal pure { + if (firstArrayLength != secondArrayLength) { + revert ArraysLengthMismatch(firstArrayLength, secondArrayLength); + } + } } From c5db72ed22127cd55c3498c3281a09e658d8e514 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 5 Sep 2024 17:29:58 +0200 Subject: [PATCH 159/177] feat: update maxNodeOperatorsPerExtraDataItemCount Update limit according to latest estimation of gas consumption in CSM module --- scripts/archive/sr-v2-deploy-holesky.ts | 2 +- scripts/staking-router-v2/sr-v2-deploy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/archive/sr-v2-deploy-holesky.ts b/scripts/archive/sr-v2-deploy-holesky.ts index 0dfbe5a6e..21e108bd9 100644 --- a/scripts/archive/sr-v2-deploy-holesky.ts +++ b/scripts/archive/sr-v2-deploy-holesky.ts @@ -36,7 +36,7 @@ async function main() { // Oracle report sanity checker // 43200 check value - const LIMITS = [9000, 500, 1000, 50, 600, 8, 62, 7680, 750000, 43200]; + const LIMITS = [9000, 500, 1000, 50, 600, 8, 24, 7680, 750000, 43200]; const MANAGERS_ROSTER = [[], [], [], [], [], [], [], [], [], [], []]; const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index ed0500f59..4780c95d4 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -36,7 +36,7 @@ function getEnvVariable(name: string, defaultValue?: string) { const SECONDS_PER_SLOT = 12; const GENESIS_TIME = 1606824023; // Oracle report sanity checker -const LIMITS = [9000, 43200, 1000, 50, 600, 8, 62, 7680, 750000, 1000, 101, 74]; +const LIMITS = [9000, 43200, 1000, 50, 600, 8, 24, 7680, 750000, 1000, 101, 74]; // DSM args const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; const MAX_OPERATORS_PER_UNVETTING = 200; From 2f0811c416b6c3d699ba4cb086d7e5e691d6c60a Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 5 Sep 2024 18:05:24 +0200 Subject: [PATCH 160/177] feat: update sanity checker scratch deploy parameters Update limits to accurate values based on - Target max third phase transaction gas cost - Max single node operators update gas cost --- scripts/scratch/deployed-testnet-defaults.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/scratch/deployed-testnet-defaults.json b/scripts/scratch/deployed-testnet-defaults.json index 8aecf4f31..6c0f6777d 100644 --- a/scripts/scratch/deployed-testnet-defaults.json +++ b/scripts/scratch/deployed-testnet-defaults.json @@ -107,8 +107,8 @@ "annualBalanceIncreaseBPLimit": 1000, "simulatedShareRateDeviationBPLimit": 250, "maxValidatorExitRequestsPerReport": 2000, - "maxItemsPerExtraDataTransaction": 100, - "maxNodeOperatorsPerExtraDataItem": 100, + "maxItemsPerExtraDataTransaction": 8, + "maxNodeOperatorsPerExtraDataItem": 24, "requestTimestampMargin": 128, "maxPositiveTokenRebase": 5000000, "initialSlashingAmountPWei": 1000, From da704b8929d6c4a7c04a0bf814c21695198d94f4 Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 30 Aug 2024 15:35:50 +0200 Subject: [PATCH 161/177] fix: prevent overflow in minDepositBlockDistance and maxDepositsPerBlock during module update Ackee L1: Overflow on type casting. In the staking router, ensure that the new values for minDepositBlockDistance and maxDepositsPerBlock are checked for potential overflow during module updates to avoid issues. --- contracts/0.8.9/StakingRouter.sol | 4 +- .../stakingRouter.module-management.test.ts | 54 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 4dbd9f903..cf19dc1cb 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -69,6 +69,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error UnrecoverableModuleError(); error InvalidPriorityExitShareThreshold(); error InvalidMinDepositBlockDistance(); + error InvalidMaxDepositPerBlockValue(); enum StakingModuleStatus { Active, // deposits and rewards allowed @@ -337,7 +338,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version if (_priorityExitShareThreshold > TOTAL_BASIS_POINTS) revert InvalidPriorityExitShareThreshold(); if (_stakeShareLimit > _priorityExitShareThreshold) revert InvalidPriorityExitShareThreshold(); if (_stakingModuleFee + _treasuryFee > TOTAL_BASIS_POINTS) revert InvalidFeeSum(); - if (_minDepositBlockDistance == 0) revert InvalidMinDepositBlockDistance(); + if (_minDepositBlockDistance == 0 || _minDepositBlockDistance > type(uint64).max) revert InvalidMinDepositBlockDistance(); + if (_maxDepositsPerBlock > type(uint64).max) revert InvalidMaxDepositPerBlockValue(); stakingModule.stakeShareLimit = uint16(_stakeShareLimit); stakingModule.priorityExitShareThreshold = uint16(_priorityExitShareThreshold); diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts index 54aaef284..63746b2de 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-management.test.ts @@ -14,6 +14,8 @@ import { StakingRouterLibraryAddresses } from "typechain-types/factories/contrac import { certainAddress, getNextBlock, proxify, randomString } from "lib"; +const UINT64_MAX = 2n ** 64n - 1n; + describe("StakingRouter:module-management", () => { let deployer: HardhatEthersSigner; let admin: HardhatEthersSigner; @@ -383,6 +385,58 @@ describe("StakingRouter:module-management", () => { ).to.be.revertedWithCustomError(stakingRouter, "InvalidMinDepositBlockDistance"); }); + it("Reverts if the new deposit block distance is great then uint64 max", async () => { + await stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + UINT64_MAX, + ); + + expect((await stakingRouter.getStakingModule(ID)).minDepositBlockDistance).to.be.equal(UINT64_MAX); + + await expect( + stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + NEW_MAX_DEPOSITS_PER_BLOCK, + UINT64_MAX + 1n, + ), + ).to.be.revertedWithCustomError(stakingRouter, "InvalidMinDepositBlockDistance"); + }); + + it("Reverts if the new max deposits per block is great then uint64 max", async () => { + await stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + UINT64_MAX, + NEW_MIN_DEPOSIT_BLOCK_DISTANCE, + ); + + expect((await stakingRouter.getStakingModule(ID)).maxDepositsPerBlock).to.be.equal(UINT64_MAX); + + await expect( + stakingRouter.updateStakingModule( + ID, + NEW_STAKE_SHARE_LIMIT, + NEW_PRIORITY_EXIT_SHARE_THRESHOLD, + NEW_MODULE_FEE, + NEW_TREASURY_FEE, + UINT64_MAX + 1n, + NEW_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ).to.be.revertedWithCustomError(stakingRouter, "InvalidMaxDepositPerBlockValue"); + }); + it("Reverts if the sum of the new module and treasury fees is greater than 100%", async () => { const NEW_MODULE_FEE_INVALID = 100_01n - TREASURY_FEE; From 6002003f22d0c02992896690537c1f2d00c98cb7 Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 30 Aug 2024 16:45:14 +0200 Subject: [PATCH 162/177] fix: prevent potential revert due to underflow Ackee L2: Potential revert on underflow. In the Staking Router, an unsafe update of the exited validators count could potentially cause the total exited validators count to exceed the total deposited validators count. This would break the pre-defined invariant: activeValidatorsCount = totalDepositedValidators - totalExitedValidators. This fix ensures the integrity of the active validators count by preventing such an underflow. --- contracts/0.8.9/StakingRouter.sol | 38 ++++++- .../stakingRouter.module-sync.test.ts | 98 +++++++++++++++++-- 2 files changed, 121 insertions(+), 15 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index cf19dc1cb..be08a6eaa 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -63,6 +63,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 currentNodeOpExitedValidatorsCount, uint256 currentNodeOpStuckValidatorsCount ); + error UnexpectedFinalExitedValidatorsCount ( + uint256 newModuleTotalExitedValidatorsCount, + uint256 newModuleTotalExitedValidatorsCountInStakingRouter + ); error InvalidDepositsValue(uint256 etherValue, uint256 depositsCount); error StakingModuleAddressExists(); error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength); @@ -484,7 +488,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 totalExitedValidators, uint256 totalDepositedValidators, /* uint256 depositableValidatorsCount */ - ) = IStakingModule(stakingModule.stakingModuleAddress).getStakingModuleSummary(); + ) = _getStakingModuleSummary(IStakingModule(stakingModule.stakingModuleAddress)); if (_exitedValidatorsCounts[i] > totalDepositedValidators) { revert ReportedExitedValidatorsExceedDeposited( @@ -610,7 +614,26 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version _correction.newNodeOperatorStuckValidatorsCount ); + ( + uint256 moduleTotalExitedValidators, + uint256 moduleTotalDepositedValidators, + ) = _getStakingModuleSummary(stakingModule); + + if (_correction.newModuleExitedValidatorsCount > moduleTotalDepositedValidators) { + revert ReportedExitedValidatorsExceedDeposited( + _correction.newModuleExitedValidatorsCount, + moduleTotalDepositedValidators + ); + } + if (_triggerUpdateFinish) { + if (moduleTotalExitedValidators != _correction.newModuleExitedValidatorsCount) { + revert UnexpectedFinalExitedValidatorsCount( + moduleTotalExitedValidators, + _correction.newModuleExitedValidatorsCount + ); + } + stakingModule.onExitedAndStuckValidatorsCountsUpdated(); } } @@ -657,7 +680,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version stakingModule = _getStakingModuleByIndex(i); moduleContract = IStakingModule(stakingModule.stakingModuleAddress); - (uint256 exitedValidatorsCount, , ) = moduleContract.getStakingModuleSummary(); + (uint256 exitedValidatorsCount, , ) = _getStakingModuleSummary(moduleContract); if (exitedValidatorsCount == stakingModule.exitedValidatorsCount) { // oracle finished updating exited validators for all node ops try moduleContract.onExitedAndStuckValidatorsCountsUpdated() {} @@ -820,7 +843,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version summary.totalExitedValidators, summary.totalDepositedValidators, summary.depositableValidatorsCount - ) = stakingModule.getStakingModuleSummary(); + ) = _getStakingModuleSummary(stakingModule); } @@ -1059,7 +1082,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 totalExitedValidators, uint256 totalDepositedValidators, /* uint256 depositableValidatorsCount */ - ) = IStakingModule(stakingModule.stakingModuleAddress).getStakingModuleSummary(); + ) = _getStakingModuleSummary(IStakingModule(stakingModule.stakingModuleAddress)); activeValidatorsCount = totalDepositedValidators - Math256.max( stakingModule.exitedValidatorsCount, totalExitedValidators @@ -1372,7 +1395,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version uint256 totalExitedValidators, uint256 totalDepositedValidators, uint256 depositableValidatorsCount - ) = IStakingModule(cacheItem.stakingModuleAddress).getStakingModuleSummary(); + ) = _getStakingModuleSummary(IStakingModule(cacheItem.stakingModuleAddress)); cacheItem.availableValidatorsCount = cacheItem.status == StakingModuleStatus.Active ? depositableValidatorsCount @@ -1475,4 +1498,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version revert ArraysLengthMismatch(firstArrayLength, secondArrayLength); } } + + /// @dev Optimizes contract deployment size by wrapping the 'stakingModule.getStakingModuleSummary' function. + function _getStakingModuleSummary(IStakingModule stakingModule) internal view returns (uint256, uint256, uint256) { + return stakingModule.getStakingModuleSummary(); + } } diff --git a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts index ba4878ad8..b7bb09878 100644 --- a/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts +++ b/test/0.8.9/stakingRouter/stakingRouter.module-sync.test.ts @@ -627,15 +627,55 @@ describe("StakingRouter:module-sync", () => { context("unsafeSetExitedValidatorsCount", () => { const nodeOperatorId = 1n; + const moduleSummary = { + totalExitedValidators: 5n, + totalDepositedValidators: 10n, + depositableValidatorsCount: 2n, + }; + + const operatorSummary = { + targetLimitMode: 1, + targetValidatorsCount: 100n, + stuckValidatorsCount: 0n, + refundedValidatorsCount: 0n, + stuckPenaltyEndTimestamp: 0n, + totalExitedValidators: 3n, + totalDepositedValidators: 5n, + depositableValidatorsCount: 1n, + }; + const correction: StakingRouter.ValidatorsCountsCorrectionStruct = { - currentModuleExitedValidatorsCount: 0n, - currentNodeOperatorExitedValidatorsCount: 0n, - currentNodeOperatorStuckValidatorsCount: 0n, - newModuleExitedValidatorsCount: 1n, - newNodeOperatorExitedValidatorsCount: 2n, - newNodeOperatorStuckValidatorsCount: 3n, + currentModuleExitedValidatorsCount: moduleSummary.totalExitedValidators, + currentNodeOperatorExitedValidatorsCount: operatorSummary.totalExitedValidators, + currentNodeOperatorStuckValidatorsCount: operatorSummary.stuckValidatorsCount, + newModuleExitedValidatorsCount: moduleSummary.totalExitedValidators, + newNodeOperatorExitedValidatorsCount: operatorSummary.totalExitedValidators + 1n, + newNodeOperatorStuckValidatorsCount: operatorSummary.stuckValidatorsCount + 1n, }; + beforeEach(async () => { + await stakingModule.mock__getStakingModuleSummary( + moduleSummary.totalExitedValidators, + moduleSummary.totalDepositedValidators, + moduleSummary.depositableValidatorsCount, + ); + + const nodeOperatorSummary: Parameters = [ + operatorSummary.targetLimitMode, + operatorSummary.targetValidatorsCount, + operatorSummary.stuckValidatorsCount, + operatorSummary.refundedValidatorsCount, + operatorSummary.stuckPenaltyEndTimestamp, + operatorSummary.totalExitedValidators, + operatorSummary.totalDepositedValidators, + operatorSummary.depositableValidatorsCount, + ]; + + await stakingModule.mock__getNodeOperatorSummary(...nodeOperatorSummary); + + await stakingRouter.updateExitedValidatorsCountByStakingModule([moduleId], [moduleSummary.totalExitedValidators]); + }); + it("Reverts if the caller does not have the role", async () => { await expect( stakingRouter.connect(user).unsafeSetExitedValidatorsCount(moduleId, nodeOperatorId, true, correction), @@ -650,7 +690,11 @@ describe("StakingRouter:module-sync", () => { }), ) .to.be.revertedWithCustomError(stakingRouter, "UnexpectedCurrentValidatorsCount") - .withArgs(0n, 0n, 0n); + .withArgs( + correction.currentModuleExitedValidatorsCount, + correction.currentNodeOperatorExitedValidatorsCount, + correction.currentNodeOperatorStuckValidatorsCount, + ); }); it("Reverts if the number of exited validators of the operator does not match what is stored on the contract", async () => { @@ -661,7 +705,11 @@ describe("StakingRouter:module-sync", () => { }), ) .to.be.revertedWithCustomError(stakingRouter, "UnexpectedCurrentValidatorsCount") - .withArgs(0n, 0n, 0n); + .withArgs( + correction.currentModuleExitedValidatorsCount, + correction.currentNodeOperatorExitedValidatorsCount, + correction.currentNodeOperatorStuckValidatorsCount, + ); }); it("Reverts if the number of stuck validators of the operator does not match what is stored on the contract", async () => { @@ -672,10 +720,40 @@ describe("StakingRouter:module-sync", () => { }), ) .to.be.revertedWithCustomError(stakingRouter, "UnexpectedCurrentValidatorsCount") - .withArgs(0n, 0n, 0n); + .withArgs( + correction.currentModuleExitedValidatorsCount, + correction.currentNodeOperatorExitedValidatorsCount, + correction.currentNodeOperatorStuckValidatorsCount, + ); }); - it("Update unsafely the number of exited validators on the staking module", async () => { + it("Reverts if the total exited validators exceed the module's deposited validators", async () => { + const newModuleExitedValidatorsCount = 50n; + + await expect( + stakingRouter.unsafeSetExitedValidatorsCount(moduleId, nodeOperatorId, true, { + ...correction, + newModuleExitedValidatorsCount, + }), + ) + .to.be.revertedWithCustomError(stakingRouter, "ReportedExitedValidatorsExceedDeposited") + .withArgs(newModuleExitedValidatorsCount, moduleSummary.totalDepositedValidators); + }); + + it("Reverts if the total exited validators count in the staking module does not match the staking router after the final update", async () => { + const newModuleExitedValidatorsCount = 10n; + + await expect( + stakingRouter.unsafeSetExitedValidatorsCount(moduleId, nodeOperatorId, true, { + ...correction, + newModuleExitedValidatorsCount, + }), + ) + .to.be.revertedWithCustomError(stakingRouter, "UnexpectedFinalExitedValidatorsCount") + .withArgs(moduleSummary.totalExitedValidators, newModuleExitedValidatorsCount); + }); + + it("Update unsafely the number of exited validators on the staking module with finalization hook triggering", async () => { await expect(stakingRouter.unsafeSetExitedValidatorsCount(moduleId, nodeOperatorId, true, correction)) .to.be.emit(stakingModule, "Mock__ValidatorsCountUnsafelyUpdated") .withArgs( From cf7e9fa95efb8964ef9010b62faa673a9ddb4629 Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 30 Aug 2024 16:46:31 +0200 Subject: [PATCH 163/177] fix: return the boolean value from clearNodeOperatorPenalty function Ackee L3: The clearNodeOperatorPenalty returns always false --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 72c345ef0..1f90fcfcf 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -1353,6 +1353,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _saveOperatorStuckPenaltyStats(_nodeOperatorId, stuckPenaltyStats); _updateSummaryMaxValidatorsCount(_nodeOperatorId); _increaseValidatorsKeysNonce(); + return true; } /// @notice Returns total number of node operators From f317759f908d884a52c9ad7c06fc128f9281ef8e Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 30 Aug 2024 16:47:21 +0200 Subject: [PATCH 164/177] fix: add event on clearNodeOperatorPenalty Ackee I2: Missing event on clearNodeOperatorPenalty --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 3 +++ test/0.4.24/nor/nor.rewards.penalties.flow.test.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 1f90fcfcf..7e23b5918 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -61,6 +61,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { ); event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); event NodeOperatorPenalized(address indexed recipientAddress, uint256 sharesPenalizedAmount); + event NodeOperatorPenaltyCleared(uint256 indexed nodeOperatorId); // Enum to represent the state of the reward distribution process enum RewardDistributionState { @@ -1353,6 +1354,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _saveOperatorStuckPenaltyStats(_nodeOperatorId, stuckPenaltyStats); _updateSummaryMaxValidatorsCount(_nodeOperatorId); _increaseValidatorsKeysNonce(); + + emit NodeOperatorPenaltyCleared(_nodeOperatorId); return true; } diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index 115a9f59e..89a676d39 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -737,13 +737,17 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { .to.emit(nor, "KeysOpIndexSet") .withArgs(nonce + 3n) .to.emit(nor, "NonceChanged") - .withArgs(nonce + 3n); + .withArgs(nonce + 3n) + .to.emit(nor, "NodeOperatorPenaltyCleared") + .withArgs(firstNodeOperatorId); await expect(await nor.clearNodeOperatorPenalty(secondNodeOperatorId)) .to.emit(nor, "KeysOpIndexSet") .withArgs(nonce + 4n) .to.emit(nor, "NonceChanged") - .withArgs(nonce + 4n); + .withArgs(nonce + 4n) + .to.emit(nor, "NodeOperatorPenaltyCleared") + .withArgs(secondNodeOperatorId); expect(await nor.isOperatorPenalized(firstNodeOperatorId)).to.be.false; expect(await nor.isOperatorPenalized(secondNodeOperatorId)).to.be.false; From bf68809f1b77a1ef70adf54464614ddddad0002e Mon Sep 17 00:00:00 2001 From: maxim Date: Fri, 30 Aug 2024 16:49:41 +0200 Subject: [PATCH 165/177] fix: typo in nor function name Ackee I3: Typo in node operator registry function name --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 7e23b5918..4bec41e0e 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -421,7 +421,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _authP(SET_NODE_OPERATOR_LIMIT_ROLE, arr(uint256(_nodeOperatorId), uint256(_vettedSigningKeysCount))); _onlyCorrectNodeOperatorState(getNodeOperatorIsActive(_nodeOperatorId)); - _updateVettedSingingKeysCount(_nodeOperatorId, _vettedSigningKeysCount, true /* _allowIncrease */); + _updateVettedSigningKeysCount(_nodeOperatorId, _vettedSigningKeysCount, true /* _allowIncrease */); _increaseValidatorsKeysNonce(); } @@ -460,12 +460,12 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { i := add(i, 1) } _requireValidRange(nodeOperatorId < totalNodeOperatorsCount); - _updateVettedSingingKeysCount(nodeOperatorId, vettedKeysCount, false /* only decrease */); + _updateVettedSigningKeysCount(nodeOperatorId, vettedKeysCount, false /* only decrease */); } _increaseValidatorsKeysNonce(); } - function _updateVettedSingingKeysCount( + function _updateVettedSigningKeysCount( uint256 _nodeOperatorId, uint256 _vettedSigningKeysCount, bool _allowIncrease From c3413719ce56f7014997d47e0047284d72b49e96 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Tue, 24 Sep 2024 15:35:33 +0400 Subject: [PATCH 166/177] fix: move limits values in constants --- scripts/staking-router-v2/sr-v2-deploy.ts | 36 ++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 4780c95d4..6632937b2 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -12,6 +12,7 @@ import { loadContract, log, persistNetworkState, + rd, readNetworkState, Sk, updateObjectInState, @@ -35,8 +36,36 @@ function getEnvVariable(name: string, defaultValue?: string) { // Accounting Oracle args const SECONDS_PER_SLOT = 12; const GENESIS_TIME = 1606824023; + // Oracle report sanity checker -const LIMITS = [9000, 43200, 1000, 50, 600, 8, 24, 7680, 750000, 1000, 101, 74]; +const EXITED_VALIDATORS_PER_DAY_LIMIT = 9000; +const APPEARED_VALIDATORS_PER_DAY_LIMIT = 43200; +const ANNUAL_BALANCE_INCREASE_BP_LIMIT = 1000; +const SIMULATED_SHARE_RATE_DEVIATION_BP_LIMIT = 50; +const MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT = 600; +const MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION = 8; +const MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM = 24; +const REQUEST_TIMESTAMP_MARGIN = 7680; +const MAX_POSITIVE_TOKEN_REBASE = 750000; +const INITIAL_SLASHING_AMOUNT_P_WEI = 1000; +const INACTIVITY_PENALTIES_AMOUNT_P_WEI = 101; +const CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 74; + +const LIMITS = [ + EXITED_VALIDATORS_PER_DAY_LIMIT, + APPEARED_VALIDATORS_PER_DAY_LIMIT, + ANNUAL_BALANCE_INCREASE_BP_LIMIT, + SIMULATED_SHARE_RATE_DEVIATION_BP_LIMIT, + MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT, + MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, + MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, + REQUEST_TIMESTAMP_MARGIN, + MAX_POSITIVE_TOKEN_REBASE, + INITIAL_SLASHING_AMOUNT_P_WEI, + INACTIVITY_PENALTIES_AMOUNT_P_WEI, + CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT, +]; + // DSM args const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; const MAX_OPERATORS_PER_UNVETTING = 200; @@ -54,6 +83,11 @@ async function main() { const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); const chainId = (await ethers.provider.getNetwork()).chainId; + if (chainId !== 1n) { + log(rd(`Expected mainnet, got chain id ${chainId}`)); + return; + } + log(cy(`Deploy of contracts on chain ${chainId}`)); const state = readNetworkState(); From beb6ceb23cc3b54a74e25f1f9aeda65d04afe03e Mon Sep 17 00:00:00 2001 From: KRogLA Date: Tue, 24 Sep 2024 15:40:50 +0200 Subject: [PATCH 167/177] fix: remove redundant check --- contracts/0.8.9/oracle/AccountingOracle.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 62461e763..8225667b2 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -100,7 +100,6 @@ contract AccountingOracle is BaseOracle { error ExtraDataAlreadyProcessed(); error UnexpectedExtraDataHash(bytes32 consensusHash, bytes32 receivedHash); error UnexpectedExtraDataFormat(uint256 expectedFormat, uint256 receivedFormat); - error UnexpectedExtraDataLength(); error ExtraDataItemsCountCannotBeZeroForNonEmptyData(); error ExtraDataHashCannotBeZeroForNonEmptyData(); error UnexpectedExtraDataItemsCount(uint256 expectedCount, uint256 receivedCount); @@ -737,11 +736,6 @@ contract AccountingOracle is BaseOracle { revert ExtraDataAlreadyProcessed(); } - // at least 32 bytes for the next hash value + 35 bytes for the first item with 1 node operator - if (data.length < 67) { - revert UnexpectedExtraDataLength(); - } - bytes32 dataHash = keccak256(data); if (dataHash != procState.dataHash) { revert UnexpectedExtraDataHash(procState.dataHash, dataHash); From 769bdfca17e84b857cfbb1818e1cfbe36c7b4b9b Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 25 Sep 2024 12:01:38 +0200 Subject: [PATCH 168/177] fix: add nonce update on refund count changes --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 4bec41e0e..1e5d30f3a 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -589,6 +589,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _auth(STAKING_ROUTER_ROLE); _updateRefundValidatorsKeysCount(_nodeOperatorId, _refundedValidatorsCount); + _increaseValidatorsKeysNonce(); } /// @notice Permissionless method for distributing all accumulated module rewards among node operators From e21a591684388fb90d89466879f2b3a451a5c311 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Wed, 25 Sep 2024 12:16:19 +0200 Subject: [PATCH 169/177] test: nonce on refund count upd --- .../nor/nor.rewards.penalties.flow.test.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index 89a676d39..9bcf1e5da 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -436,38 +436,51 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { }); it("Allows updating a single NO", async () => { + const nonce = await nor.getNonce(); await expect(nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperatorId, 1n)) .to.emit(nor, "StuckPenaltyStateChanged") .withArgs(firstNodeOperatorId, 0n, 1n, 0n) - .to.not.emit(nor, "KeysOpIndexSet") - .to.not.emit(nor, "NonceChanged"); + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 1n); }); it("Does nothing if refunded keys haven't changed", async () => { + const nonce = await nor.getNonce(); await expect(nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperatorId, 1n)) .to.emit(nor, "StuckPenaltyStateChanged") .withArgs(firstNodeOperatorId, 0n, 1n, 0n) - .to.not.emit(nor, "KeysOpIndexSet") - .to.not.emit(nor, "NonceChanged"); + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 1n); await expect(nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperatorId, 1n)) - .to.not.emit(nor, "KeysOpIndexSet") - .to.not.emit(nor, "NonceChanged") + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 2n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 2n) .to.not.emit(nor, "StuckPenaltyStateChanged"); }); it("Allows setting refunded count to zero after all", async () => { + const nonce = await nor.getNonce(); await expect(nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperatorId, 1n)) .to.emit(nor, "StuckPenaltyStateChanged") .withArgs(firstNodeOperatorId, 0n, 1n, 0n) - .to.not.emit(nor, "KeysOpIndexSet") - .to.not.emit(nor, "NonceChanged"); + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 1n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 1n); await expect(nor.connect(stakingRouter).updateRefundedValidatorsCount(firstNodeOperatorId, 0n)) .to.emit(nor, "StuckPenaltyStateChanged") .withArgs(firstNodeOperatorId, 0n, 0n, 0n) - .to.not.emit(nor, "KeysOpIndexSet") - .to.not.emit(nor, "NonceChanged"); + .to.emit(nor, "KeysOpIndexSet") + .withArgs(nonce + 2n) + .to.emit(nor, "NonceChanged") + .withArgs(nonce + 2n); }); }); @@ -605,6 +618,7 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { .withArgs(firstNodeOperatorId, 2n, 1n, 0n); idsPayload = prepIdsCountsPayload([BigInt(secondNodeOperatorId)], [2n]); + await expect(nor.connect(stakingRouter).updateStuckValidatorsCount(idsPayload.operatorIds, idsPayload.keysCounts)) .to.emit(nor, "KeysOpIndexSet") .withArgs(nonce + 2n) From 1ffbb7e49e112fcac678f59bf63ba57a7e522874 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Wed, 25 Sep 2024 14:36:16 +0400 Subject: [PATCH 170/177] fix: clear the penalized state test --- test/0.4.24/nor/nor.rewards.penalties.flow.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts index 9bcf1e5da..1f1927f12 100644 --- a/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts +++ b/test/0.4.24/nor/nor.rewards.penalties.flow.test.ts @@ -749,17 +749,17 @@ describe("NodeOperatorsRegistry:rewards-penalties", () => { await expect(await nor.clearNodeOperatorPenalty(firstNodeOperatorId)) .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 3n) + .withArgs(nonce + 5n) .to.emit(nor, "NonceChanged") - .withArgs(nonce + 3n) + .withArgs(nonce + 5n) .to.emit(nor, "NodeOperatorPenaltyCleared") .withArgs(firstNodeOperatorId); await expect(await nor.clearNodeOperatorPenalty(secondNodeOperatorId)) .to.emit(nor, "KeysOpIndexSet") - .withArgs(nonce + 4n) + .withArgs(nonce + 6n) .to.emit(nor, "NonceChanged") - .withArgs(nonce + 4n) + .withArgs(nonce + 6n) .to.emit(nor, "NodeOperatorPenaltyCleared") .withArgs(secondNodeOperatorId); From aea65047f21f47db0936a071e4e62b9cb826536b Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 26 Sep 2024 14:56:14 +0400 Subject: [PATCH 171/177] docs: description for sr deploy params --- scripts/staking-router-v2/sr-v2-deploy.ts | 66 ++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 6632937b2..2c365ffef 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -33,22 +33,67 @@ function getEnvVariable(name: string, defaultValue?: string) { } } -// Accounting Oracle args +/* Accounting Oracle args */ + +// Must comply with the specification +// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#time-parameters-1 const SECONDS_PER_SLOT = 12; + +// Must match the beacon chain genesis_time: https://beaconstate-mainnet.chainsafe.io/eth/v1/beacon/genesis +// and the current value: https://etherscan.io/address/0x852deD011285fe67063a08005c71a85690503Cee#readProxyContract#F6 const GENESIS_TIME = 1606824023; -// Oracle report sanity checker +/* Oracle report sanity checker */ const EXITED_VALIDATORS_PER_DAY_LIMIT = 9000; + +// Defines the maximum number of validators that can be reported as "appeared" +// in a single day, limited by the maximum daily deposits via DSM +// +// BLOCKS_PER_DAY = (24 * 60 * 60) / 12 = 7200 +// MAX_DEPOSITS_PER_BLOCK = 150 +// MIN_DEPOSIT_BLOCK_DISTANCE = 25 +// +// APPEARED_VALIDATORS_PER_DAY_LIMIT = BLOCKS_PER_DAY / MIN_DEPOSIT_BLOCK_DISTANCE * MAX_DEPOSITS_PER_BLOCK = 43200 +// Current limits: https://etherscan.io/address/0xC77F8768774E1c9244BEed705C4354f2113CFc09#readContract#F10 +// https://etherscan.io/address/0xC77F8768774E1c9244BEed705C4354f2113CFc09#readContract#F11 +// The proposed limits remain unchanged for curated modules and reduced for CSM const APPEARED_VALIDATORS_PER_DAY_LIMIT = 43200; + +// Must match the current value https://docs.lido.fi/guides/verify-lido-v2-upgrade-manual/#oraclereportsanitychecker const ANNUAL_BALANCE_INCREASE_BP_LIMIT = 1000; const SIMULATED_SHARE_RATE_DEVIATION_BP_LIMIT = 50; const MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT = 600; + +// The optimal number of items is greater than 6 (2 items for stuck or exited keys per 3 modules) to ensure +// a small report can fit into a single transaction. However, there is additional capacity in case a module +// requires more than 2 items. Hence, the limit of 8 items per report was chosen. const MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION = 8; + +// This parameter defines the maximum number of node operators that can be reported per extra data list item. +// Gas consumption for updating a single node operator: +// +// - CSM: +// Average: ~16,650 gas +// Max: ~41,150 gas (in cases with unstuck keys under specific conditions) +// - Curated-based: ~15,500 gas +// +// Each transaction can contain up to 8 items, and each item is limited to a maximum of 1,000,000 gas. +// Thus, the total gas consumption per transaction remains within 8,000,000 gas. +// Using the higher value of CSM (41,150 gas), the calculation is as follows: +// +// Operators per item: 1,000,000 / 41,150 = 24.3 +// Thus, the limit was set at 24 operators per item. const MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM = 24; + +// Must match the current value https://docs.lido.fi/guides/verify-lido-v2-upgrade-manual/#oraclereportsanitychecker const REQUEST_TIMESTAMP_MARGIN = 7680; const MAX_POSITIVE_TOKEN_REBASE = 750000; + +// Must match the value in LIP-23 https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-23.md const INITIAL_SLASHING_AMOUNT_P_WEI = 1000; const INACTIVITY_PENALTIES_AMOUNT_P_WEI = 101; + +// Must match the proposed number https://hackmd.io/@lido/lip-21#TVL-attack const CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 74; const LIMITS = [ @@ -66,10 +111,17 @@ const LIMITS = [ CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT, ]; -// DSM args +/* DSM args */ + +// Must match the current value https://etherscan.io/address/0xC77F8768774E1c9244BEed705C4354f2113CFc09#readContract#F13 const PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS = 6646; + +// Unvetting a single operator requires approximately 20,000 gas. Thus, the maximum number of operators per unvetting +// is defined as 200 to keep the maximum transaction cost below 4,000,000 gas. const MAX_OPERATORS_PER_UNVETTING = 200; -const guardians = [ + +// Must match the current list https://etherscan.io/address/0xC77F8768774E1c9244BEed705C4354f2113CFc09#readContract#F9 +const GUARDIANS = [ "0x5fd0dDbC3351d009eb3f88DE7Cd081a614C519F1", "0x7912Fa976BcDe9c2cf728e213e892AD7588E6AaF", "0x14D5d5B71E048d2D75a39FfC5B407e3a3AB6F314", @@ -77,7 +129,9 @@ const guardians = [ "0xa56b128Ea2Ea237052b0fA2a96a387C0E43157d8", "0xd4EF84b638B334699bcf5AF4B0410B8CCD71943f", ]; -const quorum = 4; + +// Must match the current value https://etherscan.io/address/0xC77F8768774E1c9244BEed705C4354f2113CFc09#readContract#F8 +const QUORUM = 4; async function main() { const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); @@ -162,7 +216,7 @@ async function main() { DepositSecurityModule__factory, depositSecurityModuleAddress, ); - await dsmContract.addGuardians(guardians, quorum); + await dsmContract.addGuardians(GUARDIANS, QUORUM); await dsmContract.setOwner(APP_AGENT_ADDRESS); log.success(`Guardians list: ${await dsmContract.getGuardians()}`); log.success(`Quorum: ${await dsmContract.getGuardianQuorum()}`); From 514e6e7bcd59fd3164456bb764d0b6bf3a1043da Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 26 Sep 2024 18:18:56 +0400 Subject: [PATCH 172/177] fix: read contract addresses from locator --- scripts/staking-router-v2/sr-v2-deploy.ts | 83 +++++++++++++++++++---- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 6632937b2..3d4c65e44 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -3,7 +3,12 @@ import { ethers, run } from "hardhat"; import { join } from "path"; import readline from "readline"; -import { DepositSecurityModule, DepositSecurityModule__factory } from "typechain-types"; +import { + DepositSecurityModule, + DepositSecurityModule__factory, + LidoLocator, + LidoLocator__factory, +} from "typechain-types"; import { cy, @@ -91,26 +96,46 @@ async function main() { log(cy(`Deploy of contracts on chain ${chainId}`)); const state = readNetworkState(); - state[Sk.scratchDeployGasUsed] = 0n.toString(); persistNetworkState(state); // Read contracts addresses from config const DEPOSIT_CONTRACT_ADDRESS = state[Sk.chainSpec].depositContractAddress; const APP_AGENT_ADDRESS = state[Sk.appAgent].proxy.address; const SC_ADMIN = APP_AGENT_ADDRESS; - const LIDO = state[Sk.appLido].proxy.address; - const STAKING_ROUTER = state[Sk.stakingRouter].proxy.address; const LOCATOR = state[Sk.lidoLocator].proxy.address; - const LEGACY_ORACLE = state[Sk.appOracle].proxy.address; - const ACCOUNTING_ORACLE_PROXY = state[Sk.accountingOracle].proxy.address; - const EL_REWARDS_VAULT = state[Sk.executionLayerRewardsVault].address; - const BURNER = state[Sk.burner].address; - const TREASURY_ADDRESS = APP_AGENT_ADDRESS; - const VEBO = state[Sk.validatorsExitBusOracle].proxy.address; - const WQ = state[Sk.withdrawalQueueERC721].proxy.address; - const WITHDRAWAL_VAULT = state[Sk.withdrawalVault].proxy.address; - const ORACLE_DAEMON_CONFIG = state[Sk.oracleDaemonConfig].address; + const locatorContract = await loadContract(LidoLocator__factory, LOCATOR); + // fetch contract addresses that will not changed + const ACCOUNTING_ORACLE_PROXY = await locatorContract.accountingOracle(); + const EL_REWARDS_VAULT = await locatorContract.elRewardsVault(); + const LEGACY_ORACLE = await locatorContract.legacyOracle(); + const LIDO = await locatorContract.lido(); + const POST_TOKEN_REABSE_RECEIVER = await locatorContract.postTokenRebaseReceiver(); + const BURNER = await locatorContract.burner(); + const STAKING_ROUTER = await locatorContract.stakingRouter(); + const TREASURY_ADDRESS = await locatorContract.treasury(); + const VEBO = await locatorContract.validatorsExitBusOracle(); + const WQ = await locatorContract.withdrawalQueue(); + const WITHDRAWAL_VAULT = await locatorContract.withdrawalVault(); + const ORACLE_DAEMON_CONFIG = await locatorContract.oracleDaemonConfig(); + + log.lineWithArguments( + `Fetched addresses from locator ${LOCATOR}, result: `, + getLocatorAddressesToString( + ACCOUNTING_ORACLE_PROXY, + EL_REWARDS_VAULT, + LEGACY_ORACLE, + LIDO, + POST_TOKEN_REABSE_RECEIVER, + BURNER, + STAKING_ROUTER, + TREASURY_ADDRESS, + VEBO, + WQ, + WITHDRAWAL_VAULT, + ORACLE_DAEMON_CONFIG, + ), + ); // Deploy MinFirstAllocationStrategy const minFirstAllocationStrategyAddress = ( await deployWithoutProxy(Sk.minFirstAllocationStrategy, "MinFirstAllocationStrategy", deployer) @@ -197,7 +222,7 @@ async function main() { LEGACY_ORACLE, LIDO, oracleReportSanityCheckerAddress, - LEGACY_ORACLE, + POST_TOKEN_REABSE_RECEIVER, BURNER, STAKING_ROUTER, TREASURY_ADDRESS, @@ -282,6 +307,36 @@ async function waitForPressButton(): Promise { }); } +function getLocatorAddressesToString( + ACCOUNTING_ORACLE_PROXY: string, + EL_REWARDS_VAULT: string, + LEGACY_ORACLE: string, + LIDO: string, + POST_TOKEN_REABSE_RECEIVER: string, + BURNER: string, + STAKING_ROUTER: string, + TREASURY_ADDRESS: string, + VEBO: string, + WQ: string, + WITHDRAWAL_VAULT: string, + ORACLE_DAEMON_CONFIG: string, +) { + return [ + { toString: () => `ACCOUNTING_ORACLE_PROXY: ${ACCOUNTING_ORACLE_PROXY}` }, + { toString: () => `EL_REWARDS_VAULT: ${EL_REWARDS_VAULT}` }, + { toString: () => `LEGACY_ORACLE: ${LEGACY_ORACLE}` }, + { toString: () => `LIDO: ${LIDO}` }, + { toString: () => `POST_TOKEN_REABSE_RECEIVER: ${POST_TOKEN_REABSE_RECEIVER}` }, + { toString: () => `BURNER: ${BURNER}` }, + { toString: () => `STAKING_ROUTER: ${STAKING_ROUTER}` }, + { toString: () => `TREASURY_ADDRESS: ${TREASURY_ADDRESS}` }, + { toString: () => `VEBO: ${VEBO}` }, + { toString: () => `WQ: ${WQ}` }, + { toString: () => `WITHDRAWAL_VAULT: ${WITHDRAWAL_VAULT}` }, + { toString: () => `ORACLE_DAEMON_CONFIG: ${ORACLE_DAEMON_CONFIG}` }, + ]; +} + main() .then(() => process.exit(0)) .catch((error) => { From 06f264455779be4b3206863ee495d28d00b58fc1 Mon Sep 17 00:00:00 2001 From: Anna Mukharram Date: Thu, 26 Sep 2024 18:26:01 +0400 Subject: [PATCH 173/177] fix: loging --- scripts/staking-router-v2/sr-v2-deploy.ts | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 3d4c65e44..eedbb937d 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -322,18 +322,18 @@ function getLocatorAddressesToString( ORACLE_DAEMON_CONFIG: string, ) { return [ - { toString: () => `ACCOUNTING_ORACLE_PROXY: ${ACCOUNTING_ORACLE_PROXY}` }, - { toString: () => `EL_REWARDS_VAULT: ${EL_REWARDS_VAULT}` }, - { toString: () => `LEGACY_ORACLE: ${LEGACY_ORACLE}` }, - { toString: () => `LIDO: ${LIDO}` }, - { toString: () => `POST_TOKEN_REABSE_RECEIVER: ${POST_TOKEN_REABSE_RECEIVER}` }, - { toString: () => `BURNER: ${BURNER}` }, - { toString: () => `STAKING_ROUTER: ${STAKING_ROUTER}` }, - { toString: () => `TREASURY_ADDRESS: ${TREASURY_ADDRESS}` }, - { toString: () => `VEBO: ${VEBO}` }, - { toString: () => `WQ: ${WQ}` }, - { toString: () => `WITHDRAWAL_VAULT: ${WITHDRAWAL_VAULT}` }, - { toString: () => `ORACLE_DAEMON_CONFIG: ${ORACLE_DAEMON_CONFIG}` }, + `ACCOUNTING_ORACLE_PROXY: ${ACCOUNTING_ORACLE_PROXY}`, + `EL_REWARDS_VAULT: ${EL_REWARDS_VAULT}`, + `LEGACY_ORACLE: ${LEGACY_ORACLE}`, + `LIDO: ${LIDO}`, + `POST_TOKEN_REABSE_RECEIVER: ${POST_TOKEN_REABSE_RECEIVER}`, + `BURNER: ${BURNER}`, + `STAKING_ROUTER: ${STAKING_ROUTER}`, + `TREASURY_ADDRESS: ${TREASURY_ADDRESS}`, + `VEBO: ${VEBO}`, + `WQ: ${WQ}`, + `WITHDRAWAL_VAULT: ${WITHDRAWAL_VAULT}`, + `ORACLE_DAEMON_CONFIG: ${ORACLE_DAEMON_CONFIG}`, ]; } From e5733bd4ebd13c246032d46ca0d65aa05eb576c1 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 7 Oct 2024 11:19:39 +0400 Subject: [PATCH 174/177] feat: use steth on optimism locator impl in sr2 deploy script --- scripts/staking-router-v2/sr-v2-deploy.ts | 65 ++++++++++++++++------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index 77be8eede..ab9a4cf80 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -49,6 +49,26 @@ const SECONDS_PER_SLOT = 12; const GENESIS_TIME = 1606824023; /* Oracle report sanity checker */ + +// Defines the maximum number of validators that may be reported as "exited" +// per day, depending on the consensus layer churn limit. +// +// CURRENT_ACTIVE_VALIDATORS_NUMBER = ~1100000 // https://beaconcha.in/ +// CURRENT_EXIT_CHURN_LIMIT = 16 // https://www.validatorqueue.com/ +// EPOCHS_PER_DAY = 225 // (24 * 60 * 60) sec / 12 sec per slot / 32 slots per epoch +// +// https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#validator-cycle +// MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT = 8 +// +// MAX_VALIDATORS_PER_DAY = EPOCHS_PER_DAY * MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT = 1800 // 225 * 8 +// MAX_VALIDATORS_AFTER_TWO_YEARS = MAX_VALIDATORS_PER_DAY * 365 * 2 + CURRENT_VALIDATORS_NUMBER // 1100000 + (1800 * 365 * 2) = ~2500000 +// +// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator-cycle +// CHURN_LIMIT_QUOTIENT = 65536 +// +// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#get_validator_churn_limit +// MAX_EXIT_CHURN_LIMIT_AFTER_TWO_YEARS = MAX_VALIDATORS_AFTER_TWO_YEARS / CHURN_LIMIT_QUOTIENT // 2500000 / 65536 = ~38 +// EXITED_VALIDATORS_PER_DAY_LIMIT = MAX_EXIT_CHURN_LIMIT_AFTER_TWO_YEARS * EPOCHS_PER_DAY // 38 * 225 = 8550 = ~9000 const EXITED_VALIDATORS_PER_DAY_LIMIT = 9000; // Defines the maximum number of validators that can be reported as "appeared" @@ -138,6 +158,15 @@ const GUARDIANS = [ // Must match the current value https://etherscan.io/address/0xC77F8768774E1c9244BEed705C4354f2113CFc09#readContract#F8 const QUORUM = 4; +// stETH on Optimism is going to change the locator implementation +// on October 11th after SRv2 deployment but before SRv2 voting +// +// We're taking the proposed implementation of the upcoming upgrade +// to make sure that these changes make it into our deployment +// +// Must match the proposed locator impl in the docs: https://docs.lido.fi/deployed-contracts/ +const INTERMEDIATE_LOCATOR_IMPL = "0x39aFE23cE59e8Ef196b81F0DCb165E9aD38b9463"; + async function main() { const deployer = ethers.getAddress(getEnvVariable("DEPLOYER")); const chainId = (await ethers.provider.getNetwork()).chainId; @@ -158,29 +187,29 @@ async function main() { const SC_ADMIN = APP_AGENT_ADDRESS; const LOCATOR = state[Sk.lidoLocator].proxy.address; - const locatorContract = await loadContract(LidoLocator__factory, LOCATOR); + const locatorImplContract = await loadContract(LidoLocator__factory, INTERMEDIATE_LOCATOR_IMPL); // fetch contract addresses that will not changed - const ACCOUNTING_ORACLE_PROXY = await locatorContract.accountingOracle(); - const EL_REWARDS_VAULT = await locatorContract.elRewardsVault(); - const LEGACY_ORACLE = await locatorContract.legacyOracle(); - const LIDO = await locatorContract.lido(); - const POST_TOKEN_REABSE_RECEIVER = await locatorContract.postTokenRebaseReceiver(); - const BURNER = await locatorContract.burner(); - const STAKING_ROUTER = await locatorContract.stakingRouter(); - const TREASURY_ADDRESS = await locatorContract.treasury(); - const VEBO = await locatorContract.validatorsExitBusOracle(); - const WQ = await locatorContract.withdrawalQueue(); - const WITHDRAWAL_VAULT = await locatorContract.withdrawalVault(); - const ORACLE_DAEMON_CONFIG = await locatorContract.oracleDaemonConfig(); + const ACCOUNTING_ORACLE_PROXY = await locatorImplContract.accountingOracle(); + const EL_REWARDS_VAULT = await locatorImplContract.elRewardsVault(); + const LEGACY_ORACLE = await locatorImplContract.legacyOracle(); + const LIDO = await locatorImplContract.lido(); + const POST_TOKEN_REBASE_RECEIVER = await locatorImplContract.postTokenRebaseReceiver(); + const BURNER = await locatorImplContract.burner(); + const STAKING_ROUTER = await locatorImplContract.stakingRouter(); + const TREASURY_ADDRESS = await locatorImplContract.treasury(); + const VEBO = await locatorImplContract.validatorsExitBusOracle(); + const WQ = await locatorImplContract.withdrawalQueue(); + const WITHDRAWAL_VAULT = await locatorImplContract.withdrawalVault(); + const ORACLE_DAEMON_CONFIG = await locatorImplContract.oracleDaemonConfig(); log.lineWithArguments( - `Fetched addresses from locator ${LOCATOR}, result: `, + `Fetched addresses from locator impl ${INTERMEDIATE_LOCATOR_IMPL}, result: `, getLocatorAddressesToString( ACCOUNTING_ORACLE_PROXY, EL_REWARDS_VAULT, LEGACY_ORACLE, LIDO, - POST_TOKEN_REABSE_RECEIVER, + POST_TOKEN_REBASE_RECEIVER, BURNER, STAKING_ROUTER, TREASURY_ADDRESS, @@ -276,7 +305,7 @@ async function main() { LEGACY_ORACLE, LIDO, oracleReportSanityCheckerAddress, - POST_TOKEN_REABSE_RECEIVER, + POST_TOKEN_REBASE_RECEIVER, BURNER, STAKING_ROUTER, TREASURY_ADDRESS, @@ -366,7 +395,7 @@ function getLocatorAddressesToString( EL_REWARDS_VAULT: string, LEGACY_ORACLE: string, LIDO: string, - POST_TOKEN_REABSE_RECEIVER: string, + POST_TOKEN_REBASE_RECEIVER: string, BURNER: string, STAKING_ROUTER: string, TREASURY_ADDRESS: string, @@ -380,7 +409,7 @@ function getLocatorAddressesToString( `EL_REWARDS_VAULT: ${EL_REWARDS_VAULT}`, `LEGACY_ORACLE: ${LEGACY_ORACLE}`, `LIDO: ${LIDO}`, - `POST_TOKEN_REABSE_RECEIVER: ${POST_TOKEN_REABSE_RECEIVER}`, + `POST_TOKEN_REBASE_RECEIVER: ${POST_TOKEN_REBASE_RECEIVER}`, `BURNER: ${BURNER}`, `STAKING_ROUTER: ${STAKING_ROUTER}`, `TREASURY_ADDRESS: ${TREASURY_ADDRESS}`, From 9e64ca0509a681f959c0b3ea1db47c68c60cecbf Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Tue, 8 Oct 2024 09:49:56 +0400 Subject: [PATCH 175/177] feat: update cl balances oracle error upper limit --- scripts/staking-router-v2/sr-v2-deploy.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index ab9a4cf80..b4b3e8b13 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -115,11 +115,12 @@ const REQUEST_TIMESTAMP_MARGIN = 7680; const MAX_POSITIVE_TOKEN_REBASE = 750000; // Must match the value in LIP-23 https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-23.md +// and the proposed number on the research forum https://research.lido.fi/t/lip-25-staking-router-v2-0/7773 const INITIAL_SLASHING_AMOUNT_P_WEI = 1000; const INACTIVITY_PENALTIES_AMOUNT_P_WEI = 101; -// Must match the proposed number https://hackmd.io/@lido/lip-21#TVL-attack -const CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 74; +// Must match the proposed number on the research forum https://research.lido.fi/t/lip-25-staking-router-v2-0/7773 +const CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 50; const LIMITS = [ EXITED_VALIDATORS_PER_DAY_LIMIT, From 643d46dbdc395863360e3e0efefbc7b4c63ecd4e Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Tue, 8 Oct 2024 15:55:18 +0400 Subject: [PATCH 176/177] docs: update forum post link to sr v2 upgrade announcement --- scripts/staking-router-v2/sr-v2-deploy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/staking-router-v2/sr-v2-deploy.ts b/scripts/staking-router-v2/sr-v2-deploy.ts index b4b3e8b13..0c93bfc32 100644 --- a/scripts/staking-router-v2/sr-v2-deploy.ts +++ b/scripts/staking-router-v2/sr-v2-deploy.ts @@ -115,11 +115,11 @@ const REQUEST_TIMESTAMP_MARGIN = 7680; const MAX_POSITIVE_TOKEN_REBASE = 750000; // Must match the value in LIP-23 https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-23.md -// and the proposed number on the research forum https://research.lido.fi/t/lip-25-staking-router-v2-0/7773 +// and the proposed number on the research forum https://research.lido.fi/t/staking-router-community-staking-module-upgrade-announcement/8612 const INITIAL_SLASHING_AMOUNT_P_WEI = 1000; const INACTIVITY_PENALTIES_AMOUNT_P_WEI = 101; -// Must match the proposed number on the research forum https://research.lido.fi/t/lip-25-staking-router-v2-0/7773 +// Must match the proposed number on the research forum https://research.lido.fi/t/staking-router-community-staking-module-upgrade-announcement/8612 const CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 50; const LIMITS = [ From 430e3db2631f6e503177657ffe4a1372326a24c7 Mon Sep 17 00:00:00 2001 From: hweawer Date: Tue, 8 Oct 2024 19:48:01 +0200 Subject: [PATCH 177/177] New deploy params --- deployed-mainnet.json | 94 ++++++++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/deployed-mainnet.json b/deployed-mainnet.json index bfc09113f..21fff193f 100644 --- a/deployed-mainnet.json +++ b/deployed-mainnet.json @@ -14,9 +14,8 @@ ] }, "implementation": { - "address": "0xF3c5E0A67f32CF1dc07a8817590efa102079a1aF", "contract": "contracts/0.8.9/oracle/AccountingOracle.sol", - "deployTx": "0x3e27627d3ed236aff8901df187196e9682187dfd0d259c5d5811a6e923436083", + "address": "0x0e65898527E77210fB0133D00dd4C0E86Dc29bC7", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", @@ -114,14 +113,9 @@ "address": "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" }, "implementation": { - "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", - "deployTx": "0xbec5b6ffb8fbc775a183e25cd285367993d7201752e1638a07abec8962bab750", - "constructorArgs": [], - "deployParameters": { - "stakingModuleTypeId": "curated-onchain-v1", - "stuckPenaltyDelay": "432000" - } + "address": "0x1770044a38402e3CfCa2Fcfa0C84a093c9B42135", + "constructorArgs": [] }, "aragonApp": { "fullName": "node-operators-registry.lidopm.eth", @@ -187,8 +181,8 @@ ] }, "implementation": { - "address": "0x8538930c385C0438A357d2c25CB3eAD95Ab6D8ed", "contract": "contracts/0.4.24/nos/NodeOperatorsRegistry.sol", + "address": "0x1770044a38402e3CfCa2Fcfa0C84a093c9B42135", "constructorArgs": [] } }, @@ -196,7 +190,9 @@ "implementation": { "contract": "@aragon/os/contracts/kernel/Kernel.sol", "address": "0x2b33CF282f867A7FF693A66e11B0FcC5552e4425", - "constructorArgs": [true] + "constructorArgs": [ + true + ] }, "proxy": { "address": "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", @@ -250,16 +246,15 @@ "deployCommit": "e45c4d6fb8120fd29426b8d969c19d8a798ca974", "deployer": "0x55Bc991b2edF3DDb4c520B222bE4F378418ff0fA", "depositSecurityModule": { - "address": "0xC77F8768774E1c9244BEed705C4354f2113CFc09", + "address": "0xfFA96D84dEF2EA035c7AB153D8B991128e3d72fD", "contract": "contracts/0.8.9/DepositSecurityModule.sol", "deployTx": "0x21307a2321f167f99de11ccec86d7bdd8233481bbffa493e15c519ca8d662c4f", "constructorArgs": [ "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x00000000219ab540356cBB839Cbe05303d7705Fa", "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", - 150, - 25, - 6646 + 6646, + 200 ], "deployParameters": { "maxDepositsPerBlock": 150, @@ -277,7 +272,9 @@ "address": "0x8F73e4C2A6D852bb4ab2A45E6a9CF5715b3228B7", "contract": "contracts/0.8.9/EIP712StETH.sol", "deployTx": "0xecb5010620fb13b0e2bbc98b8a0c82de0d7385491452cd36cf303cd74216ed91", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + ] }, "ensAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "executionLayerRewardsVault": { @@ -340,18 +337,17 @@ ] }, "implementation": { - "address": "0x1D920cc5bACf7eE506a271a5259f2417CaDeCE1d", "contract": "contracts/0.8.9/LidoLocator.sol", - "deployTx": "0xf90012ef0a40e47c909ab3a5b3503ecee78f6a9be134d1349a742e500d37ae33", + "address": "0x3ABc4764f0237923d52056CFba7E9AEBf87113D3", "constructorArgs": [ [ "0x852deD011285fe67063a08005c71a85690503Cee", - "0xC77F8768774E1c9244BEed705C4354f2113CFc09", + "0xfFA96D84dEF2EA035c7AB153D8B991128e3d72fD", "0x388C818CA8B9251b393131C08a736A67ccB19297", "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", - "0x442af784A788A5bd6F42A01Ebe9F287a871243fb", + "0x6232397ebac4f5772e53285B26c47914E9461E75", + "0xe6793B9e4FbA7DE0ee833F9D02bba7DB5EB27823", "0xD15a672319Cf0352560eE76d9e89eAB0889046D3", "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", @@ -376,6 +372,11 @@ "0xa0BC4B67F5FacDE4E50EAFF48691Cfc43F4E280A" ] }, + "minFirstAllocationStrategy": { + "contract": "contracts/common/lib/MinFirstAllocationStrategy.sol", + "address": "0x7e70De6D1877B3711b2bEDa7BA00013C7142d993", + "constructorArgs": [] + }, "miniMeTokenFactoryAddress": "0x909d05f384d0663ed4be59863815ab43b4f347ec", "networkId": 1, "newDaoTx": "0x3feabd79e8549ad68d1827c074fa7123815c80206498946293d5373a160fd866", @@ -383,7 +384,10 @@ "address": "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09", "contract": "contracts/0.8.9/OracleDaemonConfig.sol", "deployTx": "0xa4f380b8806f5a504ef67fce62989e09be5a48bf114af63483c01c22f0c9a36f", - "constructorArgs": ["0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", []], + "constructorArgs": [ + "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", + [] + ], "deployParameters": { "NORMALIZED_CL_REWARD_PER_EPOCH": 64, "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": 1000, @@ -397,14 +401,26 @@ } }, "oracleReportSanityChecker": { - "address": "0x9305c1Dbfe22c12c66339184C0025d7006f0f1cC", + "address": "0x6232397ebac4f5772e53285B26c47914E9461E75", "contract": "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol", "deployTx": "0x700c83996ad7deefda286044280ad86108dfef9c880909bd8e75a3746f7d631c", "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - [20000, 500, 1000, 50, 600, 2, 100, 7680, 750000], - [[], [], [], [], [], [], [], [], [], []] + [ + 9000, + 43200, + 1000, + 50, + 600, + 8, + 24, + 7680, + 750000, + 1000, + 101, + 50 + ] ], "deployParameters": { "churnValidatorsPerDayLimit": 20000, @@ -430,10 +446,11 @@ ] }, "implementation": { - "address": "0xD8784e748f59Ba711fB5643191Ec3fAdD50Fb6df", "contract": "contracts/0.8.9/StakingRouter.sol", - "deployTx": "0xd6d489f22203c835da6027ff0e532a01a08f36f0fda6c7c0a42e471ae3b3c461", - "constructorArgs": ["0x00000000219ab540356cBB839Cbe05303d7705Fa"] + "address": "0x89eDa99C0551d4320b56F82DDE8dF2f8D2eF81aA", + "constructorArgs": [ + "0x00000000219ab540356cBB839Cbe05303d7705Fa" + ] } }, "validatorsExitBusOracle": { @@ -451,7 +468,11 @@ "address": "0xA89Ea51FddE660f67d1850e03C9c9862d33Bc42c", "contract": "contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol", "deployTx": "0x5ab545276f78a72a432c3e971c96384973abfab6394e08cb077a006c25aef7a7", - "constructorArgs": [12, 1606824023, "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb"], + "constructorArgs": [ + 12, + 1606824023, + "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" + ], "deployParameters": { "consensusVersion": 1 } @@ -541,7 +562,11 @@ "address": "0xE42C659Dc09109566720EA8b2De186c2Be7D94D9", "contract": "contracts/0.8.9/WithdrawalQueueERC721.sol", "deployTx": "0x6ab0151735c01acdef518421358d41a08752169bc383c57d57f5bfa135ac6eb1", - "constructorArgs": ["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "Lido: stETH Withdrawal NFT", "unstETH"], + "constructorArgs": [ + "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + "Lido: stETH Withdrawal NFT", + "unstETH" + ], "deployParameters": { "name": "Lido: stETH Withdrawal NFT", "symbol": "unstETH" @@ -556,13 +581,18 @@ "address": "0xCC52f17756C04bBa7E377716d7062fC36D7f69Fd", "contract": "contracts/0.8.9/WithdrawalVault.sol", "deployTx": "0xd9eb2eca684770e4d2b192709b6071875f75072a0ce794a582824ee907a704f3", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", + "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" + ] } }, "wstETH": { "address": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "contract": "contracts/0.6.12/WstETH.sol", "deployTx": "0xaf2c1a501d2b290ef1e84ddcfc7beb3406f8ece2c46dee14e212e8233654ff05", - "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] + "constructorArgs": [ + "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + ] } }